Files
Convention-Unity-Demo/Assets/Scripts/Framework/GameContent/GameController.cs
2025-11-24 18:02:57 +08:00

379 lines
15 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 System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Cinemachine;
using Convention;
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;
/// <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()
{
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 (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));
}
IsHideTrackRender = (bool)MainConfig.FindItem(nameof(IsHideTrackRender), false);
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);
}
}
}
MainConfig.SaveProperties();
yield return null;
// Load Root Object
{
while (MainConfig.Contains("root") == false)
{
string defaultRootPath = "root.rscript";
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, this);
try
{
yield return rootGameObject.ParseScript2Expr(rootObject.LoadAsText());
yield return rootGameObject.ApplyScript();
}
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(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;
#if UNITY_EDITOR||Using_ProfilerMarker
public ProfilerMarker s_PreparePerfMarkerForSetSongCurrentTime = new(nameof(GameController) + "RuntimeForSetSongCurrentTime");
public ProfilerMarker s_PreparePerfMarker = new(nameof(GameController) + "Runtime");
#endif
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())
{
#if UNITY_EDITOR||Using_ProfilerMarker
s_PreparePerfMarkerForSetSongCurrentTime.Begin();
#endif
SetSongCurrentTime(CurrentTime);
#if UNITY_EDITOR||Using_ProfilerMarker
s_PreparePerfMarkerForSetSongCurrentTime.End();
#endif
MainObject.ScriptUpdate(CurrentTime, deltaTime, ScriptableObject.TickType.Update);
}
if (IsMain == false)
return;
#if UNITY_EDITOR || Using_ProfilerMarker
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||Using_ProfilerMarker
s_PreparePerfMarker.End();
#endif
}
public IEnumerator GameExit()
{
try
{
Stop();
yield return MainObject.UnloadScript();
}
finally
{
if (MainObject)
Destroy(MainObject.gameObject);
if (Editor.EditorController.instance.MainGameController == this)
{
Editor.EditorController.instance.MainGameController = null;
}
SceneManager.UnloadSceneAsync(gameObject.scene, UnloadSceneOptions.UnloadAllEmbeddedSceneObjects);
}
}
}
}