using Convention; using Convention.WindowsUI; using Convention.WindowsUI.Variant; using Demo.Game; using Dreamteck.Splines; using System; using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.SceneManagement; namespace Demo.Editor { [DefaultExecutionOrder(-9999)] public class EditorController : MonoSingleton { /// /// 必须是-1至0 /// public const float TimelineUVOffset = -0.1f; public override bool IsDontDestroyOnLoad => false; [Resources] public WindowManager ToolsWindow; [Resources] public ModernUIFillBar TotalTimelineBar; [Resources] public Text CurrentTimeText; [Resources] public Text CurrentFPS; [Setting] public const string SceneName = "GameScene"; [Setting] public bool IsLowPerformance = false; [Content] public string LastLoadProjectName = ""; public GameController MainGameController; [Header("Spectrum")] [Resources] public UnityEngine.UI.RawImage SpectrumRawImage; [Resources] public ModernUIFillBar SpectrumSeeline; [Setting] public float SpectrumRenderTP = 0.1f; [Setting] public ModernUIFillBar SongPlaySpeedBar; private float[][] spectrums = null; private float spectrumDataMax; private Color[] blank = null; /// /// TODO: 替换为Dict纹理组,这将用来支持更高的精度 /// private Texture2D SpectrumRenderTexture; private bool IsEnableUpdateSpectrumRenderTexture = false; private Color backgroundColor = new(0, 0, 0, 0); private Color waveformColor = new(1, 1, 1, 1); private Color waveformTpColor = new(1, 0, 0, 1); [Resources, SerializeField] private PropertiesWindow TimelineScriptParentWindow; public static Dictionary GetDefaultScriptableObjectTypen() { Dictionary result = new(); foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { foreach (var type in asm.GetTypes()) { if (typeof(ScriptableObject).IsAssignableFrom(type) && type.IsAbstract == false) { result.Add(type.Name, type); } } } return result; } [Header("BPM")] [Resources] public BPMLine BPMLinePrefab; [Resources] public Transform BPMLineParentTransform; [Content, SerializeField] private List BPMLineEntries = new(); [Content] public int BPMFraction = 4; [Content] public float SongOffset = 0; [Content] public float BPM = 60; private float onebarDeltaTime; private void InjectSetSongCurrentTime(float time) { // 秒,可见长度,以下相同,注意并非百分比 var duration = SpectrumSeeline.value / TotalTimelineBar.maxValue; // 这是负数 var leftClipFrom = SpectrumSeeline.value * TimelineUVOffset; // 这是正数 var rightClipTo = SpectrumSeeline.value * (1 + TimelineUVOffset); // 简单UI变更 void UpdateUI() { TotalTimelineBar.SetValue(time); CurrentTimeText.text = time.ToString(); SpectrumRawImage.uvRect = new(TotalTimelineBar.currentPercent + TimelineUVOffset * duration, 0, duration, 1); // 传递时间切片 UI.TimelineItem.clip = new(leftClipFrom + time, rightClipTo + time); } using (Profiler.BeginZone(nameof(UpdateUI))) UpdateUI(); if (IsLowPerformance) return; if (MainGameController == null || MainGameController.MainAudio == null) return; // 绘制时频 void UpdateSpectrumRenderTexture() { if (spectrums != null && MainGameController.MainAudio.IsPlaying() && MainGameController.MainAudio is Convention.AudioSystem audio) { if (Mathf.Approximately(1, SongPlaySpeedBar.Value) == false) return; // Render Current Data int x = (int)(TotalTimelineBar.currentPercent * SpectrumRenderTexture.width); spectrums[x] = new float[SpectrumRenderTexture.height]; audio.Source.GetSpectrumData(spectrums[x], 0, FFTWindow.Rectangular); var max = Mathf.Max(spectrums[x]); for (int y = 0; y < spectrums[x].Length - 1; y++) { var et = Mathf.Pow(spectrums[x][y] / max, 0.5f); if (SpectrumRenderTP < 1 - et) { SpectrumRenderTexture.SetPixel(x, y, Color.Lerp(backgroundColor, waveformColor, et / (1 - SpectrumRenderTP))); } else { SpectrumRenderTexture.SetPixel(x, y, Color.Lerp(waveformColor, waveformTpColor, (et - SpectrumRenderTP) / (SpectrumRenderTP))); } } // Render Forward Null int ie = x - 1; while (ie > 0 && (spectrums[ie] == null || spectrums[ie].Length == 0)) { ie--; } if (ie != 0 && ie != x - 1) { for (int ix = x; ix > ie; ix--) { for (int y = 0; y < spectrums[x].Length - 1; y++) { SpectrumRenderTexture.SetPixel(ix, y, Color.Lerp(SpectrumRenderTexture.GetPixel(ie, y), SpectrumRenderTexture.GetPixel(x, y), (ix - ie) / (float)(x - ie))); } } } SpectrumRenderTexture.Apply(); SpectrumRawImage.texture = SpectrumRenderTexture; } } if (IsEnableUpdateSpectrumRenderTexture) using (Profiler.BeginZone(nameof(UpdateSpectrumRenderTexture))) UpdateSpectrumRenderTexture(); } private void InjectSongLoadOverCallback(BasicAudioSystem audio) { // Speed SongPlaySpeedBar.OnTransValueChange.AddListener(x => { audio.SetSpeed(x); }); if (IsLowPerformance) return; // BPM BPM = (float)MainGameController.MainConfig.FindItem(nameof(BPM), BPM); onebarDeltaTime = 60.0f / (BPM * BPMFraction); BPMFraction = (int)MainGameController.MainConfig.FindItem(nameof(BPMFraction), BPMFraction); SongOffset = (float)MainGameController.MainConfig.FindItem(nameof(SongOffset), SongOffset); // 绘制时频 var texturePath = (string)MainGameController.MainConfig.FindItem(nameof(SpectrumRenderTexture), null); if (audio is Convention.AudioSystem caudioSystem) { int width = 16384; int height = 64; blank = new Color[width * height]; Texture2D texture = new(width, height); for (int i = 0; i < blank.Length; ++i) { blank[i] = backgroundColor; } texture.SetPixels(blank, 0); texture.Apply(); SpectrumRenderTexture = texture; spectrums = new float[width][]; SpectrumRawImage.texture = texture; } else { IEnumerator Foo() { int width = 4096 * 4; int height = 1024; var _clip = audio.CurrentClip; int resolution = 240; // 这个值可以控制频谱的密度 resolution = _clip.frequency / resolution; float[] samples = new float[_clip.samples * _clip.channels]; _clip.GetData(samples, 0); float[] waveForm = new float[(samples.Length / resolution)]; float min = 0; float max = 0; bool inited = false; for (int i = 0; i < waveForm.Length; i++) { waveForm[i] = 0; for (int j = 0; j < resolution; j++) { waveForm[i] += Mathf.Abs(samples[(i * resolution) + j]); } if (!inited) { min = waveForm[i]; max = waveForm[i]; inited = true; } else { if (waveForm[i] < min) { min = waveForm[i]; } if (waveForm[i] > max) { max = waveForm[i]; } } //waveForm[i] /= resolution; if (i % 100 == 0) yield return null; } spectrumDataMax = max; PriorityQueue tpQueue = new(); var maxQueueSize = SpectrumRenderTP * waveForm.Length; foreach (var value in waveForm) { tpQueue.Enqueue(value); } var thValue = tpQueue.GetTP(SpectrumRenderTP); blank = new Color[width * height]; Texture2D texture = new Texture2D(width, height); for (int i = 0; i < blank.Length; ++i) { blank[i] = backgroundColor; } texture.SetPixels(blank, 0); float xScale = (float)width / (float)waveForm.Length; for (int i = 0; i < waveForm.Length; ++i) { int x = (int)(i * xScale); int yOffset = (int)(waveForm[i] / max * height); int startY = 0; int endY = yOffset; for (int y = startY; y <= endY; ++y) { if (waveForm[i] > thValue) texture.SetPixel(x, y, waveformTpColor); else texture.SetPixel(x, y, waveformColor); } if (i % 100 == 0) yield return null; } texture.Apply(); SpectrumRenderTexture = texture; SpectrumRawImage.texture = texture; } StartCoroutine(Foo()); } if (spectrumDataMax == 0) { var buffer = new float[SpectrumRenderTexture.width]; audio.CurrentClip.GetData(buffer, 0); spectrumDataMax = Mathf.Max(buffer); } } //[Header("PersistentDataPath")] public string PersistentDataPath => PlatformIndicator.PersistentDataPath; public string PersistentProjectPath => Path.Combine(PersistentDataPath, "Projects/"); public string PersistentHelperPath => Path.Combine(PersistentDataPath, "Helper/"); private void OpenGameScene(string ProjectName, bool IsCreateNewProject) { if (GameContent.instance == null) { new GameObject("GameContent").AddComponent(); } var content = GameContent.instance; content.RootSourceDir = Path.Combine(PersistentProjectPath, ProjectName) + "/"; content.IsCreateNewProject = IsCreateNewProject; content.ScriptableObjectTypen = GetDefaultScriptableObjectTypen(); content.IsAutoPlay = true; content.SetupSongDuration = (x, y) => { TotalTimelineBar.minValue = x; TotalTimelineBar.maxValue = y; if (MainGameController != null && MainGameController.MainAudio.CurrentClip != null) MainGameController.ForceScriptUpdate(); }; content.SetSongCurrentTime = InjectSetSongCurrentTime; content.SongLoadOverCallback = InjectSongLoadOverCallback; SceneManager.LoadSceneAsync(SceneName, LoadSceneMode.Additive).completed += x => { LastLoadProjectName = ProjectName; StopRefreshFlag = false; MainGameController = FindFirstObjectByType(); MainGameController.IsMain = true; StartCoroutine(MainGameController.GameInit()); }; } private IEnumerator CloseGameScene() { // InjectSongLoadOverCallback中注册了一个回调 SongPlaySpeedBar.OnTransValueChange.RemoveAllListeners(); // 清理一下图片,这总是合理的 SpectrumRenderTexture = null; // 判断一下有没有 if (MainGameController == null) yield break; yield return MainGameController.GameExit(); } private IEnumerator WaitForSharedModule() { while (SharedModule.instance == null) yield return null; SharedModule.instance.OpenCustomMenu(Architecture.Get().transform as RectTransform, new SharedModule.CallbackData("Open Project", _ => { SharedModule.instance.SingleEditString("Open Project", "", (ProjectName) => { OpenGameScene(ProjectName, false); }); }), new SharedModule.CallbackData("Create New Project", _ => { SharedModule.instance.SingleEditString("Create New Project", "", (ProjectName) => { OpenGameScene(ProjectName, true); }); })); } private void RegisterVariableGenerater() { // Generate Framework var generaters = DefaultInstantiate.GetScriptableObjectInstantiate(); foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { foreach (var type in asm.GetTypes()) { string filename = Convention.RScript.Variable.CStyle.CScriptRScriptVariableGenerater.GetTypename(type); if (Convention.RScript.Variable.RScriptInjectVariableGenerater.AllRScriptInjectVariables.ContainsKey(filename)) continue; if (generaters.TryGetValue(filename, out var generater)) { new Convention.RScript.Variable.CStyle.CScriptRScriptVariableGenerater(type, () => generater(), null, filename).Register(); } else if (typeof(ScriptableObject).IsAssignableFrom(type)) { new Convention.RScript.Variable.CStyle.CScriptRScriptVariableGenerater(type, null, null, filename).Register(); } } } new Convention.RScript.Variable.CStyle.CScriptRScriptVariableGenerater(typeof(MathExtension.EaseCurveType), null, null, Convention.RScript.Variable.CStyle.CScriptRScriptVariableGenerater.GetTypename(typeof(MathExtension.EaseCurveType))).Register(); new Convention.RScript.Variable.CStyle.CScriptRScriptVariableGenerater(typeof(SplineComputer.SampleMode), null, null, Convention.RScript.Variable.CStyle.CScriptRScriptVariableGenerater.GetTypename(typeof(SplineComputer.SampleMode))).Register(); new Convention.RScript.Variable.CStyle.CScriptRScriptVariableGenerater(typeof(Spline.Type), null, null, Convention.RScript.Variable.CStyle.CScriptRScriptVariableGenerater.GetTypename(typeof(Spline.Type))).Register(); } private void Start() { Profiler.AppInfo(Application.productName); GlobalConfig.ConstConfigFile = "config.easysave"; RegisterVariableGenerater(); // Helper Files ToolFile helperHeaderDir = new ToolFile(PersistentHelperPath); helperHeaderDir.MustExistsPath(); foreach (var (name, variable) in Convention.RScript.Variable.RScriptInjectVariableGenerater.AllRScriptInjectVariables) { (helperHeaderDir | name).SaveAsText(variable.scriptIndicator); } // Reset TimelineScriptObject.TimelineWindow = this.TimelineScriptParentWindow; string ProjectName = ""; foreach (var arg in Environment.GetCommandLineArgs()) { if (arg.StartsWith("-p=")) { ProjectName = arg["-p=".Length..]; } else if (arg.StartsWith("-project=")) { ProjectName = arg["-project=".Length..]; } else if (arg == "-nfs") { Screen.fullScreen = false; } else if (arg == "-fs") { Screen.fullScreen = true; } else if (arg.StartsWith("-scale=")) { try { var item = arg["-scale=".Length..].Split(','); int x = int.Parse(item[0]); int y = int.Parse(item[1]); Screen.SetResolution(x, y, false); } catch { Screen.fullScreen = false; } } else if (arg == "-lp") { IsLowPerformance = true; } else if (arg == "-UpdateSpectrumRenderTexture") { IsEnableUpdateSpectrumRenderTexture = true; } } } private bool StopRefreshFlag = true; public void ReloadCurrentProject() { StopRefreshFlag = true; IEnumerator Foo() { try { yield return CloseGameScene(); } finally { OpenGameScene(LastLoadProjectName, false); } } StartCoroutine(Foo()); } public void CloseCurrentProject() { StopRefreshFlag = true; LastLoadProjectName = ""; IEnumerator Foo() { yield return CloseGameScene(); yield return WaitForSharedModule(); } StartCoroutine(Foo()); } #region Performance Monitoring private void MonitorFrameRate() { float fps = 1.0f / Time.unscaledDeltaTime; float frameTime = Time.unscaledDeltaTime * 1000.0f; Profiler.Plot("FPS", fps); Profiler.Plot("Frame Time (ms)", frameTime); Profiler.Plot("Time Scale", Time.timeScale); } private void MonitorMemory() { // GC 内存 long totalMemory = System.GC.GetTotalMemory(false); Profiler.Plot("GC Memory (MB)", totalMemory / (1024.0 * 1024.0)); // Unity Profiler 内存统计 long usedHeap = UnityEngine.Profiling.Profiler.usedHeapSizeLong; long totalAllocated = UnityEngine.Profiling.Profiler.GetTotalAllocatedMemoryLong(); long totalReserved = UnityEngine.Profiling.Profiler.GetTotalReservedMemoryLong(); Profiler.Plot("Used Heap (MB)", usedHeap / (1024.0 * 1024.0)); Profiler.Plot("Total Allocated (MB)", totalAllocated / (1024.0 * 1024.0)); Profiler.Plot("Total Reserved (MB)", totalReserved / (1024.0 * 1024.0)); // 纹理内存 Profiler.Plot("Texture Memory (MB)", UnityEngine.Profiling.Profiler.GetAllocatedMemoryForGraphicsDriver() / (1024.0 * 1024.0)); } #endregion private void Update() { MonitorMemory(); MonitorFrameRate(); using (Profiler.BeginZone("EditorController.Update")) { CurrentFPS.text = $"{1 / Time.smoothDeltaTime}"; if (string.IsNullOrEmpty(LastLoadProjectName)) return; if (MainGameController == null || MainGameController.MainAudio == null || MainGameController.MainAudio.CurrentClip == null) { foreach (var item in BPMLineEntries) { item.gameObject.SetActive(false); } return; } // 秒,可见长度,以下相同,注意并非百分比 var duration = SpectrumSeeline.value / MainGameController.MainAudio.CurrentClip.length; // 这是负数 var leftClipFrom = SpectrumSeeline.value * TimelineUVOffset; // 这是正数 var rightClipTo = SpectrumSeeline.value * (1 + TimelineUVOffset); var time = MainGameController.CurrentTime; int BPMLineEntriesIndex = 0; float farAplha = (1 - SpectrumSeeline.currentPercent) * 0.5f + 0.5f; float foucs = Mathf.CeilToInt((time + leftClipFrom ) / onebarDeltaTime) * onebarDeltaTime; for (float end = time + rightClipTo; foucs + BPMLineEntriesIndex * onebarDeltaTime < end; BPMLineEntriesIndex++) { if (BPMLineEntries.Count <= BPMLineEntriesIndex) { var newLine = Instantiate(BPMLinePrefab, BPMLineParentTransform); BPMLineEntries.Add(newLine); } else { BPMLineEntries[BPMLineEntriesIndex].Setup(foucs + BPMLineEntriesIndex * onebarDeltaTime, time, onebarDeltaTime, farAplha, new Vector2(time + leftClipFrom, time + rightClipTo)); } } for (; BPMLineEntriesIndex < BPMLineEntries.Count; BPMLineEntriesIndex++) { BPMLineEntries[BPMLineEntriesIndex].gameObject.SetActive(false); } if (Keyboard.current[Key.LeftCtrl].isPressed) { if (Keyboard.current[Key.R].wasPressedThisFrame && StopRefreshFlag == false) { ReloadCurrentProject(); } else if (Keyboard.current[Key.Tab].wasPressedThisFrame) { CloseCurrentProject(); } } } } private void LateUpdate() { Profiler.EmitFrameMark(); } // ToolTable public void ToolIOpenProject() { SharedModule.instance.SingleEditString("Open Project", "Project", x => { IEnumerator Foo() { try { yield return CloseGameScene(); } finally { OpenGameScene(x, false); } } StartCoroutine(Foo()); }); } public void ToolCreateProject() { SharedModule.instance.SingleEditString("Create New Project", "New Project", x => { IEnumerator Foo() { try { yield return CloseGameScene(); } finally { OpenGameScene(x, true); } } StartCoroutine(Foo()); }); } [Header("Tools Button")] [Resources] public UnityEngine.UI.Button ToolSongTimeButton; public void ToolCreateSongModule() { if (MainGameController == null) return; if (MainGameController.MainAudio == null) return; if (MainGameController.MainAudio.CurrentClip == null) return; SharedModule.instance.OpenCustomMenu(ToolSongTimeButton.transform as RectTransform, new SharedModule.CallbackData("Play(P)", _ => MainGameController.Play()), new SharedModule.CallbackData("Pause(P)", _ => MainGameController.Pause()), new SharedModule.CallbackData("Stop(S)", _ => MainGameController.Stop()) ); } [Resources] public UnityEngine.UI.Button CameraPositionButton; [Resources] public UnityEngine.UI.Button CameraRotationButton; [Resources] public FreeSceneCamera SceneVirtualCamera; public void ToolCreateInitCameraModule() { if (MainGameController != null) { if (MainGameController.MainAudio.IsPlaying()) return; } SharedModule.instance.OpenCustomMenu(ToolSongTimeButton.transform as RectTransform, new SharedModule.CallbackData("Reset Rotation(LeftShift)", _ => SceneVirtualCamera.transform.rotation = Quaternion.identity), new SharedModule.CallbackData("Reset(LeftShift+Z)", _ => SceneVirtualCamera.transform.SetLocalPositionAndRotation( Vector3.zero, Quaternion.identity))); } } }