using System; using System.Collections; using System.Collections.Generic; using System.IO; using Cinemachine; using Convention; using Convention.WindowsUI.Variant; using Unity.Profiling; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.SceneManagement; namespace Demo.Game { public partial class GameController : MonoBehaviour { [Resources, SerializeField] public BasicAudioSystem MainAudio; [Resources, SerializeField] private CinemachineVirtualCamera MainCamera; [Resources, SerializeField] public GlobalConfig MainConfig; [Content] private RootObject MainObject; [Content] public bool IsHideTrackRender = false; 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; public string ScriptEditor { get; private set; } = "{0}"; public ProjectDefaultFileStyle CurrentProjectDefaultFileStyle = default; public float SongOffset; public float CurrentTime => MainAudio.CurrentTime - SongOffset; public Transform MainCameraTransform => MainCamera.transform; [Header("Environment")] [Resources] public Transform GlobalLight; public IEnumerator GameInit() { 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()); } } else { MainAudio = Editor.EditorController.instance.MainGameController.MainAudio; yield return GameAudioSystemInit(); } yield return null; // Setup Game Rules if (Editor.EditorController.instance.MainGameController == this) { ScriptableObject.FastScriptableObjectTypen = content.ScriptableObjectTypen; ScriptableObject.IsAutoPlay = content.IsAutoPlay; ScriptableObject.OneBarTime = (float)MainConfig.FindItem(nameof(Editor.EditorController.BPM), Editor.EditorController.instance.BPM); SetupSongDuration = GameContent.instance.SetupSongDuration; SetSongCurrentTime = GameContent.instance.SetSongCurrentTime; SongOffset = GameContent.instance.SongOffset; } { IsHideTrackRender = (bool)MainConfig.FindItem(nameof(IsHideTrackRender), false); IsAutoPlay = GameContent.instance.IsAutoPlay; ScriptEditor = (string)MainConfig.FindItem(nameof(ScriptEditor), ScriptEditor); CurrentProjectDefaultFileStyle = content.CurrentProjectDefaultFileStyle; } yield return null; MainConfig.SaveProperties(); // Load Root Object { while (MainConfig.Contains("root") == false) { string defaultRootPath = "root" + CurrentProjectDefaultFileStyle switch { ProjectDefaultFileStyle.PY => ".py", _ => ".h" }; if (content.IsCreateNewProject) { MainConfig["root"] = defaultRootPath; if (MainConfig.CreateFile(defaultRootPath)) { MainConfig.SaveProperties(); break; } } 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(); rootGameObject.transform.SetParent(transform); rootGameObject.ScriptName = rootObject.GetName(true); rootGameObject.audioSystem = MainAudio; rootGameObject.EnableScript(content.RootSourceDir, rootObject.GetFullPath(), this); try { yield return rootGameObject.LoadScript(rootObject.LoadAsText()); } finally { MainObject = rootGameObject; } } } 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(0, 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) { GameContent.instance.SetupSongDuration(0, MainAudio.CurrentClip.length); } 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; #if UNITY_EDITOR public ProfilerMarker s_PreparePerfMarker = new(nameof(GameController) + "Runtime"); #endif private void Update() { float deltaTime = Time.deltaTime; var currentClip = MainAudio.CurrentClip; if (MainObject == null || IsEnableUpdate == false) return; // 因为涉及到其他UI和时间切片的全局更新,所以需要提前 // TODO : 修正这个逻辑,这个逻辑是反常的 if (MainAudio.IsPlaying()) { SetSongCurrentTime(CurrentTime); MainObject.ScriptUpdate(CurrentTime, deltaTime, ScriptableObject.TickType.Update); } if (IsMain == false) return; #if UNITY_EDITOR s_PreparePerfMarker.Begin(this); #endif #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); } } } #if UNITY_EDITOR s_PreparePerfMarker.End(); #endif } public IEnumerator GameExit() { try { yield return MainObject.UnloadScript(); } finally { Destroy(MainObject.gameObject); if (Editor.EditorController.instance.MainGameController == this) { Editor.EditorController.instance.MainGameController = null; } SceneManager.UnloadSceneAsync(gameObject.scene, UnloadSceneOptions.UnloadAllEmbeddedSceneObjects); } } } }