@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user