Files

204 lines
11 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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));
}
}
}