using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using System.Threading.Tasks; using Ichni.RhythmGame; using Ichni.RhythmGame.Beatmap; using Ichni.RhythmGame.ThemeBundles.DepartureToMultiverse; using UniRx; using UnityEngine; 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 (场景/变换操作) /// /// 传送场景相机到指定位置 /// public static void tp(Vector3 pos) { if (EditorManager.instance.cameraManager.isSceneCameraActive) { EditorManager.instance.cameraManager.sceneCamera.sceneCamera.transform.position = pos; } } /// /// 传送场景相机到选中元素位置 /// public static void tp() { if (EditorManager.instance.cameraManager.isSceneCameraActive) { EditorManager.instance.cameraManager.sceneCamera.sceneCamera.transform.position = inspector.connectedGameElement.transform.position; } } /// /// 重命名选中元素 /// public static void ReName(string message) { inspector.connectedGameElement.elementName = message; inspector.connectedGameElement.Refresh(); } #endregion #region PathNode Generation (路径节点生成) /// /// 直线路径节点生成 (Line PathNode Generator) /// 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)) { LogWindow.Log("Please select a Track first!"); return; } if (loop <= 1) { LogWindow.Log("Loop must be greater than 1!"); return; } Track track = (Track)inspector.connectedGameElement; List oldNodes = track.trackPathSubmodule.pathNodeList.ToList(); List newNodes = new List(); // 如果 Clear 且有旧节点,迁移变换 if (Clear) { if (offsetOrigin && oldNodes.Count > 0 && newNodes.Count > 0) { AdjustPathNodesToNearest(track, newNodes, oldNodes); } // 清除之前的PathNode foreach (var node in oldNodes) { EditorManager.instance.operationManager.CopyPasteDeleteModule.DeleteElement(node); } } for (int i = 0; i < loop; i++) { float t = (float)i / (loop - 1); // 修正插值 float x = start.x + (end.x - start.x) * t; float y = start.y + (end.y - start.y) * t; float z = start.z + (end.z - start.z) * t; PathNode j = PathNode.GenerateElement("PathNode" + i.ToString(), Guid.NewGuid(), new List(), true, track, true); j.transformSubmodule.originalPosition = new Vector3(x, y, z); newNodes.Add(j); } } /// /// 主轴方向的螺旋线式 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)) { LogWindow.Log("Please select a Track first!"); return; } Track track = (Track)inspector.connectedGameElement; for (int i = 0; i < loop; i++) { float t = (float)i / loop; float angle = 2 * Mathf.PI * (i % pointsPerTurn) / pointsPerTurn; Vector3 pos = new Vector3(center.x, center.y, center.z); switch (axis.ToLower()) { case "x": pos.x += h * t; pos.y += r * Mathf.Cos(angle); pos.z += r * Mathf.Sin(angle); break; case "y": pos.x += r * Mathf.Cos(angle); pos.y += h * t; pos.z += r * Mathf.Sin(angle); break; case "z": pos.x += r * Mathf.Cos(angle); pos.y += r * Mathf.Sin(angle); pos.z += h * t; break; default: pos.x += r * Mathf.Cos(angle); pos.y += h * t; pos.z += r * Mathf.Sin(angle); break; } PathNode node = PathNode.GenerateElement("SpiralNode" + i.ToString(), Guid.NewGuid(), new List(), true, track, true); node.transformSubmodule.originalPosition = pos; } } /// /// 任意方向的螺旋线式 PathNode(中心点和方向均为Vector3) /// 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)) { LogWindow.Log("Please select a Track first!"); return; } Vector3 direction = dir.normalized; if (direction == Vector3.zero) direction = Vector3.up; // 默认Y轴 Quaternion rot = Quaternion.FromToRotation(Vector3.up, direction); Track track = (Track)inspector.connectedGameElement; for (int i = 0; i < loop; i++) { float t = (float)i / loop; float angle = 2 * Mathf.PI * (i % pointsPerTurn) / pointsPerTurn; Vector3 localPos = new Vector3( r * Mathf.Cos(angle), h * t, r * Mathf.Sin(angle) ); Vector3 pos = center + rot * localPos; PathNode node = PathNode.GenerateElement("SpiralNode" + i.ToString(), Guid.NewGuid(), new List(), true, track, true); node.transformSubmodule.originalPosition = pos; } } #endregion #region PathNode/Track Utilities (路径节点/轨道工具) /// /// 将原有 PathNode 的变换(位置、旋转、缩放)迁移到新生成的最近 PathNode 上 /// public static bool AdjustPathNodesToNearest(Track track, List newNodes, List 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; } /// /// 删除父元素下所有与当前选中元素类型相同的其他元素,最后删除当前选中元素 /// public static void DelSameInParent() { Type type = inspector.connectedGameElement.GetType(); for (int i = inspector.connectedGameElement.parentElement.childElementList.Count - 1; i >= 0; i--) { GameElement element = inspector.connectedGameElement.parentElement.childElementList[i]; if (element.GetType() == type && element != inspector.connectedGameElement) { EditorManager.instance.operationManager.CopyPasteDeleteModule.DeleteElement(element); } } EditorManager.instance.operationManager.CopyPasteDeleteModule.DeleteElement(inspector.connectedGameElement); } /// /// 将选中轨道下所有音符附着到最近的 Trail 上 /// public static void AttachNoteInNearestTrail() { Track track = inspector.connectedGameElement as Track; if (track == null) { LogWindow.Log("Please select a Track first!", Color.red); return; } List noteBases = track.childElementList.OfType().ToList(); List trails = track.GetAllGameElementsFromThis().OfType().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 animationBases = gameElement1.childElementList.OfType().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不刷新 } } } /// /// 调整路径节点的 Z 坐标(可用于多轨道) /// 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()) { 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().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()) { inspector.connectedGameElement = i; AdjustPathnodeZ(OriginZpoint, scale); } } /// /// 将 Hold Note 拆分为具有路径节点的 Track /// 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(CoreServices.TimeProvider.SongTime); 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(CoreServices.TimeProvider.SongTime); Track NewTrack = Track.GenerateElement(hold.elementName + "_SplitTrack", Guid.NewGuid(), new List(), 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(), 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().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(); }); } /// /// 将选中轨道下所有 Hold Note 拆分为具有路径节点的 Track /// 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().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); } } public static void ScalePathNodesXY(float scale, Vector2 scalePoint) { if (inspector.connectedGameElement == null || (inspector.connectedGameElement.GetType() != typeof(Track) && inspector.connectedGameElement.GetType() != typeof(ElementFolder))) { LogWindow.Log("Please select a Folder or Track first!"); return; } foreach (var i in inspector.connectedGameElement.GetAllGameElementsFromThis().OfType()) { Track track = (Track)i; var pathnodes = track.trackPathSubmodule.pathNodeList; foreach (var pathnode in pathnodes) { Vector2 dir = new Vector2(pathnode.transform.position.x, pathnode.transform.position.y) - scalePoint; Vector3 newPos = new Vector3(dir.x * scale + scalePoint.x, dir.y * scale + scalePoint.y, pathnode.transform.position.z); pathnode.transform.position = newPos; pathnode.transformSubmodule.originalPosition = pathnode.transform.localPosition; pathnode.transformSubmodule.Refresh(); pathnode.Refresh(); } } } #endregion #region Note Import/Export (音符导入/导出) /// /// 从正则格式文本导入音符数据 /// public static void SamplerImport(string inputData) { if (!EditorManager.instance.useNotePrefab) { LogWindow.Log("Pleasee nable \"Note Prefab\" in EditorManager", Color.red); return; } // 改进的正则表达式:支持5个字段和负数 Regex dataPattern = new Regex( @"\(\s*([^,]+?)\s*,\s*([^,]+?)\s*,\s*([-+]?\d*\.?\d+)\s*,\s*([-+]?\d*\.?\d+)\s*(?:,\s*([-+]?\d*\.?\d+)\s*)?\)", RegexOptions.Compiled ); Debug.Log("===== ====="); MatchCollection matches = dataPattern.Matches(inputData); Debug.Log($": {matches.Count}"); int recordCount = 1; Track findTrack(string Findtext) { List tracks = EditorManager.instance.beatmapContainer.gameElementList.OfType().Where(i => i.elementName == Findtext).ToList(); if (tracks.Count == 0) { Debug.LogError($"未找到名为 {Findtext} 的轨道"); return null; } else if (tracks.Count > 1) { LogWindow.Log($"Repeat Track Of {Findtext}, please Cautious", Color.yellow); } return tracks[0]; } foreach (Match match in matches) { if (match.Success) { string action = match.Groups[1].Value.Trim(); string id = match.Groups[2].Value.Trim(); // 解析公共字段 float timestamp = float.Parse(match.Groups[3].Value); float value = float.Parse(match.Groups[4].Value); // 处理Hold操作的特殊字段 float holdDuration = 0f; bool isHold = false; if (action == "Hold" && match.Groups[5].Success) { isHold = true; holdDuration = float.Parse(match.Groups[5].Value); } // 构建输出信息 string logEntry = $"[记录#{recordCount++}] " + $"Note: {action.PadRight(5)} | " + $"ID: {id} | " + $"时间: {timestamp.ToString("0.000").PadLeft(7)} | " + $"X值: {value.ToString("0.000").PadLeft(7)}"; if (isHold) { logEntry += $" | 持续时间: {holdDuration.ToString("0.000")}"; } Debug.Log(logEntry); if (findTrack(id) is null) { Debug.LogError($"未找到名为 {id} 的轨道"); continue; } // 根据动作类型处理 switch (action) { case "Tap": Tap a = Tap.GenerateElement("New Tap", Guid.NewGuid(), new List(), true, findTrack(id), timestamp); ((TransformSubmodule)a.noteVisual.submoduleList.Where(i => i is TransformSubmodule)?.First()).originalPosition = new Vector3(value, 0, 0); a.noteVisual.SetEditorSubmodules(); // 设置selset a.Refresh(); break; case "Stay": Stay b = Stay.GenerateElement("New Stay", Guid.NewGuid(), new List(), true, findTrack(id), timestamp); ((TransformSubmodule)b.noteVisual.submoduleList.Where(i => i is TransformSubmodule)?.First()).originalPosition = new Vector3(value, 0, 0); b.noteVisual.SetEditorSubmodules(); // 设置selset b.Refresh(); break; case "Hold": Hold c = Hold.GenerateElement("New Hold", Guid.NewGuid(), new List(), true, findTrack(id), timestamp, timestamp + holdDuration); ((TransformSubmodule)c.noteVisual.submoduleList.Where(i => i is TransformSubmodule)?.First()).originalPosition = new Vector3(value, 0, 0); c.noteVisual.SetEditorSubmodules(); // 设置selset c.Refresh(); break; case "Flick": Flick d = Flick.GenerateElement("New Flick", Guid.NewGuid(), new List(), true, findTrack(id), timestamp, new List()); ((TransformSubmodule)d.noteVisual.submoduleList.Where(i => i is TransformSubmodule)?.First()).originalPosition = new Vector3(value, 0, 0); d.noteVisual.SetEditorSubmodules(); // 设置selset d.Refresh(); break; default: Debug.LogError($"未知类型: {action}"); break; } } } Debug.Log("===== ====="); } /// /// 导出选中轨道下所有音符为正则格式文本(复制到剪贴板) /// 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().OrderBy(n => n.exactJudgeTime).ToList(); List lines = new List(); 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 (高亮/视觉效果) /// /// 在游戏内设置时间相同的音符高亮显示 /// public static void SetNoteHLInGame(bool forceSetOff = false, bool SameTheme = false) { var noteBases = EditorManager.instance.beatmapContainer.gameElementList.OfType().ToList(); // 先全部关闭高亮(如果forceSetOff为true) if (forceSetOff) { foreach (var note in noteBases) { if (note.noteVisual != null) { note.noteVisual.isHighlighted = false; try { note.noteVisual?.SetHighlight(); } catch (Exception ex) { Debug.LogError($"Error setting highlight for note {note.name}: {ex.Message}"); } } } } // 按时间分组 var groups = SameTheme ? noteBases.GroupBy(n => (object)new { n.exactJudgeTime, Type = n.GetType() }) : noteBases.GroupBy(n => (object)n.exactJudgeTime); foreach (var group in groups) { if (group.Count() > 1) { foreach (var note in group) { if (note.noteVisual != null) { note.noteVisual.isHighlighted = true; try { note.noteVisual?.SetHighlight(); } catch (Exception ex) { Debug.LogError($"Error setting highlight for note {note.name}: {ex.Message}"); } } } } } } /// /// 在选中元素下设置时间相同的音符高亮显示 /// public static void SetNoteHLInElement(bool forceSetOff = false, bool SameTheme = false) { var noteBases = inspector.connectedGameElement.GetAllGameElementsFromThis().OfType().ToList(); // 先全部关闭高亮(如果forceSetOff为true) if (forceSetOff) { foreach (var note in noteBases) { if (note.noteVisual != null) { note.noteVisual.isHighlighted = false; try { note.noteVisual?.SetHighlight(); } catch (Exception ex) { Debug.LogError($"Error setting highlight for note {note.name}: {ex.Message}"); } } } } // 按时间分组 var groups = SameTheme ? noteBases.GroupBy(n => (object)new { n.exactJudgeTime, Type = n.GetType() }) : noteBases.GroupBy(n => (object)n.exactJudgeTime); foreach (var group in groups) { if (group.Count() > 1) { foreach (var note in group) { if (note.noteVisual != null) { note.noteVisual.isHighlighted = true; try { note.noteVisual?.SetHighlight(); } catch (Exception ex) { Debug.LogError($"Error setting highlight for note {note.name}: {ex.Message}"); } } } } } } public static void NoteScale(float scale) { var noteBases = inspector.connectedGameElement.GetAllGameElementsFromThis().OfType().ToList(); foreach (var note in noteBases) { if (note.noteVisual != null) { if (note.noteVisual.submoduleList.FirstOrDefault(i => i is TransformSubmodule) is TransformSubmodule ts) { ts.originalScale = ts.originalScale * scale; ts.Refresh(); } note.noteVisual.Refresh(); } } } #endregion #region Animation (动画值) /// /// 交换 Displacement 动画值的正负号 /// public static void swapDisplacement() { Displacement displacement = inspector.connectedGameElement as Displacement; if (displacement == null) { LogWindow.Log("Please select a Displacement first!", Color.red); return; } foreach (var anim in displacement.positionX.animations) { anim.endValue = -anim.endValue; anim.startValue = -anim.startValue; } foreach (var anim in displacement.positionY.animations) { anim.endValue = -anim.endValue; anim.startValue = -anim.startValue; } foreach (var anim in displacement.positionZ.animations) { anim.endValue = -anim.endValue; anim.startValue = -anim.startValue; } } /// /// 交换 Swirl 动画值的正负号 /// public static void swapSwirl() { Swirl swirl = inspector.connectedGameElement as Swirl; if (swirl == null) { LogWindow.Log("Please select a Swirl first!", Color.red); return; } foreach (var anim in swirl.eulerAngleX.animations) { anim.endValue = -anim.endValue; anim.startValue = -anim.startValue; } foreach (var anim in swirl.eulerAngleY.animations) { anim.endValue = -anim.endValue; anim.startValue = -anim.startValue; } foreach (var anim in swirl.eulerAngleZ.animations) { anim.endValue = -anim.endValue; anim.startValue = -anim.startValue; } } /// /// 交换 Scale 动画值的正负号 /// public static void swapScale() { Scale scale = inspector.connectedGameElement as Scale; if (scale == null) { LogWindow.Log("Please select a Scale first!", Color.red); return; } foreach (var anim in scale.scaleX.animations) { anim.endValue = -anim.endValue; anim.startValue = -anim.startValue; } foreach (var anim in scale.scaleY.animations) { anim.endValue = -anim.endValue; anim.startValue = -anim.startValue; } foreach (var anim in scale.scaleZ.animations) { anim.endValue = -anim.endValue; anim.startValue = -anim.startValue; } } // public static void randomAnimationValue(float range) // { // AnimationBase animationBase = inspector.connectedGameElement as AnimationBase; // if (animationBase == null) // { // LogWindow.Log("Please select a AnimationBase first!", Color.red); // return; // } // System.Random random = new System.Random(); // foreach (var property in animationBase.GetType().GetProperties()) // { // if (property.PropertyType == typeof(FlexibleFloat)) // { // FlexibleFloat ff = property.GetValue(animationBase) as FlexibleFloat; // if (ff != null) // { // foreach (var anim in ff.animations) // { // float randomOffset = (float)(random.NextDouble() * 2 - 1) * range; // anim.endValue += randomOffset; // } // } // } // } // } #endregion #region Global Utilities (全局工具) /// /// 确保所有动画的起始时间为 0 时有关键帧 /// public static void FloorAnim() { if (inspector.connectedGameElement == null) { LogWindow.Log("Please select a Element first!"); return; } List elements = inspector.connectedGameElement.GetAllGameElementsFromThis().OfType().ToList(); // 预先缓存属性信息(如果在循环外部知道具体类型) var propertiesToCheck = typeof(GameElement).GetProperties() .Where(p => p.PropertyType == typeof(FlexibleFloat)) .ToArray(); foreach (var element in elements) { bool needsRefresh = false; foreach (var prop in propertiesToCheck) { var ff = prop.GetValue(element) as FlexibleFloat; if (ff?.animations?.Count > 0 && ff.animations[0] != null) { var firstAnimation = ff.animations[0]; if (firstAnimation.startTime > 0) { ff.animations.Insert(0, new AnimatedFloat( 0, Math.Min(firstAnimation.startTime, 1), firstAnimation.startValue, firstAnimation.startValue, AnimationCurveType.Linear )); needsRefresh = true; Debug.Log($"Added 0 keyframe to {element.elementName}'s {prop.Name}"); } } } if (needsRefresh) { element.Refresh(); element.animatedObject?.Refresh(); // 使用空条件运算符 } } } /// /// 全局刷新所有元素和轨道路径 /// public static void Rebuild() { foreach (GameElement element in EditorManager.instance.beatmapContainer.gameElementList) { foreach (GameElement e in element.GetAllGameElementsFromThis()) { if (e != null) e.Refresh(); } foreach (Track track in element.GetAllGameElementsFromThis().OfType()) { track?.trackPathSubmodule.path.RebuildImmediate(true, true); } } #if UNITY_EDITOR UnityEditorInternal.InternalEditorUtility.RepaintAllViews(); #endif } public static void TrybakeAllMesh() { EditorManager.instance.beatmapContainer.gameElementList.OfType().ToList().ForEach( track => { if (track.trackTimeSubmodule is TrackTimeSubmoduleMovable || track.trackRendererSubmodule is TrackRendererSubmoduleAutoOrient || track.childElementList.OfType().Count() > 0 || (track.trackPathSubmodule.pathNodeList.Any(o => o.childElementList.OfType().Count() == 0) == false)) { Debug.LogWarning("TrackRendererSubmodule.getMeshFromGenerator: Track has animation, cannot get mesh."); } else { track.Refresh(); track.trackRendererSubmodule?.meshGenerator.Bake(true, false); track.Refresh(); } } ); } /// /// 添加 Tag Matcher 到 TagManager /// public static void AddTagMatcher(string name, string parameterName) { var tagManager = EditorManager.instance.projectInformation.tagManager; IBaseElement element = inspector.connectedGameElement; tagManager.AddTagMatcher(name, element.GetType(), parameterName); } /// /// 查找元素中匹配给定值的参数名称 /// public static void FindParameterName(object value) { // 假设 inspector.connectedGameElement 存在且是你希望搜索的对象 var element = inspector.connectedGameElement; 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 == 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 string FindParameterName(IBaseElement element, object value) { // 假设 inspector.connectedGameElement 存在且是你希望搜索的对象 if (element == null || value == null) return ""; // 1. 获取目标类型 Type targetType = value.GetType(); // 如果是 Nullable,获取其基础类型 if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>)) { targetType = Nullable.GetUnderlyingType(targetType); } // 2. 遍历 GameElement 的所有字段 foreach (var field in element.GetType().GetFields(DeepMatchFlags)) { // 检查字段类型是否匹配 if (field.FieldType == targetType) { // 获取字段的值 (作为 object) object fieldValue = field.GetValue(element); if (IsValueMatch(fieldValue, value, field.FieldType)) { Debug.Log($"Found parameter: {field.Name} (Type: {targetType.Name})"); // 假设 LogWindow 可用 return field.Name; } } } return ""; } /// /// 同步标签元素 /// public static void SyncTagedElement() { var q = inspector.connectedGameElement; 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; // 定义浮点数容差 /// /// 比较两个值是否匹配,对浮点数使用容差比较 /// 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 } }