1
0
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:
originalnicodr
2024-02-13 00:48:22 -03:00
parent 4f91fdfa66
commit 59ffa462cc
2 changed files with 229 additions and 80 deletions
+153
View File
@@ -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;
}
}
}
+76 -80
View File
@@ -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) { }
}
}