Signed-off-by: TRADER_FOER <lhf190@outlook.com>
This commit is contained in:
2026-06-12 16:25:52 +08:00
parent c99c10fd37
commit 4b7f25e47a
51 changed files with 449934 additions and 8525 deletions

View File

@@ -3,10 +3,11 @@ using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using UnityEngine.UI.Extensions;
namespace Ichni.Editor
{
[RequireComponent(typeof(RawImage))]
[RequireComponent(typeof(RectTransform))]
public class KeyframeVisualizer : MonoBehaviour
{
[Header("Settings")]
@@ -15,123 +16,123 @@ namespace Ichni.Editor
public Color gridColor = new Color(0.3f, 0.3f, 0.3f, 0.5f);
public float pointSize = 15f;
public float tangentHandleLength = 40f;
public float lineThickness = 2f;
[Header("References")]
public AnimationCurve curve;
public RawImage rawImage;
public UILineRenderer curveLine;
public UILineRenderer gridLines;
public UILineRenderer borderLine;
// 当用户松开鼠标编辑结束时触发用于同步外部UI
public Action OnEditFinished;
private Texture2D _texture;
private Color32[] _buffer;
private List<CurvePoint> _activePoints = new List<CurvePoint>();
// 缓存特定颜色以提高性能
private Color32 _cClear = new Color32(0, 0, 0, 0);
private Color32 _cCurve;
private Color32 _cGrid;
private void Awake()
{
if (rawImage == null) rawImage = GetComponent<RawImage>();
_cCurve = curveColor;
_cGrid = gridColor;
// 移除场景/预制体中残留的旧 RawImage 组件
var oldRawImage = GetComponent<RawImage>();
if (oldRawImage != null)
Destroy(oldRawImage);
SetupUILineRenderers();
}
private void SetupUILineRenderers()
{
if (curveLine == null)
curveLine = CreateChildLineRenderer("CurveLine", curveColor);
if (gridLines == null)
gridLines = CreateChildLineRenderer("GridLines", gridColor);
if (borderLine == null)
borderLine = CreateChildLineRenderer("BorderLine", Color.white);
curveLine.LineThickness = lineThickness;
gridLines.LineThickness = 1f;
borderLine.LineThickness = 1.5f;
}
private UILineRenderer CreateChildLineRenderer(string name, Color color)
{
GameObject go = new GameObject(name, typeof(RectTransform));
go.transform.SetParent(transform, false);
RectTransform rt = go.GetComponent<RectTransform>();
rt.anchorMin = Vector2.zero;
rt.anchorMax = Vector2.one;
rt.offsetMin = Vector2.zero;
rt.offsetMax = Vector2.zero;
UILineRenderer line = go.AddComponent<UILineRenderer>();
line.color = color;
line.RelativeSize = true;
line.raycastTarget = false;
return line;
}
private void OnEnable()
{
if (curve == null) curve = AnimationCurve.Linear(0, 0, 1, 1);
InitTexture();
RebuildInteractablePoints();
DrawCurveToRawImage();
DrawCurve();
}
// === 核心绘制逻辑 (高性能) ===
public void DrawCurveToRawImage()
{
if (curve == null || _texture == null) return;
// 1. 清屏
int len = _buffer.Length;
for (int i = 0; i < len; i++) _buffer[i] = _cClear;
int w = resolution.x;
int h = resolution.y;
// 2. 绘制网格 (0.25, 0.5, 0.75)
DrawGridLine(w, h, 0.25f);
DrawGridLine(w, h, 0.5f);
DrawGridLine(w, h, 0.75f);
// 绘制边框
DrawRect(0, 0, w - 1, h - 1, Color.white);
// 3. 绘制曲线
// 限制范围在 0-1
int prevY = -1;
for (int x = 0; x < w; x++)
{
float t = (float)x / (w - 1);
float val = curve.Evaluate(t);
// 映射到像素高度并Clap防止数组越界
int y = Mathf.FloorToInt(Mathf.Clamp01(val) * (h - 1));
// 绘制点
int idx = y * w + x;
if (idx >= 0 && idx < len) _buffer[idx] = _cCurve;
// 垂直补间(防止曲线断裂)
if (prevY != -1 && Mathf.Abs(y - prevY) > 1)
{
int step = y > prevY ? 1 : -1;
for (int k = prevY + step; k != y; k += step)
{
int fillIdx = k * w + x;
if (fillIdx >= 0 && fillIdx < len) _buffer[fillIdx] = _cCurve;
}
}
prevY = y;
}
_texture.SetPixels32(_buffer);
_texture.Apply();
rawImage.texture = _texture;
}
private void InitTexture()
{
if (_texture == null || _texture.width != resolution.x || _texture.height != resolution.y)
{
_texture = new Texture2D(resolution.x, resolution.y, TextureFormat.ARGB32, false);
_texture.filterMode = FilterMode.Bilinear;
_buffer = new Color32[resolution.x * resolution.y];
}
}
private void DrawGridLine(int w, int h, float percent)
{
int x = (int)(w * percent);
int y = (int)(h * percent);
for (int i = 0; i < h; i++) _buffer[i * w + x] = _cGrid; // 竖线
for (int i = 0; i < w; i++) _buffer[y * w + i] = _cGrid; // 横线
}
private void DrawRect(int x1, int y1, int x2, int y2, Color32 c)
{
int w = resolution.x;
for (int x = x1; x <= x2; x++) { _buffer[y1 * w + x] = c; _buffer[y2 * w + x] = c; }
for (int y = y1; y <= y2; y++) { _buffer[y * w + x1] = c; _buffer[y * w + x2] = c; }
}
// === 交互点生成 ===
public void RebuildInteractablePoints() // 原名 CreateKeyframeImages
// === 核心绘制逻辑 (UILineRenderer) ===
public void DrawCurve()
{
if (curve == null) return;
// 清理旧点
foreach (Transform child in transform) Destroy(child.gameObject);
int sampleCount = Mathf.Max(2, resolution.x);
// 1. 绘制曲线
Vector2[] curvePoints = new Vector2[sampleCount];
for (int i = 0; i < sampleCount; i++)
{
float t = (float)i / (sampleCount - 1);
float val = Mathf.Clamp01(curve.Evaluate(t));
curvePoints[i] = new Vector2(t, val);
}
curveLine.Points = curvePoints;
// 2. 绘制网格 (0.25, 0.5, 0.75)
float[] gridPositions = { 0.25f, 0.5f, 0.75f };
List<Vector2[]> segments = new List<Vector2[]>();
foreach (float p in gridPositions)
{
segments.Add(new Vector2[] { new Vector2(0, p), new Vector2(1, p) }); // 横线
segments.Add(new Vector2[] { new Vector2(p, 0), new Vector2(p, 1) }); // 竖线
}
gridLines.Segments = segments;
// 3. 绘制边框
borderLine.Points = new Vector2[]
{
new Vector2(0, 0),
new Vector2(1, 0),
new Vector2(1, 1),
new Vector2(0, 1),
new Vector2(0, 0)
};
}
// === 交互点生成 ===
public void RebuildInteractablePoints()
{
if (curve == null) return;
// 清理旧点保留子UILineRenderer
var toDestroy = new List<GameObject>();
foreach (Transform child in transform)
{
if (child == curveLine?.transform ||
child == gridLines?.transform ||
child == borderLine?.transform)
continue;
toDestroy.Add(child.gameObject);
}
foreach (var go in toDestroy)
Destroy(go);
_activePoints.Clear();
for (int i = 0; i < curve.length; i++)
@@ -180,8 +181,7 @@ namespace Ichni.Editor
// === 坐标同步逻辑 ===
public void RefreshPointsPosition()
{
Vector2 size = rawImage.rectTransform.rect.size;
float canvasAspect = size.x / size.y;
Vector2 size = ((RectTransform)transform).rect.size;
foreach (var point in _activePoints)
{
@@ -207,7 +207,7 @@ namespace Ichni.Editor
// 斜率 = (y / x) * aspect => y = (tangent / aspect) * x
// 令视觉上的 x 为 1 或 -1
float xDir = (point.type == PointType.InTangent) ? -1f : 1f;
float yDir = (tangent / canvasAspect) * xDir;
float yDir = (tangent / (size.x / size.y)) * xDir;
visualDir = new Vector2(xDir, yDir).normalized;
}
@@ -218,10 +218,9 @@ namespace Ichni.Editor
}
// 处理点被拖拽
// 在 KeyframeVisualizer 类中修改 OnPointDragged 方法
public void OnPointDragged(CurvePoint point, Vector2 screenDelta)
{
Vector2 size = rawImage.rectTransform.rect.size;
Vector2 size = ((RectTransform)transform).rect.size;
int index = point.keyIndex;
Keyframe key = curve.keys[index];
@@ -255,21 +254,15 @@ namespace Ichni.Editor
}
// 5. 计算斜率 (Tangent)
// 物理斜率 = (DeltaValue / DeltaTime)
// 对应 UI = (dirVec.y / size.y) / (dirVec.x / size.x)
float canvasAspect = size.x / size.y;
float tangent = (dirVec.y / dirVec.x) * canvasAspect;
if (point.type == PointType.InTangent) key.inTangent = tangent;
else key.outTangent = tangent;
// 6. 更新手柄位置:让手柄视觉上严格对齐鼠标方向,但保持固定长度(可选)
// 如果你不希望手柄被拉长,可以将 dirVec 归一化再乘上固定长度
// point.rectTransform.anchoredPosition = keyPos + dirVec.normalized * tangentHandleLength;
}
curve.MoveKey(index, key);
DrawCurveToRawImage();
DrawCurve();
RefreshPointsPosition(); // 统一刷新位置,确保手柄视觉表现一致
}
}
@@ -309,4 +302,4 @@ namespace Ichni.Editor
visualizer.OnEditFinished?.Invoke();
}
}
}
}