同步
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1197,7 +1197,7 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
serializationData:
|
||||
SerializedFormat: 0
|
||||
SerializedFormat: 2
|
||||
SerializedBytes:
|
||||
ReferencedUnityObjects: []
|
||||
SerializedBytesString:
|
||||
@@ -2074,7 +2074,7 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
serializationData:
|
||||
SerializedFormat: 0
|
||||
SerializedFormat: 2
|
||||
SerializedBytes:
|
||||
ReferencedUnityObjects: []
|
||||
SerializedBytesString:
|
||||
@@ -5734,14 +5734,20 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
serializationData:
|
||||
SerializedFormat: 0
|
||||
SerializedBytes: 2d010e000000700072006f006a0065006300740049006e0066006f005f0042004d002d010b00000073006f006e00670049006e0066006f005f0042004d00
|
||||
SerializedFormat: 2
|
||||
SerializedBytes:
|
||||
ReferencedUnityObjects: []
|
||||
SerializedBytesString:
|
||||
Prefab: {fileID: 0}
|
||||
PrefabModificationsReferencedUnityObjects: []
|
||||
PrefabModifications: []
|
||||
SerializationNodes: []
|
||||
SerializationNodes:
|
||||
- Name: projectInfo_BM
|
||||
Entry: 6
|
||||
Data:
|
||||
- Name: songInfo_BM
|
||||
Entry: 6
|
||||
Data:
|
||||
isLoadedProject: 0
|
||||
loadedProjectName:
|
||||
isRecovery: 0
|
||||
|
||||
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>
|
||||
|
||||
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
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fda5a36c77610481e976e5d7ceb764ab
|
||||
guid: 1c8fad83bf015114ca0c02e8f42058f7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
224004
Assets/StreamingAssets/AutoSave/solitudes hd/AutoSave_0.json
Normal file
224004
Assets/StreamingAssets/AutoSave/solitudes hd/AutoSave_0.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 28802a2f6f0ff2942bd2a248b9b68960
|
||||
guid: cc21fb1bb36766044abf71b576d7d12c
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
8
Assets/StreamingAssets/Export/Your_Shadow.meta
Normal file
8
Assets/StreamingAssets/Export/Your_Shadow.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6392436ca0d07464b98d158fd6cad5e2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/StreamingAssets/Export/Your_Shadow/Beatmap.bytes
Normal file
BIN
Assets/StreamingAssets/Export/Your_Shadow/Beatmap.bytes
Normal file
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88f0993afef43b34eb23a79fbdd94e9b
|
||||
guid: d0f3b53f3516db445b3bb38127c6a03f
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
BIN
Assets/StreamingAssets/Export/Your_Shadow/CommandScripts.bytes
Normal file
BIN
Assets/StreamingAssets/Export/Your_Shadow/CommandScripts.bytes
Normal file
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24c93fe194f1c4349b4ff6b0cd5aac1f
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/StreamingAssets/Export/Your_Shadow/ProjectInfo.bytes
Normal file
BIN
Assets/StreamingAssets/Export/Your_Shadow/ProjectInfo.bytes
Normal file
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d02ffa27542509e488e8d8bab70a3c16
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/StreamingAssets/Export/Your_Shadow/SongInfo.bytes
Normal file
BIN
Assets/StreamingAssets/Export/Your_Shadow/SongInfo.bytes
Normal file
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 22676d2e0ea9c194b84b33c59c48fac1
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/StreamingAssets/Export/solitudes hd.meta
Normal file
8
Assets/StreamingAssets/Export/solitudes hd.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b64bd0799a2d9a4caade060fa09c19c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/StreamingAssets/Export/solitudes hd/Beatmap.bytes
Normal file
BIN
Assets/StreamingAssets/Export/solitudes hd/Beatmap.bytes
Normal file
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7bfe901bed086ee4187e2011c5f107da
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1 @@
|
||||
ŰQ[pßńîÔˇż\Ą\-tçÎO•ţX ź¤D=zŢüLć'č+Úˇˇř‚ŁňônÓÉkV<6B>ŚŃy¨}©ép]_u€đŃŠ/ř@Ç6{ôt.üOŘDŠ—vyxł$Żé˘<C3A9>eAŻŘâ#’vFă
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ac25c78d1cee8744b81b36c6f236364
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/StreamingAssets/Export/solitudes hd/ProjectInfo.bytes
Normal file
BIN
Assets/StreamingAssets/Export/solitudes hd/ProjectInfo.bytes
Normal file
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c39126d70d6e28a49bce803a52238933
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1 @@
|
||||
ŕ“€qĎr3¬z--}ŢLĆ‘Ëc\<zű_Ô<5F>Ăž@hÉË6łżPźjD`Ţő|†vm|ť+Ë•Id‹
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08acf28f1d0ab4841a2cc84237f640b1
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/StreamingAssets/Projects/Your_Shadow.meta
Normal file
8
Assets/StreamingAssets/Projects/Your_Shadow.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1890190e17022134e8de82377cd88405
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
199809
Assets/StreamingAssets/Projects/Your_Shadow/Beatmap.json
Normal file
199809
Assets/StreamingAssets/Projects/Your_Shadow/Beatmap.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 274b0a26f8a8318458380defb69d4325
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"CommandScripts" : {
|
||||
"__type" : "Ichni.RhythmGame.Beatmap.CommandScripts_BM,Assembly-CSharp",
|
||||
"value" : {
|
||||
"commandList" : [
|
||||
|
||||
],
|
||||
"attachedElementGuid" : {
|
||||
"value" : "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e277fe47b357c964a92c78254687f2d6
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
23
Assets/StreamingAssets/Projects/Your_Shadow/ProjectInfo.json
Normal file
23
Assets/StreamingAssets/Projects/Your_Shadow/ProjectInfo.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"ProjectInformation" : {
|
||||
"__type" : "Ichni.RhythmGame.Beatmap.ProjectInformation_BM,Assembly-CSharp",
|
||||
"value" : {
|
||||
"projectName" : "Your_Shadow",
|
||||
"creatorName" : "U_Mora",
|
||||
"editorVersion" : "0.1.0",
|
||||
"createTime" : "2026\/1\/6 22:14:38",
|
||||
"lastSaveTime" : "2026\/5\/29 20:18:32",
|
||||
"selectedThemeBundleList" : [
|
||||
"basic","departure_to_multiverse"
|
||||
],
|
||||
"tagManager" : {
|
||||
"tagMatchers" : [
|
||||
|
||||
]
|
||||
},
|
||||
"attachedElementGuid" : {
|
||||
"value" : "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ce70c5a293e68145941a65c07a0e14d
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
14
Assets/StreamingAssets/Projects/Your_Shadow/SongInfo.json
Normal file
14
Assets/StreamingAssets/Projects/Your_Shadow/SongInfo.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"SongInformation" : {
|
||||
"__type" : "Ichni.RhythmGame.Beatmap.SongInformation_BM,Assembly-CSharp",
|
||||
"value" : {
|
||||
"songName" : "Your Shadow.wav",
|
||||
"bpm" : 180,
|
||||
"delay" : 0,
|
||||
"offset" : -0.6,
|
||||
"attachedElementGuid" : {
|
||||
"value" : "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d3f1337c61fbc944b94aad9c9eb0459
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/StreamingAssets/Projects/Your_Shadow/Your Shadow.wav
Normal file
BIN
Assets/StreamingAssets/Projects/Your_Shadow/Your Shadow.wav
Normal file
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4ccfa8b82ef12634c981472b34795c2c
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/StreamingAssets/Projects/solitudes hd.meta
Normal file
8
Assets/StreamingAssets/Projects/solitudes hd.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9498aebaaae72d24ca5096f71fef4552
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
224004
Assets/StreamingAssets/Projects/solitudes hd/Beatmap.json
Normal file
224004
Assets/StreamingAssets/Projects/solitudes hd/Beatmap.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31622a9f418403d4d97679cf2ef93047
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"CommandScripts" : {
|
||||
"__type" : "Ichni.RhythmGame.Beatmap.CommandScripts_BM,Assembly-CSharp",
|
||||
"value" : {
|
||||
"commandList" : [
|
||||
|
||||
],
|
||||
"attachedElementGuid" : {
|
||||
"value" : "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 337405124eca95d4288947981462d243
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"ProjectInformation" : {
|
||||
"__type" : "Ichni.RhythmGame.Beatmap.ProjectInformation_BM,Assembly-CSharp",
|
||||
"value" : {
|
||||
"projectName" : "solitudes hd",
|
||||
"creatorName" : "jiankn",
|
||||
"editorVersion" : "0.1.0",
|
||||
"createTime" : "2026\/5\/5 23:24:17",
|
||||
"lastSaveTime" : "2026\/5\/30 23:47:44",
|
||||
"selectedThemeBundleList" : [
|
||||
"basic","departure_to_multiverse"
|
||||
],
|
||||
"tagManager" : {
|
||||
"tagMatchers" : [
|
||||
|
||||
]
|
||||
},
|
||||
"attachedElementGuid" : {
|
||||
"value" : "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7ce8adba7efb90d47afa51881f4b1449
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/StreamingAssets/Projects/solitudes hd/Solitudes.mp3
Normal file
BIN
Assets/StreamingAssets/Projects/solitudes hd/Solitudes.mp3
Normal file
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 859ca5bcd1e63db4ba1502e71f916841
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
14
Assets/StreamingAssets/Projects/solitudes hd/SongInfo.json
Normal file
14
Assets/StreamingAssets/Projects/solitudes hd/SongInfo.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"SongInformation" : {
|
||||
"__type" : "Ichni.RhythmGame.Beatmap.SongInformation_BM,Assembly-CSharp",
|
||||
"value" : {
|
||||
"songName" : "Solitudes.mp3",
|
||||
"bpm" : 200,
|
||||
"delay" : 0,
|
||||
"offset" : 0,
|
||||
"attachedElementGuid" : {
|
||||
"value" : "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2aa73ff74964b742bc7b15f66001357
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because one or more lines are too long
@@ -113,6 +113,21 @@ namespace Ichni.RhythmGame.ThemeBundles.DepartureToMultiverse
|
||||
{
|
||||
base.AfterInitialize();
|
||||
ApplyColorSubmodule();
|
||||
|
||||
// 一次性初始化星座(原 Update() 中的 hasInitializedSpawning 守卫逻辑)
|
||||
if (!hasInitializedSpawning)
|
||||
{
|
||||
GenerateSingleConstellation();
|
||||
hasInitializedSpawning = true;
|
||||
}
|
||||
|
||||
// 注册到调度器 Phase 7 (Effect),每帧重建连线 Mesh
|
||||
CoreServices.UpdateScheduler.Register(UpdatePhase.Effect, this);
|
||||
}
|
||||
|
||||
public override void OnDelete()
|
||||
{
|
||||
CoreServices.UpdateScheduler.Unregister(UpdatePhase.Effect, this);
|
||||
}
|
||||
|
||||
public override void Refresh()
|
||||
@@ -153,74 +168,14 @@ namespace Ichni.RhythmGame.ThemeBundles.DepartureToMultiverse
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
/// <summary>
|
||||
/// 调度器 Phase 7 (Effect):每帧读取粒子位置,通过 Burst Job 计算连线并重建 Mesh。
|
||||
/// 替代原先的 LateUpdate()。
|
||||
/// </summary>
|
||||
public override void ScheduledUpdate(UpdatePhase phase, float songTime)
|
||||
{
|
||||
if (!hasInitializedSpawning)
|
||||
{
|
||||
GenerateSingleConstellation();
|
||||
hasInitializedSpawning = true;
|
||||
}
|
||||
}
|
||||
if (phase != UpdatePhase.Effect) return;
|
||||
|
||||
[Button("Refresh Constellation")]
|
||||
public void GenerateSingleConstellation()
|
||||
{
|
||||
starParticleSystem.Stop();
|
||||
starParticleSystem.Clear();
|
||||
|
||||
// --- 【新增】:通过代码接管并设置 Velocity over Lifetime 模块 ---
|
||||
// 注意:如果轨道旋转不为零,则强制开启该模块
|
||||
var vol = starParticleSystem.velocityOverLifetime;
|
||||
if (orbitalVelocity != Vector3.zero)
|
||||
{
|
||||
vol.enabled = true;
|
||||
vol.orbitalX = new ParticleSystem.MinMaxCurve(orbitalVelocity.x);
|
||||
vol.orbitalY = new ParticleSystem.MinMaxCurve(orbitalVelocity.y);
|
||||
vol.orbitalZ = new ParticleSystem.MinMaxCurve(orbitalVelocity.z);
|
||||
}
|
||||
else
|
||||
{
|
||||
vol.enabled = false;
|
||||
}
|
||||
|
||||
// --- 【新增】:通过代码接管并设置 Rotation over Lifetime 模块 ---
|
||||
var rol = starParticleSystem.rotationOverLifetime;
|
||||
if (angularVelocity != 0f)
|
||||
{
|
||||
rol.enabled = true;
|
||||
// 对于普通的 Billboard 粒子,Z 轴旋转就是屏幕空间上的二维自转
|
||||
rol.z = new ParticleSystem.MinMaxCurve(angularVelocity * Mathf.Deg2Rad); // 转换为弧度
|
||||
}
|
||||
else
|
||||
{
|
||||
rol.enabled = false;
|
||||
}
|
||||
|
||||
ParticleSystem.EmitParams emitParams = new ParticleSystem.EmitParams();
|
||||
|
||||
for (int i = 0; i < maxParticles; i++)
|
||||
{
|
||||
float x = Random.Range(-spreadSize.x * 0.5f, spreadSize.x * 0.5f);
|
||||
float y = Random.Range(-spreadSize.y * 0.5f, spreadSize.y * 0.5f);
|
||||
float z = Random.Range(-spreadSize.z * 0.5f, spreadSize.z * 0.5f);
|
||||
emitParams.position = new Vector3(x, y, z);
|
||||
|
||||
// startColor 使用 colorSubmodule 的当前 BaseColor(单色)
|
||||
emitParams.startColor = colorSubmodule != null
|
||||
? (Color32)colorSubmodule.currentBaseColor
|
||||
: new Color32(0, 255, 255, 255);
|
||||
|
||||
// 为了让自身旋转可见,可以在生成时赋予一个随机的初始旋转角度
|
||||
emitParams.rotation3D = new Vector3(0, 0, Random.Range(0f, 360f));
|
||||
|
||||
starParticleSystem.Emit(emitParams, 1);
|
||||
}
|
||||
|
||||
starParticleSystem.Play();
|
||||
}
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
if (starParticleSystem == null || lineMeshFilter == null) return;
|
||||
|
||||
int aliveCount = starParticleSystem.GetParticles(particles);
|
||||
@@ -287,6 +242,63 @@ namespace Ichni.RhythmGame.ThemeBundles.DepartureToMultiverse
|
||||
connectionCounts.Dispose();
|
||||
adjacencyMatrix.Dispose();
|
||||
}
|
||||
|
||||
[Button("Refresh Constellation")]
|
||||
public void GenerateSingleConstellation()
|
||||
{
|
||||
starParticleSystem.Stop();
|
||||
starParticleSystem.Clear();
|
||||
|
||||
// --- 【新增】:通过代码接管并设置 Velocity over Lifetime 模块 ---
|
||||
// 注意:如果轨道旋转不为零,则强制开启该模块
|
||||
var vol = starParticleSystem.velocityOverLifetime;
|
||||
if (orbitalVelocity != Vector3.zero)
|
||||
{
|
||||
vol.enabled = true;
|
||||
vol.orbitalX = new ParticleSystem.MinMaxCurve(orbitalVelocity.x);
|
||||
vol.orbitalY = new ParticleSystem.MinMaxCurve(orbitalVelocity.y);
|
||||
vol.orbitalZ = new ParticleSystem.MinMaxCurve(orbitalVelocity.z);
|
||||
}
|
||||
else
|
||||
{
|
||||
vol.enabled = false;
|
||||
}
|
||||
|
||||
// --- 【新增】:通过代码接管并设置 Rotation over Lifetime 模块 ---
|
||||
var rol = starParticleSystem.rotationOverLifetime;
|
||||
if (angularVelocity != 0f)
|
||||
{
|
||||
rol.enabled = true;
|
||||
// 对于普通的 Billboard 粒子,Z 轴旋转就是屏幕空间上的二维自转
|
||||
rol.z = new ParticleSystem.MinMaxCurve(angularVelocity * Mathf.Deg2Rad); // 转换为弧度
|
||||
}
|
||||
else
|
||||
{
|
||||
rol.enabled = false;
|
||||
}
|
||||
|
||||
ParticleSystem.EmitParams emitParams = new ParticleSystem.EmitParams();
|
||||
|
||||
for (int i = 0; i < maxParticles; i++)
|
||||
{
|
||||
float x = Random.Range(-spreadSize.x * 0.5f, spreadSize.x * 0.5f);
|
||||
float y = Random.Range(-spreadSize.y * 0.5f, spreadSize.y * 0.5f);
|
||||
float z = Random.Range(-spreadSize.z * 0.5f, spreadSize.z * 0.5f);
|
||||
emitParams.position = new Vector3(x, y, z);
|
||||
|
||||
// startColor 使用 colorSubmodule 的当前 BaseColor(单色)
|
||||
emitParams.startColor = colorSubmodule != null
|
||||
? (Color32)colorSubmodule.currentBaseColor
|
||||
: new Color32(0, 255, 255, 255);
|
||||
|
||||
// 为了让自身旋转可见,可以在生成时赋予一个随机的初始旋转角度
|
||||
emitParams.rotation3D = new Vector3(0, 0, Random.Range(0f, 360f));
|
||||
|
||||
starParticleSystem.Emit(emitParams, 1);
|
||||
}
|
||||
|
||||
starParticleSystem.Play();
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile(CompileSynchronously = true)]
|
||||
|
||||
@@ -7,7 +7,6 @@ using Ichni.RhythmGame.Beatmap;
|
||||
using Ichni.RhythmGame.ThemeBundles.Basic.Beatmap;
|
||||
using Ichni.RhythmGame.ThemeBundles.DepartureToMultiverse.Beatmap;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace Ichni.RhythmGame.ThemeBundles.DepartureToMultiverse
|
||||
{
|
||||
@@ -77,6 +76,17 @@ namespace Ichni.RhythmGame.ThemeBundles.DepartureToMultiverse
|
||||
return dtmTrail;
|
||||
}
|
||||
|
||||
public override void AfterInitialize()
|
||||
{
|
||||
base.AfterInitialize();
|
||||
CoreServices.UpdateScheduler.Register(UpdatePhase.Effect, this);
|
||||
}
|
||||
|
||||
public override void OnDelete()
|
||||
{
|
||||
CoreServices.UpdateScheduler.Unregister(UpdatePhase.Effect, this);
|
||||
}
|
||||
|
||||
public override void SetDefaultSubmodules()
|
||||
{
|
||||
base.SetDefaultSubmodules();
|
||||
@@ -230,9 +240,14 @@ namespace Ichni.RhythmGame.ThemeBundles.DepartureToMultiverse
|
||||
#endregion
|
||||
|
||||
#region [事件动画逻辑] Event Animation Logic
|
||||
private void Update()
|
||||
/// <summary>
|
||||
/// 调度器 Phase 7 (Effect):更新 FlexibleBool/Float 动画,驱动 Head 显隐和 TrailRenderer 参数。
|
||||
/// 替代原先的 Update()。
|
||||
/// </summary>
|
||||
public override void ScheduledUpdate(UpdatePhase phase, float songTime)
|
||||
{
|
||||
float songTime = EditorManager.instance.songInformation.songTime;
|
||||
if (phase != UpdatePhase.Effect) return;
|
||||
|
||||
enableTimes.UpdateFlexibleBool(songTime);
|
||||
if (enableTimes.value && !isHeadEnabled)
|
||||
{
|
||||
@@ -246,24 +261,10 @@ namespace Ichni.RhythmGame.ThemeBundles.DepartureToMultiverse
|
||||
}
|
||||
|
||||
visibleTimeLength.UpdateFlexibleFloat(songTime);
|
||||
if (visibleTimeLength.animations.Count > 0 && EditorManager.instance.musicPlayer.isPlaying && trailRenderer.time != visibleTimeLength.value)//为的是接口里头那个用来set的
|
||||
if (visibleTimeLength.animations.Count > 0 && EditorManager.instance.musicPlayer.isPlaying && trailRenderer.time != visibleTimeLength.value)
|
||||
{
|
||||
// Debug.Log(trailRenderer == null);
|
||||
|
||||
trailRenderer.time = visibleTimeLength.value;
|
||||
}
|
||||
|
||||
if (isHeadEnabled && headRotateSpeed.animations.Count > 0)
|
||||
{
|
||||
/*headRotateSpeed.UpdateFlexibleFloat(songTime);
|
||||
var rotationBySpeedModule = headCircle.GetComponent<ParticleSystem>().rotationOverLifetime;
|
||||
rotationBySpeedModule.z = headRotateSpeed.value;*/
|
||||
}
|
||||
|
||||
if (Keyboard.current.tKey.wasPressedThisFrame)
|
||||
{
|
||||
// TriggerInteraction();
|
||||
}
|
||||
}
|
||||
|
||||
private Sequence enableHeadSequence;
|
||||
|
||||
@@ -55,8 +55,10 @@ namespace Ichni.RhythmGame.ThemeBundles.DepartureToMultiverse
|
||||
this.hold.trackPositioner.autoUpdate = false;
|
||||
|
||||
headPoint.spline = hold.track.trackPathSubmodule.path;
|
||||
headPoint.autoUpdate = false; // 由调度器 Phase 6 通过 RebuildImmediate 手动刷新
|
||||
meshGenerator.spline = hold.track.trackPathSubmodule.path;
|
||||
tailPoint.spline = hold.track.trackPathSubmodule.path;
|
||||
tailPoint.autoUpdate = false; // 由调度器 Phase 6 通过 RebuildImmediate 手动刷新
|
||||
|
||||
TrackTimeSubmoduleMovable trackTimeSubmoduleMovable = hold.track.trackTimeSubmodule as TrackTimeSubmoduleMovable;
|
||||
float startPercent = trackTimeSubmoduleMovable.GetTrackPercent(hold.exactJudgeTime);
|
||||
@@ -161,9 +163,19 @@ namespace Ichni.RhythmGame.ThemeBundles.DepartureToMultiverse
|
||||
endPercent = trackTimeSubmoduleMovable.GetTrackPercent(hold.holdEndTime);
|
||||
}
|
||||
|
||||
// 确保所有 SplineUser 使用当前帧的最新 Spline 采样数据。
|
||||
// Phase 6 在 LateUpdate 中执行,SplineComputer 已在 Update 中重采样完毕。
|
||||
// autoUpdate=false 的 SplinePositioner 不会自行刷新,必须手动 RebuildImmediate。
|
||||
hold.trackPositioner.RebuildImmediate();
|
||||
hold.trackPositioner.SetPercent(startPercent);
|
||||
|
||||
meshGenerator.SetClipRange(startPercent, endPercent);
|
||||
meshGenerator.RebuildImmediate(); // 立即重建网格,避免与 head/tail 位置错位一帧
|
||||
|
||||
headPoint.RebuildImmediate();
|
||||
headPoint.SetPercent(startPercent);
|
||||
|
||||
tailPoint.RebuildImmediate();
|
||||
tailPoint.SetPercent(endPercent);
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -14,10 +14,11 @@
|
||||
"bezi3d"
|
||||
],
|
||||
"unity": "2018.3",
|
||||
"version": "0.86.4",
|
||||
"version": "0.90.0",
|
||||
"type": "library",
|
||||
"hideInEditor": false,
|
||||
"dependencies": {
|
||||
"com.unity.nuget.newtonsoft-json": "3.2.1"
|
||||
"com.unity.nuget.newtonsoft-json": "3.2.1",
|
||||
"com.unity.timeline": "1.6.0"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,8 @@
|
||||
"depth": 0,
|
||||
"source": "embedded",
|
||||
"dependencies": {
|
||||
"com.unity.nuget.newtonsoft-json": "3.2.1"
|
||||
"com.unity.nuget.newtonsoft-json": "3.2.1",
|
||||
"com.unity.timeline": "1.6.0"
|
||||
}
|
||||
},
|
||||
"com.unity.2d.animation": {
|
||||
|
||||
Reference in New Issue
Block a user