Files
Convention-Unity-Demo/Assets/Scripts/Framework/GameContent/GameController.cs

413 lines
18 KiB
C#
Raw Normal View History

2025-11-27 17:18:30 +08:00
using Convention;
2025-09-25 19:04:05 +08:00
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
2025-11-28 10:24:33 +08:00
using Unity.Cinemachine;
2025-12-09 15:12:25 +08:00
using Unity.VisualScripting;
2025-09-25 19:04:05 +08:00
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;
namespace Demo.Game
{
public partial class GameController : MonoBehaviour
{
#if UNITY_EDITOR
[Resources]
public void DebugGetScriptCounter()
{
Debug.Log(ScriptableObject.s_DebugContainer.Count, this);
}
#endif
2025-09-25 19:04:05 +08:00
[Resources, SerializeField] public BasicAudioSystem MainAudio;
2025-11-28 10:24:33 +08:00
[Resources, SerializeField] private CinemachineVirtualCameraBase MainCamera;
2025-09-25 19:04:05 +08:00
[Resources, SerializeField] public GlobalConfig MainConfig;
[Content] private RootObject MainObject;
public string RootSourcePath { get; private set; }
public Action<float, float> SetupSongDuration { get; private set; } = (_, _) => { };
public Action<float> SetSongCurrentTime { get; private set; } = _ => { };
public bool IsMain { get; set; } = false;
public bool IsAutoPlay { get; private set; } = false;
/// <summary>
/// 使用形如<b>"E:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\devenv.exe" {0}</b>来打开文件,
/// {0}是必要的, 指代的是将要打开的文件
/// </summary>
public string WhichOpenScript { get; private set; } = "{0}";
/// <summary>
/// 使用形如<b>"E:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\devenv.exe" {0}</b>来打开项目存贮目录,
/// {0}是必要的, 指代的是Helper的父目录, 同时也应该是项目的父目录, 同时配备有全局项目配置, 用于提供注释内容,
/// 值为null时不会在启动时打开
/// </summary>
public string WhichOpenProject { get; private set; } = null;
2025-09-25 19:04:05 +08:00
public float SongOffset = 0;
public float CurrentTime = 0;
2025-09-25 19:04:05 +08:00
public Transform MainCameraTransform => MainCamera.transform;
[Header("Environment")]
[Resources] public Transform GlobalLight;
public IEnumerator GameInit()
{
float gameInitStartTime = Time.realtimeSinceStartup;
2025-09-25 19:04:05 +08:00
try
{
2025-11-25 12:00:00 +08:00
GameContent content = GameContent.instance;
yield return new WaitUntil(() => content != null);
2025-09-25 19:04:05 +08:00
2025-11-25 12:00:00 +08:00
try
{
RootSourcePath = content.RootSourceDir;
MainConfig = new(content.RootSourceDir, content.IsCreateNewProject, true);
Debug.Log($"{content.RootSourceDir} loading", this);
}
catch (Exception ex)
{
Debug.LogException(ex, this);
Debug.LogError($"{content.RootSourceDir} is not a valid project", this);
StartCoroutine(GameExit());
yield break;
}
// Load Song
if (Editor.EditorController.instance.MainGameController == this)
2025-09-25 19:04:05 +08:00
{
2025-11-25 12:00:00 +08:00
string clipPath = (string)MainConfig.FindItem("song", "");
AudioType audioType = (AudioType)MainConfig.FindItem("audioType", BasicAudioSystem.GetAudioType(clipPath));
if (string.IsNullOrEmpty(clipPath))
2025-09-25 19:04:05 +08:00
{
2025-11-25 12:00:00 +08:00
foreach (var file in MainConfig.ConfigFile.BackToParentDir().DirToolFileIter())
2025-09-25 19:04:05 +08:00
{
2025-11-25 12:00:00 +08:00
if (file.IsFile() && !file.IsFileEmpty())
2025-09-25 19:04:05 +08:00
{
2025-11-25 12:00:00 +08:00
if (BasicAudioSystem.GetAudioType(file.GetExtension()) != AudioType.UNKNOWN)
{
clipPath = file.GetFullPath();
break;
}
2025-09-25 19:04:05 +08:00
}
}
}
2025-11-25 12:00:00 +08:00
if (string.IsNullOrEmpty(clipPath) == false)
2025-09-25 19:04:05 +08:00
{
#if ENABLE_CLASS_Interaction
var clipFile = new Interaction(clipPath);
#else
var clipFile = new ToolFile(clipPath);
#endif
if (clipFile.Exists() == false)
clipFile = new(MainConfig.GetFile(clipPath).GetFullPath());
if (clipFile.Exists() == false)
{
Debug.LogError($"Cannt load {clipPath}", this);
yield break;
}
yield return MainAudio.LoadAudio(clipFile, audioType);
content.SongLoadOverCallback(MainAudio);
StartCoroutine(GameAudioSystemInit());
2025-11-25 12:00:00 +08:00
}
2025-10-08 00:35:50 +08:00
}
2025-11-25 12:00:00 +08:00
else
2025-10-08 00:35:50 +08:00
{
2025-11-25 12:00:00 +08:00
MainAudio = Editor.EditorController.instance.MainGameController.MainAudio;
yield return GameAudioSystemInit();
2025-10-08 00:35:50 +08:00
}
2025-11-25 12:00:00 +08:00
yield return null;
// Setup Game Rules (Main)
if (Editor.EditorController.instance.MainGameController == this)
{
2025-11-25 12:00:00 +08:00
// Config ScriptableObject
{
2025-12-04 16:27:03 +08:00
ScriptableObject.EnableScriptableObjectCounter = 0;
ScriptableObject.ApplyScriptableObjectCounter = 0;
2025-11-25 12:00:00 +08:00
ScriptableObject.FastScriptableObjectTypen = content.ScriptableObjectTypen;
ScriptableObject.IsAutoPlay = content.IsAutoPlay;
ScriptableObject.OneBarTime = 60.0f / (float)MainConfig.FindItem(nameof(Editor.EditorController.BPM), Editor.EditorController.instance.BPM);
}
// Default IInteraction
{
IInteraction.DefaultInteractableIntervalLengthThatCanScoreBest = (float)MainConfig.FindItem(
nameof(IInteraction.DefaultInteractableIntervalLengthThatCanScoreBest),
IInteraction.DefaultInteractableIntervalLengthThatCanScoreBest);
IInteraction.DefaultInteractableScoreIntervalLength = (float)MainConfig.FindItem(
nameof(IInteraction.DefaultInteractableScoreIntervalLength),
IInteraction.DefaultInteractableScoreIntervalLength);
IInteraction.DefaultInteractiveLength = (float)MainConfig.FindItem(
nameof(IInteraction.DefaultInteractiveLength),
IInteraction.DefaultInteractiveLength);
IInteraction.DefaultVisibleLength = (float)MainConfig.FindItem(
nameof(IInteraction.DefaultVisibleLength),
IInteraction.DefaultVisibleLength);
}
// Config Game
{
SongOffset = (float)MainConfig.FindItem(nameof(SongOffset), SongOffset);
SetupSongDuration = GameContent.instance.SetupSongDuration;
SetSongCurrentTime = GameContent.instance.SetSongCurrentTime;
}
}
2025-11-25 12:00:00 +08:00
// Setup Game Rules
2025-09-25 19:04:05 +08:00
{
2025-11-25 12:00:00 +08:00
foreach (var ab in ((string)MainConfig.FindItem(nameof(AssetBundle), "")).Split(';'))
2025-09-25 19:04:05 +08:00
{
2025-11-25 12:00:00 +08:00
if (string.IsNullOrEmpty(ab))
continue;
StartCoroutine(AssetBundlesLoadHelper.LoadAssetBundleAsync(ab.Trim(), null));
}
2025-11-25 12:00:00 +08:00
IsAutoPlay = GameContent.instance.IsAutoPlay;
WhichOpenScript = (string)MainConfig.FindItem(nameof(WhichOpenScript), WhichOpenScript);
// Open Project
WhichOpenProject = (string)MainConfig.FindItem(nameof(WhichOpenProject), WhichOpenProject);
if (string.IsNullOrEmpty(WhichOpenProject) == false)
2025-09-25 19:04:05 +08:00
{
2025-11-25 12:00:00 +08:00
string path = string.Format($"{WhichOpenProject}", $"\"{Editor.EditorController.instance.PersistentDataPath}\"");
try
{
System.Diagnostics.Process.Start(path);
}
catch (Exception ex)
{
Debug.LogError($"Cannt open {path}", this);
Debug.LogException(ex, this);
}
2025-09-25 19:04:05 +08:00
}
}
2025-11-25 12:00:00 +08:00
// Load Root Object
2025-09-25 19:04:05 +08:00
{
2025-11-25 12:00:00 +08:00
while (MainConfig.Contains("root") == false)
{
2025-11-25 12:00:00 +08:00
string defaultRootPath = "root.rscript";
if (content.IsCreateNewProject)
{
2025-11-25 12:00:00 +08:00
MainConfig["root"] = defaultRootPath;
}
else
{
Debug.LogError($"{nameof(defaultRootPath)} is cannt create or config's root property is not exist", this);
StartCoroutine(GameExit());
yield break;
}
}
float loadRootObjectStartTime = Time.realtimeSinceStartup;
2025-11-25 12:00:00 +08:00
var rootFileName = (string)MainConfig.FindItem("root");
var rootObject = new ToolFile(Path.Combine(content.RootSourceDir, rootFileName));
rootObject.MustExistsPath();
var rootGameObject = new GameObject(rootObject.GetName(true)).AddComponent<RootObject>();
2025-11-25 17:04:19 +08:00
MainObject = rootGameObject;
2025-11-25 12:00:00 +08:00
rootGameObject.transform.SetParent(transform);
rootGameObject.ScriptName = rootObject.GetName(true);
rootGameObject.audioSystem = MainAudio;
rootGameObject.EnableScript(content.RootSourceDir, this);
2025-11-25 17:04:19 +08:00
rootGameObject.SetContent(nameof(SongOffset), SongOffset);
rootGameObject.SetContent(nameof(IsAutoPlay), IsAutoPlay ? 1 : 0);
rootGameObject.SetContent("SongLength", MainAudio.CurrentClip.length);
static IEnumerator Foo(IEnumerator ir)
2025-12-09 15:12:25 +08:00
{
Stack<IEnumerator> loadingTask = new();
loadingTask.Push(ir);
while (loadingTask.Count > 0)
2025-12-09 15:12:25 +08:00
{
if (loadingTask.Peek().MoveNext())
{
if (loadingTask.Peek().Current is IEnumerator next)
loadingTask.Push(next);
else if (loadingTask.Peek().Current is ScriptableObject)
yield return null;
}
else
{
loadingTask.Pop();
}
2025-12-09 15:12:25 +08:00
}
yield break;
2025-12-09 15:12:25 +08:00
}
yield return Foo(rootGameObject.ParseFromScriptFile2Expr(rootObject));//ConventionUtility.AvoidFakeStop(rootGameObject.ParseFromScriptFile2Expr(rootObject));
static void NDFS(ScriptableObject current)
2025-11-25 12:00:00 +08:00
{
2025-11-29 00:44:05 +08:00
foreach (var child in current.Childs)
2025-11-25 17:04:19 +08:00
{
NDFS(child);
2025-11-25 17:04:19 +08:00
}
2025-11-29 00:44:05 +08:00
if (current.IsScriptApply == false)
2025-12-02 16:39:29 +08:00
ConventionUtility.StartCoroutine(current.ApplyScript());
2025-11-25 12:00:00 +08:00
}
NDFS(rootGameObject);
float loadRootObjectEndTime = Time.realtimeSinceStartup;
float loadRootObjectElapsed = (loadRootObjectEndTime - loadRootObjectStartTime) * 1000f;
Debug.Log($"[GameInit] Load Root Object 耗时: {loadRootObjectElapsed:F2} ms", this);
2025-09-25 19:04:05 +08:00
}
}
2025-11-25 12:00:00 +08:00
finally
{
MainConfig.SaveProperties();
float gameInitEndTime = Time.realtimeSinceStartup;
float gameInitElapsed = (gameInitEndTime - gameInitStartTime) * 1000f;
Debug.Log($"[GameInit] 总耗时: {gameInitElapsed:F2} ms", this);
2025-11-25 12:00:00 +08:00
}
2025-09-25 19:04:05 +08:00
}
public IEnumerator GameInitBySubWorld(List<RootObject.InputCatchEntry> entrys)
{
yield return GameInit();
MainObject.InputCatch = entrys;
}
[Content, SerializeField] private bool IsEnableUpdate = false;
private IEnumerator GameAudioSystemInit()
{
yield return null;
IsEnableUpdate = true;
yield return new WaitUntil(() => MainObject != null);
MainObject.ScriptUpdate(SongOffset, Time.deltaTime, ScriptableObject.TickType.Reset);
2025-09-25 19:04:05 +08:00
}
public void Stop()
{
MainAudio.Stop();
if (IsMain)
{
SetSongCurrentTime(SongOffset);
2025-09-25 19:04:05 +08:00
}
MainObject.ScriptUpdate(SongOffset, Time.deltaTime, ScriptableObject.TickType.Reset);
2025-09-25 19:04:05 +08:00
}
public void Pause()
{
MainAudio.Pause();
if (IsMain)
{
SetSongCurrentTime(CurrentTime);
}
MainObject.ScriptUpdate(CurrentTime, Time.deltaTime, ScriptableObject.TickType.Pause);
}
public void Play()
{
MainAudio.Play();
if (IsMain)
{
SetupSongDuration(SongOffset, MainAudio.CurrentClip.length + SongOffset);
2025-09-25 19:04:05 +08:00
}
SetSongCurrentTime(CurrentTime);
MainObject.ScriptUpdate(CurrentTime, Time.deltaTime, ScriptableObject.TickType.Start);
}
public void ForceScriptUpdate(ScriptableObject.TickType type = ScriptableObject.TickType.Update)
{
MainObject.ScriptUpdate(CurrentTime, Time.deltaTime, type);
}
private bool IsScrollTimeline = false;
private void Update()
{
CurrentTime = MainAudio.CurrentTime + SongOffset;
2025-09-25 19:04:05 +08:00
float deltaTime = Time.deltaTime;
var currentClip = MainAudio.CurrentClip;
if (MainObject == null || IsEnableUpdate == false)
return;
// 因为涉及到其他UI和时间切片的全局更新所以需要提前
// TODO : 修正这个逻辑,这个逻辑是反常的
if (MainAudio.IsPlaying())
{
2025-11-27 17:18:30 +08:00
using (Profiler.BeginZone("GameController.SetSongCurrentTime"))
{
SetSongCurrentTime(CurrentTime);
}
using (Profiler.BeginZone("GameController.ScriptUpdate"))
{
MainObject.ScriptUpdate(CurrentTime, deltaTime, ScriptableObject.TickType.Update);
}
2025-09-25 19:04:05 +08:00
}
if (IsMain == false)
return;
2025-11-27 17:18:30 +08:00
using (Profiler.BeginZone("GameController.InputHandling"))
{
2025-09-25 19:04:05 +08:00
#if UNITY_EDITOR
2025-11-27 17:18:30 +08:00
if (Keyboard.current[Key.LeftShift].isPressed)
2025-09-25 19:04:05 +08:00
#else
if (Keyboard.current[Key.LeftCtrl].isPressed)
#endif
{
if (currentClip != null)
{
if (Keyboard.current[Key.P].wasPressedThisFrame)
{
if (MainAudio.IsPlaying())
{
Pause();
}
else
{
Play();
}
}
else if (Keyboard.current[Key.S].wasPressedThisFrame)
{
Stop();
}
var scrollTime = Mouse.current.scroll.ReadValue().y / 120.0f;
if (Mathf.Approximately(scrollTime, 0) == false)
{
IsScrollTimeline = true;
if (MainAudio.IsPlaying())
{
Pause();
}
MainAudio.CurrentTime = Mathf.Clamp(MainAudio.CurrentTime + scrollTime, 0, currentClip.length);
if (IsMain)
{
SetSongCurrentTime(CurrentTime);
}
MainObject.ScriptUpdate(CurrentTime, deltaTime, ScriptableObject.TickType.Update);
}
else if (IsScrollTimeline == true)
{
IsScrollTimeline = false;
MainObject.ScriptUpdate(CurrentTime, deltaTime, ScriptableObject.TickType.Reset);
MainObject.ScriptUpdate(CurrentTime, deltaTime, ScriptableObject.TickType.Update);
}
}
}
2025-11-27 17:18:30 +08:00
}
2025-09-25 19:04:05 +08:00
}
public IEnumerator GameExit()
{
try
{
2025-11-25 12:00:00 +08:00
if (MainObject)
{
Stop();
yield return MainObject.UnloadScript();
}
2025-09-25 19:04:05 +08:00
}
finally
{
2025-11-25 12:00:00 +08:00
// 预防一些情况
MainConfig.SaveProperties();
2025-11-12 10:18:50 +08:00
if (MainObject)
Destroy(MainObject.gameObject);
2025-09-25 19:04:05 +08:00
if (Editor.EditorController.instance.MainGameController == this)
{
Editor.EditorController.instance.MainGameController = null;
}
SceneManager.UnloadSceneAsync(gameObject.scene, UnloadSceneOptions.UnloadAllEmbeddedSceneObjects);
}
}
}
}