Files
ichni_Creator_Studio/Assets/Scripts/Manager/EditorManager.cs
SoulliesOfficial 0fb72f5bba 同步
2026-06-05 06:47:24 -04:00

282 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections;
using System.Collections.Generic;
using Ichni.Editor;
using Ichni.RhythmGame;
using SLSUtilities.General;
using TMPro;
using UnityEngine;
namespace Ichni
{
/// <summary>
/// 编辑器全局管理器。
/// 继承自 Singleton<EditorManager>,只持有编辑器基础设施引用:
/// 子管理器、音频播放器、UI、相机、设置、首选项等。
/// 游戏/谱面数据由 ProjectContainer 持有;
/// 此处的属性均为转发属性,保持所有外部调用点零改动。
/// </summary>
[DefaultExecutionOrder(-100)]
public class EditorManager : Singleton<EditorManager>
{
#region [] Singleton Alias
/// <summary>小写别名,兼容现有调用点</summary>
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 Canvas judgeHintCanvas;
public Canvas inspectorCanvas;
public Timeline timeline;
public PanelDrawer panelDrawer;
/// <summary>集中式元素更新调度器,替代原先分散的帧更新逻辑</summary>
[NonSerialized]
public ElementUpdateScheduler updateScheduler;
/// <summary>
/// NoteManager 转发属性。NoteManager 现为纯 C# 类,由 updateScheduler 内部持有。
/// 保留此属性以兼容现有调用点。
/// </summary>
public NoteManager noteManager => updateScheduler?.NoteScheduler;
#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 useQuickMove
{
get => ProjectContainer.instance.useQuickMove;
set => ProjectContainer.instance.useQuickMove = value;
}
#endregion
#region [] Prefab Asset Collections
public BasePrefabsCollection basePrefabs;
public Dictionary<string, CustomPrefabsCollection> 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<T>.Initialize(false)
isLoaded = false;
projectManager = new ProjectManager();
operationManager = new OperationManager();
// 注册时间提供者:让编辑器的所有时间相关逻辑通过 CoreServices.TimeProvider 访问,
// 不再直接依赖 EditorManager.instance.musicPlayer
CoreServices.TimeProvider = musicPlayer;
// 初始化集中式更新调度器:
// BeatmapContainer 在项目加载后才可用,使用 Func 延迟解析
// NoteManager 在调度器内部创建,不再依赖外部 Singleton
updateScheduler = new ElementUpdateScheduler(
() => beatmapContainer
);
CoreServices.UpdateScheduler = updateScheduler;
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();
float songTime = CoreServices.TimeProvider.SongTime;
updateScheduler.TickEarly(songTime);
}
private void LateUpdate()
{
if (!isLoaded) return;
updateScheduler.TickLate();
}
#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<RectTransform>())
.GetComponent<GeneralSecondaryWindow>();
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
}
}