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

413 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Convention;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Unity.Cinemachine;
using Unity.VisualScripting;
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
[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<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;
public float SongOffset = 0;
public float CurrentTime = 0;
public Transform MainCameraTransform => MainCamera.transform;
[Header("Environment")]
[Resources] public Transform GlobalLight;
public IEnumerator GameInit()
{
float gameInitStartTime = Time.realtimeSinceStartup;
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)
{
#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.EnableScriptableObjectCounter = 0;
ScriptableObject.ApplyScriptableObjectCounter = 0;
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;
}
}
float loadRootObjectStartTime = Time.realtimeSinceStartup;
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>();
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);
static IEnumerator Foo(IEnumerator ir)
{
Stack<IEnumerator> loadingTask = new();
loadingTask.Push(ir);
while (loadingTask.Count > 0)
{
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();
}
}
yield break;
}
yield return Foo(rootGameObject.ParseFromScriptFile2Expr(rootObject));//ConventionUtility.AvoidFakeStop(rootGameObject.ParseFromScriptFile2Expr(rootObject));
static void NDFS(ScriptableObject current)
{
foreach (var child in current.Childs)
{
NDFS(child);
}
if (current.IsScriptApply == false)
ConventionUtility.StartCoroutine(current.ApplyScript());
}
NDFS(rootGameObject);
float loadRootObjectEndTime = Time.realtimeSinceStartup;
float loadRootObjectElapsed = (loadRootObjectEndTime - loadRootObjectStartTime) * 1000f;
Debug.Log($"[GameInit] Load Root Object 耗时: {loadRootObjectElapsed:F2} ms", this);
}
}
finally
{
MainConfig.SaveProperties();
float gameInitEndTime = Time.realtimeSinceStartup;
float gameInitElapsed = (gameInitEndTime - gameInitStartTime) * 1000f;
Debug.Log($"[GameInit] 总耗时: {gameInitElapsed:F2} ms", this);
}
}
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);
}
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);
}
}
}
}