Files
Convention-Unity-Demo/Assets/Scripts/Framework/EditiorContent/EditorController.cs

672 lines
26 KiB
C#

using Convention;
using Convention.WindowsUI;
using Convention.WindowsUI.Variant;
using Demo.Game;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;
namespace Demo.Editor
{
public class EditorController : MonoSingleton<EditorController>
{
/// <summary>
/// 必须是-1至0
/// </summary>
public const float TimelineUVOffset = -0.1f;
public override bool IsDontDestroyOnLoad => false;
[Resources] public WindowManager ToolsWindow;
[Resources] public ModernUIFillBar TotalTimelineBar;
[Resources] public Text CurrentTimeText;
[Resources] public Text CurrentFPS;
[Setting] public const string SceneName = "GameScene";
[Setting] public bool IsLowPerformance = false;
[Content] public string LastLoadProjectName = "";
public GameController MainGameController;
[Header("Spectrum")]
[Resources] public UnityEngine.UI.RawImage SpectrumRawImage;
[Resources] public ModernUIFillBar SpectrumSeeline;
[Setting] public float SpectrumRenderTP = 0.1f;
[Setting] public ModernUIFillBar SongPlaySpeedBar;
private float[][] spectrums = null;
private float spectrumDataMax;
private Color[] blank = null;
/// <summary>
/// TODO: 替换为Dict<Tex>纹理组,这将用来支持更高的精度
/// </summary>
private Texture2D SpectrumRenderTexture;
private Color backgroundColor = new(0, 0, 0, 0);
private Color waveformColor = new(1, 1, 1, 1);
private Color waveformTpColor = new(1, 0, 0, 1);
[Resources, SerializeField] private PropertiesWindow TimelineScriptParentWindow;
public static Dictionary<string, Type> GetDefaultScriptableObjectTypen()
{
Dictionary<string, Type> result = new();
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in asm.GetTypes())
{
if (typeof(ScriptableObject).IsAssignableFrom(type) && type.IsAbstract == false)
{
result.Add(type.Name, type);
}
}
}
return result;
}
[Header("BPM")]
[Resources] public BPMLine BPMLinePrefab;
[Resources] public Transform BPMLineParentTransform;
[Content, SerializeField] private List<BPMLine> BPMLineEntries = new();
[Content] public int BPMFraction = 4;
[Content] public float SongOffset = 0;
[Content] public float BPM = 60;
private float onebarDeltaTime;
private void InjectSetSongCurrentTime(float time)
{
// 秒,可见长度,以下相同,注意并非百分比
var duration = SpectrumSeeline.value / TotalTimelineBar.maxValue;
// 这是负数
var leftClipFrom = SpectrumSeeline.value * TimelineUVOffset;
// 这是正数
var rightClipTo = SpectrumSeeline.value * (1 + TimelineUVOffset);
// 简单UI变更
void UpdateUI()
{
TotalTimelineBar.SetValue(time);
CurrentTimeText.text = time.ToString();
SpectrumRawImage.uvRect = new(TotalTimelineBar.currentPercent + TimelineUVOffset * duration, 0, duration, 1);
// 传递时间切片
UI.TimelineItem.clip = new(leftClipFrom + time, rightClipTo + time);
}
UpdateUI();
if (IsLowPerformance)
return;
if (MainGameController == null || MainGameController.MainAudio == null)
return;
// 绘制时频
void UpdateSpectrumRenderTexture()
{
if (spectrums != null &&
MainGameController.MainAudio.IsPlaying() &&
MainGameController.MainAudio is Convention.AudioSystem audio)
{
if (Mathf.Approximately(1, SongPlaySpeedBar.Value) == false)
return;
// Render Current Data
int x = (int)(TotalTimelineBar.currentPercent * SpectrumRenderTexture.width);
spectrums[x] = new float[SpectrumRenderTexture.height];
audio.Source.GetSpectrumData(spectrums[x], 0, FFTWindow.Rectangular);
var max = Mathf.Max(spectrums[x]);
for (int y = 0; y < spectrums[x].Length - 1; y++)
{
var et = Mathf.Pow(spectrums[x][y] / max, 0.5f);
if (SpectrumRenderTP < 1 - et)
{
SpectrumRenderTexture.SetPixel(x, y, Color.Lerp(backgroundColor, waveformColor, et / (1 - SpectrumRenderTP)));
}
else
{
SpectrumRenderTexture.SetPixel(x, y, Color.Lerp(waveformColor, waveformTpColor, (et - SpectrumRenderTP) / (SpectrumRenderTP)));
}
}
// Render Forward Null
int ie = x - 1;
while (ie > 0 && (spectrums[ie] == null || spectrums[ie].Length == 0))
{
ie--;
}
if (ie != 0 && ie != x - 1)
{
for (int ix = x; ix > ie; ix--)
{
for (int y = 0; y < spectrums[x].Length - 1; y++)
{
SpectrumRenderTexture.SetPixel(ix, y,
Color.Lerp(SpectrumRenderTexture.GetPixel(ie, y), SpectrumRenderTexture.GetPixel(x, y), (ix - ie) / (float)(x - ie)));
}
}
}
SpectrumRenderTexture.Apply();
SpectrumRawImage.texture = SpectrumRenderTexture;
}
}
UpdateSpectrumRenderTexture();
}
private void InjectSongLoadOverCallback(BasicAudioSystem audio)
{
// Speed
SongPlaySpeedBar.OnTransValueChange.AddListener(x =>
{
audio.SetSpeed(x);
});
if (IsLowPerformance)
return;
// BPM
BPM = (float)MainGameController.MainConfig.FindItem(nameof(BPM), BPM);
onebarDeltaTime = 60.0f / (BPM * BPMFraction);
BPMFraction = (int)MainGameController.MainConfig.FindItem(nameof(BPMFraction), BPMFraction);
SongOffset = (float)MainGameController.MainConfig.FindItem(nameof(SongOffset), SongOffset);
// 绘制时频
var texturePath = (string)MainGameController.MainConfig.FindItem(nameof(SpectrumRenderTexture), null);
if (audio is Convention.AudioSystem caudioSystem)
{
int width = 16384;
int height = 64;
blank = new Color[width * height];
Texture2D texture = new(width, height);
for (int i = 0; i < blank.Length; ++i)
{
blank[i] = backgroundColor;
}
texture.SetPixels(blank, 0);
texture.Apply();
SpectrumRenderTexture = texture;
spectrums = new float[width][];
SpectrumRawImage.texture = texture;
}
else
{
IEnumerator Foo()
{
int width = 4096 * 4;
int height = 1024;
var _clip = audio.CurrentClip;
int resolution = 240; // 这个值可以控制频谱的密度
resolution = _clip.frequency / resolution;
float[] samples = new float[_clip.samples * _clip.channels];
_clip.GetData(samples, 0);
float[] waveForm = new float[(samples.Length / resolution)];
float min = 0;
float max = 0;
bool inited = false;
for (int i = 0; i < waveForm.Length; i++)
{
waveForm[i] = 0;
for (int j = 0; j < resolution; j++)
{
waveForm[i] += Mathf.Abs(samples[(i * resolution) + j]);
}
if (!inited)
{
min = waveForm[i];
max = waveForm[i];
inited = true;
}
else
{
if (waveForm[i] < min)
{
min = waveForm[i];
}
if (waveForm[i] > max)
{
max = waveForm[i];
}
}
//waveForm[i] /= resolution;
if (i % 100 == 0)
yield return null;
}
spectrumDataMax = max;
PriorityQueue<float> tpQueue = new();
var maxQueueSize = SpectrumRenderTP * waveForm.Length;
foreach (var value in waveForm)
{
tpQueue.Enqueue(value);
}
var thValue = tpQueue.GetTP(SpectrumRenderTP);
blank = new Color[width * height];
Texture2D texture = new Texture2D(width, height);
for (int i = 0; i < blank.Length; ++i)
{
blank[i] = backgroundColor;
}
texture.SetPixels(blank, 0);
float xScale = (float)width / (float)waveForm.Length;
for (int i = 0; i < waveForm.Length; ++i)
{
int x = (int)(i * xScale);
int yOffset = (int)(waveForm[i] / max * height);
int startY = 0;
int endY = yOffset;
for (int y = startY; y <= endY; ++y)
{
if (waveForm[i] > thValue)
texture.SetPixel(x, y, waveformTpColor);
else
texture.SetPixel(x, y, waveformColor);
}
if (i % 100 == 0)
yield return null;
}
texture.Apply();
SpectrumRenderTexture = texture;
SpectrumRawImage.texture = texture;
}
StartCoroutine(Foo());
}
if (spectrumDataMax == 0)
{
var buffer = new float[SpectrumRenderTexture.width];
audio.CurrentClip.GetData(buffer, 0);
spectrumDataMax = Mathf.Max(buffer);
}
}
//[Header("PersistentDataPath")]
public string PersistentDataPath => PlatformIndicator.PersistentDataPath;
public string PersistentProjectPath => Path.Combine(PersistentDataPath, "Projects/");
public string PersistentHelperPath => Path.Combine(PersistentDataPath, "Helper/");
private void OpenGameScene(string ProjectName, bool IsCreateNewProject)
{
if (GameContent.instance == null)
{
new GameObject("GameContent").AddComponent<GameContent>();
}
var content = GameContent.instance;
content.RootSourceDir = Path.Combine(PersistentProjectPath, ProjectName) + "/";
content.IsCreateNewProject = IsCreateNewProject;
content.ScriptableObjectTypen = GetDefaultScriptableObjectTypen();
content.IsAutoPlay = true;
content.SetupSongDuration = (x, y) =>
{
TotalTimelineBar.minValue = x;
TotalTimelineBar.maxValue = y;
if (MainGameController != null && MainGameController.MainAudio.CurrentClip != null)
MainGameController.ForceScriptUpdate();
};
content.SetSongCurrentTime = InjectSetSongCurrentTime;
content.SongLoadOverCallback = InjectSongLoadOverCallback;
SceneManager.LoadSceneAsync(SceneName, LoadSceneMode.Additive).completed += x =>
{
LastLoadProjectName = ProjectName;
StopRefreshFlag = false;
MainGameController = FindFirstObjectByType<GameController>();
MainGameController.IsMain = true;
StartCoroutine(MainGameController.GameInit());
};
}
private IEnumerator CloseGameScene()
{
// InjectSongLoadOverCallback中注册了一个回调
SongPlaySpeedBar.OnTransValueChange.RemoveAllListeners();
// 清理一下图片,这总是合理的
SpectrumRenderTexture = null;
// 判断一下有没有
if (MainGameController == null)
yield break;
yield return MainGameController.GameExit();
}
private IEnumerator WaitForSharedModule()
{
while (SharedModule.instance == null)
yield return null;
SharedModule.instance.OpenCustomMenu(Architecture.Get<HierarchyWindow>().transform as RectTransform,
new SharedModule.CallbackData("Open Project", _ =>
{
SharedModule.instance.SingleEditString("Open Project", "", (ProjectName) =>
{
OpenGameScene(ProjectName, false);
});
}),
new SharedModule.CallbackData("Create New Project", _ =>
{
SharedModule.instance.SingleEditString("Create New Project", "", (ProjectName) =>
{
OpenGameScene(ProjectName, true);
});
}));
}
private void Start()
{
Profiler.AppInfo(Application.productName);
GlobalConfig.ConstConfigFile = "config.easysave";
// Generate Framework
{
var generaters = DefaultInstantiate.GetScriptableObjectInstantiate();
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in asm.GetTypes())
{
string filename = Convention.RScript.Variable.CStyle.CScriptRScriptVariableGenerater.GetTypename(type);
if (Convention.RScript.Variable.RScriptInjectVariableGenerater.AllRScriptInjectVariables.ContainsKey(filename))
continue;
if (generaters.TryGetValue(filename, out var generater))
{
new Convention.RScript.Variable.CStyle.CScriptRScriptVariableGenerater(type, () => generater(), null, filename).Register();
Debug.Log($"{filename} register");
}
else if (typeof(ScriptableObject).IsAssignableFrom(type))
{
new Convention.RScript.Variable.CStyle.CScriptRScriptVariableGenerater(type, null, null, filename).Register();
Debug.Log($"{filename} register");
}
}
}
new Convention.RScript.Variable.CStyle.CScriptRScriptVariableGenerater(
typeof(MathExtension.EaseCurveType), null, null, nameof(MathExtension.EaseCurveType) + "Getter").Register();
Debug.Log($"{typeof(MathExtension.EaseCurveType)} register");
}
// Helper Files
ToolFile helperHeaderDir = new ToolFile(PersistentHelperPath);
helperHeaderDir.MustExistsPath();
foreach (var (name, variable) in Convention.RScript.Variable.RScriptInjectVariableGenerater.AllRScriptInjectVariables)
{
(helperHeaderDir | name).SaveAsText(variable.scriptIndicator);
}
// Reset
TimelineScriptObject.TimelineWindow = this.TimelineScriptParentWindow;
string ProjectName = "";
foreach (var arg in Environment.GetCommandLineArgs())
{
if (arg.StartsWith("-p="))
{
ProjectName = arg["-p=".Length..];
}
else if (arg.StartsWith("-project="))
{
ProjectName = arg["-project=".Length..];
}
else if (arg == "-nfs")
{
Screen.fullScreen = false;
}
else if (arg == "-fs")
{
Screen.fullScreen = true;
}
else if (arg.StartsWith("-scale="))
{
try
{
var item = arg["-scale=".Length..].Split(',');
int x = int.Parse(item[0]);
int y = int.Parse(item[1]);
Screen.SetResolution(x, y, false);
}
catch
{
Screen.fullScreen = false;
}
}
else if (arg == "-lp")
{
IsLowPerformance = true;
}
}
}
private bool StopRefreshFlag = true;
public void ReloadCurrentProject()
{
StopRefreshFlag = true;
IEnumerator Foo()
{
try
{
yield return CloseGameScene();
}
finally
{
OpenGameScene(LastLoadProjectName, false);
}
}
StartCoroutine(Foo());
}
public void CloseCurrentProject()
{
StopRefreshFlag = true;
LastLoadProjectName = "";
IEnumerator Foo()
{
yield return CloseGameScene();
yield return WaitForSharedModule();
}
StartCoroutine(Foo());
}
#region Performance Monitoring
private void MonitorFrameRate()
{
float fps = 1.0f / Time.unscaledDeltaTime;
float frameTime = Time.unscaledDeltaTime * 1000.0f;
Profiler.Plot("FPS", fps);
Profiler.Plot("Frame Time (ms)", frameTime);
Profiler.Plot("Time Scale", Time.timeScale);
}
private void MonitorMemory()
{
// GC 内存
long totalMemory = System.GC.GetTotalMemory(false);
Profiler.Plot("GC Memory (MB)", totalMemory / (1024.0 * 1024.0));
// Unity Profiler 内存统计
long usedHeap = UnityEngine.Profiling.Profiler.usedHeapSizeLong;
long totalAllocated = UnityEngine.Profiling.Profiler.GetTotalAllocatedMemoryLong();
long totalReserved = UnityEngine.Profiling.Profiler.GetTotalReservedMemoryLong();
Profiler.Plot("Used Heap (MB)", usedHeap / (1024.0 * 1024.0));
Profiler.Plot("Total Allocated (MB)", totalAllocated / (1024.0 * 1024.0));
Profiler.Plot("Total Reserved (MB)", totalReserved / (1024.0 * 1024.0));
// 纹理内存
Profiler.Plot("Texture Memory (MB)", UnityEngine.Profiling.Profiler.GetAllocatedMemoryForGraphicsDriver() / (1024.0 * 1024.0));
}
#endregion
private void Update()
{
MonitorMemory();
MonitorFrameRate();
using (Profiler.BeginZone("EditorController.Update"))
{
CurrentFPS.text = $"{1 / Time.smoothDeltaTime}";
if (string.IsNullOrEmpty(LastLoadProjectName))
return;
if (MainGameController == null ||
MainGameController.MainAudio == null ||
MainGameController.MainAudio.CurrentClip == null)
{
foreach (var item in BPMLineEntries)
{
item.gameObject.SetActive(false);
}
return;
}
// 秒,可见长度,以下相同,注意并非百分比
var duration = SpectrumSeeline.value / MainGameController.MainAudio.CurrentClip.length;
// 这是负数
var leftClipFrom = SpectrumSeeline.value * TimelineUVOffset;
// 这是正数
var rightClipTo = SpectrumSeeline.value * (1 + TimelineUVOffset);
var time = MainGameController.CurrentTime;
int BPMLineEntriesIndex = 0;
float farAplha = (1 - SpectrumSeeline.currentPercent) * 0.5f + 0.5f;
float foucs = Mathf.CeilToInt((time + leftClipFrom ) / onebarDeltaTime) * onebarDeltaTime;
for (float end = time + rightClipTo; foucs + BPMLineEntriesIndex * onebarDeltaTime < end; BPMLineEntriesIndex++)
{
if (BPMLineEntries.Count <= BPMLineEntriesIndex)
{
var newLine = Instantiate(BPMLinePrefab, BPMLineParentTransform);
BPMLineEntries.Add(newLine);
}
else
{
BPMLineEntries[BPMLineEntriesIndex].Setup(foucs + BPMLineEntriesIndex * onebarDeltaTime,
time,
onebarDeltaTime,
farAplha,
new Vector2(time + leftClipFrom, time + rightClipTo));
}
}
for (; BPMLineEntriesIndex < BPMLineEntries.Count; BPMLineEntriesIndex++)
{
BPMLineEntries[BPMLineEntriesIndex].gameObject.SetActive(false);
}
if (Keyboard.current[Key.LeftCtrl].isPressed)
{
if (Keyboard.current[Key.R].wasPressedThisFrame && StopRefreshFlag == false)
{
ReloadCurrentProject();
}
else if (Keyboard.current[Key.Tab].wasPressedThisFrame)
{
CloseCurrentProject();
}
}
}
}
private void LateUpdate()
{
Profiler.EmitFrameMark();
}
// ToolTable
public void ToolIOpenProject()
{
SharedModule.instance.SingleEditString("Open Project", "Project", x =>
{
IEnumerator Foo()
{
try
{
yield return CloseGameScene();
}
finally
{
OpenGameScene(x, false);
}
}
StartCoroutine(Foo());
});
}
public void ToolCreateProject()
{
SharedModule.instance.SingleEditString("Create New Project", "New Project", x =>
{
IEnumerator Foo()
{
try
{
yield return CloseGameScene();
}
finally
{
OpenGameScene(x, true);
}
}
StartCoroutine(Foo());
});
}
[Header("Tools Button")]
[Resources] public UnityEngine.UI.Button ToolSongTimeButton;
public void ToolCreateSongModule()
{
if (MainGameController == null)
return;
if (MainGameController.MainAudio == null)
return;
if (MainGameController.MainAudio.CurrentClip == null)
return;
SharedModule.instance.OpenCustomMenu(ToolSongTimeButton.transform as RectTransform,
new SharedModule.CallbackData("Play(P)", _ => MainGameController.Play()),
new SharedModule.CallbackData("Pause(P)", _ => MainGameController.Pause()),
new SharedModule.CallbackData("Stop(S)", _ => MainGameController.Stop())
);
}
[Resources] public UnityEngine.UI.Button CameraPositionButton;
[Resources] public UnityEngine.UI.Button CameraRotationButton;
[Resources] public FreeSceneCamera SceneVirtualCamera;
public void ToolCreateInitCameraModule()
{
if (MainGameController != null)
{
if (MainGameController.MainAudio.IsPlaying())
return;
}
SharedModule.instance.OpenCustomMenu(ToolSongTimeButton.transform as RectTransform,
new SharedModule.CallbackData("Reset Rotation(LeftShift)", _ => SceneVirtualCamera.transform.rotation = Quaternion.identity),
new SharedModule.CallbackData("Reset(LeftShift+Z)", _ => SceneVirtualCamera.transform.SetLocalPositionAndRotation(
Vector3.zero, Quaternion.identity)));
}
}
}