2025-01-26 21:10:16 -05:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections;
|
|
|
|
|
|
using System.Collections.Generic;
|
2025-02-09 23:47:42 -05:00
|
|
|
|
using Ichni.Editor;
|
2025-01-26 21:10:16 -05:00
|
|
|
|
using Ichni.RhythmGame;
|
2026-03-14 02:30:26 -04:00
|
|
|
|
using SLSUtilities.General;
|
2025-06-29 21:28:49 +08:00
|
|
|
|
using TMPro;
|
2025-01-26 21:10:16 -05:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Ichni
|
|
|
|
|
|
{
|
2026-03-14 02:30:26 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 编辑器全局管理器。
|
|
|
|
|
|
/// 继承自 Singleton<EditorManager>,只持有编辑器基础设施引用:
|
|
|
|
|
|
/// 子管理器、音频播放器、UI、相机、设置、首选项等。
|
|
|
|
|
|
/// 游戏/谱面数据由 ProjectContainer 持有;
|
|
|
|
|
|
/// 此处的属性均为转发属性,保持所有外部调用点零改动。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class EditorManager : Singleton<EditorManager>
|
2025-01-26 21:10:16 -05:00
|
|
|
|
{
|
2026-03-14 02:30:26 -04:00
|
|
|
|
#region [单例别名] Singleton Alias
|
|
|
|
|
|
/// <summary>小写别名,兼容现有调用点</summary>
|
|
|
|
|
|
public new static EditorManager instance => Instance;
|
|
|
|
|
|
#endregion
|
2025-02-28 20:08:00 +08:00
|
|
|
|
|
2026-03-14 02:30:26 -04:00
|
|
|
|
#region [加载状态] Load State
|
2025-03-01 21:26:16 -05:00
|
|
|
|
public bool isLoaded;
|
2026-03-14 02:30:26 -04:00
|
|
|
|
#endregion
|
2025-03-01 21:26:16 -05:00
|
|
|
|
|
2026-03-14 02:30:26 -04:00
|
|
|
|
#region [编辑器基础设施管理器] Editor Infrastructure Managers
|
2025-02-08 23:09:50 -05:00
|
|
|
|
public ProjectManager projectManager;
|
2025-05-21 02:23:25 -04:00
|
|
|
|
public AudioManager audioManager;
|
2025-02-26 00:52:08 -05:00
|
|
|
|
public MusicPlayer musicPlayer;
|
2025-02-09 23:47:42 -05:00
|
|
|
|
public EditorUIManager uiManager;
|
2025-02-08 23:09:50 -05:00
|
|
|
|
public EditorSettings editorSettings;
|
2025-02-19 19:01:21 -05:00
|
|
|
|
public OperationManager operationManager;
|
2025-02-17 14:46:14 -05:00
|
|
|
|
public BackgroundController backgroundController;
|
2026-01-18 13:11:38 +08:00
|
|
|
|
public SimpleGridController gridController;
|
2025-02-18 10:30:11 -05:00
|
|
|
|
public CameraManager cameraManager;
|
2025-10-03 06:46:05 -04:00
|
|
|
|
public NoteManager noteManager;
|
2026-03-14 02:30:26 -04:00
|
|
|
|
public TrackManager trackManager;
|
|
|
|
|
|
public AnimationManager animationManager;
|
2025-03-11 17:28:49 -04:00
|
|
|
|
public Canvas judgeHintCanvas;
|
2025-05-02 22:34:42 +08:00
|
|
|
|
public Canvas inspectorCanvas;
|
2025-02-21 15:30:14 +08:00
|
|
|
|
public Timeline timeline;
|
2026-03-14 02:30:26 -04:00
|
|
|
|
public PanelDrawer panelDrawer;
|
|
|
|
|
|
#endregion
|
2025-02-28 20:08:00 +08:00
|
|
|
|
|
2026-03-14 02:30:26 -04:00
|
|
|
|
#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
|
2025-02-28 20:08:00 +08:00
|
|
|
|
|
2026-03-14 02:30:26 -04:00
|
|
|
|
#region [预制体资产集合] Prefab Asset Collections
|
2025-01-26 21:10:16 -05:00
|
|
|
|
public BasePrefabsCollection basePrefabs;
|
2025-06-28 03:01:03 -04:00
|
|
|
|
public Dictionary<string, CustomPrefabsCollection> customPrefabs;
|
2025-05-03 01:08:29 -04:00
|
|
|
|
public NoteAudioCollection noteAudioCollection;
|
2026-03-14 02:30:26 -04:00
|
|
|
|
#endregion
|
2025-05-02 22:34:42 +08:00
|
|
|
|
|
2026-03-14 02:30:26 -04:00
|
|
|
|
#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
|
2025-02-02 08:34:54 -05:00
|
|
|
|
|
2026-03-14 02:30:26 -04:00
|
|
|
|
#region [生命周期] Lifecycle
|
|
|
|
|
|
protected override void Awake()
|
2025-01-26 21:10:16 -05:00
|
|
|
|
{
|
2026-03-14 02:30:26 -04:00
|
|
|
|
base.Awake(); // Singleton<T>.Initialize(false)
|
2025-03-01 21:26:16 -05:00
|
|
|
|
isLoaded = false;
|
2025-02-08 23:09:50 -05:00
|
|
|
|
projectManager = new ProjectManager();
|
2025-02-19 19:01:21 -05:00
|
|
|
|
operationManager = new OperationManager();
|
2025-10-03 06:30:22 -04:00
|
|
|
|
|
2026-03-14 02:30:26 -04:00
|
|
|
|
// 注册时间提供者:让编辑器的所有时间相关逻辑通过 CoreServices.TimeProvider 访问,
|
|
|
|
|
|
// 不再直接依赖 EditorManager.instance.musicPlayer
|
|
|
|
|
|
CoreServices.TimeProvider = musicPlayer;
|
|
|
|
|
|
|
2025-02-16 11:15:42 -05:00
|
|
|
|
if (!ES3.FileExists(Application.streamingAssetsPath + "/EditorSettings.es3"))
|
|
|
|
|
|
{
|
2025-06-08 13:04:13 -04:00
|
|
|
|
editorSettings = new EditorSettings(300, 3, 100, 100, 60);
|
2025-02-16 11:15:42 -05:00
|
|
|
|
EditorSettings.SaveSettings(editorSettings);
|
2025-06-08 13:04:13 -04:00
|
|
|
|
Application.targetFrameRate = editorSettings.frameRate;
|
2025-02-16 11:15:42 -05:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
EditorSettings.LoadSettings(ref editorSettings);
|
2025-06-08 13:04:13 -04:00
|
|
|
|
Application.targetFrameRate = editorSettings.frameRate;
|
2025-02-16 11:15:42 -05:00
|
|
|
|
}
|
2025-01-26 21:10:16 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void Start()
|
|
|
|
|
|
{
|
2025-06-29 21:28:49 +08:00
|
|
|
|
StartCoroutine(StartFrameRate());
|
2026-03-14 02:30:26 -04:00
|
|
|
|
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);
|
2025-06-29 21:28:49 +08:00
|
|
|
|
|
2025-03-08 14:21:10 -05:00
|
|
|
|
if (InformationTransistor.instance.isLoadedProject)
|
|
|
|
|
|
{
|
2025-03-08 18:19:32 -05:00
|
|
|
|
LoadProject(InformationTransistor.instance.loadedProjectName);
|
2026-03-14 02:30:26 -04:00
|
|
|
|
Debug.Log("Loaded");
|
2025-03-08 14:21:10 -05:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2026-03-14 02:30:26 -04:00
|
|
|
|
projectManager.GenerateEmptyProject(
|
|
|
|
|
|
InformationTransistor.instance.projectInfo_BM,
|
|
|
|
|
|
InformationTransistor.instance.songInfo_BM);
|
2025-03-08 14:21:10 -05:00
|
|
|
|
projectManager.saveManager.Save();
|
2025-06-21 09:03:45 -04:00
|
|
|
|
musicPlayer.audioSource.clip = songInformation.song;
|
2026-03-14 02:30:26 -04:00
|
|
|
|
Debug.Log("Generated");
|
2025-03-08 14:21:10 -05:00
|
|
|
|
}
|
2025-05-02 22:34:42 +08:00
|
|
|
|
|
2025-07-26 19:03:38 +08:00
|
|
|
|
StartCoroutine(beatmapContainer.AfterLoadSet());
|
2025-03-08 14:21:10 -05:00
|
|
|
|
isLoaded = true;
|
2025-07-19 13:52:42 +08:00
|
|
|
|
songInformation.songTime = musicPlayer.audioSource.time - songInformation.offset;
|
2026-03-14 02:30:26 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-21 02:18:50 -04:00
|
|
|
|
if(element is IHaveDirtyMarkSubmodule dirtyHost)
|
|
|
|
|
|
{
|
|
|
|
|
|
dirtyHost.dirtyMarkSubmodule?.ExecuteDeferredRefresh();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-14 02:30:26 -04:00
|
|
|
|
if (element.gameObject.activeSelf)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (element is IHaveTransformSubmodule transformHost)
|
|
|
|
|
|
{
|
|
|
|
|
|
transformHost.UpdateTransform();
|
|
|
|
|
|
}
|
|
|
|
|
|
if (element is IHaveColorSubmodule colorHost)
|
|
|
|
|
|
{
|
|
|
|
|
|
colorHost.UpdateColor();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-07-22 15:38:48 +08:00
|
|
|
|
|
2026-03-14 02:30:26 -04:00
|
|
|
|
trackManager.ManualTick(songTime);
|
|
|
|
|
|
noteManager.ManualTick(songTime);
|
2025-03-01 21:26:16 -05:00
|
|
|
|
}
|
2026-03-14 02:30:26 -04:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region [FPS 监控] FPS Monitor
|
2025-06-29 21:28:49 +08:00
|
|
|
|
public float CurrentFrameRate;
|
|
|
|
|
|
public TMP_Text FPStext;
|
2025-10-01 20:41:06 +08:00
|
|
|
|
public TMP_Text UIText;
|
2026-03-14 02:30:26 -04:00
|
|
|
|
|
2025-06-29 21:28:49 +08:00
|
|
|
|
private IEnumerator StartFrameRate()
|
|
|
|
|
|
{
|
|
|
|
|
|
int frameCount = 0;
|
|
|
|
|
|
while (true)
|
|
|
|
|
|
{
|
|
|
|
|
|
CurrentFrameRate = 1f / Time.deltaTime;
|
|
|
|
|
|
if (frameCount == 2)
|
|
|
|
|
|
{
|
|
|
|
|
|
frameCount = 0;
|
2025-06-30 16:37:34 +08:00
|
|
|
|
FPStext.text = string.Format("FPS: {0:N2}", CurrentFrameRate);
|
2025-06-29 21:28:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
frameCount++;
|
|
|
|
|
|
yield return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-14 02:30:26 -04:00
|
|
|
|
#endregion
|
2025-10-01 20:41:06 +08:00
|
|
|
|
|
2026-03-14 02:30:26 -04:00
|
|
|
|
#region [项目加载] Project Loading
|
2025-03-08 14:21:10 -05:00
|
|
|
|
public void LoadProject(string projectName)
|
2025-02-28 21:12:20 +08:00
|
|
|
|
{
|
2025-08-10 16:00:46 +08:00
|
|
|
|
if (!InformationTransistor.instance.isRecovery)
|
|
|
|
|
|
{
|
|
|
|
|
|
projectManager.loadManager.Load(projectName);
|
2025-08-19 08:13:47 -04:00
|
|
|
|
Debug.Log("Loaded");
|
2025-08-10 16:00:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
projectManager.loadManager.LoadExport(projectName);
|
|
|
|
|
|
}
|
2025-10-01 20:41:06 +08:00
|
|
|
|
|
2025-02-26 00:52:08 -05:00
|
|
|
|
musicPlayer.audioSource.clip = songInformation.song;
|
2025-02-08 23:09:50 -05:00
|
|
|
|
beatmapContainer.gameElementList.ForEach(gameElement =>
|
2025-02-07 10:49:26 -05:00
|
|
|
|
{
|
2025-02-08 23:09:50 -05:00
|
|
|
|
gameElement.AfterInitialize();
|
|
|
|
|
|
gameElement.Refresh();
|
2025-02-07 10:49:26 -05:00
|
|
|
|
});
|
2025-02-17 14:46:14 -05:00
|
|
|
|
}
|
2026-03-14 02:30:26 -04:00
|
|
|
|
#endregion
|
2025-03-08 14:21:10 -05:00
|
|
|
|
|
2026-03-14 02:30:26 -04:00
|
|
|
|
#region [退出处理] Application Quit
|
2025-07-15 14:06:56 +08:00
|
|
|
|
private bool isQuit = false;
|
2026-03-14 02:30:26 -04:00
|
|
|
|
|
2025-07-15 14:06:56 +08:00
|
|
|
|
[Obsolete]
|
|
|
|
|
|
public void OnApplicationQuit()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (isQuit) return;
|
|
|
|
|
|
|
2026-03-14 02:30:26 -04:00
|
|
|
|
Application.CancelQuit(); // 退出拦截
|
2025-07-15 14:06:56 +08:00
|
|
|
|
GeneralSecondaryWindow QuitWindow =
|
2026-03-14 02:30:26 -04:00
|
|
|
|
Instantiate(instance.basePrefabs.generalSecondaryWindow,
|
|
|
|
|
|
instance.uiManager.mainPage.mainCanvas.GetComponent<RectTransform>())
|
|
|
|
|
|
.GetComponent<GeneralSecondaryWindow>();
|
2025-07-15 14:06:56 +08:00
|
|
|
|
|
2025-10-05 11:45:32 +08:00
|
|
|
|
QuitWindow.Initialize("Do You Want To Save?");
|
2025-07-15 14:06:56 +08:00
|
|
|
|
|
|
|
|
|
|
var container = QuitWindow.GenerateContainer("Save confirm");
|
|
|
|
|
|
var beatmapToolsSettings = container.GenerateSubcontainer(3);
|
2026-03-14 02:30:26 -04:00
|
|
|
|
QuitWindow.GenerateButton(beatmapToolsSettings, "Yes", () => SaveAndQuit());
|
|
|
|
|
|
QuitWindow.GenerateButton(beatmapToolsSettings, "No", () =>
|
2025-07-15 14:06:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
isQuit = true;
|
|
|
|
|
|
Application.Quit();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-03-14 02:30:26 -04:00
|
|
|
|
|
2025-10-05 11:45:32 +08:00
|
|
|
|
async void SaveAndQuit()
|
|
|
|
|
|
{
|
|
|
|
|
|
isQuit = true;
|
|
|
|
|
|
LogWindow.Log("Start Saving...", Color.yellow);
|
|
|
|
|
|
await projectManager.saveManager.SaveAllCoroutine();
|
|
|
|
|
|
Application.Quit();
|
|
|
|
|
|
}
|
2026-03-14 02:30:26 -04:00
|
|
|
|
#endregion
|
2025-01-26 21:10:16 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|