mirror of
https://github.com/originalnicodr/CinematicUnityExplorer.git
synced 2026-10-10 04:53:15 +08:00
Added support to manually play animations of entities on the scene (only for Mono builds). Also refactored the AnimatorPanel with a pool of objects.
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
using UnityEngine;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
|
||||
#if CPP
|
||||
using Animator = UnityEngine.Behaviour;
|
||||
#endif
|
||||
|
||||
namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
public class AnimatorCell : ICell
|
||||
{
|
||||
private bool autoIgnoreMasterToggleSet;
|
||||
public Toggle IgnoreMasterToggle;
|
||||
public Toggle AnimatorToggle;
|
||||
|
||||
public Text name;
|
||||
public Animator animator;
|
||||
|
||||
// ICell
|
||||
public float DefaultHeight => 25f;
|
||||
public GameObject UIRoot { get; set; }
|
||||
public RectTransform Rect { get; set; }
|
||||
|
||||
public bool Enabled => UIRoot.activeSelf;
|
||||
public void Enable() => UIRoot.SetActive(true);
|
||||
public void Disable() => UIRoot.SetActive(false);
|
||||
|
||||
// IL2CPP games seem to have animation-related code stripped from their builds
|
||||
#if MONO
|
||||
ButtonRef playButton;
|
||||
private bool manuallyPlayedAnimation;
|
||||
private bool skippedStopFrames;
|
||||
|
||||
private AnimationClip currentAnimation;
|
||||
private AnimationClip defaultAnimation;
|
||||
|
||||
public void DrawAnimatorPlayer(){
|
||||
if (playButton == null){
|
||||
List<AnimationClip> animations = animator.runtimeAnimatorController.animationClips.OrderBy(x=>x.name).Where(c => c.length > 0).Distinct().ToList();
|
||||
|
||||
//ExplorerCore.LogWarning(animations.Count);
|
||||
AnimatorClipInfo[] playingAnimations = animator.GetCurrentAnimatorClipInfo(0);
|
||||
currentAnimation = playingAnimations.Count() != 0 ? playingAnimations[0].clip : animations[0];
|
||||
|
||||
GameObject currentAnimationObj = UIFactory.CreateDropdown(UIRoot, $"Animations_{name}", out Dropdown dropdown, null, 14, (idx) => currentAnimation = animations[idx]);
|
||||
UIFactory.SetLayoutElement(currentAnimationObj, minHeight: 25, minWidth: 100);
|
||||
foreach (AnimationClip animation in animations)
|
||||
dropdown.options.Add(new Dropdown.OptionData(animation.name));
|
||||
|
||||
playButton = UIFactory.CreateButton(UIRoot, "PlayButton", "Play", new Color(0.2f, 0.26f, 0.2f));
|
||||
UIFactory.SetLayoutElement(playButton.Component.gameObject, minHeight: 5, minWidth: 90);
|
||||
playButton.OnClick += PlayButton_OnClick;
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayButton_OnClick(){
|
||||
// We save the last animation played by the game in case we want to go back to it
|
||||
if (defaultAnimation == null){
|
||||
AnimatorClipInfo[] playingAnimations = animator.GetCurrentAnimatorClipInfo(0);
|
||||
defaultAnimation = playingAnimations.Count() != 0 ? playingAnimations[0].clip : null;
|
||||
}
|
||||
|
||||
skippedStopFrames = false;
|
||||
|
||||
AnimatorToggle.isOn = true;
|
||||
manuallyPlayedAnimation = true;
|
||||
animator.Play(currentAnimation.name);
|
||||
}
|
||||
|
||||
// Disables the animator when the animation we manually triggered isn't present on the subject anymore
|
||||
public bool IsPlayingSelectedAnimation(){
|
||||
if (animator != null && currentAnimation != null && name.text == "Player"){
|
||||
if (manuallyPlayedAnimation && !GetAllCurrentAnimations().Contains(currentAnimation)){
|
||||
|
||||
if (!skippedStopFrames){
|
||||
skippedStopFrames = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
manuallyPlayedAnimation = false;
|
||||
AnimatorToggle.isOn = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<AnimationClip> GetAllCurrentAnimations(){
|
||||
List<AnimationClip> allAnimations = new List<AnimationClip>();
|
||||
for (int layer = 0; layer < animator.layerCount; layer++){
|
||||
allAnimations.AddRange(animator.GetCurrentAnimatorClipInfo(layer).Select(ainfo => ainfo.clip).ToList());
|
||||
}
|
||||
return allAnimations;
|
||||
}
|
||||
|
||||
public void ResetAnimation(){
|
||||
if (defaultAnimation != null){
|
||||
manuallyPlayedAnimation = false;
|
||||
|
||||
animator.Play(defaultAnimation.name);
|
||||
AnimatorToggle.isOn = true;
|
||||
defaultAnimation = null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// If it's the first time we are rendering the AnimatorCell, assign the ignore value automatically based on it's name
|
||||
public void MaybeSetIgnoreMasterToggleSet(){
|
||||
if (animator != null && !autoIgnoreMasterToggleSet){
|
||||
// A weird canse insensitive "Contains" to identify the player
|
||||
IgnoreMasterToggle.isOn = animator.gameObject.name.IndexOf("play", 0, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
autoIgnoreMasterToggleSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual GameObject CreateContent(GameObject parent)
|
||||
{
|
||||
GameObject AnimatorToggleObj = UIFactory.CreateToggle(parent, $"AnimatorToggle", out AnimatorToggle, out Text animatorToggleText);
|
||||
UIFactory.SetLayoutElement(AnimatorToggleObj, minHeight: 25);
|
||||
AnimatorToggle.isOn = true;
|
||||
AnimatorToggle.onValueChanged.AddListener(value => animator.enabled = value);
|
||||
|
||||
UIRoot = AnimatorToggleObj;
|
||||
UIRoot.SetActive(false);
|
||||
|
||||
Rect = UIRoot.GetComponent<RectTransform>();
|
||||
Rect.anchorMin = new Vector2(0, 1);
|
||||
Rect.anchorMax = new Vector2(0, 1);
|
||||
Rect.pivot = new Vector2(0.5f, 1);
|
||||
Rect.sizeDelta = new Vector2(25, 25);
|
||||
|
||||
UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 25, flexibleHeight: 0);
|
||||
|
||||
name = UIFactory.CreateLabel(UIRoot, "NameLabel", "", TextAnchor.MiddleLeft, Color.white, false, 14);
|
||||
UIFactory.SetLayoutElement(name.gameObject, minHeight: 25, minWidth: 30, flexibleWidth: 40);
|
||||
|
||||
#if MONO
|
||||
ButtonRef resetAnimation = UIFactory.CreateButton(UIRoot, "Reset Animation", "Reset");
|
||||
UIFactory.SetLayoutElement(resetAnimation.GameObject, minWidth: 50, minHeight: 25);
|
||||
resetAnimation.OnClick += ResetAnimation;
|
||||
#endif
|
||||
|
||||
GameObject ignoresMasterTogglerObj = UIFactory.CreateToggle(UIRoot, $"AnimatorIgnoreMasterToggle", out IgnoreMasterToggle, out Text ignoreMasterToggleText);
|
||||
UIFactory.SetLayoutElement(ignoresMasterTogglerObj, minHeight: 25);
|
||||
IgnoreMasterToggle.isOn = false;
|
||||
ignoreMasterToggleText.text = "Ignore Master Toggle ";
|
||||
|
||||
return UIRoot;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using UnityExplorer.Inspectors;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.UI.ObjectPool;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
using UnityEngine;
|
||||
#if UNHOLLOWER
|
||||
using UnhollowerRuntimeLib;
|
||||
@@ -12,9 +13,13 @@ using UnhollowerRuntimeLib;
|
||||
using Il2CppInterop.Runtime.Injection;
|
||||
#endif
|
||||
|
||||
#if CPP
|
||||
using Animator = UnityEngine.Behaviour;
|
||||
#endif
|
||||
|
||||
namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
internal class AnimatorPanel : UEPanel
|
||||
internal class AnimatorPanel : UEPanel, ICellPoolDataSource<AnimatorCell>
|
||||
{
|
||||
public AnimatorPanel(UIBase owner) : base(owner)
|
||||
{
|
||||
@@ -23,106 +28,68 @@ namespace UnityExplorer.UI.Panels
|
||||
public override string Name => "Animator";
|
||||
public override UIManager.Panels PanelType => UIManager.Panels.AnimatorPanel;
|
||||
public override int MinWidth => 500;
|
||||
public override int MinHeight => 500;
|
||||
public override int MinHeight => 200;
|
||||
public override Vector2 DefaultAnchorMin => new(0.4f, 0.4f);
|
||||
public override Vector2 DefaultAnchorMax => new(0.6f, 0.6f);
|
||||
public override bool NavButtonWanted => true;
|
||||
public override bool ShouldSaveActiveState => true;
|
||||
List<GameObject> UIElements = new List<GameObject>();
|
||||
|
||||
internal Dictionary<string,List<Behaviour>> animators = new Dictionary<string,List<Behaviour>>();
|
||||
internal Dictionary<string,List<Toggle>> animatorToggles = new Dictionary<string,List<Toggle>>();
|
||||
internal Dictionary<string,bool> ignoresMasterToggler = new Dictionary<string,bool>();
|
||||
Dictionary<Animator, Func<bool>> animationEndedFunctions = new Dictionary<Animator, Func<bool>>();
|
||||
|
||||
Toggle masterAnimatorToggle = new Toggle();
|
||||
|
||||
private static ScrollPool<AnimatorCell> animatorScrollPool;
|
||||
internal List<Animator> animators = new List<Animator>();
|
||||
public int ItemCount => animators.Count;
|
||||
|
||||
private static bool DoneScrollPoolInit;
|
||||
|
||||
public override void SetActive(bool active)
|
||||
{
|
||||
base.SetActive(active);
|
||||
|
||||
if (active && !DoneScrollPoolInit)
|
||||
{
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(this.Rect);
|
||||
animatorScrollPool.Initialize(this);
|
||||
DoneScrollPoolInit = true;
|
||||
}
|
||||
|
||||
animatorScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
private void FindAllAnimators(){
|
||||
// Enable all animators on refresh
|
||||
if (animators.Count != 0) {
|
||||
masterAnimatorToggle.isOn = true; // Will also trigger "MasterToggleAnimators(true)"
|
||||
animators.Clear();
|
||||
animatorToggles.Clear();
|
||||
ignoresMasterToggler.Clear();
|
||||
animationEndedFunctions.Clear();
|
||||
#if MONO
|
||||
foreach (AnimatorCell animatorCell in animatorScrollPool.CellPool)
|
||||
{
|
||||
animatorCell.ResetAnimation();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Type searchType = ReflectionUtility.GetTypeByName("UnityEngine.Animator");
|
||||
searchType = searchType is Type type ? type : searchType.GetActualType();
|
||||
List<Behaviour> animatorsList = RuntimeHelper.FindObjectsOfTypeAll(searchType).Select(obj => obj.TryCast<Behaviour>())
|
||||
animators = RuntimeHelper.FindObjectsOfTypeAll(searchType).Select(obj => obj.TryCast<Animator>())
|
||||
.Where(a => a.GetComponentsInChildren<SkinnedMeshRenderer>(false).Length != 0 && a.enabled && a.GetComponentsInChildren<Rigidbody>(false).Length != 0)
|
||||
.OrderBy(x=>x.name)
|
||||
.ToList();
|
||||
|
||||
foreach(Behaviour animator in animatorsList){
|
||||
GameObject gObj = animator.gameObject;
|
||||
string key = getNameWithoutClone(gObj.name);
|
||||
|
||||
if (!animators.ContainsKey(key)){
|
||||
animators.Add(key, new List<Behaviour>());
|
||||
}
|
||||
|
||||
animators[key].Add(animator);
|
||||
}
|
||||
|
||||
BuildAnimatorsTogglers();
|
||||
}
|
||||
|
||||
private string getNameWithoutClone(string name){
|
||||
string newName = name;
|
||||
while (newName.EndsWith("(Clone)")){
|
||||
newName = newName.Substring(0, newName.Length - 7);
|
||||
}
|
||||
|
||||
return newName;
|
||||
}
|
||||
|
||||
private void BuildAnimatorsTogglers(){
|
||||
foreach (var comp in UIElements){
|
||||
UnityEngine.Object.Destroy(comp);
|
||||
//UIElements.Remove(comp);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, List<Behaviour>> entry in animators)
|
||||
{
|
||||
// A weird canse insensitive "Contains" to identify the player
|
||||
ignoresMasterToggler[entry.Key] = entry.Key.IndexOf("play", 0, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
|
||||
GameObject horiGroup = UIFactory.CreateHorizontalGroup(ContentRoot, $"{entry.Key}_Group", false, false, true, false, 4,
|
||||
default, new Color(1, 1, 1, 0), TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(horiGroup, minHeight: 25, flexibleWidth: 9999);
|
||||
UIElements.Add(horiGroup);
|
||||
|
||||
// is Affected by Master Toggler toggle
|
||||
Toggle ignoresMasterTogglerToggle;
|
||||
GameObject ignoresMasterTogglerObj = UIFactory.CreateToggle(horiGroup, $"Toggle {entry.Key}", out ignoresMasterTogglerToggle, out Text ignoresMasterTogglerText);
|
||||
UIFactory.SetLayoutElement(ignoresMasterTogglerObj, minHeight: 25);
|
||||
ignoresMasterTogglerToggle.isOn = ignoresMasterToggler[entry.Key];
|
||||
ignoresMasterTogglerToggle.onValueChanged.AddListener(value => ignoresMasterToggler[entry.Key] = value);
|
||||
ignoresMasterTogglerText.text = entry.Key;
|
||||
|
||||
foreach (Behaviour entityAnimator in entry.Value){
|
||||
Toggle animatorToggle;
|
||||
GameObject animatorObj = UIFactory.CreateToggle(horiGroup, $"Toggle {entry.Key}", out animatorToggle, out Text animatorToggleText);
|
||||
|
||||
if (!animatorToggles.ContainsKey(entry.Key)){
|
||||
animatorToggles.Add(entry.Key, new List<Toggle>());
|
||||
}
|
||||
animatorToggles[entry.Key].Add(animatorToggle);
|
||||
|
||||
UIFactory.SetLayoutElement(animatorObj, minHeight: 25);
|
||||
animatorToggle.isOn = true;
|
||||
animatorToggle.onValueChanged.AddListener(value => entityAnimator.enabled = value);
|
||||
}
|
||||
|
||||
}
|
||||
animatorScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public void MasterToggleAnimators(bool enable){
|
||||
// Load animators for the first time if there are not any
|
||||
if (animators.Count == 0) FindAllAnimators();
|
||||
foreach (KeyValuePair<string, List<Behaviour>> entry in animators)
|
||||
foreach (AnimatorCell animatorCell in animatorScrollPool.CellPool)
|
||||
{
|
||||
if (!ignoresMasterToggler[entry.Key]){
|
||||
foreach (Toggle animatorToggle in animatorToggles[entry.Key]){
|
||||
animatorToggle.isOn = enable;
|
||||
}
|
||||
if (animatorCell.animator != null && !animatorCell.IgnoreMasterToggle.isOn){
|
||||
animatorCell.AnimatorToggle.isOn = enable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,6 +98,13 @@ namespace UnityExplorer.UI.Panels
|
||||
masterAnimatorToggle.isOn = !masterAnimatorToggle.isOn;
|
||||
}
|
||||
|
||||
#if MONO
|
||||
public override void Update(){
|
||||
foreach (Animator animator in animators){
|
||||
animationEndedFunctions[animator]();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// ~~~~~~~~ UI construction / callbacks ~~~~~~~~
|
||||
|
||||
protected override void ConstructPanelContent()
|
||||
@@ -154,11 +128,33 @@ namespace UnityExplorer.UI.Panels
|
||||
default, new Color(1, 1, 1, 0), TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(firstGroup, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
Text isPlayerLabel = UIFactory.CreateLabel(secondGroup, "IsAffectedByMasterToggler", "Ignores Master Toggler", TextAnchor.MiddleLeft, Color.white, false, 15);
|
||||
UIFactory.SetLayoutElement(isPlayerLabel.gameObject, minHeight: 25, minWidth: 25, flexibleWidth: 0);
|
||||
|
||||
Text enableLabel = UIFactory.CreateLabel(secondGroup, "EnableLabel", "Enable ", TextAnchor.MiddleRight, Color.white, false, 15);
|
||||
UIFactory.SetLayoutElement(enableLabel.gameObject, minHeight: 25, minWidth: 75, flexibleWidth: 9999);
|
||||
animatorScrollPool = UIFactory.CreateScrollPool<AnimatorCell>(ContentRoot, "AnimatorsList", out GameObject scrollObj,
|
||||
out GameObject scrollContent, new Color(0.03f, 0.03f, 0.03f));
|
||||
UIFactory.SetLayoutElement(scrollObj, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
}
|
||||
|
||||
public void SetCell(AnimatorCell cell, int index)
|
||||
{
|
||||
if (index >= animators.Count)
|
||||
{
|
||||
cell.Disable();
|
||||
return;
|
||||
}
|
||||
|
||||
Animator animator = animators[index];
|
||||
if (animator == null)
|
||||
return;
|
||||
|
||||
cell.animator = animator;
|
||||
cell.name.text = animator.gameObject.name;
|
||||
|
||||
cell.MaybeSetIgnoreMasterToggleSet();
|
||||
#if MONO
|
||||
cell.DrawAnimatorPlayer();
|
||||
animationEndedFunctions[animator] = cell.IsPlayingSelectedAnimation;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void OnCellBorrowed(AnimatorCell cell) { }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user