Ship the WeChat hot-update catalogs and the camp, TideWhale T2, UI atlas, and animation-lab work.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
udx
2026-10-24 18:04:41 +08:00
co-authored by Cursor
parent 6ebc9401ce
commit 45e62132c8
1432 changed files with 2971856 additions and 2798 deletions
@@ -0,0 +1,210 @@
using System;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace AIGame.Review
{
/// <summary>Scene-authored, synchronized skinning comparison. No UI/model construction at runtime.</summary>
public sealed class CharacterAnimationLab : MonoBehaviour
{
[Serializable] public sealed class Actor
{
public string label;
public GameObject rig;
public Transform hips;
public Transform rightHand;
public SkinnedMeshRenderer renderer;
// Optional full target-pose walk derived from the common source
// motion. Used where preserving source angles alone crosses feet.
public AnimationClip walkingClip;
// Optional bind translation adaptation. The shared source clip's
// rotation curves and time remain unchanged across characters.
public float translationScale = 1f;
public Vector3[] translationOffsets = Array.Empty<Vector3>();
public Quaternion[] translationDeltaRotations = Array.Empty<Quaternion>();
public int[] rotationParents = Array.Empty<int>();
public Quaternion[] bindRotationCorrections = Array.Empty<Quaternion>();
[NonSerialized] public Quaternion[] sampledWorldRotations;
[NonSerialized] public Quaternion[] mappedWorldRotations;
}
public Actor[] actors;
public AnimationClip[] clips;
public Vector3[] travel;
public Camera reviewCamera;
public Transform ui;
public Material texturedMaterial;
public Material clayMaterial;
public int motion;
public bool playing = true;
public bool inPlace = true;
public bool textured = true;
public float speed = 1f;
public float time;
public int focus = -1;
public int detail;
public float yaw;
TMP_Text stateText, timeText, pauseText, travelText, materialText, detailText;
RectTransform progress;
float progressWidth;
float zoom = 1f;
Vector3 lastMouse;
bool dragging;
SkinWeights previousWeights;
bool changedWeights;
bool previousRunInBackground;
public float Duration => clips != null && clips.Length > 0 ? clips[motion].length : 1;
public int FrameCount => Mathf.RoundToInt(Duration * 30) + 1;
void OnEnable()
{
if (!Application.isPlaying) return;
previousWeights = QualitySettings.skinWeights;
previousRunInBackground = Application.runInBackground;
Application.runInBackground = true;
QualitySettings.skinWeights = SkinWeights.Unlimited;
changedWeights = true;
}
void OnDisable()
{
if (changedWeights) { QualitySettings.skinWeights = previousWeights; Application.runInBackground = previousRunInBackground; }
changedWeights = false;
}
void Awake()
{
if (!ui) return;
var buttons = ui.GetComponentsInChildren<Button>(true).ToDictionary(b => b.name);
void Bind(string n, UnityEngine.Events.UnityAction action) => buttons[n].onClick.AddListener(action);
Bind("Btn_Walk", () => SetMotion(0));
Bind("Btn_Breathe", () => SetMotion(1));
Bind("Btn_Happy", () => SetMotion(2));
Bind("Btn_Play", () => { playing = !playing; RefreshUI(); });
Bind("Btn_Previous", () => Step(-1));
Bind("Btn_Next", () => Step(1));
Bind("Btn_Restart", () => { time = 0; Sample(); });
Bind("Btn_Speed025", () => SetSpeed(.25f));
Bind("Btn_Speed05", () => SetSpeed(.5f));
Bind("Btn_Speed1", () => SetSpeed(1));
Bind("Btn_Speed2", () => SetSpeed(2));
Bind("Btn_Travel", () => { inPlace = !inPlace; Sample(); });
Bind("Btn_Front", () => SetYaw(0));
Bind("Btn_Side", () => SetYaw(90));
Bind("Btn_Back", () => SetYaw(180));
Bind("Btn_Material", () => { textured = !textured; Sample(); });
Bind("Btn_All", () => SetFocus(-1));
Bind("Btn_Focus1", () => SetFocus(0));
Bind("Btn_Focus2", () => SetFocus(1));
Bind("Btn_Focus3", () => SetFocus(2));
if (buttons.TryGetValue("Btn_Focus4", out var focus4)) focus4.onClick.AddListener(() => SetFocus(3));
Bind("Btn_Detail", () => { detail = (detail + 1) % 3; if (detail > 0 && focus < 0) focus = 1; zoom = 1; Sample(); });
TMP_Text Text(string n) => ui.GetComponentsInChildren<TMP_Text>(true).First(t => t.name == n);
stateText = Text("Txt_State"); timeText = Text("Txt_Time");
pauseText = Text("Txt_Play"); travelText = Text("Txt_Travel");
materialText = Text("Txt_Material"); detailText = Text("Txt_Detail");
progress = ui.GetComponentsInChildren<RectTransform>(true).First(t => t.name == "Img_Progress");
progressWidth = ((RectTransform)progress.parent).rect.width;
var seek = ui.GetComponentInChildren<AnimationLabSeekBar>(true);
if (seek) seek.viewer = this;
Sample();
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space)) playing = !playing;
if (Input.GetKeyDown(KeyCode.Alpha1)) SetMotion(0);
if (Input.GetKeyDown(KeyCode.Alpha2)) SetMotion(1);
if (Input.GetKeyDown(KeyCode.Alpha3)) SetMotion(2);
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; }
if (!overUI && Mathf.Abs(Input.mouseScrollDelta.y) > .01f)
zoom = Mathf.Clamp(zoom * Mathf.Pow(.88f, Input.mouseScrollDelta.y), .35f, 2.5f);
if (playing) time = Mathf.Repeat(time + Time.unscaledDeltaTime * speed, Mathf.Max(.001f, Duration));
Sample();
}
public void SetMotion(int value) { motion = Mathf.Clamp(value, 0, clips.Length - 1); time = 0; Sample(); }
public void SetSpeed(float value) { speed = value; RefreshUI(); }
public void SetYaw(float value) { yaw = value; Sample(); }
public void SetFocus(int value) { focus = value; if (value < 0) detail = 0; zoom = 1; Sample(); }
public void Seek(float value) { playing = false; time = Mathf.Clamp01(value) * Duration; Sample(); }
public void Step(int frames) { playing = false; time = Mathf.Clamp(Mathf.Round(time * 30) + frames, 0, FrameCount - 1) / 30f; Sample(); }
public void Sample()
{
if (actors == null || clips == null || clips.Length == 0) return;
time = Mathf.Clamp(time, 0, Duration);
float phase = Duration > 0 ? time / Duration : 0;
foreach (var actor in actors)
{
actor.renderer.enabled = focus < 0 || actor == actors[focus];
bool mappedWalk = motion == 0 && actor.walkingClip;
(mappedWalk ? actor.walkingClip : clips[motion]).SampleAnimation(actor.rig, time);
bool adapt = !mappedWalk && actor.translationOffsets != null && actor.translationOffsets.Length == actor.renderer.bones.Length;
if (adapt)
{
var bones = actor.renderer.bones;
for (int i = 0; i < bones.Length; i++)
{
var delta = bones[i].localPosition * actor.translationScale;
if (actor.translationDeltaRotations != null && actor.translationDeltaRotations.Length == bones.Length)
delta = actor.translationDeltaRotations[i] * delta;
bones[i].localPosition = delta + actor.translationOffsets[i];
}
if (actor.bindRotationCorrections != null && actor.bindRotationCorrections.Length == bones.Length && actor.rotationParents.Length == bones.Length)
{
if (actor.sampledWorldRotations == null || actor.sampledWorldRotations.Length != bones.Length)
{
actor.sampledWorldRotations = new Quaternion[bones.Length];
actor.mappedWorldRotations = new Quaternion[bones.Length];
}
for (int i = 0; i < bones.Length; i++)
{
int parent = actor.rotationParents[i];
actor.sampledWorldRotations[i] = parent < 0 ? bones[i].localRotation : actor.sampledWorldRotations[parent] * bones[i].localRotation;
actor.mappedWorldRotations[i] = actor.sampledWorldRotations[i] * actor.bindRotationCorrections[i];
}
for (int i = 0; i < bones.Length; i++)
{
int parent = actor.rotationParents[i];
bones[i].localRotation = parent < 0 ? actor.mappedWorldRotations[i] : Quaternion.Inverse(actor.mappedWorldRotations[parent]) * actor.mappedWorldRotations[i];
}
}
}
if (inPlace) actor.hips.localPosition -= travel[motion] * phase * (adapt || mappedWalk ? actor.translationScale : 1f);
actor.rig.transform.localRotation = Quaternion.Euler(0, yaw, 0);
actor.renderer.sharedMaterial = textured ? texturedMaterial : clayMaterial;
}
UpdateCamera(); RefreshUI();
}
void UpdateCamera()
{
if (!reviewCamera) return;
float x = focus >= 0 ? actors[focus].rig.transform.parent.position.x : 0;
float y = detail == 1 ? 1.18f : detail == 2 ? .78f : .82f;
Vector3 target = new Vector3(x, y, 0);
if (detail == 2 && focus >= 0 && actors[focus].rightHand) target = actors[focus].rightHand.position;
reviewCamera.transform.position = target + new Vector3(0, .12f, -6);
reviewCamera.transform.LookAt(target);
reviewCamera.orthographicSize = (focus < 0 ? Mathf.Max(1.15f, (actors.Length >= 4 ? 1.8f : 1.65f) / Mathf.Max(.1f, reviewCamera.aspect)) : detail == 1 ? .62f : detail == 2 ? .37f : 1.08f) * zoom;
}
void RefreshUI()
{
if (!stateText) return;
string[] motions = { "行走", "呼吸待机", "轻松待机" };
stateText.text = motions[motion] + " · " + (focus < 0 ? (actors.Length == 4 ? "四版同步对比" : "三版同步对比") : actors[focus].label);
timeText.text = string.Format("{0:0.00} / {1:0.00} 秒 第 {2} / {3} 帧 {4:0.##}×", time, Duration, Mathf.RoundToInt(time * 30) + 1, FrameCount, speed);
pauseText.text = playing ? "暂停" : "播放";
travelText.text = inPlace ? "原地循环" : "保留位移";
materialText.text = textured ? "切灰模" : "切贴图";
detailText.text = new[] { "全身", "上身", "手部" }[detail];
if (progress)
{
if (progressWidth <= 0) progressWidth = ((RectTransform)progress.parent).rect.width;
progress.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, Mathf.Max(2, progressWidth * (time / Mathf.Max(.001f, Duration))));
}
}
}
}