8
Assets/Scripts/Editor Tools/FastNoteTracker.meta
Normal file
8
Assets/Scripts/Editor Tools/FastNoteTracker.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f852f4e83b1983d46aa3fc24b806a63b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
473
Assets/Scripts/Editor Tools/FastNoteTracker/FaseNoteTracker.cs
Normal file
473
Assets/Scripts/Editor Tools/FastNoteTracker/FaseNoteTracker.cs
Normal file
@@ -0,0 +1,473 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using DG.Tweening;
|
||||
using Dreamteck;
|
||||
using Dreamteck.Splines;
|
||||
using Dreamteck.Splines.Primitives;
|
||||
using ichni.RhythmGame;
|
||||
using Ichni;
|
||||
using Ichni.Editor;
|
||||
using Ichni.RhythmGame;
|
||||
using Ichni.RhythmGame.Beatmap;
|
||||
using TMPro;
|
||||
using UniRx;
|
||||
using Unity.VisualScripting;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// [修复] 统一命名空间引用
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace ichni.RhythmGame // [修复] 修正命名空间首字母大写,符合C#规范
|
||||
{
|
||||
public partial class FastNoteTracker : GameElement, IBeChangeInExport
|
||||
{
|
||||
public Track TrackedTrack { get => parentElement as Track; }
|
||||
private bool _isEnabled = false;
|
||||
public float horizonWidth = 5f;
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
set
|
||||
{
|
||||
_isEnabled = value;
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
BaseElement_BM IBeChangeInExport.MatchingExportElement { get => null; set { } }
|
||||
|
||||
public List<LineRenderer> BeatLines = new List<LineRenderer>();
|
||||
public List<float> Beats = new List<float>();
|
||||
public override void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
UpdateBeatLine();
|
||||
}
|
||||
|
||||
public int BeatDiver = 1; // [建议] 变量名修正为 BeatDiver (原本是 Beatdiver)
|
||||
|
||||
private LineRenderer selectedLine = null;
|
||||
|
||||
// 工具方法:计算线条的两个端点位置
|
||||
private void CalculateLinePositions(SplineSample sample, out Vector3 pos0, out Vector3 pos1)
|
||||
{
|
||||
Vector3 sideOffset = sample.rotation * Vector3.left * horizonWidth;
|
||||
pos0 = sample.position + sideOffset;
|
||||
pos1 = sample.position - sideOffset;
|
||||
}
|
||||
|
||||
// 工具方法:配置 LineRenderer 的基础属性
|
||||
private void SetupLineRenderer(LineRenderer line, Vector3 pos0, Vector3 pos1, Color color, bool refreshWidth = true)
|
||||
{
|
||||
line.useWorldSpace = true;
|
||||
line.positionCount = 2;
|
||||
line.SetPosition(0, pos0);
|
||||
line.SetPosition(1, pos1);
|
||||
if (refreshWidth)
|
||||
{
|
||||
line.startWidth = 0.05f;
|
||||
line.endWidth = 0.05f;
|
||||
}
|
||||
line.material = EditorManager.instance.basePrefabs.defaultTrailMaterial;
|
||||
line.startColor = line.endColor = color;
|
||||
}
|
||||
|
||||
private void RefreshMeshCollider(LineRenderer lineA, LineRenderer lineB)
|
||||
{
|
||||
MeshCollider col = lineA.GetComponent<MeshCollider>();
|
||||
if (col == null) col = lineA.gameObject.AddComponent<MeshCollider>();
|
||||
|
||||
Vector3 a1 = lineA.GetPosition(0);
|
||||
Vector3 a2 = lineA.GetPosition(1);
|
||||
Vector3 b1 = lineB.GetPosition(0);
|
||||
Vector3 b2 = lineB.GetPosition(1);
|
||||
|
||||
if (Vector3.Distance(a1, b1) < 0.001f)
|
||||
{
|
||||
if (col.sharedMesh != null) col.sharedMesh.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// 转换为局部坐标
|
||||
Vector3[] newVertices = new Vector3[] {
|
||||
lineA.transform.InverseTransformPoint(a1),
|
||||
lineA.transform.InverseTransformPoint(a2),
|
||||
lineA.transform.InverseTransformPoint(b1),
|
||||
lineA.transform.InverseTransformPoint(b2)
|
||||
};
|
||||
|
||||
Mesh mesh = col.sharedMesh;
|
||||
if (mesh == null)
|
||||
{
|
||||
mesh = new Mesh();
|
||||
mesh.name = "BeatLineMesh";
|
||||
}
|
||||
else
|
||||
{
|
||||
// [关键优化]:如果新旧顶点差异极小,则不刷新 Mesh,防止物理系统抖动
|
||||
if (mesh.vertexCount == 4 && Vector3.Distance(mesh.vertices[0], newVertices[0]) < 0.0001f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
mesh.Clear();
|
||||
}
|
||||
|
||||
mesh.vertices = newVertices;
|
||||
mesh.triangles = new int[] { 0, 1, 2, 2, 1, 3, 2, 1, 0, 3, 1, 2 };
|
||||
mesh.RecalculateNormals();
|
||||
mesh.RecalculateBounds();
|
||||
col.sharedMesh = mesh;
|
||||
|
||||
// 确保 Tag 正确
|
||||
if (!lineA.CompareTag("LineRenderer")) lineA.tag = "LineRenderer";
|
||||
}
|
||||
public void UpdateBeatLine()
|
||||
{
|
||||
// 1. 清理
|
||||
foreach (var line in BeatLines) if (line != null) Destroy(line.gameObject);
|
||||
BeatLines.Clear();
|
||||
Beats.Clear();
|
||||
|
||||
if (!_isEnabled) return;
|
||||
|
||||
// 获取数据源
|
||||
SplineComputer splineComputer = TrackedTrack.trackPathSubmodule.path;
|
||||
var trackTime = TrackedTrack.trackTimeSubmodule as TrackTimeSubmoduleMovable;
|
||||
float beatStart = EditorManager.instance.songInformation.beatManager.GetBeatFromTime(trackTime.trackStartTime);
|
||||
float beatEnd = EditorManager.instance.songInformation.beatManager.GetBeatFromTime(trackTime.trackEndTime);
|
||||
|
||||
// 2. 批量生成
|
||||
for (float b = beatStart - 1; b <= beatEnd + 1f / BeatDiver; b += 1f / BeatDiver)
|
||||
{
|
||||
float timeAtBeat = EditorManager.instance.songInformation.beatManager.GetTimeFromBeat(b);
|
||||
float trackPercent = Mathf.Clamp01(trackTime.GetTrackPercentRaw(timeAtBeat));
|
||||
|
||||
CalculateLinePositions(splineComputer.Evaluate(trackPercent), out Vector3 p0, out Vector3 p1);
|
||||
|
||||
GameObject obj = Instantiate(EditorManager.instance.basePrefabs.emptyObject, this.transform);
|
||||
LineRenderer lr = obj.AddComponent<LineRenderer>();
|
||||
|
||||
Color color = (Mathf.Abs(b - Mathf.Round(b)) <= 0.01f) ? Color.green : Color.cyan;
|
||||
SetupLineRenderer(lr, p0, p1, color);
|
||||
|
||||
float bi = (trackPercent >= 1f) ? EditorManager.instance.songInformation.beatManager.GetBeatFromTime(trackTime.trackEndTime) : b;
|
||||
Beats.Add(bi < 0 ? 0f : bi);
|
||||
BeatLines.Add(lr);
|
||||
}
|
||||
|
||||
// 3. 批量生成 Collider
|
||||
for (int i = 0; i < BeatLines.Count - 1; i++) RefreshMeshCollider(BeatLines[i], BeatLines[i + 1]);
|
||||
}
|
||||
|
||||
public void AdjustBeatLine()
|
||||
{
|
||||
if (!_isEnabled)
|
||||
{
|
||||
foreach (var line in BeatLines) if (line != null) line.gameObject.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
SplineComputer splineComputer = TrackedTrack.trackPathSubmodule.path;
|
||||
var trackTime = TrackedTrack.trackTimeSubmodule as TrackTimeSubmoduleMovable;
|
||||
float beatStart = EditorManager.instance.songInformation.beatManager.GetBeatFromTime(trackTime.trackStartTime);
|
||||
float beatEnd = EditorManager.instance.songInformation.beatManager.GetBeatFromTime(trackTime.trackEndTime);
|
||||
|
||||
int index = 0;
|
||||
// 重置 Beats 列表以匹配新的位置
|
||||
Beats.Clear();
|
||||
|
||||
for (float b = beatStart - 1; b <= beatEnd + 1f / BeatDiver; b += 1f / BeatDiver)
|
||||
{
|
||||
float timeAtBeat = EditorManager.instance.songInformation.beatManager.GetTimeFromBeat(b);
|
||||
float trackPercent = Mathf.Clamp01(trackTime.GetTrackPercentRaw(timeAtBeat));
|
||||
|
||||
// 如果超出范围,跳过更新(除非你想一直显示)
|
||||
// 这里沿用你 UpdateBeatLine 的逻辑
|
||||
CalculateLinePositions(splineComputer.Evaluate(trackPercent), out Vector3 p0, out Vector3 p1);
|
||||
|
||||
LineRenderer lr;
|
||||
if (index < BeatLines.Count)
|
||||
{
|
||||
lr = BeatLines[index];
|
||||
lr.gameObject.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameObject obj = Instantiate(EditorManager.instance.basePrefabs.emptyObject, this.transform);
|
||||
lr = obj.AddComponent<LineRenderer>();
|
||||
BeatLines.Add(lr);
|
||||
}
|
||||
|
||||
Color color = (Mathf.Abs(b - Mathf.Round(b)) <= 0.01f) ? Color.green : Color.cyan;
|
||||
SetupLineRenderer(lr, p0, p1, color, false);
|
||||
float bi = (trackPercent >= 1f) ? EditorManager.instance.songInformation.beatManager.GetBeatFromTime(trackTime.trackEndTime) : b;
|
||||
Beats.Add(bi < 0 ? 0f : bi);
|
||||
index++;
|
||||
}
|
||||
for (int i = index; i < BeatLines.Count; i++) BeatLines[i].gameObject.SetActive(false);
|
||||
for (int i = 0; i < index - 1; i++) RefreshMeshCollider(BeatLines[i], BeatLines[i + 1]);
|
||||
}
|
||||
public bool ForceRefresh = false;
|
||||
void Update()
|
||||
{
|
||||
if (ForceRefresh)
|
||||
{
|
||||
AdjustBeatLine();
|
||||
}
|
||||
|
||||
if (InputListener.instance.isPointerOverUI) return;
|
||||
CastRay();
|
||||
if (IsEnabled && selectedLine != null)
|
||||
{
|
||||
DetectNote();
|
||||
}
|
||||
|
||||
}
|
||||
public void CastRay()
|
||||
{
|
||||
if (EditorManager.instance.cameraManager.currentCamera == null) return;
|
||||
|
||||
Ray ray = EditorManager.instance.cameraManager.currentCamera.ScreenPointToRay(Mouse.current.position.ReadValue());
|
||||
RaycastHit hit;
|
||||
// Debug.DrawRay(ray.origin, ray.direction * 100f, Color.red);
|
||||
|
||||
if (Physics.RaycastAll(ray).FirstOrDefault(h => h.collider.CompareTag("LineRenderer")).collider != null)
|
||||
{
|
||||
hit = Physics.RaycastAll(ray).First(h => h.collider.CompareTag("LineRenderer"));
|
||||
|
||||
LineRenderer hoveredLine = hit.collider.GetComponent<LineRenderer>();
|
||||
if (BeatLines.Contains(hoveredLine))
|
||||
{
|
||||
if (Mouse.current.leftButton.wasPressedThisFrame)
|
||||
{
|
||||
Debug.Log($"Clicked on line area: {hoveredLine.gameObject.name} at {hit.point}");
|
||||
}
|
||||
if (selectedLine != hoveredLine)
|
||||
{
|
||||
if (selectedLine != null)
|
||||
{
|
||||
selectedLine.startWidth = 0.05f;
|
||||
selectedLine.endWidth = 0.05f;
|
||||
}
|
||||
selectedLine = hoveredLine;
|
||||
selectedLine.startWidth = 0.15f;
|
||||
selectedLine.endWidth = 0.15f;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (selectedLine != null)
|
||||
{
|
||||
selectedLine.startWidth = 0.05f;
|
||||
selectedLine.endWidth = 0.05f;
|
||||
selectedLine = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void InputDetected()
|
||||
{
|
||||
// if(Keyboard)
|
||||
}
|
||||
public void DetectNote()
|
||||
{
|
||||
if (Keyboard.current.digit1Key.wasPressedThisFrame)
|
||||
AddNote(0);
|
||||
else if (Keyboard.current.digit2Key.wasPressedThisFrame)
|
||||
AddNote(1);
|
||||
else if (Keyboard.current.digit3Key.wasPressedThisFrame)
|
||||
AddNote(2);
|
||||
else if (Keyboard.current.digit4Key.wasPressedThisFrame)
|
||||
AddNote(3);
|
||||
}
|
||||
public void AddNote(int NoteCode)
|
||||
{
|
||||
if (!EditorManager.instance.useNotePrefab)
|
||||
{
|
||||
LogWindow.Log("Please enable \"Note Prefab\" in EditorManager", Color.red);
|
||||
return;
|
||||
}
|
||||
if (selectedLine == null) return;
|
||||
float time = EditorManager.instance.songInformation.beatManager.GetTimeFromBeat(
|
||||
Beats[BeatLines.IndexOf(selectedLine)]
|
||||
);
|
||||
NoteBase a = null;
|
||||
switch (NoteCode)
|
||||
{
|
||||
|
||||
case 0:
|
||||
a = Tap.GenerateElement("New Tap", Guid.NewGuid(), new List<string>(), true, TrackedTrack, time);
|
||||
a.noteVisual.transformSubmodule.Refresh();
|
||||
break;
|
||||
case 3:
|
||||
a = Hold.GenerateElement("New Hold", Guid.NewGuid(), new List<string>(), true, TrackedTrack, time, time + 0.5f);
|
||||
a.noteVisual.transformSubmodule.Refresh();
|
||||
Observable.NextFrame().Subscribe(_ =>
|
||||
{
|
||||
StartCoroutine(DraggingHold((Hold)a));
|
||||
});
|
||||
|
||||
break;
|
||||
case 1:
|
||||
a = Stay.GenerateElement("New Stay", Guid.NewGuid(), new List<string>(), true, TrackedTrack, time);
|
||||
a.noteVisual.transformSubmodule.Refresh();
|
||||
break;
|
||||
case 2:
|
||||
a = Flick.GenerateElement("New Flick", Guid.NewGuid(), new List<string>(), true, TrackedTrack, time, new List<Vector2>());
|
||||
a.noteVisual.transformSubmodule.Refresh();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
Observable.NextFrame().Subscribe(_ =>
|
||||
{
|
||||
CreateTextHint(a);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
private IEnumerator DraggingHold(Hold hold)
|
||||
{
|
||||
GameObject obj = Instantiate(EditorManager.instance.basePrefabs.emptyObject, this.transform);
|
||||
LineRenderer lr = obj.AddComponent<LineRenderer>();
|
||||
lr.startWidth = lr.endWidth = 0.05f;
|
||||
lr.positionCount = 2;
|
||||
lr.material = EditorManager.instance.basePrefabs.defaultTrailMaterial;
|
||||
lr.startColor = lr.endColor = Color.yellow;
|
||||
lr.SetPosition(0, hold.noteVisual.transform.position);
|
||||
lr.SetPosition(1, hold.noteVisual.transform.position);
|
||||
while (Keyboard.current.digit4Key.isPressed && selectedLine != null)
|
||||
|
||||
{
|
||||
yield return null;
|
||||
try
|
||||
{
|
||||
|
||||
float time = EditorManager.instance.songInformation.beatManager.GetTimeFromBeat(
|
||||
Beats[BeatLines.IndexOf(selectedLine)]
|
||||
);
|
||||
hold.holdEndTime = time < hold.exactJudgeTime ? hold.exactJudgeTime + 0.1f : time;
|
||||
hold.noteVisual.transformSubmodule.Refresh();
|
||||
lr.SetPosition(1, (selectedLine.GetPosition(0) + selectedLine.GetPosition(1)) / 2);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning(e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
Destroy(lr.gameObject);
|
||||
CreateTextHint(hold);
|
||||
}
|
||||
private void CreateTextHint(NoteBase noteBase)
|
||||
{
|
||||
// 1. 创建一个新的 GameObject
|
||||
string content = noteBase.elementName;
|
||||
Vector3 worldPos = noteBase.noteVisual.transform.position;
|
||||
GameObject hintObj = new GameObject("NoteHint_" + content);
|
||||
hintObj.transform.position = worldPos;
|
||||
|
||||
// 2. 添加 TextMeshPro 组件 (注意是 TextMeshPro,不是 TextMeshProUGUI)
|
||||
// 因为我们在 3D 空间生成,所以使用非 UI 版本
|
||||
TextMeshPro text = hintObj.AddComponent<TextMeshPro>();
|
||||
// 3. 配置文字属性
|
||||
text.text = content;
|
||||
text.fontSize = 6; // 根据你的缩放调整大小
|
||||
text.alignment = TextAlignmentOptions.Center;
|
||||
text.color = Color.yellow;
|
||||
|
||||
// 4. (可选) 设置渲染层级,确保它在轨道上方显示
|
||||
// text.sortingOrder = 100;
|
||||
|
||||
// 5. 动画效果:向上移动 1 个单位,并同时淡出
|
||||
// 1 秒后执行
|
||||
hintObj.transform.DOScale(Vector3.one * 1.2f, 1f).SetEase(Ease.OutBack).From(Vector3.zero);
|
||||
hintObj.transform.DOMoveY(worldPos.y + 1f, 1f);
|
||||
text.DOFade(0, 1f).OnComplete(() => Destroy(hintObj));
|
||||
// 7. 让文字始终面向相机 (Billboard 效果)
|
||||
// 如果你的相机视角是固定的,可以直接设置旋转
|
||||
hintObj.transform.LookAt(hintObj.transform.position + Camera.main.transform.rotation * Vector3.forward,
|
||||
EditorManager.instance.cameraManager.currentCamera.transform.rotation * Vector3.up);
|
||||
}
|
||||
}
|
||||
|
||||
// 后面的 Inspector 和 Export 代码逻辑暂时不需要大改,只要注意变量名 Beatdiver -> BeatDiver
|
||||
public partial class FastNoteTracker
|
||||
{
|
||||
public BaseElement_BM MatchingExportElement { get => null; set => throw new System.NotImplementedException(); }
|
||||
|
||||
public void SaveExportBM() { }
|
||||
|
||||
public override void SetUpInspector()
|
||||
{
|
||||
base.SetUpInspector();
|
||||
|
||||
IHaveInspection inspector = EditorManager.instance.uiManager.inspector;
|
||||
var container = inspector.GenerateContainer("Fast Note Tracker");
|
||||
var sub = container.GenerateSubcontainer(2);
|
||||
inspector.GenerateToggle(this, sub, "Enabled", nameof(IsEnabled));
|
||||
// 修正变量名
|
||||
inspector.GenerateInputField(this, sub, "Beat Diver", nameof(BeatDiver));
|
||||
inspector.GenerateToggle(this, sub, "Force refresh (cost++)", nameof(ForceRefresh));
|
||||
inspector.GenerateInputField(this, sub, "Horizon Width", nameof(horizonWidth));
|
||||
}
|
||||
|
||||
public static FastNoteTracker GenerateElement(string elementName, Guid id, List<string> tags,
|
||||
bool isFirstGenerated, GameElement parentElement)
|
||||
{
|
||||
FastNoteTracker fastNoteTracker = Instantiate(EditorManager.instance.basePrefabs.emptyObject, parentElement.transform)
|
||||
.AddComponent<FastNoteTracker>();
|
||||
if (parentElement is not Track)
|
||||
{
|
||||
LogWindow.Log("FastNoteTracker must be a child of Track element.");
|
||||
return null;
|
||||
}
|
||||
fastNoteTracker.Initialize(elementName, id, tags, isFirstGenerated, parentElement);
|
||||
return fastNoteTracker;
|
||||
}
|
||||
|
||||
public override void Initialize(string elementName, Guid id, List<string> tags, bool isFirstGenerated, GameElement parentElement)
|
||||
{
|
||||
base.Initialize(elementName, id, tags, isFirstGenerated, parentElement);
|
||||
//parentElement.refreshAction += AdjustBeatLine;
|
||||
}
|
||||
|
||||
public override void SaveBM()
|
||||
{
|
||||
matchedBM = new FastNoteTracker_BM(elementName, elementGuid, tags, parentElement.elementGuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Ichni.RhythmGame.Beatmap
|
||||
{
|
||||
public class FastNoteTracker_BM : GameElement_BM
|
||||
{
|
||||
public FastNoteTracker_BM(string elementName, Guid id, List<string> tags, Guid attachedElementGuid)
|
||||
{
|
||||
this.elementName = elementName;
|
||||
this.elementGuid = id;
|
||||
this.tags = tags;
|
||||
this.attachedElementGuid = attachedElementGuid;
|
||||
}
|
||||
|
||||
public override GameElement DuplicateBM(GameElement attached)
|
||||
{
|
||||
return FastNoteTracker.GenerateElement(elementName, elementGuid, tags, false, attached);
|
||||
}
|
||||
|
||||
public override void ExecuteBM()
|
||||
{
|
||||
FastNoteTracker.GenerateElement(elementName, elementGuid, tags, false, GetElement(attachedElementGuid));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f7cd635f67bf63c4bbbb6b3ff8c1a295
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b0f36136b81e014bb5d6dbd951a3360
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,363 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using DG.Tweening;
|
||||
using Ichni;
|
||||
|
||||
using Ichni.RhythmGame;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public partial class EventPoint : MonoBehaviour
|
||||
{
|
||||
public AnimatedFloat animatedFloat;
|
||||
public EventPoint LastEventPoint;
|
||||
public EventPoint NextEventPoint;
|
||||
|
||||
public Image EvDrawimage;
|
||||
public Image OvDrawimage;
|
||||
|
||||
|
||||
public RectTransform LeftSide;
|
||||
public RectTransform RightSide;
|
||||
public Button selectButton;
|
||||
public RawImage CurveCanvas;
|
||||
|
||||
public FlexibleFloatTab FatherTab;
|
||||
public TMP_Text ViewText;
|
||||
|
||||
public static bool Locked = false;
|
||||
public int BeatDeviver => FatherTab.BeatDeviver;
|
||||
public void Initialize(AnimatedFloat animatedFloat)
|
||||
{
|
||||
this.animatedFloat = animatedFloat;
|
||||
transform.localPosition = new Vector3(
|
||||
animatedFloat.startTime / EditorManager.instance.timeline.timePerBeat * BeatDeviver, 0, 0
|
||||
);
|
||||
RightSide.localPosition = new Vector3(
|
||||
(animatedFloat.endTime - animatedFloat.startTime) / EditorManager.instance.timeline.timePerBeat * BeatDeviver, 0, 0);
|
||||
|
||||
EvDrawimage.rectTransform.sizeDelta = new Vector2(RightSide.localPosition.x - LeftSide.localPosition.x, EvDrawimage.rectTransform.sizeDelta.y);
|
||||
EvDrawimage.transform.localPosition = new Vector3(EvDrawimage.rectTransform.sizeDelta.x / 2, 0, 0);
|
||||
OvDrawimage.transform.localPosition = RightSide.localPosition;
|
||||
|
||||
CurveCanvas.rectTransform.sizeDelta = new Vector2(EvDrawimage.rectTransform.sizeDelta.x, EvDrawimage.rectTransform.sizeDelta.y);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public float value => FatherTab.scalevalue;
|
||||
public void Refresh(bool changePos = false)
|
||||
{
|
||||
|
||||
if (changePos)
|
||||
{
|
||||
Initialize(animatedFloat);
|
||||
}
|
||||
UpdateValue();
|
||||
ReDraw(value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void SelectButtonClick()//unity内:当按钮按下时
|
||||
{
|
||||
if (Locked) return;
|
||||
if (Keyboard.current.leftShiftKey.isPressed)
|
||||
{
|
||||
if (FatherTab.FatherWindow.ClipBoard[FatherTab.Title].Contains(animatedFloat))
|
||||
{
|
||||
FatherTab.FatherWindow.ClipBoard[FatherTab.Title].Remove(animatedFloat);
|
||||
LeftSide.sizeDelta = new Vector2(15, EvDrawimage.rectTransform.sizeDelta.y);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
FatherTab.FatherWindow.ClipBoard[FatherTab.Title].Add(animatedFloat);
|
||||
LeftSide.sizeDelta = EvDrawimage.rectTransform.sizeDelta;
|
||||
|
||||
}
|
||||
FatherTab.FatherWindow.updateClipBoardMuM();
|
||||
}
|
||||
else UpLoad();
|
||||
}
|
||||
public void UpLoad()
|
||||
{
|
||||
FatherTab.FatherWindow.animationCurveTypeDropdown.onValueChanged.RemoveAllListeners();
|
||||
|
||||
|
||||
// 如果当前点是已连接点,则取消连接并隐藏可见区域
|
||||
if (FatherTab.FatherWindow.ConnectedPoint == this)
|
||||
{
|
||||
FatherTab.FatherWindow.VisibleArea.SetActive(false);
|
||||
FatherTab.FatherWindow.ConnectedPoint = null;
|
||||
EvDrawimage.color = new Color(EvDrawimage.color.r, 0.3019607843137255f, EvDrawimage.color.b, 0.5f);
|
||||
FatherTab.FatherWindow.StartText.text = "";
|
||||
FatherTab.FatherWindow.EndText.text = "";
|
||||
FatherTab.FatherWindow.StartValueText.text = "";
|
||||
FatherTab.FatherWindow.EndValueText.text = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果有已连接点,则重置其颜色
|
||||
if (FatherTab.FatherWindow.ConnectedPoint != null)
|
||||
{
|
||||
FatherTab.FatherWindow.EvEndpointChangeButton.GetComponent<Image>().color = new Color(1f, 1f, 1f, 1);
|
||||
|
||||
FatherTab.FatherWindow.ConnectedPoint.EvDrawimage.color = new Color(
|
||||
FatherTab.FatherWindow.ConnectedPoint.EvDrawimage.color.r,
|
||||
0.3019607843137255f,
|
||||
FatherTab.FatherWindow.ConnectedPoint.EvDrawimage.color.b, 0.5f
|
||||
);
|
||||
}
|
||||
UpdateValue();
|
||||
|
||||
|
||||
}
|
||||
public void UpdateValue()
|
||||
{
|
||||
// 设置新的连接点并更新UI
|
||||
FatherTab.FatherWindow.ConnectedPoint = this;
|
||||
EvDrawimage.color = new Color(EvDrawimage.color.r, 0.75f, EvDrawimage.color.b, 1f);
|
||||
|
||||
FatherTab.FatherWindow.animationCurveTypeDropdown.onValueChanged.RemoveAllListeners();
|
||||
// 更新下拉选项
|
||||
FatherTab.FatherWindow.animationCurveTypeDropdown.ClearOptions();
|
||||
List<string> enumNameList = System.Enum.GetNames(typeof(AnimationCurveType)).ToList();
|
||||
FatherTab.FatherWindow.VisibleArea.SetActive(true);
|
||||
FatherTab.FatherWindow.animationCurveTypeDropdown.AddOptions(enumNameList);
|
||||
FatherTab.FatherWindow.animationCurveTypeDropdown.value = (int)animatedFloat.animationCurveType;
|
||||
// 更新文本
|
||||
FatherTab.FatherWindow.StartText.text = animatedFloat.startTime.ToString();
|
||||
FatherTab.FatherWindow.EndText.text = animatedFloat.endTime.ToString();
|
||||
FatherTab.FatherWindow.StartValueText.text = animatedFloat.startValue.ToString();
|
||||
FatherTab.FatherWindow.EndValueText.text = animatedFloat.endValue.ToString();
|
||||
|
||||
FatherTab.FatherWindow.animationCurveTypeDropdown.onValueChanged.AddListener(value => FatherTab.FatherWindow.ChangeValue());
|
||||
}
|
||||
// 添加静态方法:查找插入索引
|
||||
public static int FindInsertIndex(List<EventPoint> eventPoints, float startTime)
|
||||
{
|
||||
int low = 0;
|
||||
int high = eventPoints.Count - 1;
|
||||
while (low <= high)
|
||||
{
|
||||
int mid = (low + high) / 2;
|
||||
if (eventPoints[mid].animatedFloat.startTime < startTime)
|
||||
{
|
||||
low = mid + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
high = mid - 1;
|
||||
}
|
||||
}
|
||||
return low;
|
||||
}
|
||||
|
||||
// 添加实例方法:连接事件点
|
||||
public void LinkEventPoints(List<EventPoint> eventPoints, int index)
|
||||
{
|
||||
if (index - 1 >= 0)
|
||||
{
|
||||
LastEventPoint = eventPoints[index - 1];
|
||||
LastEventPoint.NextEventPoint = this;
|
||||
}
|
||||
else
|
||||
{
|
||||
LastEventPoint = null;
|
||||
}
|
||||
if (index == eventPoints.Count - 1)
|
||||
{
|
||||
NextEventPoint = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加实例方法:连接新事件点
|
||||
public void LinkNewEventPoint(List<EventPoint> eventPoints, bool link, float scalevalue)
|
||||
{
|
||||
int index = eventPoints.IndexOf(this);
|
||||
if (index - 1 >= 0)
|
||||
{
|
||||
LastEventPoint = eventPoints[index - 1];
|
||||
LastEventPoint.NextEventPoint = this;
|
||||
LastEventPoint.ReDraw(scalevalue);
|
||||
}
|
||||
if (index + 1 < eventPoints.Count)
|
||||
{
|
||||
NextEventPoint = eventPoints[index + 1];
|
||||
if (link) animatedFloat.endTime = NextEventPoint.animatedFloat.startTime;
|
||||
Initialize(animatedFloat);
|
||||
NextEventPoint.LastEventPoint = this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 添加静态方法:克隆 AnimatedFloat 并应用时间偏移
|
||||
/// <summary>
|
||||
/// 克隆一个 AnimatedFloat 对象,并根据偏移量调整其开始和结束时间。
|
||||
/// </summary>
|
||||
/// <param name="animatedFloat">要克隆的 AnimatedFloat 对象。</param>
|
||||
/// <param name="offset">时间偏移量。</param>
|
||||
/// <returns>克隆后的 AnimatedFloat 对象。</returns>
|
||||
public static AnimatedFloat CloneWithOffset(AnimatedFloat animatedFloat, float offset)
|
||||
{
|
||||
return new AnimatedFloat(
|
||||
animatedFloat.startTime + offset,
|
||||
animatedFloat.endTime + offset,
|
||||
animatedFloat.startValue,
|
||||
animatedFloat.endValue,
|
||||
animatedFloat.animationCurveType
|
||||
);
|
||||
}
|
||||
}
|
||||
public partial class EventPoint//显示?
|
||||
{
|
||||
public IEnumerator GenerateTextureCoroutine(int width, int height, float value)
|
||||
{
|
||||
Task<Color[]> task = Task.Run(() => GenerateTextureColors(width, height, value));
|
||||
while (!task.IsCompleted)
|
||||
{
|
||||
yield return null; // 等待下一帧
|
||||
}
|
||||
Color[] textureColors = task.Result;
|
||||
Texture2D Texture = new Texture2D(width, height);
|
||||
Texture.SetPixels(textureColors);
|
||||
Texture.Apply();
|
||||
CurveCanvas.texture = Texture;
|
||||
// CurveCanvas.color = new Color(1, 1, 1, 0);
|
||||
// CurveCanvas.DOColor(new Color(1, 1, 1, 1), 0.2f).SetEase(Ease.InOutSine);
|
||||
|
||||
}
|
||||
public Color[] GenerateTextureColors(int width, int height, float value)
|
||||
{
|
||||
Color[] pixels = new Color[width * height];
|
||||
|
||||
// 初始化所有像素为透明
|
||||
for (int i = 0; i < pixels.Length; i++)
|
||||
{
|
||||
pixels[i] = new Color(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
int LastEventPointY = 0;
|
||||
for (int i = 0; i < width; i++)
|
||||
{
|
||||
float t = (float)i / width;
|
||||
int f = (int)(
|
||||
(height / 2) + (animatedFloat.startValue * value + ((animatedFloat.endValue - animatedFloat.startValue)
|
||||
* AnimationCurveEvaluator.Evaluate(animatedFloat.animationCurveType, t) * value))
|
||||
);
|
||||
|
||||
// 绘制垂直线段 - 保留超出边界的红色标记
|
||||
if (LastEventPointY < f)
|
||||
{
|
||||
for (int j = LastEventPointY; j < f; j++)
|
||||
{
|
||||
// 检查是否超出边界
|
||||
bool isOutOfBounds = j < 0 || j >= height;
|
||||
|
||||
// 计算实际坐标(循环调整)
|
||||
int actualY = j;
|
||||
while (actualY < 0) actualY += height;
|
||||
while (actualY >= height) actualY -= height;
|
||||
|
||||
int index = actualY * width + i;
|
||||
if (index >= 0 && index < pixels.Length)
|
||||
{
|
||||
// 根据是否超出边界设置颜色
|
||||
pixels[index] = isOutOfBounds ? Color.red : Color.green;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int j = LastEventPointY; j > f; j--)
|
||||
{
|
||||
// 检查是否超出边界
|
||||
bool isOutOfBounds = j < 0 || j >= height;
|
||||
|
||||
// 计算实际坐标(循环调整)
|
||||
int actualY = j;
|
||||
while (actualY < 0) actualY += height;
|
||||
while (actualY >= height) actualY -= height;
|
||||
|
||||
int index = actualY * width + i;
|
||||
if (index >= 0 && index < pixels.Length)
|
||||
{
|
||||
// 根据是否超出边界设置颜色
|
||||
pixels[index] = isOutOfBounds ? Color.red : Color.green;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制当前点 - 保留超出边界的红色标记
|
||||
bool isFOutOfBounds = f < 0 || f >= height;
|
||||
int actualF = f;
|
||||
while (actualF < 0) actualF += height;
|
||||
while (actualF >= height) actualF -= height;
|
||||
|
||||
int currentIndex = actualF * width + i;
|
||||
if (currentIndex >= 0 && currentIndex < pixels.Length)
|
||||
{
|
||||
// 根据是否超出边界设置颜色
|
||||
pixels[currentIndex] = isFOutOfBounds ? Color.red : Color.green;
|
||||
}
|
||||
|
||||
LastEventPointY = f;
|
||||
}
|
||||
|
||||
return pixels;
|
||||
}
|
||||
|
||||
public void ReDraw(float value)
|
||||
{
|
||||
int width = (int)CurveCanvas.rectTransform.sizeDelta.x / 5;
|
||||
int height = (int)CurveCanvas.rectTransform.sizeDelta.y / 5;
|
||||
|
||||
// 获取颜色数组(可在多线程环境中调用)
|
||||
|
||||
// 在主线程中创建和设置纹理(Unity对象操作必须在主线程)
|
||||
StartCoroutine(GenerateTextureCoroutine(width, height, value));
|
||||
|
||||
// 其余的非纹理相关代码保持不变
|
||||
if (NextEventPoint != null)
|
||||
{
|
||||
OvDrawimage.transform.localPosition = new Vector3(RightSide.transform.localPosition.x,
|
||||
animatedFloat.endValue * value * 5, 0);
|
||||
OvDrawimage.rectTransform.sizeDelta = new Vector2((NextEventPoint.animatedFloat.startTime - animatedFloat.endTime) / EditorManager.instance.uiManager.timeline.timePerBeat * FatherTab.BeatDeviver,
|
||||
OvDrawimage.rectTransform.sizeDelta.y);
|
||||
|
||||
OvDrawimage.color = new Color(0, 1, 0, 1);
|
||||
while (OvDrawimage.transform.localPosition.y > 100)
|
||||
{
|
||||
OvDrawimage.color = new Color(1, 0, 0, 0.3f);
|
||||
OvDrawimage.transform.localPosition = new Vector3(OvDrawimage.transform.localPosition.x, OvDrawimage.transform.localPosition.y - 200, OvDrawimage.transform.localPosition.z);
|
||||
}
|
||||
while (OvDrawimage.transform.localPosition.y < -100)
|
||||
{
|
||||
OvDrawimage.color = new Color(1, 0, 0, 0.3f);
|
||||
OvDrawimage.transform.localPosition = new Vector3(OvDrawimage.transform.localPosition.x, OvDrawimage.transform.localPosition.y + 200, OvDrawimage.transform.localPosition.z);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
OvDrawimage.rectTransform.sizeDelta = new Vector2(0, OvDrawimage.rectTransform.sizeDelta.y);
|
||||
}
|
||||
|
||||
selectButton.transform.localPosition = EvDrawimage.transform.localPosition;
|
||||
selectButton.GetComponent<RectTransform>().sizeDelta = EvDrawimage.rectTransform.sizeDelta;
|
||||
ViewText.text = animatedFloat.startTime.ToString("0.00") + "s" + "\n" +
|
||||
animatedFloat.startValue.ToString("0.0") + "\n" + animatedFloat.endValue.ToString("0.0") + "\n" + animatedFloat.endTime.ToString("0.00") + "s" + "\n" +
|
||||
animatedFloat.animationCurveType.ToString();
|
||||
ViewText.color = new Color(1, 1, 1, EvDrawimage.rectTransform.sizeDelta.x < 100 ? 0 : 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b4c5757a536b2e14bbb3c3fa2ed0a8e4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,333 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using DG.Tweening.Core.Easing;
|
||||
using Ichni;
|
||||
using Ichni.Editor;
|
||||
using Ichni.RhythmGame;
|
||||
using Unity.VisualScripting;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class FlexibleFloatTab : MonoBehaviour
|
||||
{
|
||||
public GraphicalFlexibleFloatWindow FatherWindow;
|
||||
public List<EventPoint> eventPoints;
|
||||
public RectTransform Area;
|
||||
public RectTransform BeatArea;
|
||||
public RectTransform XBeatArea;
|
||||
public EventPoint eventPoint;
|
||||
public GameObject BeatLine;
|
||||
public Button TabButton;
|
||||
public string Title;
|
||||
public FlexibleFloat connectFloat;
|
||||
public int BeatDeviver => FatherWindow.BeatDeviver;
|
||||
public int BeatNextDeviver => FatherWindow.BeatNextDeviver;
|
||||
public bool linked = false;//TODOTODO!
|
||||
|
||||
// 初始化函数
|
||||
public void Initialize(FlexibleFloat flexibleFloat, string title)
|
||||
{
|
||||
Title = title;
|
||||
ClearChildren(Area);
|
||||
ClearChildren(BeatArea);
|
||||
eventPoints = new List<EventPoint>();
|
||||
connectFloat = flexibleFloat;
|
||||
CreateBeatLines();
|
||||
CreateEventPoints();
|
||||
Area.localPosition = new Vector3(FatherWindow.songBeat * BeatDeviver, 0, 0);
|
||||
}
|
||||
|
||||
// 清除子节点
|
||||
private void ClearChildren(RectTransform parent)
|
||||
{
|
||||
for (int i = 0; i < parent.childCount; i++)
|
||||
{
|
||||
Destroy(parent.GetChild(i).gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建节拍线
|
||||
private void CreateBeatLines()
|
||||
{
|
||||
// 先清空BeatArea下的所有子对象,防止重复生成
|
||||
for (int i = BeatArea.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
Destroy(BeatArea.GetChild(i).gameObject);
|
||||
}
|
||||
float maxX = 1400f + (3 * BeatDeviver);
|
||||
int totalBeats = (int)(EditorManager.instance.songInformation.song.length / FatherWindow.timePerBeat);
|
||||
for (int i = 0; i < totalBeats; i++)
|
||||
{
|
||||
float posX = BeatDeviver * i;
|
||||
if (posX > maxX)
|
||||
{
|
||||
break;
|
||||
}
|
||||
GameObject u = Instantiate(BeatLine, BeatArea);
|
||||
u.transform.localPosition = new Vector3(posX, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建事件点
|
||||
private void CreateEventPoints()
|
||||
{
|
||||
for (int i = 0; i <= connectFloat.animations.Count - 1; i++)
|
||||
{
|
||||
AnimatedFloat animatedFloat = connectFloat.animations[i];
|
||||
EventPoint eventPoint = Instantiate(this.eventPoint, Area);
|
||||
eventPoint.FatherTab = this;
|
||||
eventPoint.Initialize(animatedFloat);
|
||||
eventPoints.Add(eventPoint);
|
||||
LinkEventPoints(i, eventPoint);
|
||||
}
|
||||
foreach (var i in eventPoints)
|
||||
{
|
||||
i.ReDraw(scalevalue);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新函数
|
||||
public void Update()
|
||||
{
|
||||
bool isOnEv = false;
|
||||
foreach (var i in eventPoints)
|
||||
{
|
||||
isOnEv = RectTransformUtility.RectangleContainsScreenPoint(i.selectButton.GetComponent<RectTransform>(), Mouse.current.position.ReadValue());
|
||||
if (isOnEv) break;
|
||||
}
|
||||
Vector3 newPosition = new Vector3(-FatherWindow.songBeat * BeatDeviver, 0, 0);
|
||||
Area.localPosition = newPosition;
|
||||
BeatArea.localPosition = newPosition;
|
||||
while (true)
|
||||
{
|
||||
BeatArea.localPosition += new Vector3(BeatDeviver, 0, 0);
|
||||
if (BeatArea.localPosition.x > ((-200f) - BeatDeviver))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
XBeatArea.localPosition = BeatArea.localPosition;
|
||||
if (RectTransformUtility.RectangleContainsScreenPoint(TabButton.GetComponent<RectTransform>(), Mouse.current.position.ReadValue()))
|
||||
if (!isOnEv && Mouse.current.leftButton.wasPressedThisFrame)
|
||||
{
|
||||
AddEvent();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 添加事件
|
||||
public void AddEvent()
|
||||
{
|
||||
|
||||
if (Keyboard.current.ctrlKey.isPressed)
|
||||
{
|
||||
EventPoint eventPoint = Instantiate(this.eventPoint, Area);
|
||||
eventPoint.FatherTab = this;
|
||||
eventPoint.Initialize(new AnimatedFloat(GetBeat(),
|
||||
GetBeat() + (float.Parse(FatherWindow.EventMultiplier.text) * FatherWindow.timePerBeat), 0, 0, AnimationCurveType.Linear));
|
||||
eventPoints.Insert(FindInsertIndex(eventPoint.animatedFloat.startTime), eventPoint);
|
||||
LinkNewEventPoint(eventPoint, linked);
|
||||
SetEventPoint(eventPoint);
|
||||
eventPoint.ReDraw(scalevalue);
|
||||
eventPoint.selectButton.onClick.Invoke();
|
||||
FatherWindow.ChangeValue();
|
||||
connectFloat.Add(eventPoint.animatedFloat);
|
||||
connectFloat.Sort();
|
||||
}
|
||||
// else
|
||||
// {
|
||||
// if (FatherWindow.ConnectedPoint != null) FatherWindow.ConnectedPoint.UpLoad();
|
||||
// }
|
||||
}
|
||||
public void SpawnEvent(AnimatedFloat animatedFloat)
|
||||
{
|
||||
EventPoint eventPoint = Instantiate(this.eventPoint, Area);
|
||||
eventPoint.FatherTab = this;
|
||||
eventPoint.Initialize(animatedFloat);
|
||||
eventPoints.Insert(FindInsertIndex(eventPoint.animatedFloat.startTime), eventPoint);
|
||||
LinkNewEventPoint(eventPoint);
|
||||
eventPoint.ReDraw(scalevalue);
|
||||
connectFloat.Add(eventPoint.animatedFloat);
|
||||
connectFloat.Sort();
|
||||
}
|
||||
public void SetEventPoint(EventPoint eventPoint)
|
||||
{
|
||||
if (eventPoint.LastEventPoint == null) return;
|
||||
// 设置当前事件点的起始值为上一个事件点的结束值
|
||||
eventPoint.animatedFloat.startValue = eventPoint.LastEventPoint.animatedFloat.endValue;
|
||||
eventPoint.animatedFloat.animationCurveType = eventPoint.LastEventPoint.animatedFloat.animationCurveType;
|
||||
eventPoint.ReDraw(scalevalue);
|
||||
}
|
||||
// 添加调用 EventPoint 类的接口
|
||||
public void LinkEventPoints(int index, EventPoint eventPoint)
|
||||
{
|
||||
eventPoint.LinkEventPoints(eventPoints, index);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void LinkNewEventPoint(EventPoint eventPoint, bool link = false)
|
||||
{
|
||||
eventPoint.LinkNewEventPoint(eventPoints, link, scalevalue);
|
||||
}
|
||||
|
||||
public int FindInsertIndex(float startTime)
|
||||
{
|
||||
return EventPoint.FindInsertIndex(eventPoints, startTime);
|
||||
}
|
||||
|
||||
// 获取节拍
|
||||
public float GetBeat()
|
||||
{
|
||||
// 获取鼠标在 BeatArea 中的相对位置
|
||||
Vector2 localMousePosition = Area.InverseTransformPoint(Mouse.current.position.ReadValue());
|
||||
//Debug.Log(localMousePosition);
|
||||
|
||||
float mouseBeat = localMousePosition.x / BeatDeviver;
|
||||
float far = 0f;
|
||||
while (far < mouseBeat)
|
||||
{
|
||||
far += 1f / BeatNextDeviver;
|
||||
}
|
||||
far -= 1f / BeatNextDeviver;
|
||||
return far * FatherWindow.timePerBeat;
|
||||
}
|
||||
|
||||
public float scalevalue => FatherWindow.scalevalue;
|
||||
|
||||
// 曲线缩放
|
||||
public void CurveScale(float value)
|
||||
{
|
||||
foreach (EventPoint i in eventPoints)
|
||||
{
|
||||
i.ReDraw(value);
|
||||
}
|
||||
}
|
||||
|
||||
// 改变X轴节拍
|
||||
public void XbeatCnange(int num)
|
||||
{
|
||||
ClearChildren(XBeatArea);
|
||||
for (int i = 1; i < num; i++)
|
||||
{
|
||||
foreach (Transform child in BeatArea)
|
||||
{
|
||||
GameObject newChild = Instantiate(child.gameObject, XBeatArea);
|
||||
CanvasGroup canvasGroup = newChild.GetComponent<CanvasGroup>();
|
||||
newChild.transform.localPosition = new Vector3(child.localPosition.x + ((float)BeatDeviver) / num * i, child.localPosition.y, child.localPosition.z);
|
||||
if (canvasGroup == null)
|
||||
{
|
||||
canvasGroup = newChild.AddComponent<CanvasGroup>();
|
||||
}
|
||||
canvasGroup.alpha = 0.1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移除动画
|
||||
public void remoceAnim(AnimatedFloat a)
|
||||
{
|
||||
if (connectFloat.animations.Contains(a))
|
||||
{
|
||||
connectFloat.animations.Remove(a);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从事件点列表中移除指定的事件点,并更新其前后连接关系。
|
||||
/// </summary>
|
||||
/// <param name="eventPoint">要移除的事件点。</param>
|
||||
public void RemoveEventPoint(EventPoint eventPoint)
|
||||
{
|
||||
if (eventPoints.Contains(eventPoint))
|
||||
{
|
||||
// 更新前后事件点的连接关系
|
||||
if (eventPoint.LastEventPoint != null)
|
||||
{
|
||||
eventPoint.LastEventPoint.NextEventPoint = eventPoint.NextEventPoint;
|
||||
eventPoint.LastEventPoint.ReDraw(scalevalue);
|
||||
}
|
||||
if (eventPoint.NextEventPoint != null)
|
||||
{
|
||||
eventPoint.NextEventPoint.LastEventPoint = eventPoint.LastEventPoint;
|
||||
eventPoint.NextEventPoint.ReDraw(scalevalue);
|
||||
}
|
||||
|
||||
// 从列表中移除事件点
|
||||
eventPoints.Remove(eventPoint);
|
||||
|
||||
// 从连接的动画中移除
|
||||
connectFloat.animations.Remove(eventPoint.animatedFloat);
|
||||
|
||||
// 销毁事件点对象
|
||||
Destroy(eventPoint.gameObject);
|
||||
}
|
||||
}
|
||||
public IEnumerator Draging()
|
||||
{
|
||||
EventPoint.Locked = true;
|
||||
while (Keyboard.current.uKey.isPressed)
|
||||
{
|
||||
bool changed = false;
|
||||
var point = FatherWindow.ConnectedPoint;
|
||||
if (Mouse.current.leftButton.isPressed)
|
||||
{
|
||||
point.animatedFloat.startTime = GetBeat();
|
||||
changed = true;
|
||||
}
|
||||
else if (Mouse.current.rightButton.isPressed)
|
||||
{
|
||||
point.animatedFloat.endTime = GetBeat();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
// 保证时间顺序
|
||||
if (point.animatedFloat.startTime >= point.animatedFloat.endTime)
|
||||
{
|
||||
if (Mouse.current.leftButton.isPressed)
|
||||
point.animatedFloat.endTime = point.animatedFloat.startTime + 0.1f;
|
||||
else
|
||||
point.animatedFloat.startTime = point.animatedFloat.endTime - 0.1f;
|
||||
}
|
||||
// 保证不超过下一个事件点
|
||||
if (point.NextEventPoint != null && point.animatedFloat.endTime > point.NextEventPoint.animatedFloat.startTime)
|
||||
{
|
||||
if (Mouse.current.leftButton.isPressed)
|
||||
point.animatedFloat.endTime = point.NextEventPoint.animatedFloat.startTime;
|
||||
else
|
||||
point.animatedFloat.startTime = point.NextEventPoint.animatedFloat.startTime - 0.1f;
|
||||
}
|
||||
point.Initialize(point.animatedFloat);
|
||||
point.UpdateValue();
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
FatherWindow.ConnectedPoint.Refresh(true);
|
||||
EventPoint.Locked = false;
|
||||
}
|
||||
public void CutEvent()
|
||||
{
|
||||
EventPoint eventPoint = FatherWindow.ConnectedPoint;
|
||||
if (eventPoint == null) return;
|
||||
for (int i = 0; i < BeatNextDeviver; i++)
|
||||
{
|
||||
float segmentStartTime = eventPoint.animatedFloat.startTime + (eventPoint.animatedFloat.endTime - eventPoint.animatedFloat.startTime) / BeatNextDeviver * i;
|
||||
float segmentEndTime = eventPoint.animatedFloat.startTime + (eventPoint.animatedFloat.endTime - eventPoint.animatedFloat.startTime) / BeatNextDeviver * (i + 1);
|
||||
connectFloat.Add(new AnimatedFloat(
|
||||
segmentStartTime,
|
||||
segmentEndTime,
|
||||
AnimationCurveEvaluator.Evaluate(eventPoint.animatedFloat.animationCurveType, (segmentStartTime - eventPoint.animatedFloat.startTime) / (eventPoint.animatedFloat.endTime - eventPoint.animatedFloat.startTime)) * (eventPoint.animatedFloat.endValue - eventPoint.animatedFloat.startValue) + eventPoint.animatedFloat.startValue,
|
||||
AnimationCurveEvaluator.Evaluate(eventPoint.animatedFloat.animationCurveType, (segmentEndTime - eventPoint.animatedFloat.startTime) / (eventPoint.animatedFloat.endTime - eventPoint.animatedFloat.startTime)) * (eventPoint.animatedFloat.endValue - eventPoint.animatedFloat.startValue) + eventPoint.animatedFloat.startValue,
|
||||
AnimationCurveType.Linear));
|
||||
}
|
||||
RemoveEventPoint(eventPoint);
|
||||
connectFloat.Sort();
|
||||
Initialize(connectFloat, Title);
|
||||
FatherWindow.ChangeValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20904e6182baed446b49fe294fad22b2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,309 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using DG.Tweening;
|
||||
using Ichni;
|
||||
using Ichni.Editor;
|
||||
using Ichni.RhythmGame;
|
||||
using Sirenix.OdinInspector;
|
||||
using Sirenix.Utilities;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public partial class GraphicalFlexibleFloatWindow : MovableWindow
|
||||
{
|
||||
|
||||
public FlexibleFloatTab unitPrefab;
|
||||
public IBaseElement connectedBaseElement;
|
||||
public List<FlexibleFloatTab> unitList;
|
||||
|
||||
public UnityAction ApplyParameters;
|
||||
|
||||
|
||||
public float songTime => EditorManager.instance.songInformation.songTime;
|
||||
public float songBeat => EditorManager.instance.songInformation.songBeat;
|
||||
public float beatmapStartTime => -EditorManager.instance.songInformation.delay;
|
||||
public float timePerBeat => 60f / EditorManager.instance.songInformation.bpm;
|
||||
|
||||
public int BeatDeviver = 100;
|
||||
public int BeatNextDeviver = 1;
|
||||
public void Initialize(IBaseElement baseElement, string title, FlexibleFloat[] FlexibleFloats, string[] Subtitles)
|
||||
{
|
||||
InitializeWindow(title, null, new Vector3(1f, 1f, 1f));
|
||||
scalevalue = 5;
|
||||
this.connectedBaseElement = baseElement;
|
||||
this.title.text = title;
|
||||
unitList = new List<FlexibleFloatTab>();
|
||||
|
||||
|
||||
closeButton.onClick.AddListener(Quit);
|
||||
|
||||
for (int i = 0; i <= FlexibleFloats.Length - 1; i++)
|
||||
{
|
||||
ClipBoard.Add(Subtitles[i], new List<AnimatedFloat>());
|
||||
AddUnit(FlexibleFloats[i], Subtitles[i]);
|
||||
}
|
||||
|
||||
ApplyParameters = () =>
|
||||
{
|
||||
for (int i = 0; i <= unitList.Count - 1; i++)
|
||||
{
|
||||
unitList[i].connectFloat.Sort();
|
||||
}
|
||||
|
||||
};
|
||||
XDeviverScale("1");
|
||||
EvEndpointChangeButton.onClick.AddListener(EvEndpointStartChange);
|
||||
GetComponent<RectTransform>().sizeDelta = new Vector2(1400, (FlexibleFloats.Length + 1) * 200 + 50);
|
||||
}
|
||||
public void AddUnit(FlexibleFloat flexibleFloat, string Subtitle)
|
||||
{
|
||||
flexibleFloat.Sort();
|
||||
FlexibleFloatTab flexibleFloatTab = Instantiate(unitPrefab, windowRect);
|
||||
flexibleFloatTab.FatherWindow = this;
|
||||
unitList.Add(flexibleFloatTab);
|
||||
flexibleFloatTab.Initialize(flexibleFloat, Subtitle);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public void Quit()
|
||||
{
|
||||
ApplyParameters();
|
||||
//StartCoroutine(WindowAnim.HidePanel(gameObject, true));
|
||||
this.transform.DOScale(Vector3.zero, 0.15f).SetEase(Ease.InCirc).OnComplete(() =>
|
||||
{
|
||||
Destroy(gameObject);
|
||||
});
|
||||
}
|
||||
public float scalevalue;
|
||||
public void CurveScale(string Rawvalue)
|
||||
{
|
||||
float value = float.Parse(Rawvalue);
|
||||
scalevalue = value;
|
||||
foreach (FlexibleFloatTab i in unitList)
|
||||
{
|
||||
i.CurveScale(value);
|
||||
}
|
||||
}
|
||||
public void DeviverScale(string Rawvalue)
|
||||
{
|
||||
if (ConnectedPoint != null) ConnectedPoint.UpLoad();
|
||||
BeatDeviver = int.Parse(Rawvalue);
|
||||
ChangeValue();
|
||||
for (int i = unitList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
unitList[i].Initialize(unitList[i].connectFloat, unitList[i].Title);
|
||||
}
|
||||
XDeviverScale(BeatNextDeviver.ToString());
|
||||
|
||||
}
|
||||
public void XDeviverScale(string Rawvalue)
|
||||
{
|
||||
BeatNextDeviver = int.Parse(Rawvalue);
|
||||
foreach (FlexibleFloatTab i in unitList)
|
||||
{
|
||||
i.XbeatCnange(BeatNextDeviver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class GraphicalFlexibleFloatWindow
|
||||
{
|
||||
[Title("AnimEditor")]
|
||||
public TMP_InputField StartText;
|
||||
public TMP_InputField EndText;
|
||||
public TMP_InputField StartValueText;
|
||||
public TMP_InputField EndValueText;
|
||||
|
||||
public TMP_InputField EventMultiplier;
|
||||
public EventPoint ConnectedPoint;
|
||||
public TMP_Dropdown animationCurveTypeDropdown;
|
||||
public GameObject VisibleArea;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 移除当前连接的事件点,并更新 UI。
|
||||
/// </summary>
|
||||
public void RemoveConnectedPoint()
|
||||
{
|
||||
if (ConnectedPoint != null)
|
||||
{
|
||||
// 调用 FlexibleFloatTab 的 RemoveEventPoint 方法
|
||||
ConnectedPoint.FatherTab.RemoveEventPoint(ConnectedPoint);
|
||||
|
||||
// 清空连接点并隐藏可见区域
|
||||
ConnectedPoint = null;
|
||||
VisibleArea.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void ChangeValue()
|
||||
{
|
||||
if (ConnectedPoint != null)
|
||||
{
|
||||
float startTime = float.Parse(StartText.text);
|
||||
float endTime = float.Parse(EndText.text);
|
||||
float startValue = float.Parse(StartValueText.text);
|
||||
float endValue = float.Parse(EndValueText.text);
|
||||
|
||||
ConnectedPoint.animatedFloat.startTime = startTime;
|
||||
ConnectedPoint.animatedFloat.endTime = endTime;
|
||||
ConnectedPoint.animatedFloat.startValue = startValue;
|
||||
ConnectedPoint.animatedFloat.endValue = endValue;
|
||||
ConnectedPoint.animatedFloat.animationCurveType = (AnimationCurveType)animationCurveTypeDropdown.value;
|
||||
ConnectedPoint.Initialize(ConnectedPoint.animatedFloat);
|
||||
ConnectedPoint.ReDraw(scalevalue);
|
||||
}
|
||||
}
|
||||
public Button EvEndpointChangeButton;
|
||||
public void EvEndpointStartChange()
|
||||
{
|
||||
if (ConnectedPoint != null)
|
||||
{
|
||||
EndText.text = (ConnectedPoint.animatedFloat.startTime + 0.01).ToString();
|
||||
ChangeValue();
|
||||
EvEndpointChangeButton.GetComponent<Image>().color = new Color(1f, 0.5f, 0.5f, 1);
|
||||
ConnectedPoint.FatherTab.TabButton.onClick.RemoveAllListeners();
|
||||
ConnectedPoint.FatherTab.TabButton.onClick.AddListener(EvEndpointEndChange);
|
||||
|
||||
}
|
||||
}
|
||||
public void EvEndpointEndChange()
|
||||
{
|
||||
if (ConnectedPoint != null)
|
||||
{
|
||||
EvEndpointChangeButton.GetComponent<Image>().color = new Color(1f, 1f, 1f, 1);
|
||||
|
||||
float newendtime = ConnectedPoint.FatherTab.GetBeat();
|
||||
if (newendtime > ConnectedPoint.animatedFloat.startTime)
|
||||
{
|
||||
if (ConnectedPoint.NextEventPoint != null && newendtime > ConnectedPoint.NextEventPoint.animatedFloat.startTime)
|
||||
EndText.text = ConnectedPoint.NextEventPoint.animatedFloat.startTime.ToString();
|
||||
else EndText.text = newendtime.ToString();
|
||||
ChangeValue();
|
||||
}
|
||||
|
||||
ConnectedPoint.FatherTab.TabButton.onClick.RemoveAllListeners();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, List<AnimatedFloat>> ClipBoard = new();
|
||||
|
||||
public void Update()
|
||||
{
|
||||
|
||||
if (Keyboard.current.deleteKey.isPressed && ConnectedPoint != null)
|
||||
{
|
||||
RemoveConnectedPoint();
|
||||
}
|
||||
|
||||
if (Keyboard.current.shiftKey.isPressed)
|
||||
{
|
||||
if (Keyboard.current.vKey.wasPressedThisFrame)
|
||||
PasteClipboard(1f);
|
||||
else if (Keyboard.current.bKey.wasPressedThisFrame)
|
||||
PasteClipboard(-1f);
|
||||
}
|
||||
if (Keyboard.current.escapeKey.wasPressedThisFrame)
|
||||
{
|
||||
foreach (var key in ClipBoard.Keys.ToList())
|
||||
{
|
||||
ClipBoard[key] = new List<AnimatedFloat>();
|
||||
}
|
||||
foreach (FlexibleFloatTab i in unitList)
|
||||
{
|
||||
foreach (EventPoint j in i.eventPoints)
|
||||
{
|
||||
j.LeftSide.sizeDelta = new Vector2(15, j.EvDrawimage.rectTransform.sizeDelta.y);
|
||||
}
|
||||
}
|
||||
updateClipBoardMuM();
|
||||
}
|
||||
if (ConnectedPoint != null && RectTransformUtility.RectangleContainsScreenPoint(GetComponent<RectTransform>(), Mouse.current.position.ReadValue()))
|
||||
{
|
||||
if (Keyboard.current.aKey.wasPressedThisFrame)
|
||||
{
|
||||
ConnectedPoint.animatedFloat.endValue *= -1;
|
||||
ConnectedPoint.animatedFloat.startValue *= -1;
|
||||
ConnectedPoint.Refresh();
|
||||
}
|
||||
else if (Keyboard.current.sKey.wasPressedThisFrame)
|
||||
{
|
||||
float value = ConnectedPoint.animatedFloat.startValue;
|
||||
ConnectedPoint.animatedFloat.startValue = ConnectedPoint.animatedFloat.endValue;
|
||||
ConnectedPoint.animatedFloat.endValue = value;
|
||||
ConnectedPoint.Refresh();
|
||||
}
|
||||
else if (Keyboard.current.uKey.wasPressedThisFrame)
|
||||
{
|
||||
try
|
||||
{
|
||||
ConnectedPoint.FatherTab.StartCoroutine(ConnectedPoint.FatherTab.Draging());
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
EventPoint.Locked = false;
|
||||
}
|
||||
}
|
||||
else if (Keyboard.current.lKey.wasPressedThisFrame)
|
||||
{
|
||||
ConnectedPoint.FatherTab.CutEvent();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void PasteClipboard(float valuescale = 1f)
|
||||
{
|
||||
// 获取当前时间线的节拍位置
|
||||
float time = unitList[0].GetBeat();
|
||||
float MinCopyTime = float.MaxValue;
|
||||
// 遍历剪贴板中的所有动画数据MinCopyTime = float.MaxValue;
|
||||
foreach (var list in ClipBoard.Values)
|
||||
{
|
||||
foreach (var animatedFloat in list)
|
||||
{
|
||||
if (animatedFloat.startTime < MinCopyTime)
|
||||
{
|
||||
MinCopyTime = animatedFloat.startTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var key in ClipBoard.Keys)
|
||||
{
|
||||
foreach (var animatedFloat in ClipBoard[key])
|
||||
{
|
||||
// 克隆动画数据并应用时间偏移
|
||||
AnimatedFloat newFloat = EventPoint.CloneWithOffset(animatedFloat, time - MinCopyTime);
|
||||
newFloat.startValue *= valuescale;
|
||||
newFloat.endValue *= valuescale;
|
||||
// 在对应的 FlexibleFloatTab 中生成事件点
|
||||
unitList.Find(x => x.Title == key).SpawnEvent(newFloat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
public partial class GraphicalFlexibleFloatWindow
|
||||
{//以后显示类写这里,别在叠大粪了
|
||||
public TMP_Text ClipBoardMuM;
|
||||
public void updateClipBoardMuM()
|
||||
{
|
||||
int mum = 0;
|
||||
foreach (var key in ClipBoard.Keys)
|
||||
{
|
||||
mum += ClipBoard[key].Count();
|
||||
}
|
||||
ClipBoardMuM.text = "ClipBoard: " + mum.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9ccff815b775ec48abb8e62577257f8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Scripts/Editor Tools/HSV Tool.meta
Normal file
8
Assets/Scripts/Editor Tools/HSV Tool.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7418d8f37ad03114a8009e73d016d9d5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
349
Assets/Scripts/Editor Tools/HSV Tool/HsvDrawer.cs
Normal file
349
Assets/Scripts/Editor Tools/HSV Tool/HsvDrawer.cs
Normal file
@@ -0,0 +1,349 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Ichni;
|
||||
using Ichni.Editor;
|
||||
using Ichni.RhythmGame;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public partial class HsvDrawer
|
||||
{
|
||||
[Header("Color Wheel Settings")]
|
||||
public int textureSize = 256; // 纹理尺寸
|
||||
public float saturation = 1f; // 最大饱和度
|
||||
public float value = 1f; // 明度
|
||||
|
||||
[Header("Color Picker Settings")]
|
||||
public RectTransform pickerIndicator; // 取色器指示器
|
||||
public Color currentColor = Color.white; // 当前选中的颜色
|
||||
public Slider valueSlider; // 亮度拖动条
|
||||
public Slider alphaSlider; // 透明度拖动条
|
||||
public RawImage previewImage; // 颜色预览窗
|
||||
|
||||
private Texture2D _texture;
|
||||
public RawImage _rawImage;
|
||||
private RectTransform _rectTransform;
|
||||
|
||||
|
||||
public bool isEmission = false;
|
||||
public GameObject emissionObj;
|
||||
public InputField emissionInput;
|
||||
|
||||
public DynamicUIBaseColorPicker baseColorPicker;
|
||||
void Start()
|
||||
{
|
||||
_rectTransform = _rawImage.GetComponent<RectTransform>(); // 初始化
|
||||
CreateColorWheelTexture();
|
||||
ApplyTextureToRawImage();
|
||||
|
||||
if (valueSlider != null)
|
||||
{
|
||||
valueSlider.minValue = 0f;
|
||||
valueSlider.maxValue = 1f;
|
||||
valueSlider.value = value;
|
||||
valueSlider.onValueChanged.AddListener(OnValueSliderChanged);
|
||||
}
|
||||
if (alphaSlider != null)
|
||||
{
|
||||
alphaSlider.minValue = 0f;
|
||||
alphaSlider.maxValue = 1f;
|
||||
alphaSlider.onValueChanged.AddListener(OnAlphaSliderChanged);
|
||||
}
|
||||
if (previewImage != null)
|
||||
{
|
||||
previewImage.color = currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
private Coroutine valueSliderCoroutine;
|
||||
private float pendingValue = -1f;
|
||||
|
||||
// 新增:高低分辨率参数
|
||||
public int highResTextureSize = 256;
|
||||
public int lowResTextureSize = 64;
|
||||
private bool isLowRes = false;
|
||||
|
||||
void OnValueSliderChanged(float newValue)
|
||||
{
|
||||
value = newValue;
|
||||
pendingValue = newValue;
|
||||
|
||||
// 切换到低分辨率
|
||||
if (!isLowRes)
|
||||
{
|
||||
isLowRes = true;
|
||||
CreateColorWheelTexture(true);
|
||||
ApplyTextureToRawImage();
|
||||
}
|
||||
|
||||
if (valueSliderCoroutine != null)
|
||||
StopCoroutine(valueSliderCoroutine);
|
||||
valueSliderCoroutine = StartCoroutine(DelayedRedrawColorWheel());
|
||||
|
||||
// 刷新当前颜色
|
||||
if (pickerIndicator != null)
|
||||
UpdateCurrentColor(pickerIndicator.localPosition);
|
||||
}
|
||||
|
||||
IEnumerator DelayedRedrawColorWheel()
|
||||
{
|
||||
float waitTime = 0.01f;
|
||||
float timer = 0f;
|
||||
|
||||
while (timer < waitTime)
|
||||
{
|
||||
yield return null;
|
||||
timer += Time.unscaledDeltaTime;
|
||||
}
|
||||
// 恢复高分辨率
|
||||
if (isLowRes)
|
||||
{
|
||||
isLowRes = false;
|
||||
CreateColorWheelTexture(false);
|
||||
ApplyTextureToRawImage();
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateColorWheelTexture(false);
|
||||
ApplyTextureToRawImage();
|
||||
}
|
||||
valueSliderCoroutine = null;
|
||||
}
|
||||
|
||||
void OnAlphaSliderChanged(float newAlpha)
|
||||
{
|
||||
// 只更新透明度
|
||||
currentColor.a = newAlpha;
|
||||
if (previewImage != null)
|
||||
previewImage.color = currentColor;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
// 鼠标点击色轮区域即可拖动
|
||||
if (Mouse.current.leftButton.wasPressedThisFrame && RectTransformUtility.RectangleContainsScreenPoint(_rectTransform, Mouse.current.position.ReadValue()))
|
||||
{
|
||||
StartCoroutine(DraggingColorPicker());
|
||||
}
|
||||
|
||||
}
|
||||
IEnumerator DraggingColorPicker()
|
||||
{
|
||||
|
||||
while (Mouse.current.leftButton.isPressed)
|
||||
{
|
||||
Vector2 localPosition;
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(_rectTransform, Mouse.current.position.ReadValue(), null, out localPosition);
|
||||
|
||||
// 计算中心和最大半径
|
||||
Vector2 center = _rectTransform.rect.center;
|
||||
float maxRadius = _rectTransform.rect.width / 2f;
|
||||
Vector2 offset = localPosition - center;
|
||||
float distance = offset.magnitude;
|
||||
|
||||
// 如果超出圆形区域,则贴边
|
||||
if (distance > maxRadius)
|
||||
{
|
||||
offset = offset.normalized * maxRadius;
|
||||
localPosition = center + offset;
|
||||
}
|
||||
|
||||
if (pickerIndicator != null)
|
||||
pickerIndicator.localPosition = localPosition;
|
||||
|
||||
UpdateCurrentColor(localPosition);
|
||||
|
||||
yield return null; // 等待下一帧
|
||||
}
|
||||
}
|
||||
// 整合分辨率逻辑到此方法
|
||||
void CreateColorWheelTexture(bool useLowRes = false)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// 2. 核心优化:使用 Color32 数组代替 Color 数组
|
||||
// Color32 占用的内存更小,SetPixels32 的执行效率远高于 SetPixels
|
||||
Color32[] pixels = new Color32[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++)
|
||||
{
|
||||
float offX = (x + 0.5f) - halfSize;
|
||||
float distSq = offX * offX + offYSq; // 使用平方比较,避免在外部判断时使用 magnitude (开方)
|
||||
|
||||
int idx = rowOffset + x;
|
||||
|
||||
if (distSq <= halfSize * halfSize)
|
||||
{
|
||||
float distance = Mathf.Sqrt(distSq);
|
||||
|
||||
// 计算色相 (Hue)
|
||||
float angle = Mathf.Atan2(offY, offX) * Mathf.Rad2Deg;
|
||||
if (angle < 0) angle += 360f;
|
||||
|
||||
// 计算饱和度 (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] = clearColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 应用像素数据
|
||||
_texture.SetPixels32(pixels);
|
||||
_texture.Apply();
|
||||
}
|
||||
|
||||
void ApplyTextureToRawImage()
|
||||
{
|
||||
if (_rawImage == null)
|
||||
_rawImage = GetComponent<RawImage>();
|
||||
|
||||
_rawImage.texture = _texture;
|
||||
_rawImage.color = Color.white; // 确保显示原始颜色
|
||||
}
|
||||
void UpdateCurrentColor(Vector2 localPosition)
|
||||
{
|
||||
Vector2 center = _rectTransform.rect.center;
|
||||
Vector2 offset = localPosition - center;
|
||||
float maxRadius = _rectTransform.rect.width / 2f;
|
||||
|
||||
// 计算半径比例(饱和度)
|
||||
float distance = offset.magnitude;
|
||||
float sat = Mathf.Clamp01(distance / maxRadius) * saturation;
|
||||
|
||||
// 计算角度(色调)
|
||||
float angle = Mathf.Atan2(offset.y, offset.x) * Mathf.Rad2Deg;
|
||||
if (angle < 0) angle += 360;
|
||||
|
||||
// 计算颜色
|
||||
float alpha = alphaSlider != null ? alphaSlider.value : 1f;
|
||||
currentColor = Color.HSVToRGB(angle / 360f, sat, value);
|
||||
currentColor.a = alpha;
|
||||
|
||||
// 更新取色器颜色(使其与背景有对比度)
|
||||
if (pickerIndicator != null)
|
||||
{
|
||||
var img = pickerIndicator.GetComponent<Image>();
|
||||
if (img != null)
|
||||
img.color = (value > 0.5f) ? Color.black : Color.white;
|
||||
}
|
||||
// 若有亮度滑块,保持同步
|
||||
if (valueSlider != null && valueSlider.value != value)
|
||||
{
|
||||
valueSlider.SetValueWithoutNotify(value);
|
||||
}
|
||||
// 若有透明度滑块,保持同步
|
||||
if (alphaSlider != null && alphaSlider.value != alpha)
|
||||
{
|
||||
alphaSlider.SetValueWithoutNotify(alpha);
|
||||
}
|
||||
// 更新预览窗
|
||||
if (previewImage != null)
|
||||
{
|
||||
previewImage.color = currentColor;
|
||||
}
|
||||
if (baseColorPicker != null)
|
||||
{
|
||||
// 同步到BaseColorPicker
|
||||
baseColorPicker.SliderChangeWithoutNotification(currentColor);
|
||||
}
|
||||
ApplyParameters();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
public partial class HsvDrawer : DynamicUIElement
|
||||
{
|
||||
private void ApplyParameters()
|
||||
{
|
||||
Color newValue = currentColor;
|
||||
connectedBaseElement.GetType().GetField(parameterName).SetValue(connectedBaseElement, newValue);
|
||||
connectedBaseElement.Refresh();
|
||||
}
|
||||
|
||||
// 新增:同步connectedBaseElement的color到色盘
|
||||
public void SyncFromBaseElement()
|
||||
{
|
||||
if (connectedBaseElement == null || string.IsNullOrEmpty(parameterName)) return;
|
||||
Color color = (Color)connectedBaseElement.GetType().GetField(parameterName).GetValue(connectedBaseElement);
|
||||
currentColor = color;
|
||||
|
||||
// 解析HSV
|
||||
Color.RGBToHSV(color, out float h, out float s, out float v);
|
||||
value = v;
|
||||
if (valueSlider != null)
|
||||
valueSlider.SetValueWithoutNotify(v);
|
||||
if (alphaSlider != null)
|
||||
alphaSlider.SetValueWithoutNotify(color.a);
|
||||
|
||||
// 计算picker位置
|
||||
if (_rectTransform == null)
|
||||
_rectTransform = _rawImage.GetComponent<RectTransform>();
|
||||
Vector2 center = _rectTransform.rect.center;
|
||||
float maxRadius = _rectTransform.rect.width / 2f;
|
||||
float angle = h * 360f * Mathf.Deg2Rad;
|
||||
float radius = s * maxRadius;
|
||||
Vector2 offset = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * radius;
|
||||
Vector2 pickerPos = center + offset;
|
||||
if (pickerIndicator != null)
|
||||
pickerIndicator.localPosition = pickerPos;
|
||||
|
||||
// 更新色盘和预览
|
||||
CreateColorWheelTexture();
|
||||
ApplyTextureToRawImage();
|
||||
if (previewImage != null)
|
||||
previewImage.color = currentColor;
|
||||
}
|
||||
public override void Initialize(IBaseElement baseElement, string title, string parameterName)
|
||||
{
|
||||
base.Initialize(baseElement, title, parameterName);
|
||||
SyncFromBaseElement();
|
||||
}
|
||||
public override DynamicUIElement AddListenerFunction(UnityAction action)
|
||||
{
|
||||
|
||||
valueSlider.onValueChanged.AddListener(_ => action());
|
||||
|
||||
alphaSlider.onValueChanged.AddListener(_ => action());
|
||||
valueSlider.onValueChanged.AddListener(_ => UpdateCurrentColor(pickerIndicator.localPosition));
|
||||
|
||||
alphaSlider.onValueChanged.AddListener(_ => UpdateCurrentColor(pickerIndicator.localPosition));
|
||||
// 拖动色盘时也触发
|
||||
// 这里通过在 UpdateCurrentColor 里调用 action
|
||||
// 但由于没有事件,这里可以考虑用委托或事件,但为兼容性直接调用
|
||||
// 可以在 UpdateCurrentColor 末尾加一行(如果需要多次触发):
|
||||
// action?.Invoke();
|
||||
// 但一般只需响应滑块即可
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
11
Assets/Scripts/Editor Tools/HSV Tool/HsvDrawer.cs.meta
Normal file
11
Assets/Scripts/Editor Tools/HSV Tool/HsvDrawer.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81a06df931b999c4e9a0ac510cb0f3ea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Scripts/Editor Tools/KeyframeVisualizer.meta
Normal file
8
Assets/Scripts/Editor Tools/KeyframeVisualizer.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 138c996b947bada4d94d900ca8213f9a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e4743327f32eb24e86090ec474ac91a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
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:
|
||||
8
Assets/Scripts/Editor Tools/QuickMover.meta
Normal file
8
Assets/Scripts/Editor Tools/QuickMover.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bd71e53392300e547bd7aae27850df3a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
200
Assets/Scripts/Editor Tools/QuickMover/QuickMover.cs
Normal file
200
Assets/Scripts/Editor Tools/QuickMover/QuickMover.cs
Normal file
@@ -0,0 +1,200 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using Ichni.RhythmGame;
|
||||
using UnityEngine.InputSystem;
|
||||
using DG.Tweening;
|
||||
using UniRx;
|
||||
using UniRx.Triggers;
|
||||
|
||||
namespace Ichni.Editor
|
||||
{
|
||||
public class QuickMover : MonoBehaviour
|
||||
{
|
||||
public static QuickMover instance;
|
||||
|
||||
[Header("Settings")]
|
||||
public float moveSensitivity = 0.5f; // 移动灵敏度
|
||||
|
||||
[Header("References")]
|
||||
public GameObject prefab;
|
||||
public BoxCollider colliderX;
|
||||
public BoxCollider colliderY;
|
||||
public BoxCollider colliderZ;
|
||||
|
||||
// 运行时状态
|
||||
private IHaveTransformSubmodule targetElement;
|
||||
private Camera editorCamera => EditorManager.instance.cameraManager.currentCamera;
|
||||
private bool isMoving;
|
||||
private int activeMoveCode = -1;
|
||||
private Vector3 initialTargetPosition;
|
||||
private Vector3 initialMouseScreenPos;
|
||||
|
||||
public GameElement targetGameElement =>
|
||||
targetElement?.transformSubmodule?.attachedGameElement;
|
||||
|
||||
public void Initialize(IHaveTransformSubmodule targetElement)
|
||||
{
|
||||
this.targetElement = targetElement;
|
||||
|
||||
if (instance != null)
|
||||
{
|
||||
|
||||
Destroy(instance.gameObject);
|
||||
}
|
||||
instance = this;
|
||||
|
||||
if (targetGameElement != null)
|
||||
transform.position = targetGameElement.transform.position;
|
||||
transform.localScale = Vector3.zero;
|
||||
transform.DOScale(Vector3.one, 0.2f).SetEase(Ease.OutBack);
|
||||
targetGameElement.OnDestroyAsObservable()
|
||||
.Subscribe(_ =>
|
||||
{
|
||||
if (gameObject != null) Destroy(gameObject);
|
||||
instance = null;
|
||||
}).AddTo(this);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
|
||||
if (isMoving) return;
|
||||
transform.eulerAngles = targetGameElement.parentElement?.transform.eulerAngles ?? Vector3.zero;
|
||||
transform.position = targetGameElement.transform.position;
|
||||
if (Mouse.current.leftButton.wasPressedThisFrame)
|
||||
{
|
||||
Vector2 mousePosition = Mouse.current.position.ReadValue();
|
||||
Ray ray = editorCamera.ScreenPointToRay(mousePosition);
|
||||
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, float.MaxValue, LayerMask.GetMask("Selectable")))
|
||||
{
|
||||
if (hit.collider == colliderX)
|
||||
{
|
||||
colliderX.gameObject.transform.DOScale(colliderX.gameObject.transform.localScale * 1.2f, 0.2f);
|
||||
StartMovement(0);
|
||||
}
|
||||
else if (hit.collider == colliderY)
|
||||
{
|
||||
colliderY.gameObject.transform.DOScale(colliderY.gameObject.transform.localScale * 1.2f, 0.2f);
|
||||
StartMovement(1);
|
||||
}
|
||||
else if (hit.collider == colliderZ)
|
||||
{
|
||||
colliderZ.gameObject.transform.DOScale(colliderZ.gameObject.transform.localScale * 1.2f, 0.2f);
|
||||
StartMovement(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private Vector3 objPosOffset;
|
||||
private void StartMovement(int moveCode)
|
||||
{
|
||||
Debug.Log($"StartMovement: {moveCode}");
|
||||
if (targetGameElement == null) return;
|
||||
|
||||
activeMoveCode = moveCode;
|
||||
initialTargetPosition = targetGameElement.transform.position;
|
||||
initialMouseScreenPos = Mouse.current.position.ReadValue();
|
||||
objPosOffset = targetElement.transformSubmodule.currentPosition - targetElement.transformSubmodule.originalPosition;
|
||||
StartCoroutine(MovementRoutine());
|
||||
}
|
||||
|
||||
private IEnumerator MovementRoutine()
|
||||
{
|
||||
isMoving = true;
|
||||
|
||||
while (Mouse.current.leftButton.isPressed)
|
||||
{
|
||||
UpdateTargetPosition();
|
||||
targetElement.transformSubmodule.originalPosition = targetElement.transformSubmodule.currentPosition - objPosOffset;
|
||||
yield return null;
|
||||
}
|
||||
// 当鼠标释放时,结束移动
|
||||
EndMovement();
|
||||
isMoving = false;
|
||||
activeMoveCode = -1;
|
||||
}
|
||||
private void EndMovement()
|
||||
{
|
||||
targetGameElement.transform.position = transform.position;
|
||||
targetElement.transformSubmodule.currentPosition = targetGameElement.transform.localPosition;
|
||||
targetElement.transformSubmodule.originalPosition = targetElement.transformSubmodule.currentPosition - objPosOffset;
|
||||
targetGameElement.Refresh();
|
||||
if (activeMoveCode == 0)
|
||||
{
|
||||
colliderX.gameObject.transform.DOScale(colliderX.gameObject.transform.localScale / 1.2f, 0.2f);
|
||||
// Do something specific for X axis
|
||||
}
|
||||
else if (activeMoveCode == 1)
|
||||
{
|
||||
colliderY.gameObject.transform.DOScale(colliderY.gameObject.transform.localScale / 1.2f, 0.2f);
|
||||
// Do something specific for Y axis
|
||||
}
|
||||
else if (activeMoveCode == 2)
|
||||
{
|
||||
colliderZ.gameObject.transform.DOScale(colliderZ.gameObject.transform.localScale / 1.2f, 0.2f);
|
||||
// Do something specific for Z axis
|
||||
}
|
||||
}
|
||||
private void UpdateTargetPosition()
|
||||
{
|
||||
if (targetGameElement == null) return;
|
||||
|
||||
// 获取当前鼠标位置
|
||||
Vector3 currentMousePos = Mouse.current.position.ReadValue();
|
||||
Vector3 mouseDelta = currentMousePos - initialMouseScreenPos;
|
||||
|
||||
// 计算移动轴的世界方向
|
||||
Vector3 worldAxis = GetWorldAxis(activeMoveCode);
|
||||
|
||||
// 计算屏幕移动向量对应的世界移动量
|
||||
float worldDelta = CalculateWorldDelta(worldAxis, mouseDelta);
|
||||
|
||||
// 应用新位置
|
||||
Vector3 newPosition = initialTargetPosition + worldAxis * worldDelta;
|
||||
|
||||
targetGameElement.transform.position = newPosition;
|
||||
targetGameElement.transform.localPosition = new Vector3(
|
||||
Mathf.Round(targetGameElement.transform.localPosition.x * 100f) / 100f,
|
||||
Mathf.Round(targetGameElement.transform.localPosition.y * 100f) / 100f,
|
||||
Mathf.Round(targetGameElement.transform.localPosition.z * 100f) / 100f
|
||||
);
|
||||
transform.position = newPosition;
|
||||
}
|
||||
|
||||
private Vector3 GetWorldAxis(int moveCode)
|
||||
{
|
||||
return moveCode switch
|
||||
{
|
||||
0 => transform.right, // X轴
|
||||
1 => transform.up, // Y轴
|
||||
2 => transform.forward, // Z轴
|
||||
_ => Vector3.zero
|
||||
};
|
||||
}
|
||||
|
||||
private float CalculateWorldDelta(Vector3 worldAxis, Vector3 mouseDelta)
|
||||
{
|
||||
// 将世界轴转换为屏幕方向
|
||||
Vector3 screenRefPoint = editorCamera.WorldToScreenPoint(initialTargetPosition);
|
||||
Vector3 screenAxisEnd = editorCamera.WorldToScreenPoint(initialTargetPosition + worldAxis);
|
||||
|
||||
// 计算屏幕方向向量
|
||||
Vector3 screenAxis = (screenAxisEnd - screenRefPoint).normalized;
|
||||
|
||||
// 防止摄像机与轴平行时出现零向量
|
||||
if (screenAxis.sqrMagnitude < 0.01f) return 0;
|
||||
|
||||
// 计算鼠标移动在轴方向上的投影
|
||||
Vector2 screenAxis2D = new Vector2(screenAxis.x, screenAxis.y);
|
||||
Vector2 mouseDelta2D = new Vector2(mouseDelta.x, mouseDelta.y);
|
||||
|
||||
float projection = Vector2.Dot(mouseDelta2D, screenAxis2D.normalized);
|
||||
|
||||
// 应用灵敏度并返回
|
||||
return projection * moveSensitivity * 0.01f;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Editor Tools/QuickMover/QuickMover.cs.meta
Normal file
11
Assets/Scripts/Editor Tools/QuickMover/QuickMover.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cde63278924ce8944b8accfbf000a7e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Scripts/Editor Tools/Sample Assiant.meta
Normal file
8
Assets/Scripts/Editor Tools/Sample Assiant.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc3f6fb3124731f4181c766c4d3503ee
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,80 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Ichni;
|
||||
using Ichni.RhythmGame;
|
||||
using Michsky.MUIP;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class NotefabContoler : MonoBehaviour
|
||||
{
|
||||
public SampleWindow sampleWindow;
|
||||
public NoteBase noteBase;
|
||||
public RawImage ifHold;
|
||||
public void Initialize(NoteBase note, float timePerBeat, int beatDeviver, float posX)
|
||||
{
|
||||
noteBase = note;
|
||||
Image color = GetComponent<Image>();
|
||||
transform.localPosition = new Vector3(posX, note.exactJudgeTime / timePerBeat * beatDeviver, 0);
|
||||
switch (note)
|
||||
{
|
||||
case Hold hold:
|
||||
color.color = new Color(0, 1, 0, 1);
|
||||
if (ifHold != null)
|
||||
{
|
||||
ifHold.transform.localPosition = new Vector3(0, (hold.holdEndTime - hold.exactJudgeTime) / timePerBeat * beatDeviver / 2, 0);
|
||||
var rect = ifHold.GetComponent<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(rect.sizeDelta.x, (hold.holdEndTime - hold.exactJudgeTime) / timePerBeat * beatDeviver);
|
||||
ifHold.color = new Color(0, 1, 0, 1);
|
||||
}
|
||||
break;
|
||||
case Tap:
|
||||
color.color = new Color(0, 1, 1, 1);
|
||||
break;
|
||||
case Stay:
|
||||
color.color = new Color(1, 1, 0, 1);
|
||||
break;
|
||||
case Flick:
|
||||
color.color = new Color(1, 0.2f, 0, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
public void Update()
|
||||
{
|
||||
if (RectTransformUtility.RectangleContainsScreenPoint(this.GetComponent<RectTransform>(), Mouse.current.position.ReadValue()))
|
||||
{
|
||||
if (Mouse.current.leftButton.wasPressedThisFrame)
|
||||
{
|
||||
if (sampleWindow.isExpand) StartCoroutine(Moving());
|
||||
if (EditorManager.instance.uiManager.inspector.connectedGameElement != noteBase) EditorManager.instance.uiManager.hierarchy.FindTab(noteBase);
|
||||
}
|
||||
}
|
||||
}
|
||||
public IEnumerator Moving()
|
||||
{
|
||||
sampleWindow.GetComponent<WindowDragger>().Lock = true;
|
||||
float startX = transform.localPosition.x;
|
||||
while (Mouse.current.leftButton.isPressed)
|
||||
{
|
||||
Vector2 localMousePosition = GetComponent<RectTransform>().InverseTransformPoint(Mouse.current.position.ReadValue());
|
||||
// if (Mathf.Abs(localMousePosition.x - startX) > GetComponent<RectTransform>().sizeDelta.x / 2)
|
||||
{
|
||||
transform.localPosition += new Vector3(Mouse.current.delta.ReadValue().x, 0, 0);
|
||||
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
noteBase.noteVisual.transformSubmodule.originalPosition = new Vector3(
|
||||
transform.localPosition.x / sampleWindow.XWidth,
|
||||
noteBase.noteVisual.transformSubmodule.originalPosition.y,
|
||||
noteBase.noteVisual.transformSubmodule.originalPosition.z
|
||||
);
|
||||
noteBase.noteVisual.transformSubmodule.Refresh();
|
||||
|
||||
sampleWindow.GetComponent<WindowDragger>().Lock = false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae19b314bbfece546a1a8d74d38387ac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
277
Assets/Scripts/Editor Tools/Sample Assiant/SampleWindow.cs
Normal file
277
Assets/Scripts/Editor Tools/Sample Assiant/SampleWindow.cs
Normal file
@@ -0,0 +1,277 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dreamteck.Splines;
|
||||
using Ichni;
|
||||
using Ichni.Editor;
|
||||
using Ichni.RhythmGame;
|
||||
using TMPro;
|
||||
using Unity.VisualScripting;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class SampleWindow : MovableWindow//该window高度为300,横的要XWidth0和500之间切换
|
||||
{
|
||||
public static List<SampleWindow> instances = new List<SampleWindow>();
|
||||
public TMP_InputField DeviverInputField;
|
||||
public TMP_InputField verticalInputField;
|
||||
public TMP_InputField horizontalInputField;
|
||||
public RectTransform LineMovepoint;
|
||||
public RectTransform NoteMovepoint;
|
||||
|
||||
public List<NoteBase> noteBases;
|
||||
public bool isFocus = false;
|
||||
public bool isExpand = false;
|
||||
public int beatDeviver = 100;
|
||||
public int XWidth = 10;
|
||||
public int Xdevide = 1;
|
||||
public float realDevider;
|
||||
public GameObject beatLinePrefabv;
|
||||
public GameObject beatLinePrefabh;
|
||||
public GameObject NotePrefab;
|
||||
float songTime => EditorManager.instance.songInformation.songTime;
|
||||
float songBeat => EditorManager.instance.songInformation.songBeat;
|
||||
float beatmapStartTime => -EditorManager.instance.songInformation.delay;
|
||||
float timePerBeat => 60f / EditorManager.instance.songInformation.bpm;
|
||||
public GameElement gameElement;
|
||||
public SplinePositioner trackPositioner = null;
|
||||
public GameObject trackHead;
|
||||
public void Initialize(GameElement qgameElement, string title)
|
||||
{
|
||||
closeButton.onClick.AddListener(() =>
|
||||
{
|
||||
instances.Remove(this);
|
||||
});
|
||||
if (instances.Where(i => i.gameElement == qgameElement).Count() != 0)
|
||||
{
|
||||
Destroy(this.gameObject);
|
||||
foreach (SampleWindow i in instances)
|
||||
{
|
||||
i.StartCoroutine(WindowAnim.Shake(instances.Where(i => i.gameElement == qgameElement).First().windowRect.gameObject));
|
||||
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.gameElement = qgameElement;
|
||||
if (qgameElement is Track track)
|
||||
{
|
||||
trackPositioner = this.gameObject.AddComponent<SplinePositioner>();
|
||||
trackPositioner.spline = track.trackPathSubmodule.path;
|
||||
trackPositioner.enabled = isFocus;
|
||||
trackPositioner.targetObject = trackHead;
|
||||
}
|
||||
InitializeWindow(title, null, new Vector3(1.2f, 1.2f, 1.2f));
|
||||
|
||||
|
||||
//
|
||||
SpawnBeatline();
|
||||
OnceSpawnNote();
|
||||
instances.Add(this);
|
||||
}
|
||||
public void SpawnBeatline()//添加优化措施
|
||||
|
||||
{
|
||||
for (int i = LineMovepoint.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
Destroy(LineMovepoint.GetChild(i).gameObject);
|
||||
}
|
||||
for (int i = 0; i < (int)EditorManager.instance.songInformation.song.length / timePerBeat; i++)
|
||||
{
|
||||
|
||||
|
||||
for (int j = 1; j < Xdevide; j++)
|
||||
{
|
||||
GameObject v = Instantiate(beatLinePrefabh, LineMovepoint);
|
||||
v.transform.localPosition = new Vector3(0, i * beatDeviver + (beatDeviver / Xdevide * j), 0);
|
||||
RawImage g = v.GetComponent<RawImage>();
|
||||
g.color = new Color(0, g.color.g, g.color.b, 0.2f);
|
||||
if (v.transform.localPosition.y > 1200)
|
||||
{
|
||||
Destroy(v);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
GameObject u = Instantiate(beatLinePrefabh, LineMovepoint);
|
||||
u.transform.localPosition = new Vector3(0, i * beatDeviver, 0);
|
||||
|
||||
if (u.transform.localPosition.y > 600)
|
||||
{
|
||||
Destroy(u);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnceSpawnNote()
|
||||
{
|
||||
if (gameElement is Track track) noteBases = track.GetAllNotes();
|
||||
else if (gameElement is ElementFolder elementFolder) noteBases = elementFolder.GetAllNotes();
|
||||
for (int i = NoteMovepoint.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
Destroy(NoteMovepoint.GetChild(i).gameObject);
|
||||
}
|
||||
foreach (var i in noteBases)
|
||||
{
|
||||
SpawnNote(i, i.noteVisual.transformSubmodule.originalPosition.x * XWidth);
|
||||
}
|
||||
}
|
||||
private void SpawnNote(NoteBase i, float posx = 0)
|
||||
{
|
||||
GameObject u = Instantiate(NotePrefab, NoteMovepoint);
|
||||
u.GetComponent<NotefabContoler>().Initialize(i, timePerBeat, beatDeviver, posx);
|
||||
u.GetComponent<NotefabContoler>().sampleWindow = this;
|
||||
}
|
||||
|
||||
public GameObject selectedGameObject;
|
||||
void Update()
|
||||
{
|
||||
selectedGameObject = EventSystem.current.currentSelectedGameObject;
|
||||
LineMovepoint.localPosition = new(0, -beatDeviver * (songBeat - (int)songBeat), 0);
|
||||
NoteMovepoint.localPosition = new(0, -beatDeviver * songBeat, 0);
|
||||
|
||||
if (RectTransformUtility.RectangleContainsScreenPoint(windowRect, Mouse.current.position.ReadValue()) &&
|
||||
selectedGameObject != DeviverInputField.gameObject &&
|
||||
selectedGameObject != verticalInputField.gameObject &&
|
||||
selectedGameObject != horizontalInputField.gameObject)
|
||||
{
|
||||
DetectNote();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
void LateUpdate()
|
||||
{
|
||||
if (isFocus && gameElement is Track track)
|
||||
{
|
||||
if (track.trackTimeSubmodule is TrackTimeSubmoduleMovable trackTimeSubmoduleMovable)
|
||||
{
|
||||
trackPositioner.SetPercent(track.trackTimeSubmodule.headPercent);
|
||||
windowRect.GetComponent<CanvasGroup>().alpha = (songTime >= trackTimeSubmoduleMovable.trackStartTime && songTime <= trackTimeSubmoduleMovable.trackEndTime) ? 1f : 0.2f;
|
||||
|
||||
}
|
||||
else if (track.trackTimeSubmodule is TrackTimeSubmoduleStatic)
|
||||
{
|
||||
trackPositioner.SetPercent(0f);
|
||||
}
|
||||
TransformChanged();
|
||||
windowRect.GetComponent<CanvasGroup>().alpha = track.timeDurationSubmodule.CheckTimeInDuration(songTime) ? 1f : 0.2f;
|
||||
}
|
||||
}
|
||||
void TransformChanged()
|
||||
{
|
||||
RectTransform canvasRect = EditorManager.instance.inspectorCanvas.GetComponent<RectTransform>();
|
||||
Vector2 ScreenPosition = EditorManager.instance.cameraManager.currentCamera.WorldToScreenPoint(trackHead.transform.position);
|
||||
|
||||
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, ScreenPosition, null, out Vector2 uiPosition))
|
||||
{
|
||||
windowRect.anchoredPosition = new Vector2(uiPosition.x, uiPosition.y + 150f);
|
||||
}
|
||||
}
|
||||
public void ChangeFocus()
|
||||
{
|
||||
isFocus = !isFocus;
|
||||
if (trackPositioner != null) trackPositioner.enabled = isFocus;
|
||||
}
|
||||
public void ChangeXdevide(string devide)
|
||||
{
|
||||
Xdevide = int.Parse(devide);
|
||||
SpawnBeatline();
|
||||
}
|
||||
public void ChangeBeatdevide(string devide)
|
||||
{
|
||||
beatDeviver = int.Parse(devide);
|
||||
SpawnBeatline();
|
||||
OnceSpawnNote();
|
||||
}
|
||||
public void ChangeExpand()
|
||||
{
|
||||
if (isExpand)
|
||||
{
|
||||
isExpand = false;
|
||||
windowRect.sizeDelta = new Vector2(100, windowRect.sizeDelta.y);
|
||||
}
|
||||
else
|
||||
{
|
||||
isExpand = true;
|
||||
windowRect.sizeDelta = new Vector2(500, windowRect.sizeDelta.y);
|
||||
}
|
||||
}
|
||||
public void ChangeXWidth(string change)
|
||||
{
|
||||
XWidth = int.Parse(change);
|
||||
OnceSpawnNote();
|
||||
}
|
||||
public void DetectNote()
|
||||
{
|
||||
if (Keyboard.current.digit1Key.wasPressedThisFrame)
|
||||
AddNote(0);
|
||||
else if (Keyboard.current.digit2Key.wasPressedThisFrame)
|
||||
AddNote(1);
|
||||
else if (Keyboard.current.digit3Key.wasPressedThisFrame)
|
||||
AddNote(2);
|
||||
else if (Keyboard.current.digit4Key.wasPressedThisFrame)
|
||||
AddNote(3);
|
||||
}
|
||||
public void AddNote(int NoteCode)
|
||||
{
|
||||
if (!EditorManager.instance.useNotePrefab)
|
||||
{
|
||||
LogWindow.Log("Please enable \"Note Prefab\" in EditorManager", Color.red);
|
||||
return;
|
||||
}
|
||||
// 获取鼠标在 NoteMovepoint 中的相对位置
|
||||
Vector2 localMousePosition = NoteMovepoint.InverseTransformPoint(Mouse.current.position.ReadValue());
|
||||
Debug.Log(localMousePosition);
|
||||
float mouseBeat = localMousePosition.y / beatDeviver;
|
||||
float far = 0f;
|
||||
while (far < mouseBeat)
|
||||
{
|
||||
far += 1f / Xdevide;
|
||||
}
|
||||
far -= 1f / Xdevide;
|
||||
float time = Mathf.Round(far * timePerBeat * 1000f) / 1000f;
|
||||
|
||||
|
||||
switch (NoteCode)
|
||||
{
|
||||
case 0:
|
||||
Tap a = Tap.GenerateElement("New Tap", Guid.NewGuid(), new List<string>(), true, gameElement, time);
|
||||
noteBases.Add(a);
|
||||
a.noteVisual.transformSubmodule.originalPosition = new Vector3(isExpand ? (localMousePosition.x / XWidth) : 0f, 0f, 0f);
|
||||
a.noteVisual.transformSubmodule.Refresh();
|
||||
SpawnNote(a, isExpand ? localMousePosition.x : 0f);
|
||||
break;
|
||||
case 3:
|
||||
Hold b = Hold.GenerateElement("New Hold", Guid.NewGuid(), new List<string>(), true, gameElement, time, time + 0.5f);
|
||||
noteBases.Add(b);
|
||||
b.noteVisual.transformSubmodule.originalPosition = new Vector3(isExpand ? (localMousePosition.x / XWidth) : 0f, 0f, 0f);
|
||||
b.noteVisual.transformSubmodule.Refresh();
|
||||
SpawnNote(b, isExpand ? localMousePosition.x : 0f);
|
||||
break;
|
||||
case 1:
|
||||
Stay c = Stay.GenerateElement("New Stay", Guid.NewGuid(), new List<string>(), true, gameElement, time);
|
||||
noteBases.Add(c);
|
||||
c.noteVisual.transformSubmodule.originalPosition = new Vector3(isExpand ? (localMousePosition.x / XWidth) : 0f, 0f, 0f);
|
||||
c.noteVisual.transformSubmodule.Refresh();
|
||||
SpawnNote(c, isExpand ? localMousePosition.x : 0f);
|
||||
break;
|
||||
case 2:
|
||||
Flick d = Flick.GenerateElement("New Flick", Guid.NewGuid(), new List<string>(), true, gameElement, time, new List<Vector2>());
|
||||
noteBases.Add(d);
|
||||
d.noteVisual.transformSubmodule.originalPosition = new Vector3(isExpand ? (localMousePosition.x / XWidth) : 0f, 0f, 0f);
|
||||
d.noteVisual.transformSubmodule.Refresh();
|
||||
SpawnNote(d, isExpand ? localMousePosition.x : 0f);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a48a638548bdb6645bfa74867dc72087
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user