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

315 lines
12 KiB
C#
Raw Normal View History

2025-09-25 19:04:05 +08:00
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<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;
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", "");
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()
{
var clipFile = new ToolFile(clipPath);
if (clipFile.Exists() == false)
clipFile = MainConfig.GetFile(clipPath);
if (clipFile.Exists() == false)
{
Debug.LogError($"Cannt load {clipPath}", this);
yield break;
}
yield return MainAudio.LoadAudio(clipFile, BasicAudioSystem.GetAudioType(clipPath));
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;
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<RootObject>();
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<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(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);
}
}
}
}