204 lines
11 KiB
C#
204 lines
11 KiB
C#
using System;
|
||
using System.Linq;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.EventSystems;
|
||
using UnityEngine.UI;
|
||
|
||
namespace AIGame.Review
|
||
{
|
||
/// <summary>Controls scene-authored Animator instances and JSON-authored UI.</summary>
|
||
public sealed class BotAnimationLab : MonoBehaviour
|
||
{
|
||
[Serializable] public sealed class Actor
|
||
{
|
||
public string label;
|
||
public Animator animator;
|
||
public Transform stage;
|
||
public AnimationClip[] clips;
|
||
public SkinnedMeshRenderer[] skins;
|
||
public Material[] originalMaterials;
|
||
}
|
||
public Actor[] actors;
|
||
public Transform ui;
|
||
public Camera reviewCamera;
|
||
public Camera[] comparisonCameras;
|
||
public Material clayMaterial;
|
||
public bool playing = true;
|
||
public bool repeatAttacks;
|
||
public bool gray;
|
||
public float speed = 1;
|
||
public int selectedMotion;
|
||
public int focus = -1;
|
||
public int detail;
|
||
public float yaw;
|
||
public float zoom = 1;
|
||
public static readonly string[] StateNames = { "Idle", "Walk", "Attack01", "Attack02" };
|
||
TMP_Text stateText, timeText, playText, loopText, materialText, detailText;
|
||
RectTransform progress;
|
||
float progressWidth;
|
||
Vector3 lastMouse;
|
||
bool dragging;
|
||
bool previousRunInBackground;
|
||
|
||
Actor ReferenceActor => actors[focus >= 0 ? focus : 0];
|
||
public float Duration => ReferenceActor.clips[selectedMotion].length;
|
||
public float TimePosition => Mathf.Clamp01(ReferenceActor.animator.GetCurrentAnimatorStateInfo(0).normalizedTime % 1f) * Duration;
|
||
void OnEnable()
|
||
{
|
||
if (!Application.isPlaying) return;
|
||
previousRunInBackground = Application.runInBackground;
|
||
Application.runInBackground = true;
|
||
}
|
||
void OnDisable()
|
||
{
|
||
if (Application.isPlaying) Application.runInBackground = previousRunInBackground;
|
||
}
|
||
void Awake()
|
||
{
|
||
var buttons = ui.GetComponentsInChildren<Button>(true).ToDictionary(b => b.name);
|
||
void Bind(string name, UnityEngine.Events.UnityAction action) => buttons["Btn_" + name].onClick.AddListener(action);
|
||
Bind("Idle", () => SelectMotion(0)); Bind("Walk", () => SelectMotion(1));
|
||
Bind("Attack01", () => SelectMotion(2)); Bind("Attack02", () => SelectMotion(3));
|
||
Bind("Play", () => { playing = !playing; RefreshUI(); });
|
||
Bind("Previous", () => Step(-1)); Bind("Next", () => Step(1));
|
||
Bind("Restart", () => { bool resume = playing; Seek(0); playing = resume; RefreshUI(); });
|
||
Bind("Speed025", () => SetSpeed(.25f)); Bind("Speed05", () => SetSpeed(.5f));
|
||
Bind("Speed1", () => SetSpeed(1)); Bind("Speed2", () => SetSpeed(2));
|
||
Bind("Loop", () => { repeatAttacks = !repeatAttacks; RefreshUI(); });
|
||
Bind("Front", () => SetYaw(0)); Bind("Side", () => SetYaw(90)); Bind("Back", () => SetYaw(180));
|
||
Bind("Material", () => { gray = !gray; RefreshView(); });
|
||
Bind("All", () => SetFocus(-1));
|
||
Bind("Focus1", () => SetFocus(0)); Bind("Focus2", () => SetFocus(1));
|
||
Bind("Focus3", () => SetFocus(2)); Bind("Focus4", () => SetFocus(3));
|
||
Bind("Detail", () => { detail = (detail + 1) % 3; if (detail > 0 && focus < 0) focus = 0; zoom = 1; RefreshView(); });
|
||
TMP_Text Text(string name) => ui.GetComponentsInChildren<TMP_Text>(true).First(t => t.name == "Txt_" + name);
|
||
stateText = Text("State"); timeText = Text("Time"); playText = Text("Play");
|
||
loopText = Text("Loop"); materialText = Text("Material"); detailText = Text("Detail");
|
||
progress = ui.GetComponentsInChildren<RectTransform>(true).First(t => t.name == "Img_Progress");
|
||
progressWidth = ((RectTransform)progress.parent).rect.width;
|
||
ui.GetComponentInChildren<BotAnimationSeekBar>(true).viewer = this;
|
||
foreach (var actor in actors)
|
||
{
|
||
actor.animator.applyRootMotion = false;
|
||
actor.animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
|
||
actor.animator.Rebind(); actor.animator.speed = 0; actor.animator.Update(0);
|
||
}
|
||
RefreshView();
|
||
}
|
||
void Update()
|
||
{
|
||
if (Input.GetKeyDown(KeyCode.Space)) playing = !playing;
|
||
if (Input.GetKeyDown(KeyCode.Alpha1)) SelectMotion(0);
|
||
if (Input.GetKeyDown(KeyCode.Alpha2)) SelectMotion(1);
|
||
if (Input.GetKeyDown(KeyCode.Alpha3)) SelectMotion(2);
|
||
if (Input.GetKeyDown(KeyCode.Alpha4)) SelectMotion(3);
|
||
if (Input.GetKeyDown(KeyCode.LeftArrow)) Step(-1);
|
||
if (Input.GetKeyDown(KeyCode.RightArrow)) Step(1);
|
||
bool overUI = EventSystem.current && EventSystem.current.IsPointerOverGameObject();
|
||
if (Input.GetMouseButtonDown(0) && !overUI) { dragging = true; lastMouse = Input.mousePosition; }
|
||
if (!Input.GetMouseButton(0)) dragging = false;
|
||
if (dragging) { yaw -= (Input.mousePosition.x - lastMouse.x) * .35f; lastMouse = Input.mousePosition; RefreshView(); }
|
||
if (!overUI && Mathf.Abs(Input.mouseScrollDelta.y) > .01f)
|
||
{ zoom = Mathf.Clamp(zoom * Mathf.Pow(.88f, Input.mouseScrollDelta.y), .35f, 2.5f); RefreshView(); }
|
||
if (playing) Tick(Time.unscaledDeltaTime * speed);
|
||
RefreshView();
|
||
}
|
||
public void Tick(float delta)
|
||
{
|
||
foreach (var actor in actors)
|
||
{
|
||
var a = actor.animator;
|
||
if (repeatAttacks && selectedMotion >= 2 && !a.IsInTransition(0) && a.GetCurrentAnimatorStateInfo(0).IsName("Base Layer.Idle"))
|
||
a.SetTrigger(StateNames[selectedMotion]);
|
||
a.speed = 1; a.Update(delta); a.speed = 0;
|
||
}
|
||
RefreshUI();
|
||
}
|
||
public void SelectMotion(int value)
|
||
{
|
||
selectedMotion = Mathf.Clamp(value, 0, 3);
|
||
foreach (var actor in actors)
|
||
{
|
||
var a = actor.animator;
|
||
a.ResetTrigger("Attack01"); a.ResetTrigger("Attack02");
|
||
a.SetInteger("Motion", selectedMotion == 1 ? 1 : 0);
|
||
if (selectedMotion >= 2) a.SetTrigger(StateNames[selectedMotion]);
|
||
else if (!a.GetCurrentAnimatorStateInfo(0).IsName("Base Layer.Idle") && !a.GetCurrentAnimatorStateInfo(0).IsName("Base Layer.Walk"))
|
||
a.CrossFadeInFixedTime(StateNames[selectedMotion], .15f);
|
||
}
|
||
playing = true; Tick(0); RefreshUI();
|
||
}
|
||
public void Seek(float phase)
|
||
{
|
||
playing = false;
|
||
foreach (var actor in actors)
|
||
{
|
||
var a = actor.animator;
|
||
a.ResetTrigger("Attack01"); a.ResetTrigger("Attack02");
|
||
a.SetInteger("Motion", selectedMotion == 1 ? 1 : 0);
|
||
a.Play("Base Layer." + StateNames[selectedMotion], 0, Mathf.Clamp(phase, 0, .99999f));
|
||
a.speed = 1; a.Update(0); a.speed = 0;
|
||
}
|
||
RefreshUI();
|
||
}
|
||
public void Step(int direction)
|
||
{
|
||
playing = false;
|
||
if (direction > 0) Tick(1f / 30);
|
||
else Seek(Mathf.Max(0, TimePosition - 1f / 30) / Duration);
|
||
RefreshUI();
|
||
}
|
||
public void SetSpeed(float value) { speed = value; RefreshUI(); }
|
||
public void SetYaw(float value) { yaw = value; RefreshView(); }
|
||
public void SetFocus(int value) { focus = value; detail = 0; zoom = 1; RefreshView(); }
|
||
public void RefreshView()
|
||
{
|
||
if (actors == null || !reviewCamera) return;
|
||
foreach (var actor in actors)
|
||
{
|
||
actor.stage.localRotation = Quaternion.Euler(0, yaw, 0);
|
||
for (int i = 0; i < actor.skins.Length; i++)
|
||
{
|
||
actor.skins[i].enabled = focus < 0 || actor == actors[focus];
|
||
actor.skins[i].sharedMaterial = gray ? clayMaterial : actor.originalMaterials[i];
|
||
}
|
||
}
|
||
Vector3 target = new Vector3(focus < 0 ? 0 : actors[focus].stage.position.x, .85f, 0);
|
||
if (focus >= 0 && detail == 1) target.y = 1.25f;
|
||
if (focus >= 0 && detail == 2) target = actors[focus].animator.GetBoneTransform(HumanBodyBones.RightHand).position;
|
||
reviewCamera.transform.position = target + new Vector3(0, .12f, 8);
|
||
reviewCamera.transform.LookAt(target);
|
||
reviewCamera.orthographicSize = (focus < 0 ? Mathf.Max(1.1f, 3.45f / Mathf.Max(.1f, reviewCamera.aspect)) : detail == 1 ? .55f : detail == 2 ? .28f : 1.05f) * zoom;
|
||
reviewCamera.enabled = focus >= 0;
|
||
if (comparisonCameras != null)
|
||
for (int i = 0; i < comparisonCameras.Length; i++)
|
||
{
|
||
var camera = comparisonCameras[i]; camera.enabled = focus < 0;
|
||
var aim = new Vector3(actors[i].stage.position.x, .82f, 0);
|
||
camera.transform.position = aim + new Vector3(0,.1f,8); camera.transform.LookAt(aim);
|
||
camera.orthographicSize = 1.12f * zoom;
|
||
}
|
||
if (ui) foreach (var text in ui.GetComponentsInChildren<TMP_Text>(true).Where(t => t.name.StartsWith("Txt_Actor"))) text.gameObject.SetActive(focus < 0);
|
||
RefreshUI();
|
||
}
|
||
public void RefreshUI()
|
||
{
|
||
if (!stateText) return;
|
||
string State(Animator a) => StateNames.FirstOrDefault(s => a.GetCurrentAnimatorStateInfo(0).IsName("Base Layer." + s)) ?? "过渡";
|
||
stateText.text = string.Join(" ", actors.Select(a => a.label.Replace("XBot", "X").Replace("YBot", "Y") + ": " + State(a.animator)));
|
||
var reference = actors[focus >= 0 ? focus : 0];
|
||
var info = reference.animator.GetCurrentAnimatorStateInfo(0);
|
||
float phase = Mathf.Repeat(info.normalizedTime, 1);
|
||
int index = Array.IndexOf(StateNames, State(reference.animator));
|
||
float length = reference.clips[Mathf.Max(0,index)].length;
|
||
timeText.text = reference.label + " · " + State(reference.animator) + " " + (phase * length).ToString("0.00") + " / " + length.ToString("0.00") + " 秒 " + speed.ToString("0.##") + "×";
|
||
playText.text = playing ? "暂停" : "播放";
|
||
loopText.text = repeatAttacks ? "循环攻击" : "单次攻击";
|
||
materialText.text = gray ? "彩色" : "灰模";
|
||
detailText.text = new[] { "全身", "上身", "手部" }[detail];
|
||
progress.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, Mathf.Max(2, progressWidth * phase));
|
||
}
|
||
}
|
||
}
|