This commit is contained in:
SoulliesOfficial
2026-06-05 06:47:24 -04:00
parent 3995beeb29
commit 0fb72f5bba
94 changed files with 650754 additions and 4839 deletions

View 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()
{
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ab4e12be376b08f418bd2d7a7d78aec6

View File

@@ -297,7 +297,7 @@ namespace Ichni.RhythmGame
[Button("Rebuild")]
public void Rebuild()
{
trackPathSubmodule?.path.Rebuild(true);
trackPathSubmodule?.path.RebuildImmediate();
trackRendererSubmodule?.meshGenerator.Rebuild();
}
#endregion

View File

@@ -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
}
}
}

View File

@@ -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)

View File

@@ -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()
{
}
}
}
}
}

View File

@@ -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>

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: fda5a36c77610481e976e5d7ceb764ab
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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; }
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fdf08f5bfdc7b9f4b87f697c199eb1cf

View File

@@ -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
}
}
}

View 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
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6b79a50d212bc9a42b53f7f576f15ae7

View File

@@ -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()
{
}
}

View File

@@ -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

View File

@@ -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()

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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);

View File

@@ -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
}
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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()
{
}
}

View File

@@ -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.NoneRebuild(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
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}