using Convention; using System; using System.Collections; using System.Collections.Generic; using System.IO; using Unity.Cinemachine; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.SceneManagement; namespace Demo.Game { public partial class GameController : MonoBehaviour { [Resources, SerializeField] public BasicAudioSystem MainAudio; [Resources, SerializeField] private CinemachineVirtualCameraBase MainCamera; [Resources, SerializeField] public GlobalConfig MainConfig; [Content] private RootObject MainObject; public string RootSourcePath { get; private set; } public Action SetupSongDuration { get; private set; } = (_, _) => { }; public Action SetSongCurrentTime { get; private set; } = _ => { }; public bool IsMain { get; set; } = false; public bool IsAutoPlay { get; private set; } = false; /// /// 使用形如"E:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\devenv.exe" {0}来打开文件, /// {0}是必要的, 指代的是将要打开的文件 /// public string WhichOpenScript { get; private set; } = "{0}"; /// /// 使用形如"E:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\devenv.exe" {0}来打开项目存贮目录, /// {0}是必要的, 指代的是Helper的父目录, 同时也应该是项目的父目录, 同时配备有全局项目配置, 用于提供注释内容, /// 值为null时不会在启动时打开 /// public string WhichOpenProject { get; private set; } = null; public float SongOffset = 0; public float CurrentTime = 0; public Transform MainCameraTransform => MainCamera.transform; [Header("Environment")] [Resources] public Transform GlobalLight; public IEnumerator GameInit() { try { GameContent content = GameContent.instance; yield return new WaitUntil(() => content != null); 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) { string clipPath = (string)MainConfig.FindItem("song", ""); AudioType audioType = (AudioType)MainConfig.FindItem("audioType", BasicAudioSystem.GetAudioType(clipPath)); if (string.IsNullOrEmpty(clipPath)) { foreach (var file in MainConfig.ConfigFile.BackToParentDir().DirToolFileIter()) { if (file.IsFile() && !file.IsFileEmpty()) { if (BasicAudioSystem.GetAudioType(file.GetExtension()) != AudioType.UNKNOWN) { clipPath = file.GetFullPath(); break; } } } } if (string.IsNullOrEmpty(clipPath) == false) { /* IEnumerator Run() { #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); yield return GameAudioSystemInit(); } StartCoroutine(Run()); */ #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()); } } else { MainAudio = Editor.EditorController.instance.MainGameController.MainAudio; yield return GameAudioSystemInit(); } yield return null; // Setup Game Rules (Main) if (Editor.EditorController.instance.MainGameController == this) { // Config ScriptableObject { 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; } } // Setup Game Rules { foreach (var ab in ((string)MainConfig.FindItem(nameof(AssetBundle), "")).Split(';')) { if (string.IsNullOrEmpty(ab)) continue; StartCoroutine(AssetBundlesLoadHelper.LoadAssetBundleAsync(ab.Trim(), null)); } 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) { 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); } } } // Load Root Object { while (MainConfig.Contains("root") == false) { string defaultRootPath = "root.rscript"; if (content.IsCreateNewProject) { MainConfig["root"] = defaultRootPath; } else { Debug.LogError($"{nameof(defaultRootPath)} is cannt create or config's root property is not exist", this); StartCoroutine(GameExit()); yield break; } } 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(); MainObject = rootGameObject; rootGameObject.transform.SetParent(transform); rootGameObject.ScriptName = rootObject.GetName(true); rootGameObject.audioSystem = MainAudio; rootGameObject.EnableScript(content.RootSourceDir, this); rootGameObject.SetContent(nameof(SongOffset), SongOffset); rootGameObject.SetContent(nameof(IsAutoPlay), IsAutoPlay ? 1 : 0); rootGameObject.SetContent("SongLength", MainAudio.CurrentClip.length); yield return rootGameObject.ParseScript2Expr(rootObject.LoadAsText()); yield return rootGameObject.ApplyScript(); IEnumerator NDFS(ScriptableObject current) { foreach (var child in current.Childs) { yield return NDFS(child); } if (current.IsScriptApply == false) yield return current.ApplyScript(); } yield return NDFS(rootGameObject); } } finally { MainConfig.SaveProperties(); } } public IEnumerator GameInitBySubWorld(List 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); } public void Stop() { MainAudio.Stop(); if (IsMain) { SetSongCurrentTime(SongOffset); } MainObject.ScriptUpdate(SongOffset, Time.deltaTime, ScriptableObject.TickType.Reset); } 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); } 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; float deltaTime = Time.deltaTime; var currentClip = MainAudio.CurrentClip; if (MainObject == null || IsEnableUpdate == false) return; // 因为涉及到其他UI和时间切片的全局更新,所以需要提前 // TODO : 修正这个逻辑,这个逻辑是反常的 if (MainAudio.IsPlaying()) { using (Profiler.BeginZone("GameController.SetSongCurrentTime")) { SetSongCurrentTime(CurrentTime); } using (Profiler.BeginZone("GameController.ScriptUpdate")) { MainObject.ScriptUpdate(CurrentTime, deltaTime, ScriptableObject.TickType.Update); } } if (IsMain == false) return; using (Profiler.BeginZone("GameController.InputHandling")) { #if UNITY_EDITOR if (Keyboard.current[Key.LeftShift].isPressed) #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); } } } } } public IEnumerator GameExit() { try { if (MainObject) { Stop(); yield return MainObject.UnloadScript(); } } finally { // 预防一些情况 MainConfig.SaveProperties(); if (MainObject) Destroy(MainObject.gameObject); if (Editor.EditorController.instance.MainGameController == this) { Editor.EditorController.instance.MainGameController = null; } SceneManager.UnloadSceneAsync(gameObject.scene, UnloadSceneOptions.UnloadAllEmbeddedSceneObjects); } } } }