diff --git a/UnityEditorPackage/Runtime/UnityExplorer.STANDALONE.Mono.dll b/UnityEditorPackage/Runtime/UnityExplorer.STANDALONE.Mono.dll index 707214b..feef935 100644 Binary files a/UnityEditorPackage/Runtime/UnityExplorer.STANDALONE.Mono.dll and b/UnityEditorPackage/Runtime/UnityExplorer.STANDALONE.Mono.dll differ diff --git a/src/Cinematic/Cells/AnimatorCell.cs b/src/Cinematic/Cells/AnimatorCell.cs index a80a4e9..8114aae 100644 --- a/src/Cinematic/Cells/AnimatorCell.cs +++ b/src/Cinematic/Cells/AnimatorCell.cs @@ -4,18 +4,26 @@ using UniverseLib.UI.Models; using UniverseLib.UI.Widgets.ScrollView; #if CPP -using Animator = UnityEngine.Behaviour; +#if INTEROP +using Il2CppInterop.Runtime.InteropTypes.Arrays; +using Il2CppInterop.Runtime; +#else +using UnhollowerRuntimeLib; +using UnhollowerBaseLib; +#endif #endif namespace UnityExplorer.UI.Panels { public class AnimatorCell : ICell { + public AnimatorPlayer animatorPlayer; + public Toggle IgnoreMasterToggle; public Toggle AnimatorToggle; - public ButtonRef inspectButton; - public Animator animator; + ButtonRef playButton; + public Dropdown animatorDropdown; // ICell public float DefaultHeight => 25f; @@ -26,100 +34,34 @@ namespace UnityExplorer.UI.Panels 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 Dropdown animatorDropdown; + public void SetAnimatorPlayer(AnimatorPlayer animatorPlayer){ + this.animatorPlayer = animatorPlayer; + inspectButton.ButtonText.text = animatorPlayer.animator.name; + IgnoreMasterToggle.isOn = animatorPlayer.shouldIgnoreMasterToggle; + AnimatorToggle.isOn = animatorPlayer.animator.speed != 0; - private AnimationClip overridingAnimation; - private AnimationClip originalAnimation; - private RuntimeAnimatorController originalAnimatorController; - - public void DrawAnimatorPlayer(){ - if (playButton == null){ - List animations = animator.runtimeAnimatorController.animationClips.OrderBy(x=>x.name).Where(c => c.length > 0).Distinct().ToList(); - - originalAnimatorController = animator.runtimeAnimatorController; - - //ExplorerCore.LogWarning(animations.Count); - AnimatorClipInfo[] playingAnimations = animator.GetCurrentAnimatorClipInfo(0); - overridingAnimation = playingAnimations.Count() != 0 ? playingAnimations[0].clip : animations[0]; - - ButtonRef prevAnimation = UIFactory.CreateButton(UIRoot, "PreviousAnimation", "◀", new Color(0.05f, 0.05f, 0.05f)); - UIFactory.SetLayoutElement(prevAnimation.Component.gameObject, minHeight: 25, minWidth: 25); - prevAnimation.OnClick += () => animatorDropdown.value = animatorDropdown.value == 0 ? animatorDropdown.options.Count - 1 : animatorDropdown.value - 1; - - GameObject overridingAnimationObj = UIFactory.CreateDropdown(UIRoot, $"Animations_{animator.name}", out animatorDropdown, null, 14, (idx) => overridingAnimation = animations[idx]); - UIFactory.SetLayoutElement(overridingAnimationObj, minHeight: 25, minWidth: 200); - foreach (AnimationClip animation in animations) + // Updated dropdown options + if (animatorPlayer.animator.runtimeAnimatorController != null){ + animatorDropdown.options.Clear(); + foreach (IAnimationClip animation in animatorPlayer.animations) animatorDropdown.options.Add(new Dropdown.OptionData(animation.name)); - animatorDropdown.value = Math.Max(0, animations.FindIndex(a => a == overridingAnimation)); - if (animatorDropdown.value == 0) animatorDropdown.captionText.text = animations[0].name; - - ButtonRef nextAnimation = UIFactory.CreateButton(UIRoot, "NextAnimation", "▶", new Color(0.05f, 0.05f, 0.05f)); - UIFactory.SetLayoutElement(nextAnimation.Component.gameObject, minHeight: 25, minWidth: 25); - nextAnimation.OnClick += () => animatorDropdown.value = animatorDropdown.value == animatorDropdown.options.Count - 1 ? 0 : animatorDropdown.value + 1; - - playButton = UIFactory.CreateButton(UIRoot, "PlayButton", "Play", new Color(0.2f, 0.26f, 0.2f)); - UIFactory.SetLayoutElement(playButton.Component.gameObject, minHeight: 25, minWidth: 90); - playButton.OnClick += PlayButton_OnClick; + animatorDropdown.value = Math.Max(0, animatorPlayer.animations.FindIndex(a => a.name == animatorPlayer.overridingAnimation.name)); + if (animatorDropdown.value == 0) animatorDropdown.captionText.text = animatorPlayer.animations[0].name; } } private void PlayButton_OnClick(){ - // We save the last animation played by the game in case we want to go back to it - if (originalAnimation == null){ - AnimatorClipInfo[] playingAnimations = animator.GetCurrentAnimatorClipInfo(0); - originalAnimation = playingAnimations.Count() != 0 ? playingAnimations[0].clip : null; - } - // Need to assign the original animator controller back so to get the correct currentClip - animator.runtimeAnimatorController = originalAnimatorController; - AnimationClip currentClip = animator.GetCurrentAnimatorClipInfo(0)[0].clip; - - AnimatorOverrideController animatorOverrideController = new AnimatorOverrideController(); - animatorOverrideController.runtimeAnimatorController = originalAnimatorController; - animator.runtimeAnimatorController = animatorOverrideController; - - animatorOverrideController[currentClip.name] = overridingAnimation; - - animator.Play(currentClip.name); - - // Turn the toggle on to keep consistency + animatorPlayer.PlayOverridingAnimation(); + // Turn the toggle on to play the animation if (!AnimatorToggle.isOn) AnimatorToggle.isOn = true; } - private List GetAllAnimations(){ - List allAnimations = new List(); - for (int layer = 0; layer < animator.layerCount; layer++){ - allAnimations.AddRange(animator.GetCurrentAnimatorClipInfo(layer).Select(ainfo => ainfo.clip).ToList()); - } - return allAnimations; - } - - public void ResetAnimation(){ - animator.runtimeAnimatorController = originalAnimatorController; - } -#endif - 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 => { - try { - Type animatorClass = ReflectionUtility.GetTypeByName("UnityEngine.Animator"); - - PropertyInfo animatorSpeed = animatorClass.GetProperty("speed"); - animatorSpeed.SetValue(animator, value ? 1 : 0, null); - } - catch { - // Fallback in case reflection isn't working - animator.enabled = value; - } - } - ); + AnimatorToggle.onValueChanged.AddListener(EnableAnimation); UIRoot = AnimatorToggleObj; UIRoot.SetActive(false); @@ -133,29 +75,62 @@ namespace UnityExplorer.UI.Panels UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 25, flexibleHeight: 0); inspectButton = UIFactory.CreateButton(UIRoot, "InspectButton", ""); - UIFactory.SetLayoutElement(inspectButton.GameObject, minWidth: 25, minHeight: 25, flexibleWidth: 9999); - inspectButton.OnClick += () => InspectorManager.Inspect(animator.gameObject); -#if MONO + UIFactory.SetLayoutElement(inspectButton.GameObject, minWidth: 250, minHeight: 25); + inspectButton.OnClick += () => InspectorManager.Inspect(animatorPlayer.animator.gameObject); + 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); + UIFactory.SetLayoutElement(ignoresMasterTogglerObj, minHeight: 25, minWidth: 200); IgnoreMasterToggle.isOn = false; IgnoreMasterToggle.onValueChanged.AddListener(IgnoreMasterToggle_Clicked); ignoreMasterToggleText.text = "Ignore Master Toggle "; + ButtonRef prevAnimation = UIFactory.CreateButton(UIRoot, "PreviousAnimation", "◀", new Color(0.05f, 0.05f, 0.05f)); + UIFactory.SetLayoutElement(prevAnimation.Component.gameObject, minHeight: 25, minWidth: 25); + prevAnimation.OnClick += () => { + if (animatorPlayer.animator.wrappedObject == null || animatorDropdown == null) + return; + animatorDropdown.value = animatorDropdown.value == 0 ? animatorDropdown.options.Count - 1 : animatorDropdown.value - 1; + }; + + GameObject overridingAnimationObj = UIFactory.CreateDropdown(UIRoot, $"Animations_Dropdown", out animatorDropdown, null, 14, (idx) => { + if (animatorPlayer.animator.wrappedObject == null) + return; + animatorPlayer.overridingAnimation = idx < animatorPlayer.animations.Count() ? animatorPlayer.animations[idx] : animatorPlayer.overridingAnimation; + } + ); + + UIFactory.SetLayoutElement(overridingAnimationObj, minHeight: 25, minWidth: 200, flexibleWidth: 9999); + + ButtonRef nextAnimation = UIFactory.CreateButton(UIRoot, "NextAnimation", "▶", new Color(0.05f, 0.05f, 0.05f)); + UIFactory.SetLayoutElement(nextAnimation.Component.gameObject, minHeight: 25, minWidth: 25); + nextAnimation.OnClick += () => { + if (animatorPlayer.animator.wrappedObject == null || animatorDropdown == null) + return; + animatorDropdown.value = animatorDropdown.value == animatorDropdown.options.Count - 1 ? 0 : animatorDropdown.value + 1; + }; + + playButton = UIFactory.CreateButton(UIRoot, "PlayButton", "Play", new Color(0.2f, 0.26f, 0.2f)); + UIFactory.SetLayoutElement(playButton.Component.gameObject, minHeight: 25, minWidth: 90); + playButton.OnClick += PlayButton_OnClick; + return UIRoot; } - internal void IgnoreMasterToggle_Clicked(bool value){ - GetAnimatorPanel().shouldIgnoreMasterToggle[animator] = value; + public void ResetAnimation(){ + animatorPlayer.ResetAnimation(); } - public UnityExplorer.UI.Panels.AnimatorPanel GetAnimatorPanel(){ - return UIManager.GetPanel(UIManager.Panels.AnimatorPanel); + internal void IgnoreMasterToggle_Clicked(bool value){ + animatorPlayer.shouldIgnoreMasterToggle = value; + } + + internal void EnableAnimation(bool value){ + if (animatorPlayer.animator.wrappedObject != null) + animatorPlayer.animator.speed = value ? 1 : 0; } } } diff --git a/src/Cinematic/Cells/AnimatorPlayer.cs b/src/Cinematic/Cells/AnimatorPlayer.cs new file mode 100644 index 0000000..046350f --- /dev/null +++ b/src/Cinematic/Cells/AnimatorPlayer.cs @@ -0,0 +1,415 @@ +using UnityEngine; +using UniverseLib.UI; +using UniverseLib.UI.Models; +using UniverseLib.UI.Widgets.ScrollView; + +#if CPP +#if INTEROP +using Il2CppInterop.Runtime.InteropTypes.Arrays; +using Il2CppInterop.Runtime; +#else +using UnhollowerRuntimeLib; +using UnhollowerBaseLib; +#endif +#endif + +namespace UnityExplorer.UI.Panels +{ + public class AnimatorPlayer { + public IAnimator animator {get;} + public List animations = new List(); + public bool shouldIgnoreMasterToggle = false; + + public IAnimationClip overridingAnimation; + private IAnimationClip originalAnimation; + //private IAnimationClip lastManuallyPlayedAnimation; + private IRuntimeAnimatorController originalAnimatorController; + + public AnimatorPlayer(Behaviour animator){ + this.animator = new IAnimator(animator); + if (this.animator.runtimeAnimatorController != null){ + this.animations = this.animator.runtimeAnimatorController.animationClips.OrderBy(x=>x.name).Where(c => c.length > 0).Distinct().ToList(); + // Simple heuristic to try and find the player character + this.shouldIgnoreMasterToggle = animator.gameObject.name.IndexOf("play", 0, StringComparison.OrdinalIgnoreCase) >= 0; + } + + this.originalAnimatorController = this.animator.runtimeAnimatorController; + + IAnimatorClipInfo[] playingAnimations = this.animator.GetCurrentAnimatorClipInfo(0); + this.originalAnimation = playingAnimations.Count() != 0 ? playingAnimations[0].clip : null; + this.overridingAnimation = originalAnimation != null ? originalAnimation : (animations.Count > 0 ? animations[0] : null); + } + + // Include the animations being played in other layers + private List GetAllCurrentlyPlayingAnimations(){ + List allAnimations = new List(); + for (int layer = 0; layer < animator.layerCount; layer++){ + allAnimations.AddRange(animator.GetCurrentAnimatorClipInfo(layer).Select(ainfo => ainfo.clip).ToList()); + } + return allAnimations; + } + + public void ResetAnimation(){ + if (originalAnimatorController != null && animator.wrappedObject != null){ + if (animator.runtimeAnimatorController != null && originalAnimatorController != null){ + animator.runtimeAnimatorController = originalAnimatorController; + if (originalAnimation != null) animator.Play(originalAnimation.name); + } + } + } + + public void PlayOverridingAnimation(){ + // Need to assign the original animator controller back so to get the correct currentAnimation + animator.runtimeAnimatorController = originalAnimatorController; + IAnimationClip currentAnimation = animator.GetCurrentAnimatorClipInfo(0)[0].clip; + + IAnimatorOverrideController animatorOverrideController = new IAnimatorOverrideController(); + animatorOverrideController.runtimeAnimatorController = originalAnimatorController; + // Actually uses runtimeAnimatorController on the original object, should make the wrapper class a child of the runtimeAnimatorController instead + animator.runtimeAnimatorControllerOverride = animatorOverrideController; + + animatorOverrideController[originalAnimation.name] = overridingAnimation; + animator.Play(originalAnimation.name); + + // Save the last non-manually played animation as the original animations, + // So we can reset to the last animation that the game played on the entity on reset. + /* + if (currentAnimation != lastManuallyPlayedAnimation) + originalAnimation = currentAnimation; + lastManuallyPlayedAnimation = overridingAnimation; + */ + } + + public override string ToString(){ + return animator.name; + } + + public bool enabled + { + get { + return animator.enabled; + } + set { + animator.enabled = value; + } + } + } + + public class IAnimator + { + Behaviour _animator; + Type realType; + public Behaviour wrappedObject => _animator; + + public IAnimator(Behaviour animator){ + _animator = animator; + realType = ReflectionUtility.GetTypeByName("UnityEngine.Animator"); + } + + public IRuntimeAnimatorController runtimeAnimatorController + { + get { + PropertyInfo animatorRuntimeAnimatorController = realType.GetProperty("runtimeAnimatorController"); + object runtimeAnimatorControllerObject = animatorRuntimeAnimatorController.GetValue(_animator.TryCast(), null); + if (runtimeAnimatorControllerObject == null) + return null; + return new IRuntimeAnimatorController(runtimeAnimatorControllerObject); + } + set { + PropertyInfo animatorRuntimeAnimatorController = realType.GetProperty("runtimeAnimatorController"); + animatorRuntimeAnimatorController.SetValue(_animator.TryCast(), value.wrappedObject.TryCast(), null); + } + } + + public IAnimatorOverrideController runtimeAnimatorControllerOverride + { + set { + PropertyInfo animatorRuntimeAnimatorController = realType.GetProperty("runtimeAnimatorController"); + animatorRuntimeAnimatorController.SetValue(_animator.TryCast(), value.wrappedObject.TryCast(), null); + } + } + + public IAnimatorClipInfo[] GetCurrentAnimatorClipInfo(int layer){ + MethodInfo getCurrentAnimatorClipInfo = realType.GetMethod("GetCurrentAnimatorClipInfo", new Type[] {typeof(int)}); + object resultArray = getCurrentAnimatorClipInfo.Invoke(_animator.TryCast(), new object[] {layer}); + + if (resultArray is Array sourceArray){ + List convertedList = new List(); + + foreach (var item in sourceArray) + { + var convertedItem = new IAnimatorClipInfo(item.TryCast()); + convertedList.Add(convertedItem); + } + return convertedList.ToArray(); + } +#if CPP + Type genericTypeDefinition = typeof(Il2CppStructArray<>); + Type animatirClipInfoType = ReflectionUtility.GetTypeByName("UnityEngine.AnimatorClipInfo"); + Type constructedType = genericTypeDefinition.MakeGenericType(animatirClipInfoType); + + object castedResult = Convert.ChangeType(resultArray, constructedType); + Type enumerableType = typeof(IEnumerable<>).MakeGenericType(animatirClipInfoType); + MethodInfo getEnumeratorMethod = enumerableType.GetMethod("GetEnumerator"); + if (getEnumeratorMethod != null) + { + object enumerator = getEnumeratorMethod.Invoke(castedResult, null); + MethodInfo moveNextMethod = enumerator.GetType().GetMethod("MoveNext"); + PropertyInfo currentProperty = enumerator.GetType().GetProperty("Current"); + List convertedIL2CPPList = new List(); + + // Iterate over the elements using reflection + while ((bool)moveNextMethod.Invoke(enumerator, null)) + { + object item = currentProperty.GetValue(enumerator); + var convertedItem = new IAnimatorClipInfo(item.TryCast()); + convertedIL2CPPList.Add(convertedItem); + } + return convertedIL2CPPList.ToArray(); + } +#endif + return new IAnimatorClipInfo[] {}; + } + + public string name + { + get { + PropertyInfo animatorName = realType.GetProperty("name"); + return animatorName.GetValue(_animator.TryCast(), null).ToString(); + } + } + + public int layerCount + { + get { + PropertyInfo animatorLayerCount = realType.GetProperty("layerCount"); + return (int) animatorLayerCount.GetValue(_animator.TryCast(), null); + } + } + + public void Play(string animatorClip){ + MethodInfo play = realType.GetMethod("Play", new Type[] {typeof(string), typeof(int), typeof(float)}); + play.Invoke(_animator.TryCast(), new object[] {animatorClip, -1, 0f}); + } + + public float speed + { + get { + PropertyInfo animatorSpeed = realType.GetProperty("speed"); + return (float) animatorSpeed.GetValue(_animator.TryCast(), null); + } + set { + PropertyInfo animatorSpeed = realType.GetProperty("speed"); + animatorSpeed.SetValue(_animator.TryCast(), value, null); + } + } + + public bool enabled + { + get { + PropertyInfo animatorEnabled = realType.GetProperty("enabled"); + return (bool) animatorEnabled.GetValue(_animator.TryCast(), null); + } + set { + PropertyInfo animatorEnabled = realType.GetProperty("enabled"); + animatorEnabled.SetValue(_animator.TryCast(), value, null); + } + } + + public GameObject gameObject + { + get { + PropertyInfo animatorGameObject = realType.GetProperty("gameObject"); + return (GameObject) animatorGameObject.GetValue(_animator.TryCast(), null); + } + } + } + + public class IAnimationClip : IEquatable + { + object _animationClip; + Type realType; + public object wrappedObject => _animationClip; + + public IAnimationClip(object animationClip){ + _animationClip = animationClip; + realType = ReflectionUtility.GetTypeByName("UnityEngine.AnimationClip"); + } + + public string name + { + get { + PropertyInfo animatorName = realType.GetProperty("name"); + return animatorName.GetValue(_animationClip.TryCast(), null).ToString(); + } + } + + public float length + { + get { + PropertyInfo animatorName = realType.GetProperty("length"); + return (float) animatorName.GetValue(_animationClip.TryCast(), null); + } + } + + public override bool Equals(object obj) + { + if (obj == null) + { + return false; + } + + if (!(obj is IAnimationClip)) + { + return false; + } + + return Equals(obj as IAnimationClip); + } + + public bool Equals(IAnimationClip other) + { + if (other == null) + { + return false; + } + + return name == other.name; + } + + public override int GetHashCode() + { + return name.GetHashCode(); + } + } + + public class IAnimatorClipInfo + { + object _animationClipInfo; + Type realType; + + public IAnimatorClipInfo(object animationClipInfo){ + _animationClipInfo = animationClipInfo; + realType = ReflectionUtility.GetTypeByName("UnityEngine.AnimatorClipInfo"); + } + + public IAnimationClip clip + { + get { + PropertyInfo animationClipInfoClip = realType.GetProperty("clip"); + return new IAnimationClip(animationClipInfoClip.GetValue(_animationClipInfo.TryCast(), null)); + } + } + } + + public class IRuntimeAnimatorController + { + object _runtimeAnimatorController; + Type realType; + public object wrappedObject => _runtimeAnimatorController; + + public IRuntimeAnimatorController(object runtimeAnimatorController){ + _runtimeAnimatorController = runtimeAnimatorController; + realType = ReflectionUtility.GetTypeByName("UnityEngine.RuntimeAnimatorController"); + } + + public IAnimationClip this[string key] + { + get + { + PropertyInfo indexerProperty = realType.GetProperty("Item", new[] { typeof(string) }); + return new IAnimationClip(indexerProperty.GetValue(_runtimeAnimatorController.TryCast(), new object[] { key })); + } + set + { + PropertyInfo indexerProperty = realType.GetProperty("Item", new[] { typeof(string) }); + indexerProperty.SetValue(_runtimeAnimatorController.TryCast(), value, new object[] { key }); + } + } + + public IAnimationClip[] animationClips + { + get { + PropertyInfo runtimeAnimatorControllerAnimationClips = realType.GetProperty("animationClips"); + object resultArray = runtimeAnimatorControllerAnimationClips.GetValue(_runtimeAnimatorController.TryCast(), null); + + if (resultArray is Array sourceArray){ + List convertedList = new List(); + + foreach (var item in sourceArray) + { + var convertedItem = new IAnimationClip(item.TryCast()); + convertedList.Add(convertedItem); + } + return convertedList.ToArray(); + } +#if CPP + Type genericTypeDefinition = typeof(Il2CppReferenceArray<>); + Type animationClipType = ReflectionUtility.GetTypeByName("UnityEngine.AnimationClip"); + Type constructedType = genericTypeDefinition.MakeGenericType(animationClipType); + + object castedResult = Convert.ChangeType(resultArray, constructedType); + Type enumerableType = typeof(IEnumerable<>).MakeGenericType(animationClipType); + MethodInfo getEnumeratorMethod = enumerableType.GetMethod("GetEnumerator"); + if (getEnumeratorMethod != null) + { + object enumerator = getEnumeratorMethod.Invoke(castedResult, null); + MethodInfo moveNextMethod = enumerator.GetType().GetMethod("MoveNext"); + PropertyInfo currentProperty = enumerator.GetType().GetProperty("Current"); + List convertedIL2CPPList = new List(); + + // Iterate over the elements using reflection + while ((bool)moveNextMethod.Invoke(enumerator, null)) + { + object item = currentProperty.GetValue(enumerator); + var convertedItem = new IAnimationClip(item.TryCast()); + convertedIL2CPPList.Add(convertedItem); + } + return convertedIL2CPPList.ToArray(); + } +#endif + return new IAnimationClip[] {}; + } + } + } + + public class IAnimatorOverrideController + { + object _animatorOverrideController; + Type realType; + public object wrappedObject => _animatorOverrideController; + + public IAnimatorOverrideController(){ + realType = ReflectionUtility.GetTypeByName("UnityEngine.AnimatorOverrideController"); + ConstructorInfo constructor = realType.GetConstructor(Type.EmptyTypes); + _animatorOverrideController = constructor.Invoke(null); + } + + public IRuntimeAnimatorController runtimeAnimatorController + { + get { + PropertyInfo animatorOverrideControllerRuntimeAnimatorController = realType.GetProperty("runtimeAnimatorController"); + return new IRuntimeAnimatorController(animatorOverrideControllerRuntimeAnimatorController.GetValue(_animatorOverrideController.TryCast(), null)); + } + + set { + PropertyInfo animatorOverrideControllerRuntimeAnimatorController = realType.GetProperty("runtimeAnimatorController"); + animatorOverrideControllerRuntimeAnimatorController.SetValue(_animatorOverrideController.TryCast(), value.wrappedObject.TryCast(), null); + } + } + + public IAnimationClip this[string key] + { + get + { + PropertyInfo indexerProperty = realType.GetProperty("Item", new[] { typeof(string) }); + return new IAnimationClip(indexerProperty.GetValue(_animatorOverrideController.TryCast(), new object[] { key })); + } + set + { + PropertyInfo indexerProperty = realType.GetProperty("Item", new[] { typeof(string) }); + indexerProperty.SetValue(_animatorOverrideController.TryCast(), value.wrappedObject.TryCast(), new object[] { key }); + } + } + } +} diff --git a/src/UI/Panels/AnimatorPanel.cs b/src/UI/Panels/AnimatorPanel.cs index cd32fcf..1e83833 100644 --- a/src/UI/Panels/AnimatorPanel.cs +++ b/src/UI/Panels/AnimatorPanel.cs @@ -13,10 +13,6 @@ using UnhollowerRuntimeLib; using Il2CppInterop.Runtime.Injection; #endif -#if CPP -using Animator = UnityEngine.Behaviour; -#endif - namespace UnityExplorer.UI.Panels { public class AnimatorPanel : UEPanel, ICellPoolDataSource @@ -37,10 +33,8 @@ namespace UnityExplorer.UI.Panels Toggle masterAnimatorToggle = new Toggle(); private static ScrollPool animatorScrollPool; - internal List animators = new List(); - public Dictionary shouldIgnoreMasterToggle = new Dictionary(); + internal List animators = new List(); public int ItemCount => animators.Count; - private static bool DoneScrollPoolInit; public override void SetActive(bool active) @@ -60,28 +54,31 @@ namespace UnityExplorer.UI.Panels private void FindAllAnimators(){ // Enable all animators on refresh if (animators.Count != 0) { - masterAnimatorToggle.isOn = true; // Will also trigger "MasterToggleAnimators(true)" - animators.Clear(); - shouldIgnoreMasterToggle.Clear(); -#if MONO - // TODO: Move the reset behavior to this panel, because we would only get the cells being currently displayed. - foreach (AnimatorCell animatorCell in animatorScrollPool.CellPool) + foreach (AnimatorPlayer animatorPlayer in animators) { - animatorCell.ResetAnimation(); + animatorPlayer.ResetAnimation(); } -#endif + masterAnimatorToggle.isOn = true; // Will also trigger "MasterToggleAnimators(true)" } Type searchType = ReflectionUtility.GetTypeByName("UnityEngine.Animator"); searchType = searchType is Type type ? type : searchType.GetActualType(); - animators = RuntimeHelper.FindObjectsOfTypeAll(searchType).Select(obj => obj.TryCast()) + List newAnimators = RuntimeHelper.FindObjectsOfTypeAll(searchType).Select(obj => obj.TryCast()) .Where(a => a.GetComponentsInChildren(false).Length != 0 && a.enabled && a.GetComponentsInChildren(false).Length != 0) .OrderBy(x=>x.name) + .Select(a => new AnimatorPlayer(a)) .ToList(); - foreach(Animator animator in animators){ - shouldIgnoreMasterToggle[animator] = animator.gameObject.name.IndexOf("play", 0, StringComparison.OrdinalIgnoreCase) >= 0; + // If there are old animators in the new list keep the old object with its properties. + for(int i = 0; i < animators.Count; i++) + { + if (animators[i].animator.wrappedObject != null){ + int newAnimatorsIndex = newAnimators.FindIndex(a => a.animator.wrappedObject == animators[i].animator.wrappedObject); + if (newAnimatorsIndex != -1) + newAnimators[newAnimatorsIndex] = animators[i]; + } } + animators = newAnimators; animatorScrollPool.Refresh(true, false); } @@ -90,32 +87,14 @@ namespace UnityExplorer.UI.Panels // Load animators for the first time if there are not any if (animators.Count == 0) FindAllAnimators(); - foreach (AnimatorCell animatorCell in animatorScrollPool.CellPool) - { - if (animatorCell.animator != null && !animatorCell.IgnoreMasterToggle.isOn){ - animatorCell.AnimatorToggle.isOn = enable; + foreach (AnimatorPlayer animatorPlayer in animators){ + if (!animatorPlayer.shouldIgnoreMasterToggle){ + if (animatorPlayer.animator.wrappedObject != null) + animatorPlayer.animator.speed = enable ? 1 : 0; } } - // We gotta do this for the animators which cell are not being currently rendered. - foreach (Animator animator in animators){ - if (!shouldIgnoreMasterToggle[animator]){ - try { - Type animatorClass = ReflectionUtility.GetTypeByName("UnityEngine.Animator"); - if (enable) { - MethodInfo stopPlayBack = animatorClass.GetMethod("StopPlayback"); - stopPlayBack.Invoke(animator.TryCast(), null); - } else { - MethodInfo startPlayBack = animatorClass.GetMethod("StartPlayback"); - startPlayBack.Invoke(animator.TryCast(), null); - } - } - catch { - // Fallback in case reflection isn't working - animator.enabled = enable; - } - } - } + animatorScrollPool.Refresh(true, false); } public void HotkeyToggleAnimators(){ @@ -158,17 +137,19 @@ namespace UnityExplorer.UI.Panels return; } - Animator animator = animators[index]; - if (animator == null) + AnimatorPlayer animatorPlayer = animators[index]; + + if (animatorPlayer.animator.wrappedObject == null) return; + // Check if the animator wrapped object was deleted by trying to access one of its properties + try { + string check = animatorPlayer.animator.name; + } + catch { + return; + } - cell.animator = animator; - cell.inspectButton.ButtonText.text = animator.gameObject.name; - cell.IgnoreMasterToggle.isOn = shouldIgnoreMasterToggle[animator]; - -#if MONO - cell.DrawAnimatorPlayer(); -#endif + cell.SetAnimatorPlayer(animatorPlayer); } public void OnCellBorrowed(AnimatorCell cell) { }