同步
This commit is contained in:
23
Assets/Scripts/Game/DataCore/Base_BM/BaseElement_BM.cs
Normal file
23
Assets/Scripts/Game/DataCore/Base_BM/BaseElement_BM.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace Ichni.RhythmGame.Beatmap
|
||||
{
|
||||
public abstract class BaseElement_BM
|
||||
{
|
||||
public Guid attachedElementGuid;
|
||||
|
||||
/// <summary>
|
||||
/// 从存档类中生成游戏物体
|
||||
/// </summary>
|
||||
public abstract void ExecuteBM();
|
||||
|
||||
/// <summary>
|
||||
/// 在AfterInitialize中被调用,用于处理GameElement的"需要引用"的物体在此物体后面生成的情况。
|
||||
/// 注意,如果使用此函数,需要在ExecuteBM中设置 matchedElement.matchedBM = this;
|
||||
/// </summary>
|
||||
public virtual void AfterExecute()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab4e12be376b08f418bd2d7a7d78aec6
|
||||
@@ -297,7 +297,7 @@ namespace Ichni.RhythmGame
|
||||
[Button("Rebuild")]
|
||||
public void Rebuild()
|
||||
{
|
||||
trackPathSubmodule?.path.Rebuild(true);
|
||||
trackPathSubmodule?.path.RebuildImmediate();
|
||||
trackRendererSubmodule?.meshGenerator.Rebuild();
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -32,8 +32,8 @@ namespace Ichni.RhythmGame
|
||||
LogWindow.Log("AnimationBase must be attached to a GameElement with TransformSubmodule", Color.red);
|
||||
}
|
||||
|
||||
// 向 AnimationManager 注册,通过中废集权 Update 驱动,替代 MonoBehaviour.Update()
|
||||
AnimationManager.instance.RegisterAnimation(this);
|
||||
// 向 ElementUpdateScheduler 注册 Phase.Animation
|
||||
CoreServices.UpdateScheduler.Register(UpdatePhase.Animation, this);
|
||||
}
|
||||
|
||||
public virtual void OnDirtyRefresh(Dictionary<string, bool> flags)
|
||||
@@ -43,7 +43,7 @@ namespace Ichni.RhythmGame
|
||||
|
||||
public override void OnDelete()
|
||||
{
|
||||
AnimationManager.instance.UnregisterAnimation(this);
|
||||
CoreServices.UpdateScheduler.Unregister(UpdatePhase.Animation, this);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -54,7 +54,16 @@ namespace Ichni.RhythmGame
|
||||
/// <param name="songTime">歌曲时间</param>
|
||||
protected abstract void UpdateAnimation(float songTime);
|
||||
|
||||
// Update() 已移除,改由 AnimationManager.ManualTick 集中驱动
|
||||
/// <summary>
|
||||
/// IScheduledElement 实现:在 Phase.Animation 阶段检查时间范围并执行动画更新。
|
||||
/// </summary>
|
||||
public override void ScheduledUpdate(UpdatePhase phase, float songTime)
|
||||
{
|
||||
if (phase == UpdatePhase.Animation && timeDurationSubmodule.CheckTimeInDuration(songTime))
|
||||
{
|
||||
UpdateAnimation(songTime);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Vector3 getValue(float time)
|
||||
{
|
||||
@@ -75,10 +84,14 @@ namespace Ichni.RhythmGame
|
||||
timeDurationSubmodule.endTime += offset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一次性调用入口,供 BeatmapContainer.AfterLoadSet() 等非调度器路径使用。
|
||||
/// 不再用于逐帧驱动。
|
||||
/// </summary>
|
||||
public virtual void InvokeUpdate()
|
||||
{
|
||||
UpdateAnimation(EditorManager.instance.songInformation.songTime);
|
||||
UpdateAnimation(CoreServices.TimeProvider.SongTime);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,11 @@ namespace Ichni.RhythmGame
|
||||
look.targetTransformSubmodule = (animatedObject as IHaveTransformSubmodule).transformSubmodule;
|
||||
look.targetTransformSubmodule.lookAt = look;
|
||||
look.gameCamera = EditorManager.instance.cameraManager.gameCamera;
|
||||
|
||||
// 注册到 Phase 7 (Effect) — TickLate 中在 Track/TrackFollower/Note 全部更新完毕后执行旋转覆盖
|
||||
// 避免因轨道位置尚未就绪导致的旋转抖动
|
||||
CoreServices.UpdateScheduler.Register(UpdatePhase.Effect, look);
|
||||
|
||||
return look;
|
||||
}
|
||||
|
||||
@@ -39,12 +44,29 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
timeDurationSubmodule = new TimeDurationSubmodule(this);
|
||||
}
|
||||
|
||||
public override void OnDelete()
|
||||
{
|
||||
base.OnDelete(); // AnimationBase 注销 Phase 1 (Animation)
|
||||
CoreServices.UpdateScheduler.Unregister(UpdatePhase.Effect, this);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region [动画更新] Animation Update
|
||||
void LateUpdate()
|
||||
/// <summary>
|
||||
/// 调度器多阶段处理:
|
||||
/// Phase 1 (Animation) → 由 base.ScheduledUpdate 调用 UpdateAnimation,更新 enabling 状态
|
||||
/// Phase 7 (Effect) → 在所有 Track/TrackFollower/Note 位置更新完毕后执行旋转覆盖
|
||||
/// 替代原先的 LateUpdate()。
|
||||
/// </summary>
|
||||
public override void ScheduledUpdate(UpdatePhase phase, float songTime)
|
||||
{
|
||||
if (enabling.value) (animatedObject as IHaveTransformSubmodule)?.UpdateLookAt(this);
|
||||
base.ScheduledUpdate(phase, songTime); // AnimationBase 处理 Phase 1
|
||||
|
||||
if (phase == UpdatePhase.Effect)
|
||||
{
|
||||
if (enabling.value) (animatedObject as IHaveTransformSubmodule)?.UpdateLookAt(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateAnimation(float songTime)
|
||||
|
||||
@@ -56,27 +56,4 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
public void TriggerInteraction();
|
||||
}
|
||||
|
||||
|
||||
namespace Beatmap
|
||||
{
|
||||
public abstract class BaseElement_BM
|
||||
{
|
||||
public Guid attachedElementGuid;
|
||||
|
||||
/// <summary>
|
||||
/// 从存档类中生成游戏物体
|
||||
/// </summary>
|
||||
public abstract void ExecuteBM();
|
||||
|
||||
/// <summary>
|
||||
/// 在AfterInitialize中被调用,用于处理GameElement的“需要引用”的物体在此物体后面生成的情况。
|
||||
/// 注意,如果使用此函数,需要在ExecuteBM中设置 matchedElement.matchedBM = this;
|
||||
/// </summary>
|
||||
public virtual void AfterExecute()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,13 @@ namespace Ichni.RhythmGame
|
||||
public static class CoreServices
|
||||
{
|
||||
public static ISongTimeProvider TimeProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 集中式元素更新调度器。
|
||||
/// 所有 GameElement 子类应通过此属性访问调度器进行 Register / Unregister,
|
||||
/// 而非直接引用 EditorManager 或 GameManager。
|
||||
/// </summary>
|
||||
public static ElementUpdateScheduler UpdateScheduler { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fda5a36c77610481e976e5d7ceb764ab
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
22
Assets/Scripts/Game/RuntimeCore/Base/IScheduledElement.cs
Normal file
22
Assets/Scripts/Game/RuntimeCore/Base/IScheduledElement.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace Ichni.RhythmGame
|
||||
{
|
||||
/// <summary>
|
||||
/// 所有参与集中更新调度的元素需实现的接口。
|
||||
/// 同一元素可注册到多个 <see cref="UpdatePhase"/>,
|
||||
/// 通过 <paramref name="phase"/> 参数区分当前所处阶段并执行对应逻辑。
|
||||
/// </summary>
|
||||
public interface IScheduledElement
|
||||
{
|
||||
/// <summary>
|
||||
/// 由 <see cref="ElementUpdateScheduler"/> 在对应阶段调用。
|
||||
/// </summary>
|
||||
/// <param name="phase">当前执行的更新阶段</param>
|
||||
/// <param name="songTime">当前音频播放时间(秒)</param>
|
||||
void ScheduledUpdate(UpdatePhase phase, float songTime);
|
||||
|
||||
/// <summary>
|
||||
/// 元素是否处于活跃状态。调度器跳过非活跃元素以节省开销。
|
||||
/// </summary>
|
||||
bool IsScheduledActive { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fdf08f5bfdc7b9f4b87f697c199eb1cf
|
||||
@@ -4,7 +4,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Ichni.Editor;
|
||||
using Ichni.RhythmGame.Beatmap;
|
||||
using UniRx;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
@@ -27,11 +26,15 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
gameElementList = new List<GameElement>();
|
||||
lowPriorityActions = new List<UnityAction>();
|
||||
Observable.EveryUpdate().Subscribe(_ => ExecuteLowPriorityActions());
|
||||
// 低优先级动作由 ElementUpdateScheduler Phase 8 Misc 尾部统一调用,
|
||||
// 不再使用 UniRx Observable.EveryUpdate 订阅。
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region [低优先级动作调度] Low Priority Action Dispatch
|
||||
/// <summary>
|
||||
/// 执行并清空低优先级动作队列。由 ElementUpdateScheduler.TickLate() Phase 8 调用。
|
||||
/// </summary>
|
||||
public void ExecuteLowPriorityActions()
|
||||
{
|
||||
if (lowPriorityActions.Count > 0)
|
||||
@@ -83,4 +86,4 @@ namespace Ichni.RhythmGame
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
38
Assets/Scripts/Game/RuntimeCore/Base/UpdatePhase.cs
Normal file
38
Assets/Scripts/Game/RuntimeCore/Base/UpdatePhase.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace Ichni.RhythmGame
|
||||
{
|
||||
/// <summary>
|
||||
/// 集中式更新调度器的阶段定义。
|
||||
/// 每帧按数值升序执行,保证严格的依赖顺序:
|
||||
/// 动画先于变换应用 → 变换先于 Spline 重建 → 轨道先于音符。
|
||||
/// 数值留有间隔,便于未来插入新阶段。
|
||||
/// </summary>
|
||||
public enum UpdatePhase
|
||||
{
|
||||
/// <summary>判定元素激活/隐藏状态</summary>
|
||||
TimeDuration = 0,
|
||||
|
||||
/// <summary>更新动画值,设置脏标记</summary>
|
||||
Animation = 10,
|
||||
|
||||
/// <summary>执行 DirtyRefresh + Transform + Color</summary>
|
||||
Apply = 20,
|
||||
|
||||
/// <summary>手动重建 Dreamteck SplineComputer;同时执行 LookAt 等 Transform 后处理覆盖</summary>
|
||||
SplineRebuild = 30,
|
||||
|
||||
/// <summary>更新轨道时间、裁剪区间</summary>
|
||||
TrackCore = 40,
|
||||
|
||||
/// <summary>更新轨道跟踪器(CrossTrackPoint / ObjectTracker 等)</summary>
|
||||
TrackFollower = 50,
|
||||
|
||||
/// <summary>音符可见性、轨道位置、判定、特效</summary>
|
||||
Note = 60,
|
||||
|
||||
/// <summary>ParticleEmitter / TimeEffectsCollection 等特效</summary>
|
||||
Effect = 70,
|
||||
|
||||
/// <summary>SkyboxSubsetter / LowPriorityActions 等杂项</summary>
|
||||
Misc = 80
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Game/RuntimeCore/Base/UpdatePhase.cs.meta
Normal file
2
Assets/Scripts/Game/RuntimeCore/Base/UpdatePhase.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b79a50d212bc9a42b53f7f576f15ae7
|
||||
@@ -1,18 +1,5 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class Dodger : MonoBehaviour
|
||||
{
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ using Ichni.Editor;
|
||||
|
||||
namespace Ichni.RhythmGame
|
||||
{
|
||||
public abstract partial class GameElement : SerializedMonoBehaviour, IBaseElement, IComparable<GameElement>
|
||||
public abstract partial class GameElement : SerializedMonoBehaviour, IBaseElement, IComparable<GameElement>, IScheduledElement
|
||||
{
|
||||
#region [属性和标记] Essential & Tracking Info
|
||||
//物体名
|
||||
@@ -141,6 +141,19 @@ namespace Ichni.RhythmGame
|
||||
return HierarchyPriority.CompareTo(other.HierarchyPriority);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region [调度更新] IScheduledElement Implementation
|
||||
/// <summary>
|
||||
/// 由 ElementUpdateScheduler 在对应阶段调用。
|
||||
/// 子类按需重写以实现特定阶段的更新逻辑。
|
||||
/// </summary>
|
||||
public virtual void ScheduledUpdate(UpdatePhase phase, float songTime) { }
|
||||
|
||||
/// <summary>
|
||||
/// 元素是否处于活跃状态。调度器跳过非活跃元素以节省开销。
|
||||
/// </summary>
|
||||
public virtual bool IsScheduledActive => isActiveAndEnabled;
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region [额外交互及存档重写预留] Editor Interaction & Interfaces Overrides
|
||||
|
||||
@@ -66,6 +66,17 @@ namespace Ichni.RhythmGame
|
||||
transformSubmodule = new TransformSubmodule(this);
|
||||
colorSubmodule = new ColorSubmodule(this, Color.white, true, Color.white, 0);
|
||||
}
|
||||
|
||||
public override void AfterInitialize()
|
||||
{
|
||||
base.AfterInitialize();
|
||||
CoreServices.UpdateScheduler.Register(UpdatePhase.Effect, this);
|
||||
}
|
||||
|
||||
public override void OnDelete()
|
||||
{
|
||||
CoreServices.UpdateScheduler.Unregister(UpdatePhase.Effect, this);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region [控制与刷新] Control & Refresh
|
||||
@@ -84,18 +95,21 @@ namespace Ichni.RhythmGame
|
||||
(this as IHaveParticles).SetParticleSettings(prewarm, simulationSpace, density, lifeTime, speed, radius, isAutoOrient, particleRotation);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
/// <summary>
|
||||
/// IScheduledElement 实现:在 Phase.Effect 阶段控制粒子播放/暂停/停止。
|
||||
/// 替代原 MonoBehaviour.Update(),由 ElementUpdateScheduler 集中驱动。
|
||||
/// </summary>
|
||||
public override void ScheduledUpdate(UpdatePhase phase, float songTime)
|
||||
{
|
||||
float songTime = EditorManager.instance.songInformation.songTime;
|
||||
if (phase != UpdatePhase.Effect) return;
|
||||
|
||||
if (playTime > songTime || stopTime < songTime)
|
||||
{
|
||||
if (particle.isPlaying || particle.isPaused)
|
||||
{
|
||||
particle.Stop();
|
||||
|
||||
if (songTime < playTime) { particle.Clear(); }
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -107,7 +121,6 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
particle.Play();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public override void Refresh()
|
||||
|
||||
@@ -16,6 +16,12 @@ namespace Ichni.RhythmGame
|
||||
public float time; //触发效果的时间
|
||||
#endregion
|
||||
|
||||
#region [运行时缓存] Cached Effect Lists
|
||||
private List<EffectBase> _priorEffects;
|
||||
private List<EffectBase> _defaultEffects;
|
||||
private List<EffectBase> _lateEffects;
|
||||
#endregion
|
||||
|
||||
#region [生成与初始化] Generation & Initialization
|
||||
public static TimeEffectsCollection GenerateElement(string name, Guid guid, List<string> tags,
|
||||
bool isFirstGenerated, GameElement parentElement, float time)
|
||||
@@ -33,15 +39,54 @@ namespace Ichni.RhythmGame
|
||||
transformSubmodule = new TransformSubmodule(this);
|
||||
effectSubmodule = new EffectSubmodule(this);
|
||||
}
|
||||
|
||||
public override void AfterInitialize()
|
||||
{
|
||||
base.AfterInitialize();
|
||||
CacheEffectLists();
|
||||
CoreServices.UpdateScheduler.Register(UpdatePhase.Effect, this);
|
||||
}
|
||||
|
||||
public override void OnDelete()
|
||||
{
|
||||
CoreServices.UpdateScheduler.Unregister(UpdatePhase.Effect, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 缓存 effectCollection 中的 Prior/Default/Late 列表引用,
|
||||
/// 避免 ScheduledUpdate 中每帧执行 Dictionary string key 查找和 lambda 委托分配。
|
||||
/// </summary>
|
||||
private void CacheEffectLists()
|
||||
{
|
||||
if (effectSubmodule?.effectCollection == null) return;
|
||||
effectSubmodule.effectCollection.TryGetValue("Prior", out _priorEffects);
|
||||
effectSubmodule.effectCollection.TryGetValue("Default", out _defaultEffects);
|
||||
effectSubmodule.effectCollection.TryGetValue("Late", out _lateEffects);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region [控制与刷新] Control & Refresh
|
||||
private void Update()
|
||||
/// <summary>
|
||||
/// IScheduledElement 实现:在 Phase.Effect 阶段更新时间特效集合。
|
||||
/// 替代原 MonoBehaviour.Update(),由 ElementUpdateScheduler 集中驱动。
|
||||
/// </summary>
|
||||
public override void ScheduledUpdate(UpdatePhase phase, float songTime)
|
||||
{
|
||||
effectSubmodule.effectCollection["Prior"].ForEach(effect => effect.UpdateEffect(time));
|
||||
effectSubmodule.effectCollection["Default"].ForEach(effect => effect.UpdateEffect(time));
|
||||
effectSubmodule.effectCollection["Late"].ForEach(effect => effect.UpdateEffect(time));
|
||||
if (phase != UpdatePhase.Effect) return;
|
||||
|
||||
UpdateEffectList(_priorEffects, time);
|
||||
UpdateEffectList(_defaultEffects, time);
|
||||
UpdateEffectList(_lateEffects, time);
|
||||
}
|
||||
|
||||
private static void UpdateEffectList(List<EffectBase> effects, float effectTime)
|
||||
{
|
||||
if (effects == null) return;
|
||||
for (int i = 0; i < effects.Count; i++)
|
||||
{
|
||||
effects[i].UpdateEffect(effectTime);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,17 @@ namespace Ichni.RhythmGame
|
||||
skyboxBlender.makeFirstMaterialSkybox = true;
|
||||
skyboxBlender.InspectorAndAwakeChanges();
|
||||
}
|
||||
|
||||
public override void AfterInitialize()
|
||||
{
|
||||
base.AfterInitialize();
|
||||
CoreServices.UpdateScheduler.Register(UpdatePhase.Misc, this);
|
||||
}
|
||||
|
||||
public override void OnDelete()
|
||||
{
|
||||
CoreServices.UpdateScheduler.Unregister(UpdatePhase.Misc, this);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region [控制与刷新] Control & Refresh
|
||||
@@ -92,11 +103,16 @@ namespace Ichni.RhythmGame
|
||||
skyboxBlender.InspectorAndAwakeChanges();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
/// <summary>
|
||||
/// IScheduledElement 实现:在 Phase.Misc 阶段处理天空盒切换与混合。
|
||||
/// 替代原 MonoBehaviour.Update(),由 ElementUpdateScheduler 集中驱动。
|
||||
/// </summary>
|
||||
public override void ScheduledUpdate(UpdatePhase phase, float songTime)
|
||||
{
|
||||
if (phase != UpdatePhase.Misc) return;
|
||||
|
||||
if (skyBoxThemeBundleList.Count > 1)
|
||||
{
|
||||
float songTime = EditorManager.instance.songInformation.songTime;
|
||||
float delay = EditorManager.instance.songInformation.delay;
|
||||
float finalTime = EditorManager.instance.songInformation.songLength;
|
||||
|
||||
@@ -117,4 +133,4 @@ namespace Ichni.RhythmGame
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
flick.track = track;
|
||||
flick.trackPositioner = flick.gameObject.GetComponent<SplinePositioner>() ?? flick.gameObject.AddComponent<SplinePositioner>();
|
||||
flick.trackPositioner.autoUpdate = false; // 由调度器 Phase 6 手动刷新采样
|
||||
flick.trackPositioner.spline = track.trackPathSubmodule.path;
|
||||
flick.isOnTrack = true;
|
||||
flick.UpdateNoteInTrack(EditorManager.instance.songInformation.songTime);
|
||||
|
||||
@@ -41,8 +41,8 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
hold.track = track;
|
||||
hold.trackPositioner = hold.gameObject.GetComponent<SplinePositioner>() ?? hold.gameObject.AddComponent<SplinePositioner>();
|
||||
hold.trackPositioner.autoUpdate = false; // 由调度器 Phase 6 手动刷新采样
|
||||
hold.trackPositioner.spline = track.trackPathSubmodule.path;
|
||||
hold.trackPositioner.updateMethod = SplineUser.UpdateMethod.LateUpdate;
|
||||
hold.isOnTrack = true;
|
||||
hold.UpdateNoteInTrack(EditorManager.instance.songInformation.songTime);
|
||||
Observable.NextFrame().Subscribe(_ =>
|
||||
@@ -177,6 +177,15 @@ namespace Ichni.RhythmGame
|
||||
holdingTime = songTime - exactJudgeTime;
|
||||
}
|
||||
}
|
||||
|
||||
// 轨道位置更新:必须在状态机之后执行,确保 holdingTime 等字段已更新。
|
||||
// 在旧代码中,ManualUpdate(Update) 先更新状态 → LateUpdate 再更新位置。
|
||||
// 现在两者都在 LateUpdate Phase 6 的同一个 ManualUpdate 中,因此将位置更新移至状态机之后。
|
||||
if (isOnTrack)
|
||||
{
|
||||
UpdateNoteInTrack(songTime);
|
||||
}
|
||||
|
||||
if (noteJudgeSubmodule != null && !EditorManager.instance.cameraManager.isSceneCameraActive)
|
||||
{
|
||||
foreach (NoteJudgeUnit unit in noteJudgeSubmodule.judgeUnitList.Where(unit => unit.isShowingJudge))
|
||||
@@ -241,14 +250,6 @@ namespace Ichni.RhythmGame
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (isOnTrack)
|
||||
{
|
||||
UpdateNoteInTrack(EditorManager.instance.songInformation.songTime);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -93,6 +93,7 @@ namespace Ichni.RhythmGame
|
||||
public virtual void UpdateNoteInMovableTrack(float songTime)
|
||||
{
|
||||
TrackTimeSubmoduleMovable trackTimeSubmoduleMovable = track.trackTimeSubmodule as TrackTimeSubmoduleMovable;
|
||||
trackPositioner.RebuildImmediate();
|
||||
trackPositioner.SetPercent(trackTimeSubmoduleMovable.GetTrackPercent(exactJudgeTime));
|
||||
}
|
||||
|
||||
@@ -105,6 +106,7 @@ namespace Ichni.RhythmGame
|
||||
|
||||
percent = Mathf.Clamp01(percent);
|
||||
|
||||
trackPositioner.RebuildImmediate();
|
||||
trackPositioner.SetPercent(1 - percent);
|
||||
}
|
||||
|
||||
@@ -119,10 +121,10 @@ namespace Ichni.RhythmGame
|
||||
var editor = EditorManager.instance;
|
||||
var cameraManager = editor.cameraManager;
|
||||
|
||||
// 轨道位置更新
|
||||
if (isOnTrack && track.trackTimeSubmodule is TrackTimeSubmoduleStatic)
|
||||
// 轨道位置更新(Phase 6 在 LateUpdate 中执行,SplineComputer 已完成采样)
|
||||
if (isOnTrack)
|
||||
{
|
||||
UpdateNoteInStaticTrack(currentSongTime);
|
||||
UpdateNoteInTrack(currentSongTime);
|
||||
}
|
||||
|
||||
// 判定状态更新
|
||||
@@ -245,10 +247,11 @@ namespace Ichni.RhythmGame
|
||||
if (exactJudgeTime - beyondTime - 0.5f > -EditorManager.instance.songInformation.delay)
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
var noteScheduler = CoreServices.UpdateScheduler.NoteScheduler;
|
||||
if (isNewOne)
|
||||
EditorManager.instance.noteManager.RegisterNote(this, exactJudgeTime - beyondTime - 0.1f, (this is Hold hold ? hold.holdEndTime : exactJudgeTime) + finishTime + 0.1f);
|
||||
noteScheduler.RegisterNote(this, exactJudgeTime - beyondTime - 0.1f, (this is Hold hold ? hold.holdEndTime : exactJudgeTime) + finishTime + 0.1f);
|
||||
else
|
||||
NoteManager.instance.ChangeNoteInfo(this, exactJudgeTime - beyondTime - 0.1f, (this is Hold hold ? hold.holdEndTime : exactJudgeTime) + finishTime + 0.1f);
|
||||
noteScheduler.ChangeNoteInfo(this, exactJudgeTime - beyondTime - 0.1f, (this is Hold hold ? hold.holdEndTime : exactJudgeTime) + finishTime + 0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
stay.track = track;
|
||||
stay.trackPositioner = stay.gameObject.GetComponent<SplinePositioner>() ?? stay.gameObject.AddComponent<SplinePositioner>();
|
||||
stay.trackPositioner.autoUpdate = false; // 由调度器 Phase 6 手动刷新采样
|
||||
stay.trackPositioner.spline = track.trackPathSubmodule.path;
|
||||
stay.isOnTrack = true;
|
||||
stay.UpdateNoteInTrack(EditorManager.instance.songInformation.songTime);
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Ichni.RhythmGame
|
||||
{
|
||||
tap.track = track;
|
||||
tap.trackPositioner = tap.gameObject.GetComponent<SplinePositioner>() ?? tap.gameObject.AddComponent<SplinePositioner>();
|
||||
tap.trackPositioner.autoUpdate = false; // 由调度器 Phase 6 手动刷新采样
|
||||
tap.trackPositioner.spline = track.trackPathSubmodule.path;
|
||||
tap.isOnTrack = true;
|
||||
tap.UpdateNoteInTrack(EditorManager.instance.songInformation.songTime);
|
||||
|
||||
@@ -48,15 +48,26 @@ namespace Ichni.RhythmGame
|
||||
trackPathSubmodule.ClosePath();
|
||||
}
|
||||
|
||||
// 向 TrackManager 注册,通过中废集权 Update 驱动,替代 MonoBehaviour.Update()
|
||||
TrackManager.instance.RegisterTrack(this);
|
||||
// 向 ElementUpdateScheduler 注册 Phase.TrackCore + SplineRebuild
|
||||
var scheduler = CoreServices.UpdateScheduler;
|
||||
scheduler.Register(UpdatePhase.TrackCore, this);
|
||||
scheduler.RegisterTrackSpline(this);
|
||||
|
||||
Refresh();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region [主循环与更新] Update & Refresh
|
||||
// Update() 已移除,改由 TrackManager.ManualTick 集中驱动
|
||||
/// <summary>
|
||||
/// IScheduledElement 实现:在 Phase.TrackCore 阶段更新轨道时间子模块。
|
||||
/// </summary>
|
||||
public override void ScheduledUpdate(UpdatePhase phase, float songTime)
|
||||
{
|
||||
if (phase == UpdatePhase.TrackCore && timeDurationSubmodule.CheckTimeInDuration(songTime))
|
||||
{
|
||||
(trackTimeSubmodule as TrackTimeSubmoduleMovable)?.UpdateTrackPart(songTime);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Refresh()
|
||||
{
|
||||
@@ -69,7 +80,9 @@ namespace Ichni.RhythmGame
|
||||
public override void OnDelete()
|
||||
{
|
||||
if (parentElement is ElementFolder folder) folder.trackList.Remove(this);
|
||||
TrackManager.instance.UnregisterTrack(this);
|
||||
var scheduler = CoreServices.UpdateScheduler;
|
||||
scheduler.Unregister(UpdatePhase.TrackCore, this);
|
||||
scheduler.UnregisterTrackSpline(this);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -88,4 +101,4 @@ namespace Ichni.RhythmGame
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace Ichni.RhythmGame
|
||||
.AddComponent<CrossTrackPoint>();
|
||||
point.Initialize(elementName, id, tags, isFirstGenerated, elementFolder);
|
||||
point.trackPositioner = point.gameObject.AddComponent<SplinePositioner>();
|
||||
point.trackPositioner.autoUpdate = false; // 由调度器 Phase 5 手动刷新采样
|
||||
point.nowAttachedTrackIndex = -1;
|
||||
point.trackListFolder = elementFolder;
|
||||
point.trackSwitch = trackSwitch;
|
||||
@@ -51,13 +52,24 @@ namespace Ichni.RhythmGame
|
||||
public override void AfterInitialize()
|
||||
{
|
||||
base.AfterInitialize();
|
||||
TrackManager.instance.RegisterCrossPoint(this);
|
||||
// 向 ElementUpdateScheduler 注册 Phase.TrackFollower
|
||||
CoreServices.UpdateScheduler.Register(UpdatePhase.TrackFollower, this);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region [生命周期与更新] Update & Refresh
|
||||
// Update() 已移除,改由 TrackManager.ManualTick 集中驱动
|
||||
public void ManualTick(float songTime)
|
||||
/// <summary>
|
||||
/// IScheduledElement 实现:在 Phase.TrackFollower 阶段更新跨轨切分点。
|
||||
/// </summary>
|
||||
public override void ScheduledUpdate(UpdatePhase phase, float songTime)
|
||||
{
|
||||
if (phase == UpdatePhase.TrackFollower)
|
||||
{
|
||||
ManualTick(songTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void ManualTick(float songTime)
|
||||
{
|
||||
if (trackPercent.animations.Count > 0)
|
||||
{
|
||||
@@ -69,7 +81,7 @@ namespace Ichni.RhythmGame
|
||||
|
||||
public override void OnDelete()
|
||||
{
|
||||
TrackManager.instance.UnregisterCrossPoint(this);
|
||||
CoreServices.UpdateScheduler.Unregister(UpdatePhase.TrackFollower, this);
|
||||
}
|
||||
|
||||
public override void Refresh()
|
||||
@@ -92,8 +104,9 @@ namespace Ichni.RhythmGame
|
||||
trackPositioner.spline = trackListFolder.trackList[trackSwitch.value].trackPathSubmodule.path;
|
||||
}
|
||||
|
||||
trackPositioner.RebuildImmediate();
|
||||
trackPositioner.SetPercent(trackPercent.value);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Ichni.RhythmGame
|
||||
public TimeDurationSubmodule timeDurationSubmodule { get; set; }
|
||||
public bool motionApplyRotation;
|
||||
public Vector3 motionEulerAngles;
|
||||
private float SongTime => EditorManager.instance.songInformation.songTime;
|
||||
private float SongTime => CoreServices.TimeProvider.SongTime;
|
||||
#endregion
|
||||
|
||||
#region [生成与初始化] Generation & Initialization
|
||||
@@ -28,6 +28,7 @@ namespace Ichni.RhythmGame
|
||||
head.Initialize(elementName, id, tags, isFirstGenerated, track);
|
||||
head.track = track;
|
||||
head.trackPositioner = head.gameObject.AddComponent<SplinePositioner>();
|
||||
head.trackPositioner.autoUpdate = false; // 由调度器 Phase 5 手动刷新采样
|
||||
head.trackPositioner.spline = track.trackPathSubmodule.path;
|
||||
head.trackTimeSubmoduleMovable = track.trackTimeSubmodule as TrackTimeSubmoduleMovable;
|
||||
|
||||
@@ -54,23 +55,28 @@ namespace Ichni.RhythmGame
|
||||
public override void AfterInitialize()
|
||||
{
|
||||
base.AfterInitialize();
|
||||
TrackManager.instance.RegisterHeadPoint(this);
|
||||
// 向 ElementUpdateScheduler 注册 Phase.TrackFollower
|
||||
CoreServices.UpdateScheduler.Register(UpdatePhase.TrackFollower, this);
|
||||
}
|
||||
|
||||
public override void OnDelete()
|
||||
{
|
||||
base.OnDelete();
|
||||
TrackManager.instance.UnregisterHeadPoint(this);
|
||||
CoreServices.UpdateScheduler.Unregister(UpdatePhase.TrackFollower, this);
|
||||
}
|
||||
|
||||
#region [运行时刷新] Update & Tracking
|
||||
public void ManualTick(float songTime)
|
||||
/// <summary>
|
||||
/// IScheduledElement 实现:在 Phase.TrackFollower 阶段更新轨道头节点位置。
|
||||
/// </summary>
|
||||
public override void ScheduledUpdate(UpdatePhase phase, float songTime)
|
||||
{
|
||||
if (track.timeDurationSubmodule.CheckTimeInDuration(songTime))
|
||||
if (phase == UpdatePhase.TrackFollower && track.timeDurationSubmodule.CheckTimeInDuration(songTime))
|
||||
{
|
||||
trackPositioner.RebuildImmediate();
|
||||
trackPositioner.SetPercent(trackTimeSubmoduleMovable.headPercent);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace Ichni.RhythmGame
|
||||
point.Initialize(elementName, id, tags, isFirstGenerated, track);
|
||||
point.track = track;
|
||||
point.trackPositioner = point.gameObject.AddComponent<SplinePositioner>();
|
||||
point.trackPositioner.autoUpdate = false; // 由调度器 Phase 5 手动刷新采样
|
||||
point.trackPositioner.spline = track.trackPathSubmodule.path;
|
||||
point.trackPercent = trackPercent;
|
||||
|
||||
@@ -52,28 +53,35 @@ namespace Ichni.RhythmGame
|
||||
public override void AfterInitialize()
|
||||
{
|
||||
base.AfterInitialize();
|
||||
TrackManager.instance.RegisterPercentPoint(this);
|
||||
// 向 ElementUpdateScheduler 注册 Phase.TrackFollower
|
||||
CoreServices.UpdateScheduler.Register(UpdatePhase.TrackFollower, this);
|
||||
}
|
||||
|
||||
public override void OnDelete()
|
||||
{
|
||||
base.OnDelete();
|
||||
TrackManager.instance.UnregisterPercentPoint(this);
|
||||
CoreServices.UpdateScheduler.Unregister(UpdatePhase.TrackFollower, this);
|
||||
}
|
||||
|
||||
#region [刷新计算] Update & Refresh
|
||||
public void ManualTick(float songTime)
|
||||
/// <summary>
|
||||
/// IScheduledElement 实现:在 Phase.TrackFollower 阶段更新轨道百分比位置。
|
||||
/// </summary>
|
||||
public override void ScheduledUpdate(UpdatePhase phase, float songTime)
|
||||
{
|
||||
if (trackPercent.animations.Count > 0)
|
||||
if (phase == UpdatePhase.TrackFollower)
|
||||
{
|
||||
trackPercent.UpdateFlexibleFloat(songTime);
|
||||
if (trackPercent.returnType == FlexibleReturnType.MiddleExecuting)
|
||||
if (trackPercent.animations.Count > 0)
|
||||
{
|
||||
float finalValue = trackPercent.value;
|
||||
if (finalValue > 1 && finalValue > Mathf.Floor(finalValue)) finalValue -= Mathf.Floor(finalValue);
|
||||
trackPercent.UpdateFlexibleFloat(songTime);
|
||||
if (trackPercent.returnType == FlexibleReturnType.MiddleExecuting)
|
||||
{
|
||||
float finalValue = trackPercent.value;
|
||||
if (finalValue > 1 && finalValue > Mathf.Floor(finalValue)) finalValue -= Mathf.Floor(finalValue);
|
||||
|
||||
|
||||
trackPositioner.SetPercent(finalValue);
|
||||
trackPositioner.RebuildImmediate();
|
||||
trackPositioner.SetPercent(finalValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,4 +96,4 @@ namespace Ichni.RhythmGame
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class TrackExtraModifier : MonoBehaviour
|
||||
{
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,10 @@ namespace Ichni.RhythmGame
|
||||
|
||||
this.sampleRate = sampleRate;
|
||||
this.path.sampleRate = sampleRate;
|
||||
this.path.updateMode = SplineComputer.UpdateMode.LateUpdate;
|
||||
// 保持默认 UpdateMode.Update,由 SplineComputer 自行检测 transform.hasChanged
|
||||
// 并在自身 Update 周期中按需重采样、通知订阅者(延迟)。
|
||||
// 不可使用 UpdateMode.None:Rebuild(true) 在 None 模式下不触发重采样;
|
||||
// 不可使用 RebuildImmediate:同步重建所有订阅者(含 SplineRenderer 网格生成)导致 ~50ms 帧耗。
|
||||
SetUpSplineComputer(this.trackSpaceType, this.trackSamplingType);
|
||||
|
||||
this.isShowingDisplay = isShowingDisplay;
|
||||
@@ -106,7 +109,7 @@ namespace Ichni.RhythmGame
|
||||
SetUpSplineComputer(trackSpaceType, trackSamplingType);
|
||||
ClosePath();
|
||||
#endif
|
||||
path.Rebuild(true);
|
||||
path.RebuildImmediate();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -59,11 +59,17 @@ namespace Ichni.RhythmGame
|
||||
applyRotationOffset, rotationOffsetMin, rotationOffsetMax, customRotationRuleName,
|
||||
applyScaleOffset, scaleOffsetMin, scaleOffsetMax, customScaleRuleName);
|
||||
|
||||
// 向 TrackManager 注册
|
||||
TrackManager.instance.RegisterObjectTracker(objectTracker);
|
||||
if (isFirstGenerated) objectTracker.AfterInitialize();
|
||||
return objectTracker;
|
||||
}
|
||||
|
||||
public override void AfterInitialize()
|
||||
{
|
||||
base.AfterInitialize();
|
||||
// 向 ElementUpdateScheduler 注册 Phase.TrackFollower
|
||||
CoreServices.UpdateScheduler.Register(UpdatePhase.TrackFollower, this);
|
||||
}
|
||||
|
||||
public void SetSpawnSettings(int spawnCount,
|
||||
Vector2 positionOffsetMin, Vector2 positionOffsetMax, string customPositionRuleName,
|
||||
bool applyRotationOffset, Vector3 rotationOffsetMin, Vector3 rotationOffsetMax, string customRotationRuleName,
|
||||
@@ -99,28 +105,33 @@ namespace Ichni.RhythmGame
|
||||
#endregion
|
||||
|
||||
#region [运行时控制] Update
|
||||
// Update() 已移除,改由 TrackManager.ManualTick 集中驱动
|
||||
public void ManualTick(float songTime)
|
||||
/// <summary>
|
||||
/// IScheduledElement 实现:在 Phase.TrackFollower 阶段控制 objectController 的启用/禁用。
|
||||
/// </summary>
|
||||
public override void ScheduledUpdate(UpdatePhase phase, float songTime)
|
||||
{
|
||||
if (playTime > songTime || stopTime < songTime)
|
||||
if (phase == UpdatePhase.TrackFollower)
|
||||
{
|
||||
if (objectController.enabled)
|
||||
objectController.enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!objectController.enabled)
|
||||
if (playTime > songTime || stopTime < songTime)
|
||||
{
|
||||
objectController.enabled = true;
|
||||
objectController.Spawn();
|
||||
if (objectController.enabled)
|
||||
objectController.enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!objectController.enabled)
|
||||
{
|
||||
objectController.enabled = true;
|
||||
objectController.Spawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDelete()
|
||||
{
|
||||
TrackManager.instance.UnregisterObjectTracker(this);
|
||||
CoreServices.UpdateScheduler.Unregister(UpdatePhase.TrackFollower, this);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,11 +60,17 @@ namespace Ichni.RhythmGame
|
||||
particleTracker.particlesContainer.SetParticleMaterial(themeBundleName, materialName);
|
||||
particleTracker.SetParticleSettings(prewarm, is3D, width, extendDirection, density, lifeTime, isAutoOrient, particleRotation);
|
||||
|
||||
// 向 TrackManager 注册
|
||||
TrackManager.instance.RegisterParticleTracker(particleTracker);
|
||||
if (isFirstGenerated) particleTracker.AfterInitialize();
|
||||
return particleTracker;
|
||||
}
|
||||
|
||||
public override void AfterInitialize()
|
||||
{
|
||||
base.AfterInitialize();
|
||||
// 向 ElementUpdateScheduler 注册 Phase.TrackFollower
|
||||
CoreServices.UpdateScheduler.Register(UpdatePhase.TrackFollower, this);
|
||||
}
|
||||
|
||||
public override void SetDefaultSubmodules()
|
||||
{
|
||||
colorSubmodule = new ColorSubmodule(this, Color.white, true, Color.white, 0);
|
||||
@@ -97,33 +103,38 @@ namespace Ichni.RhythmGame
|
||||
#endregion
|
||||
|
||||
#region [运行更新与刷新] Update & Refresh
|
||||
// Update() 已移除,改由 TrackManager.ManualTick 集中驱动
|
||||
public void ManualTick(float songTime)
|
||||
/// <summary>
|
||||
/// IScheduledElement 实现:在 Phase.TrackFollower 阶段控制粒子播放/暂停/停止。
|
||||
/// </summary>
|
||||
public override void ScheduledUpdate(UpdatePhase phase, float songTime)
|
||||
{
|
||||
if (playTime > songTime || stopTime < songTime)
|
||||
if (phase == UpdatePhase.TrackFollower)
|
||||
{
|
||||
if (particle.isPlaying || particle.isPaused)
|
||||
if (playTime > songTime || stopTime < songTime)
|
||||
{
|
||||
particle.Stop();
|
||||
if (songTime < playTime) { particle.Clear(); }
|
||||
if (particle.isPlaying || particle.isPaused)
|
||||
{
|
||||
particle.Stop();
|
||||
if (songTime < playTime) { particle.Clear(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!CoreServices.TimeProvider.IsPlaying)
|
||||
else
|
||||
{
|
||||
particle.Pause();
|
||||
}
|
||||
else if (!particle.isPlaying)
|
||||
{
|
||||
particle.Play();
|
||||
if (!CoreServices.TimeProvider.IsPlaying)
|
||||
{
|
||||
particle.Pause();
|
||||
}
|
||||
else if (!particle.isPlaying)
|
||||
{
|
||||
particle.Play();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDelete()
|
||||
{
|
||||
TrackManager.instance.UnregisterParticleTracker(this);
|
||||
CoreServices.UpdateScheduler.Unregister(UpdatePhase.TrackFollower, this);
|
||||
}
|
||||
|
||||
public override void Refresh()
|
||||
@@ -143,4 +154,4 @@ namespace Ichni.RhythmGame
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Ichni.RhythmGame;
|
||||
using SLSUtilities.General;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni
|
||||
{
|
||||
/// <summary>
|
||||
/// 编辑器 AnimationManager:集中管理场上所有 AnimationBase 实例的逐帧更新。
|
||||
/// 替代 AnimationBase.Update() 中大量零散的 MonoBehaviour 帧回调,
|
||||
/// 由 EditorManager.Update 统一驱动,减少 Update() 调用开销。
|
||||
/// 倒序遍历防止在更新途中某个动画自行销毁导致越界。
|
||||
/// </summary>
|
||||
public class AnimationManager : Singleton<AnimationManager>
|
||||
{
|
||||
#region [单例别名] Singleton Alias
|
||||
public new static AnimationManager instance => Instance;
|
||||
#endregion
|
||||
|
||||
#region [活跃动画列表] Active Animation List
|
||||
private readonly List<AnimationBase> _activeAnimations = new List<AnimationBase>(200);
|
||||
#endregion
|
||||
|
||||
#region [注册与注销] Registration
|
||||
public void RegisterAnimation(AnimationBase anim)
|
||||
{
|
||||
if (!_activeAnimations.Contains(anim)) _activeAnimations.Add(anim);
|
||||
}
|
||||
public void UnregisterAnimation(AnimationBase anim) => _activeAnimations.Remove(anim);
|
||||
#endregion
|
||||
|
||||
#region [中央集权主循环] Manager-Driven Tick
|
||||
/// <summary>
|
||||
/// 由 EditorManager.Update 统一调度。
|
||||
/// 倒序遍历以防在更新途中某个动画自行销毁导致列表越界。
|
||||
/// </summary>
|
||||
public void ManualTick(float songTime)
|
||||
{
|
||||
for (int i = _activeAnimations.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var anim = _activeAnimations[i];
|
||||
if (!anim.isActiveAndEnabled) continue;
|
||||
if (anim.timeDurationSubmodule.CheckTimeInDuration(songTime))
|
||||
{
|
||||
anim.InvokeUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f068825cf8c22b4fa573d55654c2474
|
||||
@@ -16,6 +16,7 @@ namespace Ichni
|
||||
/// 游戏/谱面数据由 ProjectContainer 持有;
|
||||
/// 此处的属性均为转发属性,保持所有外部调用点零改动。
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(-100)]
|
||||
public class EditorManager : Singleton<EditorManager>
|
||||
{
|
||||
#region [单例别名] Singleton Alias
|
||||
@@ -37,13 +38,20 @@ namespace Ichni
|
||||
public BackgroundController backgroundController;
|
||||
public SimpleGridController gridController;
|
||||
public CameraManager cameraManager;
|
||||
public NoteManager noteManager;
|
||||
public TrackManager trackManager;
|
||||
public AnimationManager animationManager;
|
||||
public Canvas judgeHintCanvas;
|
||||
public Canvas inspectorCanvas;
|
||||
public Timeline timeline;
|
||||
public PanelDrawer panelDrawer;
|
||||
|
||||
/// <summary>集中式元素更新调度器,替代原先分散的帧更新逻辑</summary>
|
||||
[NonSerialized]
|
||||
public ElementUpdateScheduler updateScheduler;
|
||||
|
||||
/// <summary>
|
||||
/// NoteManager 转发属性。NoteManager 现为纯 C# 类,由 updateScheduler 内部持有。
|
||||
/// 保留此属性以兼容现有调用点。
|
||||
/// </summary>
|
||||
public NoteManager noteManager => updateScheduler?.NoteScheduler;
|
||||
#endregion
|
||||
|
||||
#region [编辑器首选项转发属性] Editor Preferences Forwarding Properties
|
||||
@@ -124,6 +132,14 @@ namespace Ichni
|
||||
// 不再直接依赖 EditorManager.instance.musicPlayer
|
||||
CoreServices.TimeProvider = musicPlayer;
|
||||
|
||||
// 初始化集中式更新调度器:
|
||||
// BeatmapContainer 在项目加载后才可用,使用 Func 延迟解析
|
||||
// NoteManager 在调度器内部创建,不再依赖外部 Singleton
|
||||
updateScheduler = new ElementUpdateScheduler(
|
||||
() => beatmapContainer
|
||||
);
|
||||
CoreServices.UpdateScheduler = updateScheduler;
|
||||
|
||||
if (!ES3.FileExists(Application.streamingAssetsPath + "/EditorSettings.es3"))
|
||||
{
|
||||
editorSettings = new EditorSettings(300, 3, 100, 100, 60);
|
||||
@@ -173,42 +189,14 @@ namespace Ichni
|
||||
|
||||
projectManager.autoSaveManager.UpdateAutoSave();
|
||||
|
||||
// 统一调度: Animation → Submodules → Track → Note
|
||||
float songTime = CoreServices.TimeProvider.SongTime;
|
||||
updateScheduler.TickEarly(songTime);
|
||||
}
|
||||
|
||||
animationManager.ManualTick(songTime);
|
||||
|
||||
// 手动执行原本属于 UniRx 的每帧调度,消灭不可控的时序错乱
|
||||
for (int i = 0; i < beatmapContainer.gameElementList.Count; i++)
|
||||
{
|
||||
var element = beatmapContainer.gameElementList[i];
|
||||
if (element == null) continue;
|
||||
|
||||
if (element is IHaveTimeDurationSubmodule timeHost && !(element is NoteBase))
|
||||
{
|
||||
timeHost.timeDurationSubmodule?.UpdateTimeDuration(songTime);
|
||||
}
|
||||
|
||||
if (element is IHaveDirtyMarkSubmodule dirtyHost)
|
||||
{
|
||||
dirtyHost.dirtyMarkSubmodule?.ExecuteDeferredRefresh();
|
||||
}
|
||||
|
||||
if (element.gameObject.activeSelf)
|
||||
{
|
||||
if (element is IHaveTransformSubmodule transformHost)
|
||||
{
|
||||
transformHost.UpdateTransform();
|
||||
}
|
||||
if (element is IHaveColorSubmodule colorHost)
|
||||
{
|
||||
colorHost.UpdateColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trackManager.ManualTick(songTime);
|
||||
noteManager.ManualTick(songTime);
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
updateScheduler.TickLate();
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
304
Assets/Scripts/Manager/ElementUpdateScheduler.cs
Normal file
304
Assets/Scripts/Manager/ElementUpdateScheduler.cs
Normal file
@@ -0,0 +1,304 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Ichni.RhythmGame;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni
|
||||
{
|
||||
/// <summary>
|
||||
/// 集中式元素更新调度器。
|
||||
/// 将所有 GameElement 的帧更新按 9 个阶段有序执行,
|
||||
/// 替代原先分散在 EditorManager.Update()、各子管理器、MonoBehaviour.Update() 中的零散更新。
|
||||
///
|
||||
/// 调度器分为两个执行阶段,分别在 Update 和 LateUpdate 中调用:
|
||||
///
|
||||
/// TickEarly (Update, EditorManager [order -100])
|
||||
/// Phase 0 TimeDuration — 判定元素激活/隐藏
|
||||
/// Phase 1 Animation — 更新动画值,设置脏标记
|
||||
/// Phase 2 Apply — 执行 DirtyRefresh + Transform + Color
|
||||
///
|
||||
/// --- Unity 执行其他 Update (包括 SplineComputer.Update [order 0]) ---
|
||||
/// SplineComputer 检测 transform.hasChanged,重新采样,延迟通知订阅者。
|
||||
///
|
||||
/// TickLate (LateUpdate, EditorManager [order -100])
|
||||
/// Phase 3 SplineRebuild — 保留占位(SplineComputer 已在 Update 中自行处理)
|
||||
/// Phase 4 TrackCore — 更新轨道时间、裁剪区间
|
||||
/// Phase 5 TrackFollower — CrossTrackPoint / Tracker / HeadPoint / PercentPoint
|
||||
/// Phase 6 Note — 音符可见性、轨道位置、判定、特效
|
||||
/// Phase 7 Effect — ParticleEmitter / TimeEffectsCollection / LookAt 旋转覆盖
|
||||
/// Phase 8 Misc — SkyboxSubsetter / LowPriorityActions
|
||||
///
|
||||
/// 此分离设计消除了 Dreamteck Spline 的一帧延迟:
|
||||
/// Phase 2 (Update) 修改 Transform → SplineComputer.Update() 检测 hasChanged 并重采样
|
||||
/// → Phase 5/6 (LateUpdate) 调用 RebuildImmediate() 获取最新采样后 SetPercent()。
|
||||
///
|
||||
/// 所有通过调度器管理的 SplinePositioner 应设置 autoUpdate = false,
|
||||
/// 由调度器在 Phase 5/6 中手动调用 RebuildImmediate() 刷新采样。
|
||||
///
|
||||
/// 设计要点:
|
||||
/// - AnimationManager 和 TrackManager 已删除,其逻辑由各 GameElement 子类通过
|
||||
/// IScheduledElement.ScheduledUpdate() 自行处理。
|
||||
/// - NoteManager 因内部逻辑复杂(二分搜索、时间可逆),保留为纯 C# 类,
|
||||
/// 由本调度器内部持有和驱动。
|
||||
/// - Phase 0(TimeDuration)和 Phase 2(Apply)仍有 [Legacy] 内联循环,
|
||||
/// 待后续将 TimeDurationSubmodule / TransformSubmodule / ColorSubmodule
|
||||
/// 迁移到 IScheduledElement 后移除。
|
||||
/// </summary>
|
||||
public class ElementUpdateScheduler
|
||||
{
|
||||
#region [常量] Constants
|
||||
|
||||
/// <summary>阶段总数(TimeDuration..Misc,步长 10,共 9 个)</summary>
|
||||
private const int PhaseCount = 9;
|
||||
|
||||
/// <summary>阶段枚举值到数组索引的步长除数</summary>
|
||||
private const int PhaseStep = 10;
|
||||
|
||||
private static readonly UpdatePhase[] AllPhases = (UpdatePhase[])Enum.GetValues(typeof(UpdatePhase));
|
||||
|
||||
/// <summary>TickEarly 执行的最大 Phase(含)</summary>
|
||||
private const UpdatePhase EarlyPhaseCutoff = UpdatePhase.Apply;
|
||||
|
||||
#endregion
|
||||
|
||||
#region [阶段元素列表] Phase Element Lists
|
||||
|
||||
/// <summary>
|
||||
/// 每个阶段的已注册 IScheduledElement 列表。
|
||||
/// 使用数组替代 Dictionary<UpdatePhase, ...>,
|
||||
/// 索引通过 (int)phase / 10 直接映射,消除哈希查找开销。
|
||||
/// </summary>
|
||||
private readonly List<IScheduledElement>[] _phaseElements;
|
||||
|
||||
/// <summary>将 UpdatePhase 枚举值转换为数组索引。</summary>
|
||||
private static int PhaseIndex(UpdatePhase phase) => (int)phase / PhaseStep;
|
||||
|
||||
#endregion
|
||||
|
||||
#region [轨道 Spline 列表] Track Spline List
|
||||
|
||||
/// <summary>Phase 3 专用:需要手动重建 SplineComputer 的 Track 列表(保留供后续优化)</summary>
|
||||
private readonly List<Track> _trackSplines = new List<Track>(50);
|
||||
|
||||
#endregion
|
||||
|
||||
#region [内部管理器] Internal Managers
|
||||
|
||||
/// <summary>音符管理器:由调度器内部持有,Phase 6 调用</summary>
|
||||
private readonly NoteManager _noteManager;
|
||||
|
||||
/// <summary>BeatmapContainer 延迟提供器(项目加载后才可用)</summary>
|
||||
private readonly Func<BeatmapContainer> _beatmapContainerProvider;
|
||||
|
||||
/// <summary>公开 NoteManager 以供 NoteBase 等外部类注册/更新音符信息</summary>
|
||||
public NoteManager NoteScheduler => _noteManager;
|
||||
|
||||
#endregion
|
||||
|
||||
#region [缓存] Cached State
|
||||
|
||||
/// <summary>TickEarly 中缓存的 songTime,供 TickLate 使用</summary>
|
||||
private float _cachedSongTime;
|
||||
|
||||
/// <summary>TickEarly 中缓存的 BeatmapContainer 引用</summary>
|
||||
private BeatmapContainer _cachedBeatmapContainer;
|
||||
|
||||
#endregion
|
||||
|
||||
#region [构造函数] Constructor
|
||||
|
||||
/// <summary>
|
||||
/// 创建调度器实例。NoteManager 在内部创建,不再依赖外部 Singleton。
|
||||
/// </summary>
|
||||
/// <param name="beatmapContainerProvider">
|
||||
/// BeatmapContainer 延迟提供器。因 BeatmapContainer 在项目加载后才可用,
|
||||
/// 使用 Func 延迟解析,避免构造时空引用。
|
||||
/// </param>
|
||||
public ElementUpdateScheduler(Func<BeatmapContainer> beatmapContainerProvider)
|
||||
{
|
||||
_noteManager = new NoteManager();
|
||||
_beatmapContainerProvider = beatmapContainerProvider;
|
||||
|
||||
_phaseElements = new List<IScheduledElement>[PhaseCount];
|
||||
for (int i = 0; i < PhaseCount; i++)
|
||||
{
|
||||
_phaseElements[i] = new List<IScheduledElement>();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [注册与注销] Registration
|
||||
|
||||
/// <summary>将元素注册到指定的更新阶段。同一元素可注册到多个阶段。</summary>
|
||||
public void Register(UpdatePhase phase, IScheduledElement element)
|
||||
{
|
||||
var list = _phaseElements[PhaseIndex(phase)];
|
||||
if (!list.Contains(element))
|
||||
{
|
||||
list.Add(element);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>将元素从指定阶段注销。</summary>
|
||||
public void Unregister(UpdatePhase phase, IScheduledElement element)
|
||||
{
|
||||
_phaseElements[PhaseIndex(phase)].Remove(element);
|
||||
}
|
||||
|
||||
/// <summary>注册 Track 的 SplineComputer 到 Phase 3(SplineRebuild)。</summary>
|
||||
public void RegisterTrackSpline(Track track)
|
||||
{
|
||||
if (!_trackSplines.Contains(track))
|
||||
{
|
||||
_trackSplines.Add(track);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>从 Phase 3 注销 Track 的 SplineComputer。</summary>
|
||||
public void UnregisterTrackSpline(Track track)
|
||||
{
|
||||
_trackSplines.Remove(track);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [主循环 - 早期阶段] Main Tick — Early Phases (Update)
|
||||
|
||||
/// <summary>
|
||||
/// 早期阶段调度,由 EditorManager.Update() 调用。
|
||||
/// 执行 Phase 0(TimeDuration)→ Phase 1(Animation)→ Phase 2(Apply)。
|
||||
/// Phase 2 完成后 Transform 已更新;SplineComputer 将在后续的 Update [order 0] 中检测变化。
|
||||
/// </summary>
|
||||
public void TickEarly(float songTime)
|
||||
{
|
||||
_cachedBeatmapContainer = _beatmapContainerProvider?.Invoke();
|
||||
_cachedSongTime = songTime;
|
||||
if (_cachedBeatmapContainer == null) return;
|
||||
|
||||
// ─── Phase 0: TimeDuration ────────────────────────────────────────
|
||||
TickPhase(UpdatePhase.TimeDuration, songTime);
|
||||
TickTimeDurationLegacy(_cachedBeatmapContainer, songTime);
|
||||
|
||||
// ─── Phase 1: Animation ───────────────────────────────────────────
|
||||
TickPhase(UpdatePhase.Animation, songTime);
|
||||
|
||||
// ─── Phase 2: Apply ───────────────────────────────────────────────
|
||||
TickPhase(UpdatePhase.Apply, songTime);
|
||||
TickApplyLegacy(_cachedBeatmapContainer);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [主循环 - 晚期阶段] Main Tick — Late Phases (LateUpdate)
|
||||
|
||||
/// <summary>
|
||||
/// 晚期阶段调度,由 EditorManager.LateUpdate() 调用。
|
||||
/// 此时所有 Update() 已完成,SplineComputer 已检测 transform.hasChanged 并重新采样。
|
||||
/// 各跟踪器通过 RebuildImmediate() 获取最新 Spline 采样后再 SetPercent()。
|
||||
/// </summary>
|
||||
public void TickLate()
|
||||
{
|
||||
if (_cachedBeatmapContainer == null) return;
|
||||
float songTime = _cachedSongTime;
|
||||
|
||||
// ─── Phase 3: SplineRebuild ───────────────────────────────────────
|
||||
// SplineComputer 使用默认 UpdateMode.Update,已在 Update 中自行处理。
|
||||
// 此阶段保留占位,不执行手动 Rebuild。
|
||||
TickPhase(UpdatePhase.SplineRebuild, songTime);
|
||||
|
||||
// ─── Phase 4: TrackCore ───────────────────────────────────────────
|
||||
TickPhase(UpdatePhase.TrackCore, songTime);
|
||||
|
||||
// ─── Phase 5: TrackFollower ───────────────────────────────────────
|
||||
// 各跟踪器在 ScheduledUpdate 中先调用 RebuildImmediate() 再 SetPercent()
|
||||
TickPhase(UpdatePhase.TrackFollower, songTime);
|
||||
|
||||
// ─── Phase 6: Note ────────────────────────────────────────────────
|
||||
TickPhase(UpdatePhase.Note, songTime);
|
||||
_noteManager.ManualTick(songTime);
|
||||
|
||||
// ─── Phase 7: Effect ──────────────────────────────────────────────
|
||||
TickPhase(UpdatePhase.Effect, songTime);
|
||||
|
||||
// ─── Phase 8: Misc ────────────────────────────────────────────────
|
||||
TickPhase(UpdatePhase.Misc, songTime);
|
||||
_cachedBeatmapContainer.ExecuteLowPriorityActions();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [阶段执行] Phase Execution
|
||||
|
||||
/// <summary>
|
||||
/// 执行指定阶段的所有已注册 IScheduledElement。
|
||||
/// 倒序遍历防止更新途中元素自行销毁导致越界。
|
||||
/// </summary>
|
||||
private void TickPhase(UpdatePhase phase, float songTime)
|
||||
{
|
||||
var list = _phaseElements[PhaseIndex(phase)];
|
||||
for (int i = list.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var element = list[i];
|
||||
if (element != null && element.IsScheduledActive)
|
||||
{
|
||||
element.ScheduledUpdate(phase, songTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Legacy] 过渡期内联逻辑(元素迁移后移除)
|
||||
|
||||
/// <summary>
|
||||
/// [Legacy] Phase 0 内联逻辑:遍历 gameElementList 执行 TimeDuration 检查。
|
||||
/// 当所有 IHaveTimeDurationSubmodule 元素迁移到调度器后,此方法将被移除。
|
||||
/// </summary>
|
||||
private void TickTimeDurationLegacy(BeatmapContainer beatmapContainer, float songTime)
|
||||
{
|
||||
for (int i = 0; i < beatmapContainer.gameElementList.Count; i++)
|
||||
{
|
||||
var element = beatmapContainer.gameElementList[i];
|
||||
if (element == null) continue;
|
||||
|
||||
if (element is IHaveTimeDurationSubmodule timeHost && !(element is NoteBase))
|
||||
{
|
||||
timeHost.timeDurationSubmodule?.UpdateTimeDuration(songTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [Legacy] Phase 2 内联逻辑:遍历 gameElementList 执行 DirtyRefresh / Transform / Color。
|
||||
/// 当所有相关子模块接口元素迁移到调度器后,此方法将被移除。
|
||||
/// </summary>
|
||||
private void TickApplyLegacy(BeatmapContainer beatmapContainer)
|
||||
{
|
||||
for (int i = 0; i < beatmapContainer.gameElementList.Count; i++)
|
||||
{
|
||||
var element = beatmapContainer.gameElementList[i];
|
||||
if (element == null) continue;
|
||||
|
||||
if (element is IHaveDirtyMarkSubmodule dirtyHost)
|
||||
{
|
||||
dirtyHost.dirtyMarkSubmodule?.ExecuteDeferredRefresh();
|
||||
}
|
||||
|
||||
if (element.gameObject.activeSelf)
|
||||
{
|
||||
if (element is IHaveTransformSubmodule transformHost)
|
||||
{
|
||||
transformHost.UpdateTransform();
|
||||
}
|
||||
if (element is IHaveColorSubmodule colorHost)
|
||||
{
|
||||
colorHost.UpdateColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Manager/ElementUpdateScheduler.cs.meta
Normal file
2
Assets/Scripts/Manager/ElementUpdateScheduler.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2086c4a4da4d364428cdff9ac604ad38
|
||||
@@ -1,10 +1,8 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Ichni.RhythmGame;
|
||||
using SLSUtilities.General;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni.RhythmGame
|
||||
namespace Ichni
|
||||
{
|
||||
/// <summary>
|
||||
/// 编辑器 NoteManager:集中管理场上所有 Note 的激活/隐藏与逐帧更新。
|
||||
@@ -16,13 +14,12 @@ namespace Ichni.RhythmGame
|
||||
/// - 时间区间内的 Note → 激活并更新
|
||||
/// - 时间区间外的 Note → 隐藏
|
||||
/// 保证无论时间如何跳转,Note 的可见性和状态始终与 songTime 一致。
|
||||
///
|
||||
/// 此类为纯 C# 类,由 ElementUpdateScheduler 内部持有和驱动,
|
||||
/// 不再继承 Singleton / MonoBehaviour。
|
||||
/// </summary>
|
||||
public class NoteManager : Singleton<NoteManager>
|
||||
public class NoteManager
|
||||
{
|
||||
#region [单例别名] Singleton Alias
|
||||
public new static NoteManager instance => Instance;
|
||||
#endregion
|
||||
|
||||
#region [数据结构] Note Record
|
||||
/// <summary>Note 条目:存储 Note 本身及其激活/消失的时间阈值</summary>
|
||||
private struct NoteRecord
|
||||
@@ -91,7 +88,7 @@ namespace Ichni.RhythmGame
|
||||
|
||||
#region [中央集权主循环] Manager-Driven Tick
|
||||
/// <summary>
|
||||
/// 由 EditorManager.Update 统一调度。
|
||||
/// 由 ElementUpdateScheduler.Tick 在 Phase.Note 阶段调用。
|
||||
///
|
||||
/// <para>
|
||||
/// 编辑器时间可逆策略:每帧通过二分查找定位当前 songTime 覆盖的激活窗口,
|
||||
@@ -124,9 +121,6 @@ namespace Ichni.RhythmGame
|
||||
right = mid - 1;
|
||||
}
|
||||
}
|
||||
// firstInWindow 此时指向最后一个 activationTime <= songTime 的 Note,
|
||||
// 从 0 向左扫描(也就是从头扫描到 firstInWindow)都可能在窗口内
|
||||
// 实际需要:activationTime <= songTime && finishTime >= songTime
|
||||
|
||||
// 3. 遍历所有 Note,判断是否在当前窗口内
|
||||
for (int i = 0; i < count; i++)
|
||||
@@ -156,4 +150,4 @@ namespace Ichni.RhythmGame
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Ichni.RhythmGame;
|
||||
using SLSUtilities.General;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni
|
||||
{
|
||||
/// <summary>
|
||||
/// 编辑器 TrackManager:集中管理场上所有轨道相关组件的逐帧更新。
|
||||
/// 替代各组件自身持有的 Update() 调用,消除大量零散的 MonoBehaviour 帧回调开销。
|
||||
/// 通过 ManualTick() 由 EditorManager 统一调度,确保时序可控。
|
||||
/// </summary>
|
||||
public class TrackManager : Singleton<TrackManager>
|
||||
{
|
||||
#region [单例别名] Singleton Alias
|
||||
public new static TrackManager instance => Instance;
|
||||
#endregion
|
||||
|
||||
#region [活跃组件列表] Active Component Lists
|
||||
private readonly List<Track> _activeTracks = new List<Track>(50);
|
||||
private readonly List<CrossTrackPoint> _activeCrossPoints = new List<CrossTrackPoint>(50);
|
||||
private readonly List<ObjectTracker> _activeObjectTrackers = new List<ObjectTracker>(50);
|
||||
private readonly List<ParticleTracker> _activeParticleTrackers = new List<ParticleTracker>(50);
|
||||
private readonly List<TrackHeadPoint> _activeHeadPoints = new List<TrackHeadPoint>(50);
|
||||
private readonly List<TrackPercentPoint> _activePercentPoints = new List<TrackPercentPoint>(50);
|
||||
#endregion
|
||||
|
||||
#region [注册与注销] Registration
|
||||
public void RegisterTrack(Track track)
|
||||
{
|
||||
if (!_activeTracks.Contains(track)) _activeTracks.Add(track);
|
||||
}
|
||||
public void UnregisterTrack(Track track) => _activeTracks.Remove(track);
|
||||
|
||||
public void RegisterCrossPoint(CrossTrackPoint point)
|
||||
{
|
||||
if (!_activeCrossPoints.Contains(point)) _activeCrossPoints.Add(point);
|
||||
}
|
||||
public void UnregisterCrossPoint(CrossTrackPoint point) => _activeCrossPoints.Remove(point);
|
||||
|
||||
public void RegisterObjectTracker(ObjectTracker tracker)
|
||||
{
|
||||
if (!_activeObjectTrackers.Contains(tracker)) _activeObjectTrackers.Add(tracker);
|
||||
}
|
||||
public void UnregisterObjectTracker(ObjectTracker tracker) => _activeObjectTrackers.Remove(tracker);
|
||||
|
||||
public void RegisterParticleTracker(ParticleTracker tracker)
|
||||
{
|
||||
if (!_activeParticleTrackers.Contains(tracker)) _activeParticleTrackers.Add(tracker);
|
||||
}
|
||||
public void UnregisterParticleTracker(ParticleTracker tracker) => _activeParticleTrackers.Remove(tracker);
|
||||
|
||||
public void RegisterHeadPoint(TrackHeadPoint point)
|
||||
{
|
||||
if (!_activeHeadPoints.Contains(point)) _activeHeadPoints.Add(point);
|
||||
}
|
||||
public void UnregisterHeadPoint(TrackHeadPoint point) => _activeHeadPoints.Remove(point);
|
||||
|
||||
public void RegisterPercentPoint(TrackPercentPoint point)
|
||||
{
|
||||
if (!_activePercentPoints.Contains(point)) _activePercentPoints.Add(point);
|
||||
}
|
||||
public void UnregisterPercentPoint(TrackPercentPoint point) => _activePercentPoints.Remove(point);
|
||||
#endregion
|
||||
|
||||
#region [中央集权主循环] Manager-Driven Tick
|
||||
/// <summary>
|
||||
/// 由 EditorManager.Update 统一调度。
|
||||
/// </summary>
|
||||
public void ManualTick(float songTime)
|
||||
{
|
||||
// 1. Track:更新轨道时间子模块
|
||||
for (int i = 0; i < _activeTracks.Count; i++)
|
||||
{
|
||||
var track = _activeTracks[i];
|
||||
if (!track.isActiveAndEnabled) continue;
|
||||
if (track.timeDurationSubmodule.CheckTimeInDuration(songTime))
|
||||
{
|
||||
(track.trackTimeSubmodule as TrackTimeSubmoduleMovable)?.UpdateTrackPart(songTime);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. CrossTrackPoint:更新跨轨切分点
|
||||
for (int i = 0; i < _activeCrossPoints.Count; i++)
|
||||
{
|
||||
var point = _activeCrossPoints[i];
|
||||
if (!point.isActiveAndEnabled) continue;
|
||||
point.ManualTick(songTime);
|
||||
}
|
||||
|
||||
// 3. ObjectTracker:更新轨道物体跟踪器
|
||||
for (int i = 0; i < _activeObjectTrackers.Count; i++)
|
||||
{
|
||||
var tracker = _activeObjectTrackers[i];
|
||||
if (!tracker.isActiveAndEnabled) continue;
|
||||
tracker.ManualTick(songTime);
|
||||
}
|
||||
|
||||
// 4. ParticleTracker:更新轨道粒子跟踪器
|
||||
for (int i = 0; i < _activeParticleTrackers.Count; i++)
|
||||
{
|
||||
var tracker = _activeParticleTrackers[i];
|
||||
if (!tracker.isActiveAndEnabled) continue;
|
||||
tracker.ManualTick(songTime);
|
||||
}
|
||||
|
||||
// 5. TrackHeadPoint:更新轨道头节点
|
||||
for (int i = 0; i < _activeHeadPoints.Count; i++)
|
||||
{
|
||||
var point = _activeHeadPoints[i];
|
||||
if (!point.isActiveAndEnabled) continue;
|
||||
point.ManualTick(songTime);
|
||||
}
|
||||
|
||||
// 6. TrackPercentPoint:更新轨道百分比节点
|
||||
for (int i = 0; i < _activePercentPoints.Count; i++)
|
||||
{
|
||||
var point = _activePercentPoints[i];
|
||||
if (!point.isActiveAndEnabled) continue;
|
||||
point.ManualTick(songTime);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3435f487f7c1c654e93adba65c7de915
|
||||
Reference in New Issue
Block a user