@@ -240,63 +240,87 @@ namespace Ichni.Editor
|
||||
public KeyframeVisualizer keyframeVisualizer;
|
||||
public CompositeParameterWindow SetAsCustomCurve()
|
||||
{
|
||||
|
||||
//生成一个预览器先
|
||||
keyframeVisualizer.gameObject.SetActive(true);
|
||||
//
|
||||
|
||||
// 获取字段信息和曲线数据
|
||||
unitPrefab = EditorManager.instance.basePrefabs.customCurveKeyframeUnit;
|
||||
FieldInfo fieldInfo = connectedBaseElement.GetType().GetField(parameterName);
|
||||
AnimationCurve curve = fieldInfo.GetValue(connectedBaseElement) as AnimationCurve;
|
||||
|
||||
if (curve == null) curve = AnimationCurve.Linear(0, 0, 1, 1);
|
||||
|
||||
// 初始化预览器
|
||||
keyframeVisualizer.curve = curve;
|
||||
keyframeVisualizer.DrawCurveToRawImage();
|
||||
keyframeVisualizer.RebuildInteractablePoints();
|
||||
|
||||
// 关键点:松开手柄时的回调
|
||||
keyframeVisualizer.OnEditFinished = () =>
|
||||
{
|
||||
// 同步左侧 Unit 列表的数值显示(假设 unitList[0] 是 WrapMode)
|
||||
for (int i = 0; i < keyframeVisualizer.curve.length; i++)
|
||||
{
|
||||
int unitIdx = i + 1;
|
||||
if (unitIdx < unitList.Count)
|
||||
{
|
||||
var unit = unitList[unitIdx] as DynamicUICustomCurveKeyframeUnit;
|
||||
if (unit != null) unit.SetUnit(this, keyframeVisualizer.curve.keys[i]);
|
||||
}
|
||||
}
|
||||
ApplyParameters?.Invoke();
|
||||
};
|
||||
|
||||
// 生成 WrapMode 单元
|
||||
WarpModes warpModes = new WarpModes(curve.preWrapMode, curve.postWrapMode);
|
||||
var wmUnit = Instantiate(EditorManager.instance.basePrefabs.customCurveWrapModeUnit, windowRect)
|
||||
.GetComponent<DynamicUICustomCurveWrapModeUnit>();
|
||||
unitList.Add(wmUnit);
|
||||
wmUnit.SetUnit(this, warpModes);
|
||||
|
||||
// 生成 Keyframe 单元
|
||||
void GenerateUnit(Keyframe content)
|
||||
{
|
||||
DynamicUICustomCurveKeyframeUnit unit = Instantiate(unitPrefab, windowRect).GetComponent<DynamicUICustomCurveKeyframeUnit>();
|
||||
var unit = Instantiate(unitPrefab, windowRect).GetComponent<DynamicUICustomCurveKeyframeUnit>();
|
||||
unitList.Add(unit);
|
||||
unit.SetUnit(this, content);
|
||||
}
|
||||
|
||||
unitPrefab = EditorManager.instance.basePrefabs.customCurveKeyframeUnit;
|
||||
AnimationCurve curve = connectedBaseElement.GetType().GetField(parameterName).GetValue(connectedBaseElement) as AnimationCurve;
|
||||
List<Keyframe> keyframes = curve.keys.ToList();
|
||||
WarpModes warpModes = new WarpModes(curve.preWrapMode, curve.postWrapMode);
|
||||
|
||||
//生成warpModes的Unit
|
||||
DynamicUICustomCurveWrapModeUnit warpModesUnit =
|
||||
Instantiate(EditorManager.instance.basePrefabs.customCurveWrapModeUnit, windowRect).GetComponent<DynamicUICustomCurveWrapModeUnit>();
|
||||
unitList.Add(warpModesUnit);
|
||||
warpModesUnit.SetUnit(this, warpModes);
|
||||
//
|
||||
keyframeVisualizer.curve = curve;
|
||||
keyframeVisualizer.DrawCurveToRawImage();
|
||||
keyframeVisualizer.CreateKeyframeImages();
|
||||
//
|
||||
|
||||
foreach (Keyframe keyframe in keyframes)
|
||||
{
|
||||
GenerateUnit(keyframe);
|
||||
}
|
||||
foreach (Keyframe k in curve.keys) GenerateUnit(k);
|
||||
|
||||
addNewUnitButton.GetComponent<RectTransform>().SetAsLastSibling();
|
||||
|
||||
addNewUnitButton.onClick.AddListener(() =>
|
||||
{
|
||||
GenerateUnit(new Keyframe(0, 0, 0, 0));
|
||||
Keyframe newKey = new Keyframe(0.5f, 0.5f, 0, 0);
|
||||
keyframeVisualizer.curve.AddKey(newKey);
|
||||
GenerateUnit(newKey);
|
||||
keyframeVisualizer.RebuildInteractablePoints();
|
||||
keyframeVisualizer.DrawCurveToRawImage();
|
||||
addNewUnitButton.GetComponent<RectTransform>().SetAsLastSibling();
|
||||
});
|
||||
|
||||
// 真正的应用/保存逻辑
|
||||
ApplyParameters = () =>
|
||||
{
|
||||
AnimationCurve newCurve = new AnimationCurve();
|
||||
DynamicUICustomCurveWrapModeUnit warpModesUnit = unitList[0] as DynamicUICustomCurveWrapModeUnit;
|
||||
newCurve.preWrapMode = warpModesUnit.GetValue().preWrapMode;
|
||||
newCurve.postWrapMode = warpModesUnit.GetValue().postWrapMode;
|
||||
// 1. 获取 WrapMode
|
||||
var wUnit = unitList[0] as DynamicUICustomCurveWrapModeUnit;
|
||||
newCurve.preWrapMode = wUnit.GetValue().preWrapMode;
|
||||
newCurve.postWrapMode = wUnit.GetValue().postWrapMode;
|
||||
|
||||
// 2. 获取所有 Keyframes
|
||||
for (int i = 1; i < unitList.Count; i++)
|
||||
{
|
||||
DynamicUICustomCurveKeyframeUnit unit = unitList[i] as DynamicUICustomCurveKeyframeUnit;
|
||||
newCurve.AddKey(unit.GetValue());
|
||||
var kUnit = unitList[i] as DynamicUICustomCurveKeyframeUnit;
|
||||
newCurve.AddKey(kUnit.GetValue());
|
||||
}
|
||||
FieldInfo fieldInfo = connectedBaseElement.GetType().GetField(parameterName);
|
||||
|
||||
fieldInfo.SetValue(connectedBaseElement, newCurve);
|
||||
keyframeVisualizer.curve = newCurve;
|
||||
keyframeVisualizer.DrawCurveToRawImage();
|
||||
};
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CompositeParameterWindow SetAsGradientColorKeys()
|
||||
{
|
||||
void GenerateUnit(GradientColorKey content)
|
||||
|
||||
@@ -164,37 +164,61 @@ public partial class HsvDrawer
|
||||
int targetSize = useLowRes ? lowResTextureSize : highResTextureSize;
|
||||
textureSize = targetSize;
|
||||
|
||||
// 1. 优化:仅在尺寸变化时重新分配纹理
|
||||
if (_texture == null || _texture.width != textureSize || _texture.height != textureSize)
|
||||
{
|
||||
_texture = new Texture2D(textureSize, textureSize, TextureFormat.RGBA32, false);
|
||||
_texture.filterMode = FilterMode.Bilinear; // 确保平滑
|
||||
_texture.wrapMode = TextureWrapMode.Clamp;
|
||||
}
|
||||
|
||||
_texture.wrapMode = TextureWrapMode.Clamp;
|
||||
// 2. 核心优化:使用 Color32 数组代替 Color 数组
|
||||
// Color32 占用的内存更小,SetPixels32 的执行效率远高于 SetPixels
|
||||
Color32[] pixels = new Color32[textureSize * textureSize];
|
||||
|
||||
Vector2 center = new Vector2(textureSize * 0.5f, textureSize * 0.5f);
|
||||
float maxRadius = textureSize * 0.5f;
|
||||
Color[] pixels = new Color[textureSize * textureSize];
|
||||
float halfSize = textureSize * 0.5f;
|
||||
float invMaxRadius = 1.0f / halfSize; // 预计算半径倒数,将除法转为乘法
|
||||
Color32 clearColor = new Color32(0, 0, 0, 0);
|
||||
|
||||
// 3. 循环优化
|
||||
for (int y = 0; y < textureSize; y++)
|
||||
{
|
||||
// 预计算当前行的偏移量 Y 轴部分
|
||||
float offY = (y + 0.5f) - halfSize;
|
||||
float offYSq = offY * offY;
|
||||
int rowOffset = y * textureSize;
|
||||
|
||||
for (int x = 0; x < textureSize; x++)
|
||||
{
|
||||
Vector2 offset = new Vector2(x + 0.5f, y + 0.5f) - center;
|
||||
float distance = offset.magnitude;
|
||||
int idx = y * textureSize + x;
|
||||
float offX = (x + 0.5f) - halfSize;
|
||||
float distSq = offX * offX + offYSq; // 使用平方比较,避免在外部判断时使用 magnitude (开方)
|
||||
|
||||
if (distance <= maxRadius)
|
||||
int idx = rowOffset + x;
|
||||
|
||||
if (distSq <= halfSize * halfSize)
|
||||
{
|
||||
float angle = Mathf.Atan2(offset.y, offset.x) * Mathf.Rad2Deg;
|
||||
float distance = Mathf.Sqrt(distSq);
|
||||
|
||||
// 计算色相 (Hue)
|
||||
float angle = Mathf.Atan2(offY, offX) * Mathf.Rad2Deg;
|
||||
if (angle < 0) angle += 360f;
|
||||
float sat = Mathf.Clamp01(distance / maxRadius) * saturation;
|
||||
pixels[idx] = Color.HSVToRGB(angle / 360f, sat, value);
|
||||
|
||||
// 计算饱和度 (Saturation),受外部变量 saturation 影响
|
||||
float sat = (distance * invMaxRadius) * saturation;
|
||||
|
||||
// 转换 HSV 到 RGB 并存入 Color32
|
||||
// 注意:Color.HSVToRGB 返回 Color,赋值给 Color32 会自动隐式转换
|
||||
pixels[idx] = Color.HSVToRGB(angle / 360f, Mathf.Clamp01(sat), value);
|
||||
}
|
||||
else
|
||||
{
|
||||
pixels[idx] = Color.clear;
|
||||
pixels[idx] = clearColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
_texture.SetPixels(pixels);
|
||||
|
||||
// 4. 应用像素数据
|
||||
_texture.SetPixels32(pixels);
|
||||
_texture.Apply();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace Ichni.Editor
|
||||
{
|
||||
[RequireComponent(typeof(RawImage))]
|
||||
public class KeyframeVisualizer : MonoBehaviour
|
||||
{
|
||||
[Header("Settings")]
|
||||
public Vector2Int resolution = new Vector2Int(512, 256);
|
||||
public Color curveColor = Color.green;
|
||||
public Color gridColor = new Color(0.3f, 0.3f, 0.3f, 0.5f);
|
||||
public float pointSize = 15f;
|
||||
public float tangentHandleLength = 40f;
|
||||
|
||||
[Header("References")]
|
||||
public AnimationCurve curve;
|
||||
public RawImage rawImage;
|
||||
|
||||
// 当用户松开鼠标(编辑结束)时触发,用于同步外部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;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (curve == null) curve = AnimationCurve.Linear(0, 0, 1, 1);
|
||||
InitTexture();
|
||||
RebuildInteractablePoints();
|
||||
DrawCurveToRawImage();
|
||||
}
|
||||
|
||||
// === 核心绘制逻辑 (高性能) ===
|
||||
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
|
||||
{
|
||||
if (curve == null) return;
|
||||
|
||||
// 清理旧点
|
||||
foreach (Transform child in transform) Destroy(child.gameObject);
|
||||
_activePoints.Clear();
|
||||
|
||||
for (int i = 0; i < curve.length; i++)
|
||||
{
|
||||
// 关键帧点 (Key)
|
||||
var keyPoint = CreatePoint(i, PointType.Key, Color.red, pointSize);
|
||||
|
||||
// 入切线 (In Tangent) - 第一个点通常不需要
|
||||
if (i > 0)
|
||||
{
|
||||
var inPoint = CreatePoint(i, PointType.InTangent, Color.cyan, pointSize * 0.6f);
|
||||
inPoint.relatedKeyPoint = keyPoint;
|
||||
keyPoint.inHandle = inPoint;
|
||||
}
|
||||
|
||||
// 出切线 (Out Tangent) - 最后一个点通常不需要
|
||||
if (i < curve.length - 1)
|
||||
{
|
||||
var outPoint = CreatePoint(i, PointType.OutTangent, Color.cyan, pointSize * 0.6f);
|
||||
outPoint.relatedKeyPoint = keyPoint;
|
||||
keyPoint.outHandle = outPoint;
|
||||
}
|
||||
}
|
||||
|
||||
RefreshPointsPosition();
|
||||
}
|
||||
|
||||
private CurvePoint CreatePoint(int index, PointType type, Color color, float size)
|
||||
{
|
||||
GameObject go = new GameObject($"{type}_{index}");
|
||||
go.transform.SetParent(transform, false);
|
||||
|
||||
Image img = go.AddComponent<Image>();
|
||||
img.color = color;
|
||||
|
||||
RectTransform rt = go.GetComponent<RectTransform>();
|
||||
rt.sizeDelta = Vector2.one * size;
|
||||
rt.anchorMin = rt.anchorMax = Vector2.zero; // 使用绝对坐标定位
|
||||
|
||||
CurvePoint cp = go.AddComponent<CurvePoint>();
|
||||
cp.Init(this, index, type);
|
||||
_activePoints.Add(cp);
|
||||
return cp;
|
||||
}
|
||||
|
||||
// === 坐标同步逻辑 ===
|
||||
public void RefreshPointsPosition()
|
||||
{
|
||||
Vector2 size = rawImage.rectTransform.rect.size;
|
||||
float canvasAspect = size.x / size.y;
|
||||
|
||||
foreach (var point in _activePoints)
|
||||
{
|
||||
Keyframe key = curve.keys[point.keyIndex];
|
||||
Vector2 keyNormPos = new Vector2(key.time, key.value);
|
||||
Vector2 keyPixelPos = new Vector2(keyNormPos.x * size.x, keyNormPos.y * size.y);
|
||||
|
||||
if (point.type == PointType.Key)
|
||||
{
|
||||
point.rectTransform.anchoredPosition = keyPixelPos;
|
||||
}
|
||||
else
|
||||
{
|
||||
float tangent = (point.type == PointType.InTangent) ? key.inTangent : key.outTangent;
|
||||
|
||||
Vector2 visualDir;
|
||||
if (float.IsInfinity(tangent))
|
||||
{
|
||||
visualDir = new Vector2(0, tangent > 0 ? 1 : -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 斜率 = (y / x) * aspect => y = (tangent / aspect) * x
|
||||
// 令视觉上的 x 为 1 或 -1
|
||||
float xDir = (point.type == PointType.InTangent) ? -1f : 1f;
|
||||
float yDir = (tangent / canvasAspect) * xDir;
|
||||
visualDir = new Vector2(xDir, yDir).normalized;
|
||||
}
|
||||
|
||||
// 顺着计算出的向量方向,放置手柄
|
||||
point.rectTransform.anchoredPosition = keyPixelPos + visualDir * tangentHandleLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理点被拖拽
|
||||
// 在 KeyframeVisualizer 类中修改 OnPointDragged 方法
|
||||
public void OnPointDragged(CurvePoint point, Vector2 screenDelta)
|
||||
{
|
||||
Vector2 size = rawImage.rectTransform.rect.size;
|
||||
int index = point.keyIndex;
|
||||
Keyframe key = curve.keys[index];
|
||||
|
||||
if (point.type == PointType.Key)
|
||||
{
|
||||
// 关键帧移动逻辑不变
|
||||
Vector2 deltaNorm = new Vector2(screenDelta.x / size.x, screenDelta.y / size.y);
|
||||
key.time = Mathf.Clamp01(key.time + deltaNorm.x);
|
||||
key.value = Mathf.Clamp01(key.value + deltaNorm.y);
|
||||
}
|
||||
else
|
||||
{
|
||||
// === 切线处理优化:基于向量构建 ===
|
||||
// 1. 获取关键帧当前的屏幕坐标
|
||||
Vector2 keyPos = point.relatedKeyPoint.rectTransform.anchoredPosition;
|
||||
|
||||
// 2. 获取鼠标当前的目标坐标(当前手柄位置 + 增量)
|
||||
Vector2 mouseTargetPos = point.rectTransform.anchoredPosition + screenDelta;
|
||||
|
||||
// 3. 计算从关键帧指向鼠标的向量
|
||||
Vector2 dirVec = mouseTargetPos - keyPos;
|
||||
|
||||
// 4. 强制方向约束 (InTangent必须在左, OutTangent必须在右)
|
||||
if (point.type == PointType.InTangent)
|
||||
{
|
||||
if (dirVec.x > -1f) dirVec.x = -1f; // 至少向左偏 1 像素,防止除零或方向翻转
|
||||
}
|
||||
else if (point.type == PointType.OutTangent)
|
||||
{
|
||||
if (dirVec.x < 1f) dirVec.x = 1f; // 至少向右偏 1 像素
|
||||
}
|
||||
|
||||
// 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();
|
||||
RefreshPointsPosition(); // 统一刷新位置,确保手柄视觉表现一致
|
||||
}
|
||||
}
|
||||
|
||||
public enum PointType { Key, InTangent, OutTangent }
|
||||
|
||||
// 辅助交互类
|
||||
public class CurvePoint : MonoBehaviour, IDragHandler, IEndDragHandler
|
||||
{
|
||||
public KeyframeVisualizer visualizer;
|
||||
public int keyIndex;
|
||||
public PointType type;
|
||||
public RectTransform rectTransform;
|
||||
|
||||
// 引用关联点,方便计算
|
||||
public CurvePoint relatedKeyPoint;
|
||||
public CurvePoint inHandle;
|
||||
public CurvePoint outHandle;
|
||||
|
||||
public void Init(KeyframeVisualizer v, int index, PointType t)
|
||||
{
|
||||
visualizer = v;
|
||||
keyIndex = index;
|
||||
type = t;
|
||||
rectTransform = GetComponent<RectTransform>();
|
||||
}
|
||||
|
||||
public void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
// 实时更新曲线和画面
|
||||
visualizer.OnPointDragged(this, eventData.delta);
|
||||
}
|
||||
|
||||
public void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
// 拖拽结束后,通知外部同步 (关键一步)
|
||||
visualizer.OnEditFinished?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Assets/Scripts/Editor Tools/Panel Drawer.meta
Normal file
8
Assets/Scripts/Editor Tools/Panel Drawer.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 665007fcbd599ff458ca12322de17e90
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
62
Assets/Scripts/Editor Tools/Panel Drawer/PanelDrawer.cs
Normal file
62
Assets/Scripts/Editor Tools/Panel Drawer/PanelDrawer.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using DG.Tweening;
|
||||
using Ichni;
|
||||
using Ichni.Editor;
|
||||
using Ichni.RhythmGame;
|
||||
using Unity.VisualScripting;
|
||||
using UnityEngine;
|
||||
|
||||
public class PanelDrawer//暂时支持xz
|
||||
{
|
||||
public bool isEditing = false;
|
||||
public CameraManager cameraManager => EditorManager.instance.cameraManager;
|
||||
public SceneCamera sceneCamera => cameraManager.sceneCamera;
|
||||
|
||||
public float height
|
||||
{
|
||||
get
|
||||
{
|
||||
return _height;
|
||||
}
|
||||
set
|
||||
{
|
||||
_height = value;
|
||||
}
|
||||
}
|
||||
private float _height = 10f;
|
||||
public float baseHeight
|
||||
{
|
||||
get
|
||||
{
|
||||
return _baseHeight;
|
||||
}
|
||||
set
|
||||
{
|
||||
_baseHeight = value;
|
||||
}
|
||||
}
|
||||
private float _baseHeight = 0f;
|
||||
public void startEdit()
|
||||
{
|
||||
isEditing = true;
|
||||
if (!cameraManager.isSceneCameraActive)
|
||||
{
|
||||
|
||||
cameraManager.sceneCamera.transform.position = cameraManager.gameCamera.transform.position;
|
||||
cameraManager.sceneCamera.transform.rotation = cameraManager.gameCamera.transform.rotation;
|
||||
cameraManager.SwitchCamera();
|
||||
cameraManager.sceneCamera.transform.DOMove(cameraManager.gameCamera.transform.position + new Vector3(0, 0, _baseHeight + _height), 0.5f).SetEase(Ease.InOutQuad);
|
||||
cameraManager.sceneCamera.transform.DORotate(new Vector3(90, 0, 0), 0.5f).SetEase(Ease.InOutQuad);
|
||||
}
|
||||
else
|
||||
{
|
||||
cameraManager.sceneCamera.transform.DOMove(cameraManager.gameCamera.transform.position + new Vector3(0, 0, _baseHeight + _height), 0.5f).SetEase(Ease.InOutQuad);
|
||||
}
|
||||
|
||||
}
|
||||
public void endEdit()
|
||||
{
|
||||
isEditing = false;
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Editor Tools/Panel Drawer/PanelDrawer.cs.meta
Normal file
11
Assets/Scripts/Editor Tools/Panel Drawer/PanelDrawer.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5003e915880eb8e48967ab8231f6f228
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,587 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using Sirenix.OdinInspector;
|
||||
using System.Linq;
|
||||
using UnityEngine.InputSystem;
|
||||
using Michsky.MUIP;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace Ichni.Editor
|
||||
{
|
||||
[RequireComponent(typeof(RawImage))]
|
||||
public class KeyframeVisualizer : MonoBehaviour
|
||||
{
|
||||
public AnimationCurve curve;
|
||||
public float timeRange = 10f;
|
||||
public float valueRange = 10f;
|
||||
public float keyframeSize = 0.2f;
|
||||
public float tangentLength = 1f;
|
||||
public Color curveColor = Color.green;
|
||||
public Color keyframeColor = Color.red;
|
||||
public Color tangentColor = Color.blue;
|
||||
public int segments = 5;
|
||||
|
||||
[Header("Curve Preview")]
|
||||
public RawImage curveRawImage;
|
||||
public Vector2Int curveTextureSize = new Vector2Int(256, 128);
|
||||
|
||||
[System.Serializable]
|
||||
public struct KeyframeImageInfo
|
||||
{
|
||||
public Image keyImage;
|
||||
public Image inTangentImage;
|
||||
public Image outTangentImage;
|
||||
}
|
||||
|
||||
public List<KeyframeImageInfo> keyframeImages = new List<KeyframeImageInfo>();
|
||||
|
||||
// 合并min/max到类成员,便于全局一致使用
|
||||
private float minTime, maxTime, minValue = 0f, maxValue = 1f;
|
||||
|
||||
private void UpdateCurveRange()
|
||||
{
|
||||
if (curve == null || curve.length < 2)
|
||||
{
|
||||
minTime = maxTime = valueRange = timeRange = 0;
|
||||
minValue = 0f;
|
||||
maxValue = 1f;
|
||||
return;
|
||||
}
|
||||
minTime = 0f;
|
||||
maxTime = 1f;
|
||||
minValue = 0f; // 固定下界为0
|
||||
maxValue = 1f; // 固定上界为1
|
||||
valueRange = Mathf.Max(0.0001f, maxValue - minValue);
|
||||
timeRange = Mathf.Max(0.0001f, maxTime - minTime);
|
||||
}
|
||||
|
||||
[Button("Draw Curve To RawImage")]
|
||||
public void DrawCurveToRawImage()
|
||||
{
|
||||
UpdateCurveRange();
|
||||
if (curveRawImage == null || curve == null || curve.length < 2) return;
|
||||
|
||||
int texWidth = curveTextureSize.x;
|
||||
int texHeight = curveTextureSize.y;
|
||||
Texture2D tex = new Texture2D(texWidth, texHeight, TextureFormat.ARGB32, false);
|
||||
tex.filterMode = FilterMode.Point;
|
||||
tex.wrapMode = TextureWrapMode.Clamp;
|
||||
|
||||
// 清空
|
||||
for (int i = 0; i < texWidth; i++)
|
||||
for (int j = 0; j < texHeight; j++)
|
||||
tex.SetPixel(i, j, new Color(0, 0, 0, 0));
|
||||
|
||||
// === 新增:绘制网格 ===
|
||||
int gridXCount = 8;
|
||||
int gridYCount = 4;
|
||||
Color gridColor = new Color(0.3f, 0.3f, 0.3f, 0.7f);
|
||||
for (int gx = 1; gx < gridXCount; gx++)
|
||||
{
|
||||
int x = gx * texWidth / gridXCount;
|
||||
DrawVerticalLineOnTexture(tex, x, 0, texHeight - 1, gridColor);
|
||||
}
|
||||
for (int gy = 1; gy < gridYCount; gy++)
|
||||
{
|
||||
int y = gy * texHeight / gridYCount;
|
||||
DrawHorizontalLineOnTexture(tex, 0, texWidth - 1, y, gridColor);
|
||||
}
|
||||
|
||||
// === 新增:绘制边框 ===
|
||||
Color borderColor = Color.white;
|
||||
DrawHorizontalLineOnTexture(tex, 0, texWidth - 1, 0, borderColor);
|
||||
DrawHorizontalLineOnTexture(tex, 0, texWidth - 1, texHeight - 1, borderColor);
|
||||
DrawVerticalLineOnTexture(tex, 0, 0, texHeight - 1, borderColor);
|
||||
DrawVerticalLineOnTexture(tex, texWidth - 1, 0, texHeight - 1, borderColor);
|
||||
|
||||
int lastY = -1;
|
||||
for (int x = 0; x < texWidth; x++)
|
||||
{
|
||||
float t = (float)x / (texWidth - 1);
|
||||
float time = Mathf.Lerp(minTime, maxTime, t);
|
||||
float value = curve.Evaluate(time);
|
||||
float normY = (value - minValue) / valueRange;
|
||||
int y = (int)(normY * (texHeight - 1));
|
||||
|
||||
bool outOfRange = y < 0 || y >= texHeight;
|
||||
int drawY = y;
|
||||
|
||||
Color drawColor = outOfRange
|
||||
? new Color(1f - curveColor.r, 1f - curveColor.g, 1f - curveColor.b, 1f)
|
||||
: curveColor;
|
||||
|
||||
if (lastY >= 0)
|
||||
{
|
||||
int y0 = lastY;
|
||||
int y1 = y;
|
||||
int x0 = x - 1;
|
||||
int x1 = x;
|
||||
// === 修正:补全断点 ===
|
||||
if (x0 >= 0 && x0 < texWidth && x1 >= 0 && x1 < texWidth)
|
||||
{
|
||||
if (Mathf.Abs(y1 - y0) > 1)
|
||||
{
|
||||
int step = y1 > y0 ? 1 : -1;
|
||||
for (int yyy = y0; yyy != y1; yyy += step)
|
||||
{
|
||||
if (yyy >= 0 && yyy < texHeight)
|
||||
tex.SetPixel(x0, yyy, curveColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
int dy = Mathf.Abs(y1 - y0);
|
||||
int sy = y0 < y1 ? 1 : -1;
|
||||
int err = dy / 2;
|
||||
int yy = y0;
|
||||
for (int xx = x0; xx <= x1; xx++)
|
||||
{
|
||||
bool segOutOfRange = yy < 0 || yy >= texHeight;
|
||||
Color segColor = segOutOfRange
|
||||
? new Color(1f - curveColor.r, 1f - curveColor.g, 1f - curveColor.b, 1f)
|
||||
: curveColor;
|
||||
if (xx >= 0 && xx < texWidth)
|
||||
{
|
||||
for (int j = lastY; j < yy; j++) tex.SetPixel(x0, j, curveColor);
|
||||
for (int j = lastY; j > yy; j--) tex.SetPixel(x0, j, curveColor);
|
||||
tex.SetPixel(xx, yy, segColor);
|
||||
}
|
||||
err -= dy;
|
||||
if (err < 0)
|
||||
{
|
||||
yy += sy;
|
||||
err += (x1 - x0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (x >= 0 && x < texWidth)
|
||||
tex.SetPixel(x, drawY, drawColor);
|
||||
|
||||
lastY = y;
|
||||
}
|
||||
|
||||
foreach (var key in curve.keys)
|
||||
{
|
||||
float tangentScale = (maxTime - minTime) * 0.08f;
|
||||
if (!float.IsInfinity(key.inTangent))
|
||||
{
|
||||
float t0 = key.time;
|
||||
float v0 = key.value;
|
||||
float t1 = t0 - tangentScale;
|
||||
float v1 = v0 - key.inTangent * tangentScale;
|
||||
int x0 = (int)(((t0 - minTime) / timeRange) * (texWidth - 1));
|
||||
int y0 = (int)(((v0 - minValue) / valueRange) * (texHeight - 1));
|
||||
int x1 = (int)(((t1 - minTime) / timeRange) * (texWidth - 1));
|
||||
int y1 = (int)(((v1 - minValue) / valueRange) * (texHeight - 1));
|
||||
DrawLineOnTexture(tex, x0, y0, x1, y1, tangentColor);
|
||||
}
|
||||
if (!float.IsInfinity(key.outTangent))
|
||||
{
|
||||
float t0 = key.time;
|
||||
float v0 = key.value;
|
||||
float t1 = t0 + tangentScale;
|
||||
float v1 = v0 + key.outTangent * tangentScale;
|
||||
int x0 = (int)(((t0 - minTime) / timeRange) * (texWidth - 1));
|
||||
int y0 = (int)(((v0 - minValue) / valueRange) * (texHeight - 1));
|
||||
int x1 = (int)(((t1 - minTime) / timeRange) * (texWidth - 1));
|
||||
int y1 = (int)(((v1 - minValue) / valueRange) * (texHeight - 1));
|
||||
DrawLineOnTexture(tex, x0, y0, x1, y1, tangentColor * 0.8f);
|
||||
}
|
||||
}
|
||||
|
||||
tex.Apply();
|
||||
curveRawImage.texture = tex;
|
||||
}
|
||||
|
||||
private void DrawLineOnTexture(Texture2D tex, int x0, int y0, int x1, int y1, Color color)
|
||||
{
|
||||
int dx = Mathf.Abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
|
||||
int dy = Mathf.Abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
|
||||
int err = (dx > dy ? dx : -dy) / 2, e2;
|
||||
int lastY = y0;
|
||||
while (true)
|
||||
{
|
||||
for (int j = lastY; j < y0; j++) tex.SetPixel(x0, j, color);
|
||||
for (int j = lastY; j > y0; j--) tex.SetPixel(x0, j, color);
|
||||
tex.SetPixel(x0, y0, color);
|
||||
lastY = y0;
|
||||
if (x0 == x1 && y0 == y1) break;
|
||||
e2 = err;
|
||||
if (e2 > -dx) { err -= dy; x0 += sx; }
|
||||
if (e2 < dy) { err += dx; y0 += sy; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取关键帧在RawImage中的localPosition(以RawImage中心为原点)
|
||||
/// </summary>
|
||||
public Vector2 GetKeyframeLocalPositionInRawImage(Keyframe key, float scale = 1f)
|
||||
{
|
||||
UpdateCurveRange();
|
||||
if (curveRawImage == null || curve == null || curve.length < 2)
|
||||
return Vector2.zero;
|
||||
|
||||
// 这里直接使用固定的minValue/maxValue
|
||||
float normX = (key.time - minTime) / timeRange;
|
||||
float normY = (key.value - 0f) / Mathf.Max(0.0001f, 1f - 0f);
|
||||
|
||||
float px = normX * curveTextureSize.x;
|
||||
float py = normY * curveTextureSize.y;
|
||||
|
||||
Vector2 localPos = new Vector2(
|
||||
px - curveTextureSize.x * 0.5f,
|
||||
py - curveTextureSize.y * 0.5f
|
||||
);
|
||||
return localPos * scale;
|
||||
}
|
||||
|
||||
[Button("Create Keyframe Images")]
|
||||
public void CreateKeyframeImages()
|
||||
{
|
||||
UpdateCurveRange();
|
||||
if (curveRawImage == null || curve == null || curve.length < 1) return;
|
||||
|
||||
for (int i = curveRawImage.transform.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
var child = curveRawImage.transform.GetChild(i);
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
DestroyImmediate(child.gameObject);
|
||||
else
|
||||
#endif
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
keyframeImages.Clear();
|
||||
|
||||
for (int i = 0; i < curve.length; i++)
|
||||
{
|
||||
Keyframe key = curve.keys[i];
|
||||
Vector2 localPos = GetKeyframeLocalPositionInRawImage(key);
|
||||
|
||||
GameObject go = new GameObject($"KeyframeImage_{i}", typeof(RectTransform), typeof(Image));
|
||||
go.transform.SetParent(curveRawImage.transform, false);
|
||||
|
||||
RectTransform rt = go.GetComponent<RectTransform>();
|
||||
rt.sizeDelta = Vector2.one * 16f;
|
||||
rt.anchoredPosition = localPos;
|
||||
|
||||
Image img = go.GetComponent<Image>();
|
||||
img.color = keyframeColor;
|
||||
img.raycastTarget = false;
|
||||
PointMover pointMover = go.AddComponent<PointMover>();
|
||||
pointMover.parent = this;
|
||||
pointMover.keyIndex = i;
|
||||
|
||||
Image inImg = null;
|
||||
if (!float.IsInfinity(key.inTangent))
|
||||
{
|
||||
Vector2 tangentLocalPos = GetTangentLocalPosition(key, true);
|
||||
GameObject tgo = new GameObject($"TangentInImage_{i}", typeof(RectTransform), typeof(Image));
|
||||
tgo.transform.SetParent(curveRawImage.transform, false);
|
||||
RectTransform trt = tgo.GetComponent<RectTransform>();
|
||||
trt.sizeDelta = Vector2.one * 10f;
|
||||
trt.anchoredPosition = tangentLocalPos;
|
||||
inImg = tgo.GetComponent<Image>();
|
||||
inImg.color = tangentColor;
|
||||
inImg.raycastTarget = false;
|
||||
PointMover pointMover1 = tgo.AddComponent<PointMover>();
|
||||
pointMover1.IO = 1;
|
||||
pointMover1.parent = this;
|
||||
pointMover1.keyIndex = i;
|
||||
pointMover.subpointMover.Add(pointMover1);
|
||||
}
|
||||
|
||||
Image outImg = null;
|
||||
if (!float.IsInfinity(key.outTangent))
|
||||
{
|
||||
Vector2 tangentLocalPos = GetTangentLocalPosition(key, false);
|
||||
GameObject tgo = new GameObject($"TangentOutImage_{i}", typeof(RectTransform), typeof(Image));
|
||||
tgo.transform.SetParent(curveRawImage.transform, false);
|
||||
RectTransform trt = tgo.GetComponent<RectTransform>();
|
||||
trt.sizeDelta = Vector2.one * 10f;
|
||||
trt.anchoredPosition = tangentLocalPos;
|
||||
outImg = tgo.GetComponent<Image>();
|
||||
outImg.color = tangentColor * 0.8f;
|
||||
outImg.raycastTarget = false;
|
||||
PointMover pointMover2 = tgo.AddComponent<PointMover>();
|
||||
pointMover2.IO = -1;
|
||||
pointMover2.parent = this;
|
||||
pointMover2.keyIndex = i;
|
||||
pointMover.subpointMover.Add(pointMover2);
|
||||
}
|
||||
|
||||
keyframeImages.Add(new KeyframeImageInfo
|
||||
{
|
||||
keyImage = img,
|
||||
inTangentImage = inImg,
|
||||
outTangentImage = outImg
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 修复后的切线位置计算方法
|
||||
private Vector2 GetTangentLocalPosition(Keyframe key, bool isIn)
|
||||
{
|
||||
Vector2 keyLocalPos = GetKeyframeLocalPositionInRawImage(key);
|
||||
float tangent = isIn ? key.inTangent : key.outTangent;
|
||||
|
||||
// 计算像素/单位转换比例
|
||||
float pixelsPerTimeUnit = curveTextureSize.x / timeRange;
|
||||
float pixelsPerValueUnit = curveTextureSize.y / valueRange;
|
||||
|
||||
// 处理无穷斜率的情况(垂直切线)
|
||||
if (float.IsInfinity(tangent))
|
||||
{
|
||||
return keyLocalPos + new Vector2(
|
||||
0,
|
||||
isIn ? -tangentLength * 20f : tangentLength * 20f
|
||||
);
|
||||
}
|
||||
|
||||
// 计算方向向量
|
||||
Vector2 direction = new Vector2(
|
||||
isIn ? -1f : 1f,
|
||||
tangent * (pixelsPerValueUnit / pixelsPerTimeUnit)
|
||||
).normalized;
|
||||
|
||||
// 动态计算像素长度
|
||||
float dynamicLength = tangentLength * Mathf.Min(curveTextureSize.x, curveTextureSize.y) * 0.1f;
|
||||
|
||||
return keyLocalPos + direction * dynamicLength;
|
||||
}
|
||||
[Button("Update Curve From Images")]
|
||||
public void UpdateCurveFromImages()
|
||||
{
|
||||
UpdateCurveRange();
|
||||
if (curve == null || keyframeImages == null || keyframeImages.Count < curve.length) return;
|
||||
|
||||
Keyframe[] newKeys = new Keyframe[curve.length];
|
||||
for (int i = 0; i < curve.length; i++)
|
||||
{
|
||||
RectTransform rt = keyframeImages[i].keyImage.rectTransform;
|
||||
Vector2 localPos = rt.anchoredPosition;
|
||||
float px = localPos.x + curveTextureSize.x * 0.5f;
|
||||
float py = localPos.y + curveTextureSize.y * 0.5f;
|
||||
float normX = px / curveTextureSize.x;
|
||||
float normY = py / curveTextureSize.y;
|
||||
float time = Mathf.Lerp(minTime, maxTime, normX);
|
||||
float value = Mathf.Clamp01(0f + normY * Mathf.Max(0.0001f, 1f - 0f));
|
||||
|
||||
float inTangent = curve.keys[i].inTangent;
|
||||
float outTangent = curve.keys[i].outTangent;
|
||||
float timeScale = timeRange / curveTextureSize.x;
|
||||
float valueScale = valueRange / curveTextureSize.y;
|
||||
|
||||
// 修复切线斜率计算
|
||||
if (keyframeImages[i].inTangentImage != null)
|
||||
{
|
||||
Vector2 inLocalPos = keyframeImages[i].inTangentImage.rectTransform.anchoredPosition;
|
||||
Vector2 keyLocalPos = keyframeImages[i].keyImage.rectTransform.anchoredPosition;
|
||||
|
||||
// 使用像素坐标差计算真实斜率
|
||||
float dx = (keyLocalPos.x - inLocalPos.x) * timeScale;
|
||||
float dy = (keyLocalPos.y - inLocalPos.y) * valueScale;
|
||||
|
||||
if (Mathf.Abs(dx) > 0.001f)
|
||||
inTangent = dy / dx;
|
||||
}
|
||||
|
||||
if (keyframeImages[i].outTangentImage != null)
|
||||
{
|
||||
Vector2 outLocalPos = keyframeImages[i].outTangentImage.rectTransform.anchoredPosition;
|
||||
Vector2 keyLocalPos = keyframeImages[i].keyImage.rectTransform.anchoredPosition;
|
||||
|
||||
// 使用像素坐标差计算真实斜率
|
||||
float dx = (outLocalPos.x - keyLocalPos.x) * timeScale;
|
||||
float dy = (outLocalPos.y - keyLocalPos.y) * valueScale;
|
||||
|
||||
if (Mathf.Abs(dx) > 0.001f)
|
||||
outTangent = dy / dx;
|
||||
}
|
||||
|
||||
Keyframe newKey = new Keyframe(time, value, inTangent, outTangent);
|
||||
newKeys[i] = newKey;
|
||||
}
|
||||
curve.keys = newKeys;
|
||||
}
|
||||
|
||||
[Button("Refresh Keyframe Images Position")]
|
||||
public void RefreshKeyframeImagesPosition()
|
||||
{
|
||||
UpdateCurveRange();
|
||||
if (curve == null || keyframeImages == null || keyframeImages.Count < curve.length) return;
|
||||
|
||||
for (int i = 0; i < curve.length; i++)
|
||||
{
|
||||
Keyframe key = curve.keys[i];
|
||||
var info = keyframeImages[i];
|
||||
|
||||
if (info.keyImage != null)
|
||||
{
|
||||
Vector2 localPos = GetKeyframeLocalPositionInRawImage(key);
|
||||
info.keyImage.rectTransform.anchoredPosition = localPos;
|
||||
}
|
||||
|
||||
if (info.inTangentImage != null && !float.IsInfinity(key.inTangent))
|
||||
{
|
||||
Vector2 tangentLocalPos = GetTangentLocalPosition(key, true);
|
||||
info.inTangentImage.rectTransform.anchoredPosition = tangentLocalPos;
|
||||
}
|
||||
|
||||
if (info.outTangentImage != null && !float.IsInfinity(key.outTangent))
|
||||
{
|
||||
Vector2 tangentLocalPos = GetTangentLocalPosition(key, false);
|
||||
info.outTangentImage.rectTransform.anchoredPosition = tangentLocalPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CompositeParameterWindow compositeParameterWindow;
|
||||
public void UpadteValue()
|
||||
{
|
||||
compositeParameterWindow.connectedBaseElement.GetType().GetField(compositeParameterWindow.parameterName).SetValue(compositeParameterWindow.connectedBaseElement, curve);
|
||||
}
|
||||
|
||||
// === 新增:绘制水平线辅助方法 ===
|
||||
private void DrawHorizontalLineOnTexture(Texture2D tex, int x0, int x1, int y, Color color)
|
||||
{
|
||||
int width = tex.width;
|
||||
int height = tex.height;
|
||||
if (y < 0 || y >= height) return;
|
||||
int minX = Mathf.Clamp(Mathf.Min(x0, x1), 0, width - 1);
|
||||
int maxX = Mathf.Clamp(Mathf.Max(x0, x1), 0, width - 1);
|
||||
for (int x = minX; x <= maxX; x++)
|
||||
tex.SetPixel(x, y, color);
|
||||
}
|
||||
|
||||
// === 新增:绘制垂直线辅助方法 ===
|
||||
private void DrawVerticalLineOnTexture(Texture2D tex, int x, int y0, int y1, Color color)
|
||||
{
|
||||
int width = tex.width;
|
||||
int height = tex.height;
|
||||
if (x < 0 || x >= width) return;
|
||||
int minY = Mathf.Clamp(Mathf.Min(y0, y1), 0, height - 1);
|
||||
int maxY = Mathf.Clamp(Mathf.Max(y0, y1), 0, height - 1);
|
||||
for (int y = minY; y <= maxY; y++)
|
||||
tex.SetPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
|
||||
public class PointMover : MonoBehaviour
|
||||
{
|
||||
|
||||
public RectTransform rectTransform;
|
||||
public KeyframeVisualizer parent;
|
||||
public int keyIndex = -1; // 记录所属关键帧索引
|
||||
public PointMover parentPointMover;
|
||||
public List<PointMover> subpointMover = new();
|
||||
public bool Pressed;
|
||||
public int IO = 0;//0关键帧 1in -1out
|
||||
|
||||
private Dictionary<PointMover, Vector2> initialOffsets; // 存储切线点初始偏移
|
||||
private Vector2 startPosition; // 拖拽开始位置
|
||||
|
||||
private void Start()
|
||||
{
|
||||
rectTransform = gameObject.GetComponent<RectTransform>();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (RectTransformUtility.RectangleContainsScreenPoint(rectTransform, Mouse.current.position.ReadValue()))
|
||||
{
|
||||
if (Mouse.current.leftButton.wasPressedThisFrame)
|
||||
{
|
||||
StartCoroutine(Moving());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator Moving()
|
||||
{
|
||||
var windowDragger = parent.compositeParameterWindow.GetComponent<WindowDragger>();
|
||||
if (windowDragger != null)
|
||||
{
|
||||
windowDragger.Lock = true;
|
||||
}
|
||||
|
||||
// 获取边界尺寸
|
||||
float halfWidth = parent.curveTextureSize.x * 0.5f;
|
||||
float halfHeight = parent.curveTextureSize.y * 0.5f;
|
||||
|
||||
// 如果是切线点,记录初始位置和方向
|
||||
Vector2 initialPosition = rectTransform.anchoredPosition;
|
||||
Vector2 initialDirection = Vector2.zero;
|
||||
if (IO != 0)
|
||||
{
|
||||
PointMover keyPoint = parentPointMover != null ? parentPointMover : this;
|
||||
Vector2 keyPos = keyPoint.rectTransform.anchoredPosition;
|
||||
initialDirection = (initialPosition - keyPos).normalized;
|
||||
}
|
||||
|
||||
while (Mouse.current.leftButton.isPressed)
|
||||
{
|
||||
Vector2 mouseDelta = Mouse.current.delta.ReadValue();
|
||||
Vector2 newPos = rectTransform.anchoredPosition + mouseDelta;
|
||||
|
||||
// 边界约束
|
||||
newPos.x = Mathf.Clamp(newPos.x, -halfWidth, halfWidth);
|
||||
newPos.y = Mathf.Clamp(newPos.y, -halfHeight, halfHeight);
|
||||
|
||||
// 如果是切线点,约束移动方向
|
||||
if (IO != 0 && initialDirection != Vector2.zero)
|
||||
{
|
||||
Vector2 keyPos = parentPointMover.rectTransform.anchoredPosition;
|
||||
Vector2 toNewPos = newPos - keyPos;
|
||||
|
||||
// 计算与初始方向的夹角
|
||||
float angle = Vector2.Angle(initialDirection, toNewPos);
|
||||
|
||||
// 如果偏离初始方向超过45度,修正方向
|
||||
if (angle > 45f)
|
||||
{
|
||||
// 投影到初始方向
|
||||
float dot = Vector2.Dot(toNewPos, initialDirection);
|
||||
newPos = keyPos + initialDirection * Mathf.Sign(dot) * toNewPos.magnitude;
|
||||
}
|
||||
}
|
||||
|
||||
rectTransform.anchoredPosition = newPos;
|
||||
|
||||
// 如果是关键帧点,同时移动切线点
|
||||
if (IO == 0)
|
||||
{
|
||||
foreach (PointMover tangentPoint in subpointMover)
|
||||
{
|
||||
if (tangentPoint != null)
|
||||
{
|
||||
tangentPoint.rectTransform.anchoredPosition += mouseDelta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (windowDragger != null)
|
||||
{
|
||||
windowDragger.Lock = false;
|
||||
}
|
||||
UpdateParentCurve();
|
||||
}
|
||||
|
||||
public void UpdateParentCurve()
|
||||
{
|
||||
parent.UpdateCurveFromImages();
|
||||
parent.DrawCurveToRawImage();
|
||||
parent.UpadteValue();
|
||||
parent.CreateKeyframeImages();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -11,26 +11,28 @@ namespace Ichni.Editor
|
||||
public class CameraManager : MonoBehaviour, IBaseElement
|
||||
{
|
||||
public Camera backgroundCamera;
|
||||
|
||||
|
||||
public bool isSceneCameraActive;
|
||||
public SceneCamera sceneCamera;
|
||||
public float sceneCameraMoveSpeed;
|
||||
public float sceneCameraRotateSpeed;
|
||||
|
||||
|
||||
public PanelDrawer panelDrawer = new();
|
||||
|
||||
public GameCamera gameCamera;
|
||||
public bool haveGameCamera => gameCamera != null;
|
||||
public Camera currentCamera => haveGameCamera && !isSceneCameraActive ? gameCamera.gameCamera : sceneCamera.sceneCamera;
|
||||
|
||||
|
||||
public BaseElement_BM matchedBM { get; set; }
|
||||
|
||||
|
||||
public void SwitchCamera()
|
||||
{
|
||||
if (!haveGameCamera)
|
||||
{
|
||||
{
|
||||
LogWindow.Log("GameCamera is not assigned", Color.red);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
isSceneCameraActive = !isSceneCameraActive;
|
||||
sceneCamera.sceneCamera.enabled = isSceneCameraActive;
|
||||
gameCamera.gameCamera.enabled = !isSceneCameraActive;
|
||||
@@ -41,17 +43,33 @@ namespace Ichni.Editor
|
||||
public void SetUpInspector()
|
||||
{
|
||||
string ShowCameraType() => isSceneCameraActive ? "Scene Camera" : "Game Camera";
|
||||
|
||||
|
||||
IHaveInspection inspector = EditorManager.instance.uiManager.inspector;
|
||||
|
||||
|
||||
var container = inspector.GenerateContainer("Camera Manager");
|
||||
|
||||
|
||||
//切换摄像机,设置场景相机
|
||||
var cameraSettings = container.GenerateSubcontainer(3);
|
||||
var cameraTypeText = inspector.GenerateHintText(this, cameraSettings, ShowCameraType);
|
||||
var switchCameraButton = inspector.GenerateButton(this, cameraSettings, "Switch Camera", SwitchCamera);
|
||||
var cameraMoveSpeedField = inspector.GenerateInputField(this, cameraSettings, "Scene Camera Move Speed", nameof(sceneCameraMoveSpeed));
|
||||
var camMethods = container.GenerateSubcontainer(1);
|
||||
if (!panelDrawer.isEditing)
|
||||
{
|
||||
var butt1 = inspector.GenerateButton(this, camMethods, "startEdit", () =>
|
||||
{
|
||||
panelDrawer.startEdit();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var butt2 = inspector.GenerateButton(this, camMethods, "endEdit", () =>
|
||||
{
|
||||
panelDrawer.endEdit();
|
||||
});
|
||||
}
|
||||
sceneCamera.SetUpInspector();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,66 +68,69 @@ namespace Ichni.Editor
|
||||
private bool isRotatingSceneCamera;
|
||||
private Transform sceneCameraTransform;
|
||||
|
||||
private Vector3 _currentRotation; // 在类成员中缓存旋转角度
|
||||
|
||||
private void SceneCameraOperation()
|
||||
{
|
||||
if (Keyboard.current.ctrlKey.isPressed || Keyboard.current.altKey.isPressed || Keyboard.current.shiftKey.isPressed)
|
||||
var kb = Keyboard.current;
|
||||
var mouse = Mouse.current;
|
||||
var camManager = EditorManager.instance.cameraManager;
|
||||
|
||||
// 1. 组合按键拦截(保持原有逻辑,但缓存引用)
|
||||
if (kb.ctrlKey.isPressed || kb.altKey.isPressed || kb.shiftKey.isPressed) return;
|
||||
|
||||
bool isActive = camManager.isSceneCameraActive;
|
||||
|
||||
// 2. 位移逻辑优化
|
||||
if (isActive && !isPointerOverUI)
|
||||
{
|
||||
return;
|
||||
}
|
||||
float moveSpeed = camManager.sceneCameraMoveSpeed * Time.deltaTime;
|
||||
Vector3 moveDir = Vector3.zero;
|
||||
|
||||
if (EditorManager.instance.cameraManager.isSceneCameraActive && !isPointerOverUI) // 场景相机的移动和旋转
|
||||
{
|
||||
float cameraMoveSpeed = EditorManager.instance.cameraManager.sceneCameraMoveSpeed * Time.deltaTime;
|
||||
// 计算合成位移向量
|
||||
if (kb.wKey.isPressed) moveDir += sceneCameraTransform.forward;
|
||||
if (kb.sKey.isPressed) moveDir -= sceneCameraTransform.forward;
|
||||
if (kb.dKey.isPressed) moveDir += sceneCameraTransform.right;
|
||||
if (kb.aKey.isPressed) moveDir -= sceneCameraTransform.right;
|
||||
if (kb.eKey.isPressed) moveDir += sceneCameraTransform.up;
|
||||
if (kb.qKey.isPressed) moveDir -= sceneCameraTransform.up;
|
||||
|
||||
if (Keyboard.current.wKey.isPressed)
|
||||
// 一次性应用位移(如果存在移动)
|
||||
if (moveDir != Vector3.zero)
|
||||
{
|
||||
sceneCameraTransform.position += sceneCameraTransform.forward * cameraMoveSpeed;
|
||||
}
|
||||
|
||||
if (Keyboard.current.sKey.isPressed)
|
||||
{
|
||||
sceneCameraTransform.position -= sceneCameraTransform.forward * cameraMoveSpeed;
|
||||
}
|
||||
|
||||
if (Keyboard.current.dKey.isPressed)
|
||||
{
|
||||
sceneCameraTransform.position += sceneCameraTransform.right * cameraMoveSpeed;
|
||||
}
|
||||
|
||||
if (Keyboard.current.aKey.isPressed)
|
||||
{
|
||||
sceneCameraTransform.position -= sceneCameraTransform.right * cameraMoveSpeed;
|
||||
}
|
||||
|
||||
if (Keyboard.current.eKey.isPressed)
|
||||
{
|
||||
sceneCameraTransform.position += sceneCameraTransform.up * cameraMoveSpeed;
|
||||
}
|
||||
|
||||
if (Keyboard.current.qKey.isPressed)
|
||||
{
|
||||
sceneCameraTransform.position -= sceneCameraTransform.up * cameraMoveSpeed;
|
||||
sceneCameraTransform.position += moveDir.normalized * moveSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
if (EditorManager.instance.cameraManager.isSceneCameraActive)
|
||||
// 3. 旋转状态切换
|
||||
if (isActive)
|
||||
{
|
||||
if (Mouse.current.rightButton.wasPressedThisFrame && !isPointerOverUI)
|
||||
if (mouse.rightButton.wasPressedThisFrame && !isPointerOverUI)
|
||||
{
|
||||
isRotatingSceneCamera = true;
|
||||
// 初始化当前旋转角度,防止旋转跳变
|
||||
_currentRotation = sceneCameraTransform.eulerAngles;
|
||||
}
|
||||
else if (Mouse.current.rightButton.wasReleasedThisFrame)
|
||||
else if (mouse.rightButton.wasReleasedThisFrame)
|
||||
{
|
||||
isRotatingSceneCamera = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isRotatingSceneCamera)
|
||||
// 4. 旋转逻辑优化:使用欧拉角避免万向节锁
|
||||
if (isRotatingSceneCamera && EditorManager.instance.cameraManager.sceneCamera.CanBeFreeRotate)
|
||||
{
|
||||
float cameraRotateSpeed = EditorManager.instance.cameraManager.sceneCameraRotateSpeed * Time.deltaTime;
|
||||
Vector2 mouseDelta = Mouse.current.delta.ReadValue();
|
||||
sceneCameraTransform.Rotate(Vector3.up, -mouseDelta.x * cameraRotateSpeed, Space.World);
|
||||
sceneCameraTransform.Rotate(sceneCameraTransform.right, mouseDelta.y * cameraRotateSpeed, Space.World);
|
||||
float rotateSpeed = camManager.sceneCameraRotateSpeed * Time.deltaTime;
|
||||
Vector2 mouseDelta = mouse.delta.ReadValue();
|
||||
|
||||
// 计算新的旋转角度(Y轴左右转,X轴上下抬头)
|
||||
_currentRotation.y += mouseDelta.x * rotateSpeed;
|
||||
_currentRotation.x -= mouseDelta.y * rotateSpeed;
|
||||
|
||||
// 限制抬头角度(防止翻转)
|
||||
_currentRotation.x = Mathf.Clamp(_currentRotation.x, -89f, 89f);
|
||||
|
||||
sceneCameraTransform.rotation = Quaternion.Euler(_currentRotation.x, _currentRotation.y, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,24 +19,40 @@ namespace Ichni.Editor
|
||||
public BaseElement_BM matchedBM { get; set; }
|
||||
|
||||
[HideInInspector]
|
||||
public Vector3 cameraPosition; //注意,这里的Position和EulerAngles,是transform的中介变量,仅能用于Inspector显示!
|
||||
public Vector3 cameraPosition
|
||||
{
|
||||
get => sceneCamera.transform.position;
|
||||
set
|
||||
{
|
||||
sceneCamera.transform.position = value;
|
||||
}
|
||||
}
|
||||
[HideInInspector]
|
||||
public Vector3 cameraEulerAngles;
|
||||
public Vector3 cameraEulerAngles
|
||||
{
|
||||
get => sceneCamera.transform.eulerAngles;
|
||||
set
|
||||
{
|
||||
sceneCamera.transform.eulerAngles = value;
|
||||
}
|
||||
}
|
||||
public bool CanBeFreeRotate => EditorManager.instance.cameraManager.panelDrawer.isEditing;
|
||||
|
||||
|
||||
public void SetUpInspector()
|
||||
{
|
||||
IHaveInspection inspector = EditorManager.instance.uiManager.inspector;
|
||||
var container = inspector.GenerateContainer("Scene Camera");
|
||||
|
||||
|
||||
//摄像机参数设置
|
||||
var cameraSettings = container.GenerateSubcontainer(3);
|
||||
var viewTypeDropdown =
|
||||
var viewTypeDropdown =
|
||||
inspector.GenerateDropdown(this, cameraSettings, "View Type", typeof(GameCamera.CameraViewType), nameof(viewType))
|
||||
.AddListenerFunction(() => sceneCamera.orthographic = viewType == GameCamera.CameraViewType.Orthographic);
|
||||
var perspectiveAngleField =
|
||||
var perspectiveAngleField =
|
||||
inspector.GenerateInputField(this, cameraSettings, "Perspective Angle", nameof(perspectiveAngle))
|
||||
.AddListenerFunction(() => sceneCamera.fieldOfView = perspectiveAngle);
|
||||
var orthographicSizeField =
|
||||
var orthographicSizeField =
|
||||
inspector.GenerateInputField(this, cameraSettings, "Orthographic Size", nameof(orthographicSize))
|
||||
.AddListenerFunction(() => sceneCamera.orthographicSize = orthographicSize);
|
||||
|
||||
@@ -45,7 +61,7 @@ namespace Ichni.Editor
|
||||
var positionInputFields =
|
||||
inspector.GenerateVector3InputField(this, transformSettings, "Position", nameof(cameraPosition), true)
|
||||
.AddListenerFunction(() => sceneCamera.transform.position = cameraPosition);
|
||||
var eulerAnglesInputFields =
|
||||
var eulerAnglesInputFields =
|
||||
inspector.GenerateVector3InputField(this, transformSettings, "Euler Angles", nameof(cameraEulerAngles), true)
|
||||
.AddListenerFunction(() => sceneCamera.transform.eulerAngles = cameraEulerAngles);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user