using System; using System.Collections; using System.Collections.Generic; using Ichni.Editor; using Ichni.RhythmGame; using SLSUtilities.General; using TMPro; using UnityEngine; namespace Ichni { /// /// 编辑器全局管理器。 /// 继承自 Singleton,只持有编辑器基础设施引用: /// 子管理器、音频播放器、UI、相机、设置、首选项等。 /// 游戏/谱面数据由 ProjectContainer 持有; /// 此处的属性均为转发属性,保持所有外部调用点零改动。 /// public class EditorManager : Singleton { #region [单例别名] Singleton Alias /// 小写别名,兼容现有调用点 public new static EditorManager instance => Instance; #endregion #region [加载状态] Load State public bool isLoaded; #endregion #region [编辑器基础设施管理器] Editor Infrastructure Managers public ProjectManager projectManager; public AudioManager audioManager; public MusicPlayer musicPlayer; public EditorUIManager uiManager; public EditorSettings editorSettings; public OperationManager operationManager; public BackgroundController backgroundController; public SimpleGridController gridController; public CameraManager cameraManager; public NoteManager noteManager; public TrackManager trackManager; public AnimationManager animationManager; public Canvas judgeHintCanvas; public Canvas inspectorCanvas; public Timeline timeline; public PanelDrawer panelDrawer; #endregion #region [编辑器首选项转发属性] Editor Preferences Forwarding Properties // 实际字段在 ProjectContainer 中定义,此处为转发属性以保持所有调用点不变 public NoteBase.NoteJudgeType currentJudgeType { get => ProjectContainer.instance.currentJudgeType; set => ProjectContainer.instance.currentJudgeType = value; } public bool useClickSelect { get => ProjectContainer.instance.useClickSelect; set => ProjectContainer.instance.useClickSelect = value; } public bool useNotePrefab { get => ProjectContainer.instance.useNotePrefab; set => ProjectContainer.instance.useNotePrefab = value; } public bool ExpandWhileClick { get => ProjectContainer.instance.ExpandWhileClick; set => ProjectContainer.instance.ExpandWhileClick = value; } public bool useQuickMove { get => ProjectContainer.instance.useQuickMove; set => ProjectContainer.instance.useQuickMove = value; } #endregion #region [预制体资产集合] Prefab Asset Collections public BasePrefabsCollection basePrefabs; public Dictionary customPrefabs; public NoteAudioCollection noteAudioCollection; #endregion #region [ProjectContainer 转发属性] ProjectContainer Forwarding Properties // 以下属性保持与原 EditorManager 完全相同的访问路径, // 内部转发至 ProjectContainer,无需修改任何调用点。 public ProjectInformation projectInformation { get => ProjectContainer.instance.projectInformation; set => ProjectContainer.instance.projectInformation = value; } public SongInformation songInformation { get => ProjectContainer.instance.songInformation; set => ProjectContainer.instance.songInformation = value; } public BeatmapContainer beatmapContainer { get => ProjectContainer.instance.beatmapContainer; set => ProjectContainer.instance.beatmapContainer = value; } public CommandScripts commandScripts { get => ProjectContainer.instance.commandScripts; set => ProjectContainer.instance.commandScripts = value; } public VariablesContainer variablesContainer { get => ProjectContainer.instance.variablesContainer; set => ProjectContainer.instance.variablesContainer = value; } public BackgroundSetter backgroundSetter { get => ProjectContainer.instance.backgroundSetter; set => ProjectContainer.instance.backgroundSetter = value; } #endregion #region [生命周期] Lifecycle protected override void Awake() { base.Awake(); // Singleton.Initialize(false) isLoaded = false; projectManager = new ProjectManager(); operationManager = new OperationManager(); // 注册时间提供者:让编辑器的所有时间相关逻辑通过 CoreServices.TimeProvider 访问, // 不再直接依赖 EditorManager.instance.musicPlayer CoreServices.TimeProvider = musicPlayer; if (!ES3.FileExists(Application.streamingAssetsPath + "/EditorSettings.es3")) { editorSettings = new EditorSettings(300, 3, 100, 100, 60); EditorSettings.SaveSettings(editorSettings); Application.targetFrameRate = editorSettings.frameRate; } else { EditorSettings.LoadSettings(ref editorSettings); Application.targetFrameRate = editorSettings.frameRate; } } private void Start() { StartCoroutine(StartFrameRate()); Debug.Log("EditorManager Start: Initializing UI and Loading Project..."); // ProjectContainer 自身作为根节点注册到层级视图 ProjectContainer.instance.elementName = "EditorManager"; ProjectContainer.instance.elementGuid = Guid.Empty; uiManager.hierarchy.GenerateTab(ProjectContainer.instance, null); ProjectContainer.instance.connectedTab.deleteButton.gameObject.SetActive(false); if (InformationTransistor.instance.isLoadedProject) { LoadProject(InformationTransistor.instance.loadedProjectName); Debug.Log("Loaded"); } else { projectManager.GenerateEmptyProject( InformationTransistor.instance.projectInfo_BM, InformationTransistor.instance.songInfo_BM); projectManager.saveManager.Save(); musicPlayer.audioSource.clip = songInformation.song; Debug.Log("Generated"); } StartCoroutine(beatmapContainer.AfterLoadSet()); isLoaded = true; songInformation.songTime = musicPlayer.audioSource.time - songInformation.offset; } private void Update() { if (!isLoaded) return; projectManager.autoSaveManager.UpdateAutoSave(); // 统一调度: Animation → Submodules → Track → Note float songTime = CoreServices.TimeProvider.SongTime; animationManager.ManualTick(songTime); // 手动执行原本属于 UniRx 的每帧调度,消灭不可控的时序错乱 for (int i = 0; i < beatmapContainer.gameElementList.Count; i++) { var element = beatmapContainer.gameElementList[i]; if (element == null) continue; if (element is IHaveTimeDurationSubmodule timeHost && !(element is NoteBase)) { timeHost.timeDurationSubmodule?.UpdateTimeDuration(songTime); } if (element.gameObject.activeSelf) { if (element is IHaveTransformSubmodule transformHost) { transformHost.UpdateTransform(); } if (element is IHaveColorSubmodule colorHost) { colorHost.UpdateColor(); } } } trackManager.ManualTick(songTime); noteManager.ManualTick(songTime); } #endregion #region [FPS 监控] FPS Monitor public float CurrentFrameRate; public TMP_Text FPStext; public TMP_Text UIText; private IEnumerator StartFrameRate() { int frameCount = 0; while (true) { CurrentFrameRate = 1f / Time.deltaTime; if (frameCount == 2) { frameCount = 0; FPStext.text = string.Format("FPS: {0:N2}", CurrentFrameRate); } frameCount++; yield return null; } } #endregion #region [项目加载] Project Loading public void LoadProject(string projectName) { if (!InformationTransistor.instance.isRecovery) { projectManager.loadManager.Load(projectName); Debug.Log("Loaded"); } else { projectManager.loadManager.LoadExport(projectName); } musicPlayer.audioSource.clip = songInformation.song; beatmapContainer.gameElementList.ForEach(gameElement => { gameElement.AfterInitialize(); gameElement.Refresh(); }); } #endregion #region [退出处理] Application Quit private bool isQuit = false; [Obsolete] public void OnApplicationQuit() { if (isQuit) return; Application.CancelQuit(); // 退出拦截 GeneralSecondaryWindow QuitWindow = Instantiate(instance.basePrefabs.generalSecondaryWindow, instance.uiManager.mainPage.mainCanvas.GetComponent()) .GetComponent(); QuitWindow.Initialize("Do You Want To Save?"); var container = QuitWindow.GenerateContainer("Save confirm"); var beatmapToolsSettings = container.GenerateSubcontainer(3); QuitWindow.GenerateButton(beatmapToolsSettings, "Yes", () => SaveAndQuit()); QuitWindow.GenerateButton(beatmapToolsSettings, "No", () => { isQuit = true; Application.Quit(); }); } async void SaveAndQuit() { isQuit = true; LogWindow.Log("Start Saving...", Color.yellow); await projectManager.saveManager.SaveAllCoroutine(); Application.Quit(); } #endregion } }