mirror of
https://github.com/originalnicodr/CinematicUnityExplorer.git
synced 2026-10-11 09:58:02 +08:00
Added a "bones panel" in the animator to access and control the bones of a character.
This commit is contained in:
1 parent
171ee3f0d8
commit
d9ebe5819c
5 files changed
+421
-3
No files matched your search
@@ -25,6 +25,7 @@ namespace UnityExplorer.UI.Panels
|
||||
ButtonRef playButton;
|
||||
public Dropdown animatorDropdown;
|
||||
ButtonRef favAnimation;
|
||||
ButtonRef openBonesPanelButton;
|
||||
|
||||
// ICell
|
||||
public float DefaultHeight => 25f;
|
||||
@@ -155,6 +156,11 @@ namespace UnityExplorer.UI.Panels
|
||||
UIFactory.SetLayoutElement(playButton.Component.gameObject, minHeight: 25, minWidth: 90);
|
||||
playButton.OnClick += PlayButton_OnClick;
|
||||
|
||||
openBonesPanelButton = UIFactory.CreateButton(UIRoot, "OpenBonesPanelButton", "Open bones panel");
|
||||
UIFactory.SetLayoutElement(openBonesPanelButton.Component.gameObject, minWidth: 125, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0);
|
||||
|
||||
openBonesPanelButton.OnClick += () => { animatorPlayer.OpenBonesPanel(); };
|
||||
|
||||
return UIRoot;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ namespace UnityExplorer.UI.Panels
|
||||
public List<IAnimationClip> animations = new List<IAnimationClip>();
|
||||
public bool shouldIgnoreMasterToggle = false;
|
||||
|
||||
BonesManager bonesManager;
|
||||
private List<Transform> bones = new List<Transform>();
|
||||
|
||||
public IAnimationClip overridingAnimation;
|
||||
private IAnimationClip lastCurrentAnimation;
|
||||
private IRuntimeAnimatorController originalAnimatorController;
|
||||
@@ -54,6 +57,7 @@ namespace UnityExplorer.UI.Panels
|
||||
}
|
||||
|
||||
public void ResetAnimation(){
|
||||
if (bonesManager != null) bonesManager.turnOffAnimatorToggle.isOn = true;
|
||||
// Let the game change animations again
|
||||
animator.StopPlayback();
|
||||
if (originalAnimatorController != null && animator.wrappedObject != null){
|
||||
@@ -115,6 +119,20 @@ namespace UnityExplorer.UI.Panels
|
||||
animator.enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void FindBones(){
|
||||
GameObject parentObj = animator.wrappedObject.gameObject;
|
||||
SkinnedMeshRenderer skeleton = parentObj.GetComponentInChildren<SkinnedMeshRenderer>();
|
||||
bones = new List<Transform>(skeleton.bones);
|
||||
}
|
||||
|
||||
public void OpenBonesPanel(){
|
||||
if (!bones.Any()) FindBones();
|
||||
if (bonesManager == null){
|
||||
bonesManager = new BonesManager(UIManager.GetPanel<UnityExplorer.UI.Panels.AnimatorPanel>(UIManager.Panels.AnimatorPanel).Owner, bones, animator);
|
||||
}
|
||||
bonesManager.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
public class IAnimator
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UniverseLib.Input;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
|
||||
namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
public class BonesCell : ICell
|
||||
{
|
||||
public Transform bone;
|
||||
public TransformControls transformControls;
|
||||
|
||||
Text boneName;
|
||||
ComponentControl positionControl;
|
||||
ComponentControl rotationControl;
|
||||
ComponentControl scaleControl;
|
||||
public AxisComponentControl CurrentSlidingAxisControl { get; set; }
|
||||
|
||||
// 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);
|
||||
|
||||
public void SetBone(Transform bone){
|
||||
this.bone = bone;
|
||||
boneName.text = bone.name;
|
||||
}
|
||||
|
||||
public virtual GameObject CreateContent(GameObject parent)
|
||||
{
|
||||
UIRoot = UIFactory.CreateUIObject("BoneCell", parent, new Vector2(25, 25));
|
||||
Rect = UIRoot.GetComponent<RectTransform>();
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(UIRoot, false, false, true, true, 3);
|
||||
UIFactory.SetLayoutElement(UIRoot, minHeight: 25, minWidth: 50, flexibleWidth: 9999);
|
||||
|
||||
boneName = UIFactory.CreateLabel(UIRoot, $"BoneLabel", "");
|
||||
UIFactory.SetLayoutElement(boneName.gameObject, minWidth: 100, minHeight: 25);
|
||||
|
||||
positionControl = ComponentControl.Create(this, UIRoot, "Local Position", TransformType.LocalPosition);
|
||||
rotationControl = ComponentControl.Create(this, UIRoot, "Rotation", TransformType.Rotation);
|
||||
scaleControl = ComponentControl.Create(this, UIRoot, "Scale", TransformType.Scale);
|
||||
|
||||
return UIRoot;
|
||||
}
|
||||
|
||||
// TransformControls-like functions
|
||||
public void UpdateTransformControlValues(bool force){
|
||||
positionControl.Update(force);
|
||||
rotationControl.Update(force);
|
||||
scaleControl.Update(force);
|
||||
}
|
||||
|
||||
public void UpdateVectorSlider()
|
||||
{
|
||||
AxisComponentControl control = CurrentSlidingAxisControl;
|
||||
|
||||
if (control == null)
|
||||
return;
|
||||
|
||||
if (!IInputManager.GetMouseButton(0))
|
||||
{
|
||||
control.slider.value = 0f;
|
||||
control = null;
|
||||
return;
|
||||
}
|
||||
|
||||
AxisControlOperation(control.slider.value, control.parent, control.axis);
|
||||
}
|
||||
|
||||
public void AxisControlOperation(float value, ComponentControl parent, int axis)
|
||||
{
|
||||
Transform transform = bone;
|
||||
|
||||
Vector3 vector = parent.Type switch
|
||||
{
|
||||
TransformType.Position => transform.position,
|
||||
TransformType.LocalPosition => transform.localPosition,
|
||||
TransformType.Rotation => transform.localEulerAngles,
|
||||
TransformType.Scale => transform.localScale,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
// apply vector value change
|
||||
switch (axis)
|
||||
{
|
||||
case 0:
|
||||
vector.x += value; break;
|
||||
case 1:
|
||||
vector.y += value; break;
|
||||
case 2:
|
||||
vector.z += value; break;
|
||||
}
|
||||
|
||||
// set vector back to transform
|
||||
switch (parent.Type)
|
||||
{
|
||||
case TransformType.Position:
|
||||
transform.position = vector; break;
|
||||
case TransformType.LocalPosition:
|
||||
transform.localPosition = vector; break;
|
||||
case TransformType.Rotation:
|
||||
transform.localEulerAngles = vector; break;
|
||||
case TransformType.Scale:
|
||||
transform.localScale = vector; break;
|
||||
}
|
||||
|
||||
UpdateTransformControlValues(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Duplication of Vector3Control class
|
||||
public class ComponentControl
|
||||
{
|
||||
public BonesCell Owner { get; }
|
||||
public Transform Transform => Owner.bone;
|
||||
public TransformType Type { get; }
|
||||
|
||||
public InputFieldRef MainInput { get; }
|
||||
|
||||
public AxisComponentControl[] axisComponentControl { get; } = new AxisComponentControl[3];
|
||||
|
||||
public InputFieldRef IncrementInput { get; set; }
|
||||
public float Increment { get; set; } = 0.1f;
|
||||
|
||||
Vector3 lastValue;
|
||||
|
||||
Vector3 CurrentValue => Type switch
|
||||
{
|
||||
TransformType.Position => Transform.position,
|
||||
TransformType.LocalPosition => Transform.localPosition,
|
||||
TransformType.Rotation => Transform.localEulerAngles,
|
||||
TransformType.Scale => Transform.localScale,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
public ComponentControl(BonesCell cell, TransformType type, InputFieldRef input)
|
||||
{
|
||||
this.Owner = cell;
|
||||
this.Type = type;
|
||||
this.MainInput = input;
|
||||
}
|
||||
|
||||
public void Update(bool force)
|
||||
{
|
||||
// Probably not needed
|
||||
if (Transform == null) return;
|
||||
|
||||
Vector3 currValue = CurrentValue;
|
||||
if (force || (!MainInput.Component.isFocused && !lastValue.Equals(currValue)))
|
||||
{
|
||||
MainInput.Text = ParseUtility.ToStringForInput<Vector3>(currValue);
|
||||
lastValue = currValue;
|
||||
}
|
||||
}
|
||||
|
||||
void OnTransformInputEndEdit(TransformType type, string input)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case TransformType.Position:
|
||||
{
|
||||
if (ParseUtility.TryParse(input, out Vector3 val, out _))
|
||||
Transform.position = val;
|
||||
}
|
||||
break;
|
||||
case TransformType.LocalPosition:
|
||||
{
|
||||
if (ParseUtility.TryParse(input, out Vector3 val, out _))
|
||||
Transform.localPosition = val;
|
||||
}
|
||||
break;
|
||||
case TransformType.Rotation:
|
||||
{
|
||||
if (ParseUtility.TryParse(input, out Vector3 val, out _))
|
||||
Transform.localEulerAngles = val;
|
||||
}
|
||||
break;
|
||||
case TransformType.Scale:
|
||||
{
|
||||
if (ParseUtility.TryParse(input, out Vector3 val, out _))
|
||||
Transform.localScale = val;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Owner.UpdateTransformControlValues(true);
|
||||
}
|
||||
|
||||
void IncrementInput_OnEndEdit(string value)
|
||||
{
|
||||
if (!ParseUtility.TryParse(value, out float increment, out _))
|
||||
IncrementInput.Text = ParseUtility.ToStringForInput<float>(Increment);
|
||||
else
|
||||
{
|
||||
Increment = increment;
|
||||
foreach (AxisComponentControl slider in axisComponentControl)
|
||||
{
|
||||
slider.slider.minValue = -increment;
|
||||
slider.slider.maxValue = increment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ComponentControl Create(BonesCell cell, GameObject transformGroup, string title, TransformType type)
|
||||
{
|
||||
GameObject rowObj = UIFactory.CreateUIObject($"Row_{title}", transformGroup);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(rowObj, false, false, true, true, 5, 0, 0, 0, 0, default);
|
||||
UIFactory.SetLayoutElement(rowObj, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
Text titleLabel = UIFactory.CreateLabel(rowObj, "PositionLabel", title, TextAnchor.MiddleRight, Color.grey);
|
||||
UIFactory.SetLayoutElement(titleLabel.gameObject, minHeight: 25, minWidth: 110);
|
||||
|
||||
InputFieldRef inputField = UIFactory.CreateInputField(rowObj, "InputField", "...");
|
||||
UIFactory.SetLayoutElement(inputField.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 999);
|
||||
|
||||
ComponentControl control = new(cell, type, inputField);
|
||||
|
||||
inputField.Component.GetOnEndEdit().AddListener((string value) => { control.OnTransformInputEndEdit(type, value); });
|
||||
|
||||
control.axisComponentControl[0] = AxisComponentControl.Create(rowObj, "X", 0, control);
|
||||
control.axisComponentControl[1] = AxisComponentControl.Create(rowObj, "Y", 1, control);
|
||||
control.axisComponentControl[2] = AxisComponentControl.Create(rowObj, "Z", 2, control);
|
||||
|
||||
control.IncrementInput = UIFactory.CreateInputField(rowObj, "IncrementInput", "...");
|
||||
control.IncrementInput.Text = "0.1";
|
||||
UIFactory.SetLayoutElement(control.IncrementInput.GameObject, minWidth: 30, flexibleWidth: 0, minHeight: 25);
|
||||
control.IncrementInput.Component.GetOnEndEdit().AddListener(control.IncrementInput_OnEndEdit);
|
||||
|
||||
if (type == TransformType.Scale){
|
||||
GameObject extraRowObj = UIFactory.CreateUIObject($"Row_UniformScale", transformGroup);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(extraRowObj, false, false, true, true, 5, 0, 0, 0, 0, default);
|
||||
UIFactory.SetLayoutElement(extraRowObj, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
Text uniformScaleTitleLabel = UIFactory.CreateLabel(extraRowObj, "UniformScaleLabel", "Uniform Scale:", TextAnchor.MiddleRight, Color.grey);
|
||||
UIFactory.SetLayoutElement(uniformScaleTitleLabel.gameObject, minHeight: 25, minWidth: 110);
|
||||
|
||||
GameObject uniformScaleControlObj = UIFactory.CreateSlider(extraRowObj, "UniformScaleSlider", out Slider uniformScaleControl);
|
||||
UIFactory.SetLayoutElement(uniformScaleControlObj, minHeight: 25, minWidth: 200, flexibleHeight: 0);
|
||||
uniformScaleControl.minValue = 0.0001f;
|
||||
uniformScaleControl.maxValue = 10;
|
||||
uniformScaleControl.value = 1;
|
||||
uniformScaleControl.onValueChanged.AddListener((float val) => { cell.bone.localScale = new Vector3(val, val, val); });
|
||||
}
|
||||
|
||||
return control;
|
||||
}
|
||||
}
|
||||
|
||||
// // Duplication of AxisControl class
|
||||
public class AxisComponentControl
|
||||
{
|
||||
public readonly ComponentControl parent;
|
||||
|
||||
public readonly int axis;
|
||||
public readonly Slider slider;
|
||||
|
||||
public AxisComponentControl(int axis, Slider slider, ComponentControl parentControl)
|
||||
{
|
||||
this.parent = parentControl;
|
||||
this.axis = axis;
|
||||
this.slider = slider;
|
||||
}
|
||||
|
||||
void OnVectorSliderChanged(float value)
|
||||
{
|
||||
parent.Owner.CurrentSlidingAxisControl = value == 0f ? null : this;
|
||||
}
|
||||
|
||||
void OnVectorMinusClicked()
|
||||
{
|
||||
parent.Owner.AxisControlOperation(-this.parent.Increment, this.parent, this.axis);
|
||||
}
|
||||
|
||||
void OnVectorPlusClicked()
|
||||
{
|
||||
parent.Owner.AxisControlOperation(this.parent.Increment, this.parent, this.axis);
|
||||
}
|
||||
|
||||
public static AxisComponentControl Create(GameObject parent, string title, int axis, ComponentControl owner)
|
||||
{
|
||||
Text label = UIFactory.CreateLabel(parent, $"Label_{title}", $"{title}:", TextAnchor.MiddleRight, Color.grey);
|
||||
UIFactory.SetLayoutElement(label.gameObject, minHeight: 25, minWidth: 30);
|
||||
|
||||
GameObject sliderObj = UIFactory.CreateSlider(parent, $"Slider_{title}", out Slider slider);
|
||||
UIFactory.SetLayoutElement(sliderObj, minHeight: 25, minWidth: 75, flexibleWidth: 0);
|
||||
slider.m_FillImage.color = Color.clear;
|
||||
|
||||
slider.minValue = -0.1f;
|
||||
slider.maxValue = 0.1f;
|
||||
|
||||
AxisComponentControl sliderControl = new(axis, slider, owner);
|
||||
|
||||
slider.onValueChanged.AddListener(sliderControl.OnVectorSliderChanged);
|
||||
|
||||
ButtonRef minusButton = UIFactory.CreateButton(parent, "MinusIncrementButton", "-");
|
||||
UIFactory.SetLayoutElement(minusButton.GameObject, minWidth: 20, flexibleWidth: 0, minHeight: 25);
|
||||
minusButton.OnClick += sliderControl.OnVectorMinusClicked;
|
||||
|
||||
ButtonRef plusButton = UIFactory.CreateButton(parent, "PlusIncrementButton", "+");
|
||||
UIFactory.SetLayoutElement(plusButton.GameObject, minWidth: 20, flexibleWidth: 0, minHeight: 25);
|
||||
plusButton.OnClick += sliderControl.OnVectorPlusClicked;
|
||||
|
||||
return sliderControl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ namespace UnityExplorer.UI.Panels
|
||||
|
||||
public override string Name => "Animator";
|
||||
public override UIManager.Panels PanelType => UIManager.Panels.AnimatorPanel;
|
||||
public override int MinWidth => 900;
|
||||
public override int MinWidth => 1050;
|
||||
public override int MinHeight => 200;
|
||||
public override Vector2 DefaultAnchorMin => new(0.4f, 0.4f);
|
||||
public override Vector2 DefaultAnchorMax => new(0.6f, 0.6f);
|
||||
@@ -47,8 +47,6 @@ namespace UnityExplorer.UI.Panels
|
||||
animatorScrollPool.Initialize(this);
|
||||
DoneScrollPoolInit = true;
|
||||
}
|
||||
|
||||
animatorScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
private void FindAllAnimators(){
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Panels;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
|
||||
namespace UnityExplorer.UI.Panels
|
||||
{
|
||||
public class BonesManager : PanelBase, ICellPoolDataSource<BonesCell>
|
||||
{
|
||||
public override string Name => $"Bones Manager";
|
||||
public override int MinWidth => 1000;
|
||||
public override int MinHeight => 400;
|
||||
public override Vector2 DefaultAnchorMin => Vector2.zero;
|
||||
public override Vector2 DefaultAnchorMax => Vector2.zero;
|
||||
public Toggle turnOffAnimatorToggle;
|
||||
|
||||
private IAnimator animator;
|
||||
private Text skeletonName;
|
||||
private List<Transform> bones = new List<Transform>();
|
||||
|
||||
private ScrollPool<BonesCell> boneScrollPool;
|
||||
public int ItemCount => bones.Count;
|
||||
private bool DoneScrollPoolInit;
|
||||
|
||||
public BonesManager(UIBase owner, List<Transform> bones, IAnimator animator) : base(owner)
|
||||
{
|
||||
this.bones = bones;
|
||||
this.animator = animator;
|
||||
skeletonName.text = $"Skeleton: {animator?.name}";
|
||||
}
|
||||
|
||||
public override void SetActive(bool active)
|
||||
{
|
||||
base.SetActive(active);
|
||||
|
||||
if (active && !DoneScrollPoolInit)
|
||||
{
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(this.Rect);
|
||||
boneScrollPool.Initialize(this);
|
||||
DoneScrollPoolInit = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ConstructPanelContent()
|
||||
{
|
||||
skeletonName = UIFactory.CreateLabel(ContentRoot, $"SkeletonName", "");
|
||||
UIFactory.SetLayoutElement(skeletonName.gameObject, minWidth: 100, minHeight: 25);
|
||||
skeletonName.fontSize = 16;
|
||||
|
||||
GameObject turnOffAnimatorToggleObj = UIFactory.CreateToggle(ContentRoot, "Animator toggle", out turnOffAnimatorToggle, out Text turnOffAnimatorToggleText);
|
||||
UIFactory.SetLayoutElement(turnOffAnimatorToggleObj, minHeight: 25, flexibleWidth: 9999);
|
||||
turnOffAnimatorToggle.onValueChanged.AddListener(value => { animator.enabled = value; });
|
||||
turnOffAnimatorToggleText.text = "Toggle animator (needs to be off to move bones)";
|
||||
|
||||
boneScrollPool = UIFactory.CreateScrollPool<BonesCell>(ContentRoot, "BonesList", out GameObject scrollObj,
|
||||
out GameObject scrollContent, new Color(0.06f, 0.06f, 0.06f));
|
||||
UIFactory.SetLayoutElement(scrollObj, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
}
|
||||
|
||||
public void SetCell(BonesCell cell, int index)
|
||||
{
|
||||
if (index >= bones.Count)
|
||||
{
|
||||
cell.Disable();
|
||||
return;
|
||||
}
|
||||
|
||||
Transform bone = bones[index];
|
||||
cell.SetBone(bone);
|
||||
cell.UpdateTransformControlValues(true);
|
||||
}
|
||||
|
||||
public void OnCellBorrowed(BonesCell cell) {
|
||||
cell.UpdateVectorSlider();
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
foreach(BonesCell boneCell in boneScrollPool.CellPool) {
|
||||
boneCell.UpdateVectorSlider();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user