This commit is contained in:
SoulliesOfficial
2026-03-14 02:30:26 -04:00
parent cf86f0ee51
commit aee62cd637
2041 changed files with 246771 additions and 129128 deletions

View File

@@ -1,24 +1,33 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Dreamteck.Splines.Primitives;
using Ichni.Editor;
using Ichni.RhythmGame;
using Ichni.RhythmGame.Beatmap;
using Ichni.RhythmGame.ThemeBundles.Basic;
using Sirenix.OdinInspector;
using SLSUtilities.General;
using TMPro;
using Unity.VisualScripting;
using UnityEngine;
namespace Ichni
{
public class EditorManager : GameElement
/// <summary>
/// 编辑器全局管理器。
/// 继承自 Singleton<EditorManager>,只持有编辑器基础设施引用:
/// 子管理器、音频播放器、UI、相机、设置、首选项等。
/// 游戏/谱面数据由 ProjectContainer 持有;
/// 此处的属性均为转发属性,保持所有外部调用点零改动。
/// </summary>
public class EditorManager : Singleton<EditorManager>
{
public static EditorManager instance;
#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;
@@ -29,39 +38,96 @@ namespace Ichni
public SimpleGridController gridController;
public CameraManager cameraManager;
public NoteManager noteManager;
public Ichni.Editor.PostProcessingManager postProcessingManager;
public TrackManager trackManager;
public AnimationManager animationManager;
public Canvas judgeHintCanvas;
public Canvas inspectorCanvas;
public Timeline timeline;
public ProjectInformation projectInformation;
public SongInformation songInformation;
public BeatmapContainer beatmapContainer;
public CommandScripts commandScripts;
public PanelDrawer panelDrawer;
public NoteBase.NoteJudgeType currentJudgeType;
public bool useClickSelect;
public bool useNotePrefab;
public bool ExpandWhileClick;
public bool useQuickMove;
#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<string, CustomPrefabsCollection> customPrefabs;
public NoteAudioCollection noteAudioCollection;
#endregion
[Title("Runtime Global Elements")]
public VariablesContainer variablesContainer;
public BackgroundSetter backgroundSetter;
private void Awake()
#region [ProjectContainer ] ProjectContainer Forwarding Properties
// 以下属性保持与原 EditorManager 完全相同的访问路径,
// 内部转发至 ProjectContainer无需修改任何调用点。
public ProjectInformation projectInformation
{
instance = this;
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;
if (!ES3.FileExists(Application.streamingAssetsPath + "/EditorSettings.es3"))
{
editorSettings = new EditorSettings(300, 3, 100, 100, 60);
@@ -78,31 +144,78 @@ namespace Ichni
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);
this.elementName = "EditorManager";
this.elementGuid = Guid.Empty;
uiManager.hierarchy.GenerateTab(this, null);
this.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.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;
@@ -118,13 +231,9 @@ namespace Ichni
yield return null;
}
}
#endregion
private void Update()
{
if (isLoaded) projectManager.autoSaveManager.UpdateAutoSave();
}
#region [] Project Loading
public void LoadProject(string projectName)
{
if (!InformationTransistor.instance.isRecovery)
@@ -144,107 +253,34 @@ namespace Ichni
gameElement.Refresh();
});
}
#endregion
public override void SetUpInspector()
{
IHaveInspection inspector = uiManager.inspector;
var container = inspector.GenerateContainer("Editor Manager");
var inGameSettings = container.GenerateSubcontainer(3);
var judgeTypeDropdown = inspector.GenerateDropdown(this, inGameSettings, "Judge Type",
typeof(NoteBase.NoteJudgeType), nameof(currentJudgeType)).AddListenerFunction(() =>
{
foreach (GameElement gameElement in beatmapContainer.gameElementList)
{
if (gameElement is NoteVisualBase noteVisual)
{
noteVisual.Recover();
}
}
});
var useNotePrefabToggle =
inspector.GenerateToggle(this, inGameSettings, "Use Note Prefab", nameof(useNotePrefab));
var useClickSelectToggle =
inspector.GenerateToggle(this, inGameSettings, "Use Click Select", nameof(useClickSelect));
var ExpandWhileClickToggle =
inspector.GenerateToggle(this, inGameSettings, "Expand Tab While Click", nameof(ExpandWhileClick));
var useQuickMoveToggle =
inspector.GenerateToggle(this, inGameSettings, "Use Quick Move", nameof(useQuickMove));
var generation = container.GenerateSubcontainer(3);
var generateFolderButton =
inspector.GenerateButton(this, generation, "Generate Folder",
() => ElementFolder.GenerateElement("Folder", Guid.NewGuid(),
new List<string>(), true, null));
var generateBackgroundSetterButton =
inspector.GenerateButton(this, generation, "Generate Background Setter",
() => BackgroundSetter.GenerateElement("Background Setter", Guid.NewGuid(),
new List<string>(), true, null, false,
"basic", "Skybox", "Background"));
var generateVariablesContainerButton =
inspector.GenerateButton(this, generation, "Generate Variables Container",
() => VariablesContainer.GenerateElement("Variables Container", Guid.NewGuid(),
new List<string>(), true, null, new Dictionary<string, int>()));
projectInformation.SetUpInspector();
songInformation.SetUpInspector();
cameraManager.SetUpInspector();
var oo = inspector.GenerateContainer("Grid");
var p = oo.GenerateSubcontainer(3);
var po = inspector.GenerateToggle(this, p, "Enable Grid");
po.AddListenerFunction(() =>
{
gridController.gameObject.SetActive(po.toggle.isOn);
});
var o = inspector.GenerateInputField(p, "Grid Size", (gridController.baseGridSize * 10).ToString());
o.AddListenerFunction(() =>
{
gridController.baseGridSize = float.Parse(o.inputField.text) / 10;
});
var c = inspector.GenerateToggle(this, p, "Show Coordinates", nameof(gridController) + "." + nameof(gridController.showCoordinates));
}
#region [退] Application Quit
private bool isQuit = false;
[Obsolete]
public void OnApplicationQuit()
{
if (isQuit) return;
Application.CancelQuit();//退出拦截
Application.CancelQuit(); // 退出拦截
GeneralSecondaryWindow QuitWindow =
Instantiate(EditorManager.instance.basePrefabs.generalSecondaryWindow,
EditorManager.instance.uiManager.mainPage.mainCanvas.GetComponent<RectTransform>()).GetComponent<GeneralSecondaryWindow>();
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);
var yesButton = QuitWindow.GenerateButton(beatmapToolsSettings, "Yes", () =>
{
SaveAndQuit();
});
var noButton = QuitWindow.GenerateButton(beatmapToolsSettings, "No", () =>
QuitWindow.GenerateButton(beatmapToolsSettings, "Yes", () => SaveAndQuit());
QuitWindow.GenerateButton(beatmapToolsSettings, "No", () =>
{
isQuit = true;
Application.Quit();
});
// if (isQuit)
// {
// Application.CancelQuit();//退出拦截
// MessageCtrl.Instance.OpenConfirmView("关闭界面将终止,确认关闭?", "", () =>
// {
// isQuit = false;
// Application.Quit();
// });
// }
}
async void SaveAndQuit()
{
isQuit = true;
@@ -252,5 +288,6 @@ namespace Ichni
await projectManager.saveManager.SaveAllCoroutine();
Application.Quit();
}
#endregion
}
}