我死去
目前添加了一个能够深度查找和设置反射的helper,但是Tag的系统仍然不完全,另外我觉得console可以重构了 Signed-off-by: TRAfoer <lhf190@outlook.com>
This commit is contained in:
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Ichni.RhythmGame;
|
||||
@@ -15,11 +16,16 @@ namespace Ichni.Editor
|
||||
{
|
||||
public static class EditorConsoleMethods
|
||||
{
|
||||
#region UI/Manager Accessors (UI/管理器访问器)
|
||||
public static Inspector inspector => EditorManager.instance.uiManager.inspector;
|
||||
public static Hierarchy hierarchy => EditorManager.instance.uiManager.hierarchy;
|
||||
public static LogWindow logWindow => EditorManager.instance.uiManager.mainPage.logWindow;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Scene/Transform Operations (场景/变换操作)
|
||||
/// <summary>
|
||||
/// 传送场景相机到指定位置
|
||||
/// </summary>
|
||||
public static void tp(Vector3 pos)
|
||||
{
|
||||
if (EditorManager.instance.cameraManager.isSceneCameraActive)
|
||||
@@ -27,7 +33,10 @@ namespace Ichni.Editor
|
||||
EditorManager.instance.cameraManager.sceneCamera.sceneCamera.transform.position = pos;
|
||||
}
|
||||
}
|
||||
// 保留无参tp
|
||||
|
||||
/// <summary>
|
||||
/// 传送场景相机到选中元素位置
|
||||
/// </summary>
|
||||
public static void tp()
|
||||
{
|
||||
if (EditorManager.instance.cameraManager.isSceneCameraActive)
|
||||
@@ -37,13 +46,20 @@ namespace Ichni.Editor
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重命名选中元素
|
||||
/// </summary>
|
||||
public static void ReName(string message)
|
||||
{
|
||||
inspector.connectedGameElement.elementName = message;
|
||||
inspector.connectedGameElement.Refresh();
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region PathNode Generation (路径节点生成)
|
||||
/// <summary>
|
||||
/// 直线路径节点生成 (Line PathNode Generator)
|
||||
/// </summary>
|
||||
public static void Lgp(int loop, Vector3 start, Vector3 end, bool Clear = false, bool offsetOrigin = false)
|
||||
{
|
||||
if (inspector.connectedGameElement == null || inspector.connectedGameElement.GetType() != typeof(Track))
|
||||
@@ -88,35 +104,8 @@ namespace Ichni.Editor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将原有 PathNode 的变换(位置、旋转、缩放)迁移到新生成的最近 PathNode 上
|
||||
/// 主轴方向的螺旋线式 PathNode
|
||||
/// </summary>
|
||||
public static bool AdjustPathNodesToNearest(Track track, List<PathNode> newNodes, List<PathNode> oldNodes)
|
||||
{
|
||||
foreach (var oldNode in oldNodes)
|
||||
{
|
||||
// 找到距离 oldNode 最近的新节点
|
||||
PathNode nearest = newNodes
|
||||
.OrderBy(n => Vector3.Distance(n.transformSubmodule.originalPosition, oldNode.transformSubmodule.originalPosition))
|
||||
.FirstOrDefault();
|
||||
|
||||
if (nearest != null)
|
||||
{
|
||||
// 计算 oldNode 的变换(直接用欧拉角,不用四元数)
|
||||
Vector3 deltaPos = oldNode.transformSubmodule.originalPosition - oldNode.transformSubmodule.originalPosition;
|
||||
Vector3 deltaEuler = oldNode.transformSubmodule.originalEulerAngles - oldNode.transformSubmodule.originalEulerAngles;
|
||||
Vector3 deltaScale = oldNode.transformSubmodule.originalScale - oldNode.transformSubmodule.originalScale;
|
||||
|
||||
// 将变换应用到新节点
|
||||
nearest.transformSubmodule.originalPosition += deltaPos;
|
||||
nearest.transformSubmodule.originalEulerAngles += deltaEuler;
|
||||
nearest.transformSubmodule.originalScale += deltaScale;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// 支持主轴方向的螺旋线式 PathNode
|
||||
public static void Spiral(int loop, Vector3 center, float r, float h, int pointsPerTurn, string axis = "y")
|
||||
{
|
||||
if (inspector.connectedGameElement == null || inspector.connectedGameElement.GetType() != typeof(Track))
|
||||
@@ -160,7 +149,9 @@ namespace Ichni.Editor
|
||||
}
|
||||
}
|
||||
|
||||
// 任意方向的螺旋线式 PathNode(中心点和方向均为Vector3)
|
||||
/// <summary>
|
||||
/// 任意方向的螺旋线式 PathNode(中心点和方向均为Vector3)
|
||||
/// </summary>
|
||||
public static void Spiral(int loop, Vector3 center, float r, float h, int pointsPerTurn, Vector3 dir)
|
||||
{
|
||||
if (inspector.connectedGameElement == null || inspector.connectedGameElement.GetType() != typeof(Track))
|
||||
@@ -189,6 +180,41 @@ namespace Ichni.Editor
|
||||
node.transformSubmodule.originalPosition = pos;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region PathNode/Track Utilities (路径节点/轨道工具)
|
||||
|
||||
/// <summary>
|
||||
/// 将原有 PathNode 的变换(位置、旋转、缩放)迁移到新生成的最近 PathNode 上
|
||||
/// </summary>
|
||||
public static bool AdjustPathNodesToNearest(Track track, List<PathNode> newNodes, List<PathNode> oldNodes)
|
||||
{
|
||||
foreach (var oldNode in oldNodes)
|
||||
{
|
||||
// 找到距离 oldNode 最近的新节点
|
||||
PathNode nearest = newNodes
|
||||
.OrderBy(n => Vector3.Distance(n.transformSubmodule.originalPosition, oldNode.transformSubmodule.originalPosition))
|
||||
.FirstOrDefault();
|
||||
|
||||
if (nearest != null)
|
||||
{
|
||||
// 计算 oldNode 的变换(直接用欧拉角,不用四元数)
|
||||
Vector3 deltaPos = oldNode.transformSubmodule.originalPosition - oldNode.transformSubmodule.originalPosition;
|
||||
Vector3 deltaEuler = oldNode.transformSubmodule.originalEulerAngles - oldNode.transformSubmodule.originalEulerAngles;
|
||||
Vector3 deltaScale = oldNode.transformSubmodule.originalScale - oldNode.transformSubmodule.originalScale;
|
||||
|
||||
// 将变换应用到新节点
|
||||
nearest.transformSubmodule.originalPosition += deltaPos;
|
||||
nearest.transformSubmodule.originalEulerAngles += deltaEuler;
|
||||
nearest.transformSubmodule.originalScale += deltaScale;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除父元素下所有与当前选中元素类型相同的其他元素,最后删除当前选中元素
|
||||
/// </summary>
|
||||
public static void DelSameInParent()
|
||||
{
|
||||
Type type = inspector.connectedGameElement.GetType();
|
||||
@@ -206,6 +232,213 @@ namespace Ichni.Editor
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将选中轨道下所有音符附着到最近的 Trail 上
|
||||
/// </summary>
|
||||
public static void AttachNoteInNearestTrail()
|
||||
{
|
||||
Track track = inspector.connectedGameElement as Track;
|
||||
if (track == null)
|
||||
{
|
||||
LogWindow.Log("Please select a Track first!", Color.red);
|
||||
return;
|
||||
}
|
||||
List<NoteBase> noteBases = track.childElementList.OfType<NoteBase>().ToList();
|
||||
List<IHaveTrail> trails = track.GetAllGameElementsFromThis().OfType<IHaveTrail>().ToList();
|
||||
if (trails.Count == 0)
|
||||
{
|
||||
LogWindow.Log("The Track has no Trail!", Color.red);
|
||||
return;
|
||||
}
|
||||
foreach (var note in noteBases)
|
||||
{
|
||||
IHaveTrail nearestTrail = null;
|
||||
Vector3 FinalPos = Vector3.positiveInfinity;
|
||||
foreach (var trail in trails)
|
||||
{
|
||||
if (trail is IHaveTransformSubmodule haveTransform && trail is GameElement gameElement)
|
||||
{
|
||||
Vector3 pos = haveTransform.transformSubmodule.originalPosition;
|
||||
GameElement gameElement1 = gameElement;
|
||||
while (gameElement1 != track)
|
||||
{
|
||||
if (gameElement1 is not IHaveTransformSubmodule)
|
||||
{
|
||||
gameElement1 = gameElement1.parentElement;
|
||||
continue;
|
||||
}
|
||||
List<Displacement> animationBases = gameElement1.childElementList.OfType<Displacement>().ToList();
|
||||
|
||||
foreach (var displacement in animationBases)
|
||||
{
|
||||
pos += displacement.getValue(note.exactJudgeTime);
|
||||
}
|
||||
gameElement1 = gameElement1.parentElement;
|
||||
}
|
||||
if (Vector3.Distance(pos, (note.noteVisual.submoduleList.First(i => i is TransformSubmodule) as TransformSubmodule).originalPosition) <=
|
||||
Vector3.Distance(FinalPos, (note.noteVisual.submoduleList.First(i => i is TransformSubmodule) as TransformSubmodule).originalPosition))
|
||||
{
|
||||
nearestTrail = trail;
|
||||
FinalPos = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nearestTrail != null)
|
||||
{
|
||||
(note.noteVisual.submoduleList.First(i => i is TransformSubmodule) as TransformSubmodule).originalPosition = FinalPos;
|
||||
note.Refresh();
|
||||
(note.noteVisual.submoduleList.First(i => i is TransformSubmodule) as TransformSubmodule).Refresh();//捏妈妈滴为什么notevisual的TransformSubmodule不刷新
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调整路径节点的 Z 坐标(可用于多轨道)
|
||||
/// </summary>
|
||||
public static void AdjustPathnodeZ(float OriginZpoint, float scale)
|
||||
{
|
||||
if (inspector.connectedGameElement == null)
|
||||
{
|
||||
LogWindow.Log($"please select a element (folder or track)", Color.red);
|
||||
|
||||
return;
|
||||
}
|
||||
if (inspector.connectedGameElement.GetType() != typeof(Track))
|
||||
{
|
||||
foreach (var i in inspector.connectedGameElement.childElementList.OfType<Track>())
|
||||
{
|
||||
inspector.connectedGameElement = i;
|
||||
AdjustPathnodeZ(OriginZpoint, scale);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Track track = (Track)inspector.connectedGameElement;
|
||||
var pathnodes = track.trackPathSubmodule.pathNodeList;
|
||||
foreach (var pathnode in pathnodes)
|
||||
{
|
||||
if (pathnode.childElementList.OfType<Displacement>().Count() > 0)
|
||||
{
|
||||
LogWindow.Log($"PathNode {pathnode.elementName} has Displacement, which may cause issues", Color.yellow);
|
||||
}
|
||||
float worldZ = pathnode.transform.position.z;
|
||||
float deltaZ = worldZ - OriginZpoint;
|
||||
float newZ = OriginZpoint + deltaZ * scale;
|
||||
pathnode.transform.position = new Vector3(pathnode.transform.position.x, pathnode.transform.position.y, newZ);
|
||||
pathnode.transformSubmodule.originalPosition = pathnode.transform.localPosition;
|
||||
pathnode.transformSubmodule.Refresh();
|
||||
pathnode.Refresh();
|
||||
}
|
||||
foreach (var i in track.childElementList.OfType<Track>())
|
||||
{
|
||||
inspector.connectedGameElement = i;
|
||||
AdjustPathnodeZ(OriginZpoint, scale);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 Hold Note 拆分为具有路径节点的 Track
|
||||
/// </summary>
|
||||
public static void SplitHoldToTrack(int PathnodesCount)
|
||||
{
|
||||
if (inspector.connectedGameElement == null || inspector.connectedGameElement.GetType() != typeof(Hold))
|
||||
{
|
||||
LogWindow.Log("Please select a Hold first!");
|
||||
return;
|
||||
}
|
||||
Hold hold = (Hold)inspector.connectedGameElement;
|
||||
Track parentTrack = hold.parentElement as Track;
|
||||
if (parentTrack == null || parentTrack.trackTimeSubmodule is not TrackTimeSubmoduleMovable)
|
||||
{
|
||||
LogWindow.Log("Track Illegal (Only Movable)", Color.red);
|
||||
return;
|
||||
}
|
||||
TrackTimeSubmoduleMovable trackTimeSubmoduleMovable = hold.track.trackTimeSubmodule as TrackTimeSubmoduleMovable;
|
||||
|
||||
if (PathnodesCount < 2)
|
||||
{
|
||||
LogWindow.Log("PathnodesCount must be greater than 1!", Color.red);
|
||||
return;
|
||||
}
|
||||
float startTime = hold.exactJudgeTime;
|
||||
float endTime = hold.holdEndTime;
|
||||
float interval = 1f / (PathnodesCount - 1);
|
||||
|
||||
hold.UpdateNoteInMovableTrack();
|
||||
Vector3 HoldStartPos = default;
|
||||
Vector3 HoldEndPos = default;
|
||||
if (hold.noteVisual is DTMNoteVisualHold dTMNoteVisualHold)
|
||||
{
|
||||
dTMNoteVisualHold.headPoint.SetPercent(trackTimeSubmoduleMovable.GetTrackPercent(hold.exactJudgeTime));
|
||||
dTMNoteVisualHold.tailPoint.SetPercent(trackTimeSubmoduleMovable.GetTrackPercent(hold.holdEndTime));
|
||||
HoldStartPos = dTMNoteVisualHold.headPoint.transform.position;
|
||||
HoldEndPos = dTMNoteVisualHold.tailPoint.transform.position;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogWindow.Log("The selected Hold's NoteVisual is not DTMNoteVisualHold!", Color.red);
|
||||
return;
|
||||
}
|
||||
if (hold.track.trackPathSubmodule.pathNodeList.Count > 2)
|
||||
{
|
||||
LogWindow.Log("the Hold may not be split currently", Color.yellow);
|
||||
return;
|
||||
}
|
||||
hold.UpdateNoteInMovableTrack();
|
||||
Track NewTrack = Track.GenerateElement(hold.elementName + "_SplitTrack", Guid.NewGuid(), new List<string>(), true, parentTrack);
|
||||
new TrackTimeSubmoduleMovable(NewTrack, startTime, endTime, 1, AnimationCurveType.Linear);
|
||||
for (int i = 0; i < PathnodesCount; i++)
|
||||
{
|
||||
PathNode j = PathNode.GenerateElement("PathNode" + i.ToString(), Guid.NewGuid(), new List<string>(), true, NewTrack, true);
|
||||
j.transform.position = Vector3.Lerp(HoldStartPos, HoldEndPos, i * interval);
|
||||
j.transformSubmodule.originalPosition = j.transform.localPosition;
|
||||
}
|
||||
EditorManager.instance.operationManager.CopyPasteDeleteModule.CopyElement(hold);
|
||||
EditorManager.instance.operationManager.CopyPasteDeleteModule.PasteElement(NewTrack);
|
||||
|
||||
EditorManager.instance.operationManager.CopyPasteDeleteModule.DeleteElement(hold);
|
||||
Hold newHold = NewTrack.childElementList.OfType<Hold>().First();
|
||||
newHold.noteVisual.transformSubmodule.originalPosition = Vector3.zero;
|
||||
NewTrack.Refresh();
|
||||
Observable.Timer(TimeSpan.FromSeconds(0.3f)).Subscribe(_ =>
|
||||
{
|
||||
NewTrack?.trackPathSubmodule.path.RebuildImmediate(true, true);
|
||||
DTMNoteVisualHold dTMNoteVisualHold = newHold.noteVisual as DTMNoteVisualHold;
|
||||
dTMNoteVisualHold.meshGenerator.Rebuild();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将选中轨道下所有 Hold Note 拆分为具有路径节点的 Track
|
||||
/// </summary>
|
||||
public static void SplitAllHoldToTrack(int PathnodesCount)
|
||||
{
|
||||
if (inspector.connectedGameElement == null || inspector.connectedGameElement.GetType() != typeof(Track))
|
||||
{
|
||||
LogWindow.Log("Please select a Track first!");
|
||||
return;
|
||||
}
|
||||
Track track = (Track)inspector.connectedGameElement;
|
||||
var holds = track.childElementList.OfType<Hold>().ToList();
|
||||
if (track.trackPathSubmodule.pathNodeList.Count > 2)
|
||||
{
|
||||
LogWindow.Log("the Hold may not be split currently", Color.yellow);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var hold in holds)
|
||||
{
|
||||
inspector.connectedGameElement = hold;
|
||||
SplitHoldToTrack(PathnodesCount);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Note Import/Export (音符导入/导出)
|
||||
/// <summary>
|
||||
/// 从正则格式文本导入音符数据
|
||||
/// </summary>
|
||||
public static void SamplerImport(string inputData)
|
||||
{
|
||||
if (!EditorManager.instance.useNotePrefab)
|
||||
@@ -316,6 +549,61 @@ namespace Ichni.Editor
|
||||
}
|
||||
Debug.Log("===== =====");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出选中轨道下所有音符为正则格式文本(复制到剪贴板)
|
||||
/// </summary>
|
||||
public static void ExportNotesFromTrack()
|
||||
{
|
||||
if (inspector.connectedGameElement == null || inspector.connectedGameElement.GetType() != typeof(Track))
|
||||
{
|
||||
LogWindow.Log("Please select a Track first!");
|
||||
return;
|
||||
}
|
||||
Track track = (Track)inspector.connectedGameElement;
|
||||
var notes = track.childElementList.OfType<NoteBase>().OrderBy(n => n.exactJudgeTime).ToList();
|
||||
List<string> lines = new List<string>();
|
||||
foreach (var note in notes)
|
||||
{
|
||||
string type = note switch
|
||||
{
|
||||
Tap => "Tap",
|
||||
Stay => "Stay",
|
||||
Hold => "Hold",
|
||||
Flick => "Flick",
|
||||
_ => "Unknown"
|
||||
};
|
||||
string id = track.elementName;
|
||||
float time = note.exactJudgeTime;
|
||||
float x = 0f;
|
||||
// 获取X值
|
||||
if (note.noteVisual.submoduleList.FirstOrDefault(i => i is TransformSubmodule) is TransformSubmodule ts)
|
||||
{
|
||||
x = ts.originalPosition.x;
|
||||
}
|
||||
if (note is Hold hold)
|
||||
{
|
||||
float duration = hold.holdEndTime - hold.exactJudgeTime;
|
||||
lines.Add($"({type}, {id}, {time:0.###}, {x:0.###}, {duration:0.###})");
|
||||
}
|
||||
else
|
||||
{
|
||||
lines.Add($"({type}, {id}, {time:0.###}, {x:0.###})");
|
||||
}
|
||||
}
|
||||
string result = string.Join("\n", lines);
|
||||
Debug.Log(result);
|
||||
//LogWindow.Log(result, Color.green);
|
||||
// 复制到剪贴板
|
||||
GUIUtility.systemCopyBuffer = result;
|
||||
LogWindow.Log("Colped Done!", Color.green);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Highlighting/Visuals (高亮/视觉效果)
|
||||
/// <summary>
|
||||
/// 在游戏内设置时间相同的音符高亮显示
|
||||
/// </summary>
|
||||
public static void SetNoteHLInGame(bool forceSetOff = false, bool SameTheme = false)
|
||||
{
|
||||
var noteBases = EditorManager.instance.beatmapContainer.gameElementList.OfType<NoteBase>().ToList();
|
||||
@@ -369,6 +657,9 @@ namespace Ichni.Editor
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在选中元素下设置时间相同的音符高亮显示
|
||||
/// </summary>
|
||||
public static void SetNoteHLInElement(bool forceSetOff = false, bool SameTheme = false)
|
||||
{
|
||||
var noteBases = inspector.connectedGameElement.GetAllGameElementsFromThis().OfType<NoteBase>().ToList();
|
||||
@@ -422,6 +713,12 @@ namespace Ichni.Editor
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Animation Swaps (动画值交换)
|
||||
/// <summary>
|
||||
/// 交换 Displacement 动画值的正负号
|
||||
/// </summary>
|
||||
public static void swapDisplacement()
|
||||
{
|
||||
Displacement displacement = inspector.connectedGameElement as Displacement;
|
||||
@@ -446,6 +743,10 @@ namespace Ichni.Editor
|
||||
anim.startValue = -anim.startValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 交换 Swirl 动画值的正负号
|
||||
/// </summary>
|
||||
public static void swapSwirl()
|
||||
{
|
||||
Swirl swirl = inspector.connectedGameElement as Swirl;
|
||||
@@ -470,6 +771,10 @@ namespace Ichni.Editor
|
||||
anim.startValue = -anim.startValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 交换 Scale 动画值的正负号
|
||||
/// </summary>
|
||||
public static void swapScale()
|
||||
{
|
||||
Scale scale = inspector.connectedGameElement as Scale;
|
||||
@@ -494,147 +799,12 @@ namespace Ichni.Editor
|
||||
anim.startValue = -anim.startValue;
|
||||
}
|
||||
}
|
||||
public static void AttachNoteInNearestTrail()
|
||||
{
|
||||
Track track = inspector.connectedGameElement as Track;
|
||||
if (track == null)
|
||||
{
|
||||
LogWindow.Log("Please select a Track first!", Color.red);
|
||||
return;
|
||||
}
|
||||
List<NoteBase> noteBases = track.childElementList.OfType<NoteBase>().ToList();
|
||||
List<IHaveTrail> trails = track.GetAllGameElementsFromThis().OfType<IHaveTrail>().ToList();
|
||||
if (trails.Count == 0)
|
||||
{
|
||||
LogWindow.Log("The Track has no Trail!", Color.red);
|
||||
return;
|
||||
}
|
||||
foreach (var note in noteBases)
|
||||
{
|
||||
IHaveTrail nearestTrail = null;
|
||||
Vector3 FinalPos = Vector3.positiveInfinity;
|
||||
foreach (var trail in trails)
|
||||
{
|
||||
if (trail is IHaveTransformSubmodule haveTransform && trail is GameElement gameElement)
|
||||
{
|
||||
Vector3 pos = haveTransform.transformSubmodule.originalPosition;
|
||||
GameElement gameElement1 = gameElement;
|
||||
while (gameElement1 != track)
|
||||
{
|
||||
if (gameElement1 is not IHaveTransformSubmodule)
|
||||
{
|
||||
gameElement1 = gameElement1.parentElement;
|
||||
continue;
|
||||
}
|
||||
List<Displacement> animationBases = gameElement1.childElementList.OfType<Displacement>().ToList();
|
||||
#endregion
|
||||
|
||||
foreach (var displacement in animationBases)
|
||||
{
|
||||
pos += displacement.getValue(note.exactJudgeTime);
|
||||
}
|
||||
gameElement1 = gameElement1.parentElement;
|
||||
}
|
||||
if (Vector3.Distance(pos, (note.noteVisual.submoduleList.First(i => i is TransformSubmodule) as TransformSubmodule).originalPosition) <=
|
||||
Vector3.Distance(FinalPos, (note.noteVisual.submoduleList.First(i => i is TransformSubmodule) as TransformSubmodule).originalPosition))
|
||||
{
|
||||
nearestTrail = trail;
|
||||
FinalPos = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nearestTrail != null)
|
||||
{
|
||||
(note.noteVisual.submoduleList.First(i => i is TransformSubmodule) as TransformSubmodule).originalPosition = FinalPos;
|
||||
note.Refresh();
|
||||
(note.noteVisual.submoduleList.First(i => i is TransformSubmodule) as TransformSubmodule).Refresh();//捏妈妈滴为什么notevisual的TransformSubmodule不刷新
|
||||
}
|
||||
}
|
||||
}
|
||||
// 导出Track下所有Note为正则格式文本
|
||||
public static void ExportNotesFromTrack()
|
||||
{
|
||||
if (inspector.connectedGameElement == null || inspector.connectedGameElement.GetType() != typeof(Track))
|
||||
{
|
||||
LogWindow.Log("Please select a Track first!");
|
||||
return;
|
||||
}
|
||||
Track track = (Track)inspector.connectedGameElement;
|
||||
var notes = track.childElementList.OfType<NoteBase>().OrderBy(n => n.exactJudgeTime).ToList();
|
||||
List<string> lines = new List<string>();
|
||||
foreach (var note in notes)
|
||||
{
|
||||
string type = note switch
|
||||
{
|
||||
Tap => "Tap",
|
||||
Stay => "Stay",
|
||||
Hold => "Hold",
|
||||
Flick => "Flick",
|
||||
_ => "Unknown"
|
||||
};
|
||||
string id = track.elementName;
|
||||
float time = note.exactJudgeTime;
|
||||
float x = 0f;
|
||||
// 获取X值
|
||||
if (note.noteVisual.submoduleList.FirstOrDefault(i => i is TransformSubmodule) is TransformSubmodule ts)
|
||||
{
|
||||
x = ts.originalPosition.x;
|
||||
}
|
||||
if (note is Hold hold)
|
||||
{
|
||||
float duration = hold.holdEndTime - hold.exactJudgeTime;
|
||||
lines.Add($"({type}, {id}, {time:0.###}, {x:0.###}, {duration:0.###})");
|
||||
}
|
||||
else
|
||||
{
|
||||
lines.Add($"({type}, {id}, {time:0.###}, {x:0.###})");
|
||||
}
|
||||
}
|
||||
string result = string.Join("\n", lines);
|
||||
Debug.Log(result);
|
||||
//LogWindow.Log(result, Color.green);
|
||||
// 复制到剪贴板
|
||||
GUIUtility.systemCopyBuffer = result;
|
||||
LogWindow.Log("Colped Done!", Color.green);
|
||||
}
|
||||
public static void AdjustPathnodeZ(float OriginZpoint, float scale)
|
||||
{
|
||||
if (inspector.connectedGameElement == null)
|
||||
{
|
||||
LogWindow.Log($"please select a element (folder or track)", Color.red);
|
||||
|
||||
return;
|
||||
}
|
||||
if (inspector.connectedGameElement.GetType() != typeof(Track))
|
||||
{
|
||||
foreach (var i in inspector.connectedGameElement.childElementList.OfType<Track>())
|
||||
{
|
||||
inspector.connectedGameElement = i;
|
||||
AdjustPathnodeZ(OriginZpoint, scale);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Track track = (Track)inspector.connectedGameElement;
|
||||
var pathnodes = track.trackPathSubmodule.pathNodeList;
|
||||
foreach (var pathnode in pathnodes)
|
||||
{
|
||||
if (pathnode.childElementList.OfType<Displacement>().Count() > 0)
|
||||
{
|
||||
LogWindow.Log($"PathNode {pathnode.elementName} has Displacement, which may cause issues", Color.yellow);
|
||||
}
|
||||
float worldZ = pathnode.transform.position.z;
|
||||
float deltaZ = worldZ - OriginZpoint;
|
||||
float newZ = OriginZpoint + deltaZ * scale;
|
||||
pathnode.transform.position = new Vector3(pathnode.transform.position.x, pathnode.transform.position.y, newZ);
|
||||
pathnode.transformSubmodule.originalPosition = pathnode.transform.localPosition;
|
||||
pathnode.transformSubmodule.Refresh();
|
||||
pathnode.Refresh();
|
||||
}
|
||||
foreach (var i in track.childElementList.OfType<Track>())
|
||||
{
|
||||
inspector.connectedGameElement = i;
|
||||
AdjustPathnodeZ(OriginZpoint, scale);
|
||||
}
|
||||
}
|
||||
#region Global Utilities (全局工具)
|
||||
/// <summary>
|
||||
/// 确保所有动画的起始时间为 0 时有关键帧
|
||||
/// </summary>
|
||||
public static void FloorAnim()
|
||||
{
|
||||
if (inspector.connectedGameElement == null)
|
||||
@@ -681,97 +851,10 @@ namespace Ichni.Editor
|
||||
}
|
||||
}
|
||||
}
|
||||
public static void SplitHoldToTrack(int PathnodesCount)
|
||||
{
|
||||
if (inspector.connectedGameElement == null || inspector.connectedGameElement.GetType() != typeof(Hold))
|
||||
{
|
||||
LogWindow.Log("Please select a Hold first!");
|
||||
return;
|
||||
}
|
||||
Hold hold = (Hold)inspector.connectedGameElement;
|
||||
Track parentTrack = hold.parentElement as Track;
|
||||
if (parentTrack == null || parentTrack.trackTimeSubmodule is not TrackTimeSubmoduleMovable)
|
||||
{
|
||||
LogWindow.Log("Track Illegal (Only Movable)", Color.red);
|
||||
return;
|
||||
}
|
||||
TrackTimeSubmoduleMovable trackTimeSubmoduleMovable = hold.track.trackTimeSubmodule as TrackTimeSubmoduleMovable;
|
||||
|
||||
if (PathnodesCount < 2)
|
||||
{
|
||||
LogWindow.Log("PathnodesCount must be greater than 1!", Color.red);
|
||||
return;
|
||||
}
|
||||
float startTime = hold.exactJudgeTime;
|
||||
float endTime = hold.holdEndTime;
|
||||
float interval = 1f / (PathnodesCount - 1);
|
||||
|
||||
hold.UpdateNoteInMovableTrack();
|
||||
Vector3 HoldStartPos = default;
|
||||
Vector3 HoldEndPos = default;
|
||||
if (hold.noteVisual is DTMNoteVisualHold dTMNoteVisualHold)
|
||||
{
|
||||
dTMNoteVisualHold.headPoint.SetPercent(trackTimeSubmoduleMovable.GetTrackPercent(hold.exactJudgeTime));
|
||||
dTMNoteVisualHold.tailPoint.SetPercent(trackTimeSubmoduleMovable.GetTrackPercent(hold.holdEndTime));
|
||||
HoldStartPos = dTMNoteVisualHold.headPoint.transform.position;
|
||||
HoldEndPos = dTMNoteVisualHold.tailPoint.transform.position;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogWindow.Log("The selected Hold's NoteVisual is not DTMNoteVisualHold!", Color.red);
|
||||
return;
|
||||
}
|
||||
if (hold.track.trackPathSubmodule.pathNodeList.Count > 2)
|
||||
{
|
||||
LogWindow.Log("the Hold may not be split currently", Color.yellow);
|
||||
return;
|
||||
}
|
||||
hold.UpdateNoteInMovableTrack();
|
||||
Track NewTrack = Track.GenerateElement(hold.elementName + "_SplitTrack", Guid.NewGuid(), new List<string>(), true, parentTrack);
|
||||
new TrackTimeSubmoduleMovable(NewTrack, startTime, endTime, 1, AnimationCurveType.Linear);
|
||||
for (int i = 0; i < PathnodesCount; i++)
|
||||
{
|
||||
PathNode j = PathNode.GenerateElement("PathNode" + i.ToString(), Guid.NewGuid(), new List<string>(), true, NewTrack, true);
|
||||
j.transform.position = Vector3.Lerp(HoldStartPos, HoldEndPos, i * interval);
|
||||
j.transformSubmodule.originalPosition = j.transform.localPosition;
|
||||
}
|
||||
EditorManager.instance.operationManager.CopyPasteDeleteModule.CopyElement(hold);
|
||||
EditorManager.instance.operationManager.CopyPasteDeleteModule.PasteElement(NewTrack);
|
||||
|
||||
EditorManager.instance.operationManager.CopyPasteDeleteModule.DeleteElement(hold);
|
||||
Hold newHold = NewTrack.childElementList.OfType<Hold>().First();
|
||||
newHold.noteVisual.transformSubmodule.originalPosition = Vector3.zero;
|
||||
NewTrack.Refresh();
|
||||
Observable.Timer(TimeSpan.FromSeconds(0.3f)).Subscribe(_ =>
|
||||
{
|
||||
NewTrack?.trackPathSubmodule.path.RebuildImmediate(true, true);
|
||||
DTMNoteVisualHold dTMNoteVisualHold = newHold.noteVisual as DTMNoteVisualHold;
|
||||
dTMNoteVisualHold.meshGenerator.Rebuild();
|
||||
});
|
||||
|
||||
}
|
||||
public static void SplitAllHoldToTrack(int PathnodesCount)
|
||||
{
|
||||
if (inspector.connectedGameElement == null || inspector.connectedGameElement.GetType() != typeof(Track))
|
||||
{
|
||||
LogWindow.Log("Please select a Track first!");
|
||||
return;
|
||||
}
|
||||
Track track = (Track)inspector.connectedGameElement;
|
||||
var holds = track.childElementList.OfType<Hold>().ToList();
|
||||
if (track.trackPathSubmodule.pathNodeList.Count > 2)
|
||||
{
|
||||
LogWindow.Log("the Hold may not be split currently", Color.yellow);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var hold in holds)
|
||||
{
|
||||
inspector.connectedGameElement = hold;
|
||||
SplitHoldToTrack(PathnodesCount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全局刷新所有元素和轨道路径
|
||||
/// </summary>
|
||||
public static void Rebuild()
|
||||
{
|
||||
foreach (GameElement element in EditorManager.instance.beatmapContainer.gameElementList)
|
||||
@@ -789,6 +872,10 @@ namespace Ichni.Editor
|
||||
UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加 Tag Matcher 到 TagManager
|
||||
/// </summary>
|
||||
public static void AddTagMatcher(string name, string parameterName)
|
||||
{
|
||||
|
||||
@@ -796,102 +883,67 @@ namespace Ichni.Editor
|
||||
IBaseElement element = inspector.connectedGameElement;
|
||||
tagManager.AddTagMatcher(name, element.GetType(), parameterName);
|
||||
}
|
||||
public static void FindParameterName(float value)
|
||||
|
||||
/// <summary>
|
||||
/// 查找元素中匹配给定值的参数名称
|
||||
/// </summary>
|
||||
public static void FindParameterName(object value)
|
||||
{
|
||||
// 假设 inspector.connectedGameElement 存在且是你希望搜索的对象
|
||||
var element = inspector.connectedGameElement;
|
||||
foreach (var i in element.GetType().GetFields())
|
||||
if (element == null || value == null) return;
|
||||
string o = FindParameterName(element, value);
|
||||
if (!string.IsNullOrEmpty(o)) LogWindow.Log(o);
|
||||
|
||||
foreach (var i in element.GetType().GetProperties(DeepMatchFlags))
|
||||
{
|
||||
if (i.FieldType == typeof(float) && (float)i.GetValue(element) == value)//获取对应变量的值)
|
||||
{
|
||||
Debug.Log($"Found parameter: {i.Name}");
|
||||
LogWindow.Log($"Found parameter: {i.Name}", Color.green);
|
||||
}
|
||||
if (i == null) continue;
|
||||
var propertyInfo = element.GetType().GetProperty(i.Name);
|
||||
if (propertyInfo == null) continue;
|
||||
var propertyValue = propertyInfo.GetValue(element);
|
||||
var p = FindParameterName(propertyValue as IBaseElement, value);
|
||||
if (!string.IsNullOrEmpty(p))
|
||||
LogWindow.Log($"{i.Name}.{p}");
|
||||
}
|
||||
}
|
||||
public static void FindParameterName(Vector3 value)
|
||||
public static string FindParameterName(IBaseElement element, object value)
|
||||
{
|
||||
var element = inspector.connectedGameElement;
|
||||
foreach (var field in element.GetType().GetFields())
|
||||
// 假设 inspector.connectedGameElement 存在且是你希望搜索的对象
|
||||
|
||||
if (element == null || value == null) return "";
|
||||
|
||||
// 1. 获取目标类型
|
||||
Type targetType = value.GetType();
|
||||
|
||||
// 如果是 Nullable<T>,获取其基础类型
|
||||
if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
if (field.FieldType == typeof(Vector3))
|
||||
targetType = Nullable.GetUnderlyingType(targetType);
|
||||
}
|
||||
|
||||
// 2. 遍历 GameElement 的所有字段
|
||||
foreach (var field in element.GetType().GetFields(DeepMatchFlags))
|
||||
{
|
||||
// 检查字段类型是否匹配
|
||||
if (field.FieldType == targetType)
|
||||
{
|
||||
Vector3 fieldValue = (Vector3)field.GetValue(element);
|
||||
if (Vector3.Distance(fieldValue, value) < 0.001f) // 使用容差比较Vector3
|
||||
// 获取字段的值 (作为 object)
|
||||
object fieldValue = field.GetValue(element);
|
||||
|
||||
if (IsValueMatch(fieldValue, value, field.FieldType))
|
||||
{
|
||||
Debug.Log($"Found Vector3 parameter: {field.Name}");
|
||||
LogWindow.Log($"Found Vector3 parameter: {field.Name}", Color.green);
|
||||
Debug.Log($"Found parameter: {field.Name} (Type: {targetType.Name})");
|
||||
// 假设 LogWindow 可用
|
||||
return field.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static void FindParameterName(Vector2 value)
|
||||
{
|
||||
var element = inspector.connectedGameElement;
|
||||
foreach (var field in element.GetType().GetFields())
|
||||
{
|
||||
if (field.FieldType == typeof(Vector2))
|
||||
{
|
||||
Vector2 fieldValue = (Vector2)field.GetValue(element);
|
||||
if (Vector2.Distance(fieldValue, value) < 0.001f) // 使用容差比较Vector2
|
||||
{
|
||||
Debug.Log($"Found Vector2 parameter: {field.Name}");
|
||||
LogWindow.Log($"Found Vector2 parameter: {field.Name}", Color.green);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void FindParameterName(Color value)
|
||||
{
|
||||
var element = inspector.connectedGameElement;
|
||||
foreach (var field in element.GetType().GetFields())
|
||||
{
|
||||
if (field.FieldType == typeof(Color))
|
||||
{
|
||||
Color fieldValue = (Color)field.GetValue(element);
|
||||
if (fieldValue == value) // Color可以直接比较
|
||||
{
|
||||
Debug.Log($"Found Color parameter: {field.Name}");
|
||||
LogWindow.Log($"Found Color parameter: {field.Name}", Color.green);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void FindParameterName(int value)
|
||||
{
|
||||
var element = inspector.connectedGameElement;
|
||||
foreach (var field in element.GetType().GetFields())
|
||||
{
|
||||
if (field.FieldType == typeof(int))
|
||||
{
|
||||
int fieldValue = (int)field.GetValue(element);
|
||||
if (fieldValue == value) // int可以直接比较
|
||||
{
|
||||
Debug.Log($"Found int parameter: {field.Name}");
|
||||
LogWindow.Log($"Found int parameter: {field.Name}", Color.green);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void FindParameterName(string value)
|
||||
{
|
||||
var element = inspector.connectedGameElement;
|
||||
foreach (var field in element.GetType().GetFields())
|
||||
{
|
||||
if (field.FieldType == typeof(string))
|
||||
{
|
||||
string fieldValue = (string)field.GetValue(element);
|
||||
if (fieldValue == value) // string可以直接比较
|
||||
{
|
||||
Debug.Log($"Found string parameter: {field.Name}");
|
||||
LogWindow.Log($"Found string parameter: {field.Name}", Color.green);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 同步标签元素
|
||||
/// </summary>
|
||||
public static void SyncTagedElement()
|
||||
{
|
||||
var q = inspector.connectedGameElement;
|
||||
@@ -899,5 +951,98 @@ namespace Ichni.Editor
|
||||
var tagManager = EditorManager.instance.projectInformation.tagManager;
|
||||
tagManager.SyncTagedElement((q));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internal Helpers (内部辅助方法)
|
||||
private const BindingFlags DeepMatchFlags = BindingFlags.Instance | BindingFlags.Public;
|
||||
private const float FloatEpsilon = 0.0001f; // 定义浮点数容差
|
||||
|
||||
/// <summary>
|
||||
/// 比较两个值是否匹配,对浮点数使用容差比较
|
||||
/// </summary>
|
||||
private static bool IsValueMatch(object fieldVal, object targetVal, Type t)
|
||||
{
|
||||
if (fieldVal == null || targetVal == null)
|
||||
{
|
||||
return fieldVal == targetVal;
|
||||
}
|
||||
|
||||
// 1. 类型是 float
|
||||
if (t == typeof(float))
|
||||
{
|
||||
// 必须进行类型转换
|
||||
float f1 = (float)fieldVal;
|
||||
float f2 = (float)targetVal;
|
||||
return Mathf.Abs(f1 - f2) < FloatEpsilon;
|
||||
}
|
||||
|
||||
// 2. 类型是 Vector3
|
||||
if (t == typeof(Vector3))
|
||||
{
|
||||
Vector3 v1 = (Vector3)fieldVal;
|
||||
Vector3 v2 = (Vector3)targetVal;
|
||||
// 使用 Unity 的 Distance 容差比较
|
||||
return Vector3.Distance(v1, v2) < FloatEpsilon;
|
||||
}
|
||||
|
||||
// 3. 类型是 Color
|
||||
if (t == typeof(Color))
|
||||
{
|
||||
Color c1 = (Color)fieldVal;
|
||||
Color c2 = (Color)targetVal;
|
||||
|
||||
// 手动解构 Color 的四个 float 分量,使用容差
|
||||
return Mathf.Abs(c1.r - c2.r) < FloatEpsilon &&
|
||||
Mathf.Abs(c1.g - c2.g) < FloatEpsilon &&
|
||||
Mathf.Abs(c1.b - c2.b) < FloatEpsilon &&
|
||||
Mathf.Abs(c1.a - c2.a) < FloatEpsilon;
|
||||
}
|
||||
|
||||
// 4. 其他包含 float 的复杂 Struct/Class 的浅层解构
|
||||
if (!t.IsPrimitive && t != typeof(string) && (t.IsClass || t.IsValueType))
|
||||
{
|
||||
// 如果字段值是复杂类型,并且我们想比较它的内部 float 字段 (浅层)
|
||||
// 这里只能进行浅层比较,因为深层递归需要辅助函数来管理栈。
|
||||
|
||||
// 遍历所有顶层字段,检查是否有 float
|
||||
foreach (var field in t.GetFields(DeepMatchFlags))
|
||||
{
|
||||
object fieldValMember = field.GetValue(fieldVal);
|
||||
object targetValMember = field.GetValue(targetVal);
|
||||
|
||||
if (field.FieldType == typeof(float))
|
||||
{
|
||||
// 对 float 成员进行容差比较
|
||||
if (fieldValMember != null && targetValMember != null)
|
||||
{
|
||||
float f1 = (float)fieldValMember;
|
||||
float f2 = (float)targetValMember;
|
||||
if (Mathf.Abs(f1 - f2) >= FloatEpsilon)
|
||||
{
|
||||
return false; // 找到不匹配的 float
|
||||
}
|
||||
}
|
||||
else if (fieldValMember != targetValMember) // 检查是否一个为 null,另一个不为 null
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 对其他类型使用默认 Equals
|
||||
if (!fieldValMember.Equals(targetValMember))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 所有字段都匹配
|
||||
return true;
|
||||
}
|
||||
|
||||
// 5. 默认路径:对于所有其他类型(int, string, bool, etc.),使用默认的 Equals 比较
|
||||
return fieldVal.Equals(targetVal);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user