着重优化调度

Signed-off-by: TRADER_FOER <lhf190@outlook.com>
This commit is contained in:
2026-07-21 09:58:21 +08:00
parent 04ec368a59
commit 02617c1385
117 changed files with 107322 additions and 1326 deletions

View File

@@ -60,7 +60,7 @@ namespace Ichni.RhythmGame
if(!forceUpdate) animationReturnType = FlexibleReturnType.MiddleExecuting;
targetColorSubmodule.currentBaseColor = new Color(colorR.value, colorG.value, colorB.value, colorA.value);
targetColorSubmodule.baseColorDirtyMark = true;
targetColorSubmodule.MarkBaseColorDirty();
}
else
{

View File

@@ -60,7 +60,7 @@ namespace Ichni.RhythmGame
targetColorSubmodule.currentEmissionColor = new Color(colorR.value, colorG.value, colorB.value, 1);
targetColorSubmodule.currentEmissionIntensity = colorI.value;
targetColorSubmodule.emissionColorDirtyMark = true;
targetColorSubmodule.MarkEmissionColorDirty();
}
else
{

View File

@@ -81,7 +81,7 @@ namespace Ichni.RhythmGame
{
Vector3 currentPosition = new Vector3(positionX.value, positionY.value, positionZ.value);
targetTransformSubmodule.positionOffset += currentPosition;
targetTransformSubmodule.positionDirtyMark = true;
targetTransformSubmodule.MarkPositionDirty();
}
}
else if (isSwitching)
@@ -107,7 +107,7 @@ namespace Ichni.RhythmGame
animationReturnType = FlexibleReturnType.MiddleExecuting;
Vector3 currentPosition = new Vector3(positionX.value, positionY.value, positionZ.value);
targetTransformSubmodule.positionOffset += currentPosition;
targetTransformSubmodule.positionDirtyMark = true;
targetTransformSubmodule.MarkPositionDirty();
}
}
else

View File

@@ -100,12 +100,12 @@ namespace Ichni.RhythmGame
if (enabling.value)
{
animationReturnType = FlexibleReturnType.MiddleExecuting;
targetTransformSubmodule.eulerAnglesDirtyMark = true;
targetTransformSubmodule.MarkEulerAnglesDirty();
}
else if (animationReturnType != FlexibleReturnType.MiddleInterval)
{
animationReturnType = FlexibleReturnType.MiddleInterval;
targetTransformSubmodule.eulerAnglesDirtyMark = true;
targetTransformSubmodule.MarkEulerAnglesDirty();
}
}

View File

@@ -86,7 +86,7 @@ namespace Ichni.RhythmGame
{
Vector3 currentScale = new Vector3(scaleX.value, scaleY.value, scaleZ.value);
targetTransformSubmodule.scaleOffset += currentScale;
targetTransformSubmodule.scaleDirtyMark = true;
targetTransformSubmodule.MarkScaleDirty();
}
}
else if (isSwitching)
@@ -112,7 +112,7 @@ namespace Ichni.RhythmGame
animationReturnType = FlexibleReturnType.MiddleExecuting; // 使系统认为有有效活动
Vector3 currentScale = new Vector3(scaleX.value, scaleY.value, scaleZ.value);
targetTransformSubmodule.scaleOffset += currentScale;
targetTransformSubmodule.scaleDirtyMark = true;
targetTransformSubmodule.MarkScaleDirty();
}
}
else

View File

@@ -79,7 +79,7 @@ namespace Ichni.RhythmGame
{
Vector3 currentEulerAngles = new Vector3(eulerAngleX.value, eulerAngleY.value, eulerAngleZ.value);
targetTransformSubmodule.eulerAnglesOffset += currentEulerAngles;
targetTransformSubmodule.eulerAnglesDirtyMark = true;
targetTransformSubmodule.MarkEulerAnglesDirty();
}
}
else if (isSwitching)
@@ -105,7 +105,7 @@ namespace Ichni.RhythmGame
animationReturnType = FlexibleReturnType.MiddleExecuting;
Vector3 currentEulerAngles = new Vector3(eulerAngleX.value, eulerAngleY.value, eulerAngleZ.value);
targetTransformSubmodule.eulerAnglesOffset += currentEulerAngles;
targetTransformSubmodule.eulerAnglesDirtyMark = true;
targetTransformSubmodule.MarkEulerAnglesDirty();
}
}
else

View File

@@ -3,6 +3,7 @@ using System.Linq;
using Ichni.RhythmGame.Beatmap;
using UniRx;
using UniRx.Triggers;
using Unity.Profiling;
using Unity.VisualScripting;
using UnityEngine;
@@ -25,7 +26,8 @@ namespace Ichni.RhythmGame
public bool baseColorDirtyMark;
public bool emissionColorDirtyMark;
public bool queuedForApply;
public IDisposable observer;
public Color GetCurrentEmissionColor()
@@ -73,7 +75,7 @@ namespace Ichni.RhythmGame
this.baseColorDirtyMark = true;
this.emissionColorDirtyMark = true;
if (!HaveSameSubmodule && attachedGameElement is IHaveColorSubmodule host)
{
host.colorSubmodule = this;
@@ -89,8 +91,34 @@ namespace Ichni.RhythmGame
currentBaseColor = originalBaseColor;
currentEmissionColor = originalEmissionColor;
currentEmissionIntensity = originalEmissionIntensity;
MarkAllDirty();
}
public void MarkBaseColorDirty()
{
baseColorDirtyMark = true;
RequestApply();
}
public void MarkEmissionColorDirty()
{
emissionColorDirtyMark = true;
RequestApply();
}
public void MarkAllDirty()
{
baseColorDirtyMark = true;
emissionColorDirtyMark = true;
RequestApply();
}
private void RequestApply()
{
if (attachedGameElement is IHaveColorSubmodule host)
{
CoreServices.UpdateScheduler?.RegisterDirtyColor(host);
}
}
private bool HaveAnimation() => attachedGameElement.childElementList
@@ -109,9 +137,12 @@ namespace Ichni.RhythmGame
#region [] Component Interface
public interface IHaveColorSubmodule
{
private static readonly ProfilerMarker PhaseMarker = new ProfilerMarker("Ichni.UpdateScheduler.RefreshColor");
private static readonly ProfilerMarker PhaseMarker1 = new ProfilerMarker("Ichni.UpdateScheduler.UpdateColor");
public ColorSubmodule colorSubmodule { get; set; }
public virtual bool haveEmission => false;
public void SetColorObserver()
{
// 旧版的 UniRx 各自监听已淘汰,现由 GameManager 中枢在 LateUpdate 统一下发 UpdateColor()
@@ -119,27 +150,33 @@ namespace Ichni.RhythmGame
public void UpdateColor(bool refreshAll = true)
{
if(colorSubmodule == null) return;
bool willRefresh = false;
if (colorSubmodule.baseColorDirtyMark)
using (PhaseMarker1.Auto())
{
//在动画物体中改变currentColor
colorSubmodule.baseColorDirtyMark = false;
willRefresh = true;
}
if (colorSubmodule.emissionColorDirtyMark)
{
//在动画物体中改变currentColor
colorSubmodule.emissionColorDirtyMark = false;
willRefresh = true;
}
if (willRefresh && refreshAll)
{
colorSubmodule.attachedGameElement.Refresh();
if (colorSubmodule == null) return;
bool willRefresh = false;
if (colorSubmodule.baseColorDirtyMark)
{
//在动画物体中改变currentColor
colorSubmodule.baseColorDirtyMark = false;
willRefresh = true;
}
if (colorSubmodule.emissionColorDirtyMark)
{
//在动画物体中改变currentColor
colorSubmodule.emissionColorDirtyMark = false;
willRefresh = true;
}
if (willRefresh && refreshAll)
{
using (PhaseMarker.Auto())
{
colorSubmodule.attachedGameElement.Refresh();
}
}
}
}
}

View File

@@ -5,6 +5,7 @@ using System.Linq;
using Ichni.RhythmGame.Beatmap;
using UniRx;
using UniRx.Triggers;
using Unity.Profiling;
using Unity.VisualScripting;
using UnityEngine;
using Object = UnityEngine.Object;
@@ -25,13 +26,14 @@ namespace Ichni.RhythmGame
public Vector3 currentPosition;
public Vector3 currentEulerAngles;
public Vector3 currentScale;
public bool positionDirtyMark;
public bool eulerAnglesDirtyMark;
public bool scaleDirtyMark;
public bool queuedForApply;
public bool eulerAnglesOffsetLock;
public IDisposable observer;
#endregion
@@ -49,7 +51,7 @@ namespace Ichni.RhythmGame
currentPosition = Vector3.zero;
currentEulerAngles = Vector3.zero;
currentScale = Vector3.one;
positionDirtyMark = true;
eulerAnglesDirtyMark = true;
scaleDirtyMark = true;
@@ -77,13 +79,13 @@ namespace Ichni.RhythmGame
currentPosition = originalPosition;
currentEulerAngles = originalEulerAngles;
currentScale = originalScale;
positionDirtyMark = true;
eulerAnglesDirtyMark = true;
scaleDirtyMark = true;
eulerAnglesOffsetLock = false;
if (!HaveSameSubmodule && attachedGameElement is IHaveTransformSubmodule host)
{
host.transformSubmodule = this;
@@ -91,14 +93,46 @@ namespace Ichni.RhythmGame
}
}
#endregion
#region [] Lifecycle & State Refresh
public override void Refresh()
{
MarkAllDirty();
}
public void MarkPositionDirty()
{
positionDirtyMark = true;
RequestApply();
}
public void MarkEulerAnglesDirty()
{
eulerAnglesDirtyMark = true;
RequestApply();
}
public void MarkScaleDirty()
{
scaleDirtyMark = true;
RequestApply();
}
public void MarkAllDirty()
{
positionDirtyMark = true;
eulerAnglesDirtyMark = true;
scaleDirtyMark = true;
RequestApply();
}
private void RequestApply()
{
if (attachedGameElement is IHaveTransformSubmodule host)
{
CoreServices.UpdateScheduler?.RegisterDirtyTransform(host);
}
}
private bool HaveAnimation() => attachedGameElement.childElementList
@@ -117,6 +151,9 @@ namespace Ichni.RhythmGame
#region [] Component Interface
public interface IHaveTransformSubmodule
{
private static readonly ProfilerMarker PhaseMarker = new ProfilerMarker("Ichni.UpdateScheduler.RefreshTransform");
private static readonly ProfilerMarker PhaseMarker1 = new ProfilerMarker("Ichni.UpdateScheduler.UpdateTransform");
TransformSubmodule transformSubmodule { get; set; }
/// <summary>
@@ -131,50 +168,54 @@ namespace Ichni.RhythmGame
public void UpdateTransform(bool refreshAll = true)
{
if(transformSubmodule == null) return;
GameElement attachedGameElement = transformSubmodule.attachedGameElement;
bool willRefresh = false;
if (transformSubmodule.scaleDirtyMark)
if (transformSubmodule == null) return;
using (PhaseMarker1.Auto())
{
transformSubmodule.currentScale = transformSubmodule.originalScale + transformSubmodule.scaleOffset;
attachedGameElement.transform.localScale = transformSubmodule.currentScale;
transformSubmodule.scaleDirtyMark = false;
willRefresh = true;
transformSubmodule.scaleOffset = Vector3.zero;
}
GameElement attachedGameElement = transformSubmodule.attachedGameElement;
bool willRefresh = false;
if (!transformSubmodule.eulerAnglesOffsetLock && transformSubmodule.eulerAnglesDirtyMark)
{
transformSubmodule.currentEulerAngles = transformSubmodule.originalEulerAngles + transformSubmodule.eulerAnglesOffset;
attachedGameElement.transform.localEulerAngles = transformSubmodule.currentEulerAngles;
transformSubmodule.eulerAnglesDirtyMark = false;
willRefresh = true;
transformSubmodule.eulerAnglesOffset = Vector3.zero;
}
if (transformSubmodule.scaleDirtyMark)
{
transformSubmodule.currentScale = transformSubmodule.originalScale + transformSubmodule.scaleOffset;
if (transformSubmodule.positionDirtyMark)
{
transformSubmodule.currentPosition = transformSubmodule.originalPosition + transformSubmodule.positionOffset;
attachedGameElement.transform.localPosition = transformSubmodule.currentPosition;
transformSubmodule.positionDirtyMark = false;
willRefresh = true;
transformSubmodule.positionOffset = Vector3.zero;
}
if(refreshAll && willRefresh)
{
attachedGameElement.Refresh();
attachedGameElement.transform.localScale = transformSubmodule.currentScale;
transformSubmodule.scaleDirtyMark = false;
willRefresh = true;
transformSubmodule.scaleOffset = Vector3.zero;
}
if (!transformSubmodule.eulerAnglesOffsetLock && transformSubmodule.eulerAnglesDirtyMark)
{
transformSubmodule.currentEulerAngles = transformSubmodule.originalEulerAngles + transformSubmodule.eulerAnglesOffset;
attachedGameElement.transform.localEulerAngles = transformSubmodule.currentEulerAngles;
transformSubmodule.eulerAnglesDirtyMark = false;
willRefresh = true;
transformSubmodule.eulerAnglesOffset = Vector3.zero;
}
if (transformSubmodule.positionDirtyMark)
{
transformSubmodule.currentPosition = transformSubmodule.originalPosition + transformSubmodule.positionOffset;
attachedGameElement.transform.localPosition = transformSubmodule.currentPosition;
transformSubmodule.positionDirtyMark = false;
willRefresh = true;
transformSubmodule.positionOffset = Vector3.zero;
}
if (refreshAll && willRefresh)
{
using (PhaseMarker.Auto())
attachedGameElement.Refresh();
}
}
}
public void UpdateLookAt(LookAt lookAt) // 处理LookAt
{
Transform target = lookAt.targetGameElement.transform;
Transform self = transformSubmodule.attachedGameElement.transform;
if (transformSubmodule.eulerAnglesOffsetLock && transformSubmodule.eulerAnglesDirtyMark)
{
Vector3 lookingDirection = (target.position - self.position).normalized;

View File

@@ -61,11 +61,13 @@ namespace Ichni.RhythmGame
if (this is IHaveTransformSubmodule transformSource && !GameManager.Instance.activeTransformSubmodules.Contains(transformSource))
{
GameManager.Instance.activeTransformSubmodules.Add(transformSource);
CoreServices.UpdateScheduler?.RegisterDirtyTransform(transformSource);
}
if (this is IHaveColorSubmodule colorSource && !GameManager.Instance.activeColorSubmodules.Contains(colorSource))
{
GameManager.Instance.activeColorSubmodules.Add(colorSource);
CoreServices.UpdateScheduler?.RegisterDirtyColor(colorSource);
}
if (this is IHaveDirtyMarkSubmodule dirtySource && !GameManager.Instance.activeDirtyMarkSubmodules.Contains(dirtySource))
@@ -177,10 +179,12 @@ namespace Ichni.RhythmGame
if (this is IHaveTransformSubmodule transformSource)
{
GameManager.Instance.activeTransformSubmodules.Remove(transformSource);
CoreServices.UpdateScheduler?.UnregisterDirtyTransform(transformSource);
}
if (this is IHaveColorSubmodule colorSource)
{
GameManager.Instance.activeColorSubmodules.Remove(colorSource);
CoreServices.UpdateScheduler?.UnregisterDirtyColor(colorSource);
}
if (this is IHaveDirtyMarkSubmodule dirtySource)
{

View File

@@ -10,7 +10,7 @@ using UnityEngine.Serialization;
namespace Ichni.RhythmGame
{
public partial class Track : GameElement, IHaveTransformSubmodule, IHaveTimeDurationSubmodule, IScheduledElement
public partial class Track : GameElement, IHaveTransformSubmodule, IHaveTimeDurationSubmodule, IScheduledElement, IHaveDirtyMarkSubmodule
{
#region [] Essential Configs
public GameObject trackRenderer;
@@ -22,6 +22,8 @@ namespace Ichni.RhythmGame
public TrackPathSubmodule trackPathSubmodule { get; set; }
public TrackTimeSubmodule trackTimeSubmodule { get; set; }
public TrackRendererSubmodule trackRendererSubmodule { get; set; }
public DirtyMarkSubmodule dirtyMarkSubmodule { get; set; }
#endregion
#region [] Lifecycle & Factory
@@ -36,34 +38,37 @@ namespace Ichni.RhythmGame
return track;
}
public override void SetDefaultSubmodules()
{
transformSubmodule = new TransformSubmodule(this);
timeDurationSubmodule = new TimeDurationSubmodule(this);
trackPathSubmodule = new TrackPathSubmodule(this, TrackSpaceType.CatmullRom, TrackSamplingType.TimeDistributed, false, false);
dirtyMarkSubmodule = new DirtyMarkSubmodule(this);
trackTimeSubmodule = null;
trackRendererSubmodule = null;
}
public override void AfterInitialize()
{
base.AfterInitialize();
// 保留 TrackManager 注册以维持 ManualLateUpdate 的 refreshedThisFrame 清除逻辑
GameManager.Instance.trackManager.RegisterTrack(this);
// 注册调度器 Phase 4TrackCore驱动 ManualUpdate
CoreServices.UpdateScheduler.Register(UpdatePhase.TrackCore, this);
CoreServices.UpdateScheduler.RegisterTrackSpline(this);
if (trackPathSubmodule != null && trackPathSubmodule.pathNodeList.Count > 3)
if (trackPathSubmodule != null)
{
trackPathSubmodule.ClosePath();
if (trackPathSubmodule.pathNodeList.Count > 3) trackPathSubmodule.ClosePath();
}
if(trackRendererSubmodule != null)
if (trackRendererSubmodule != null)
{
trackRendererSubmodule.meshGenerator.autoUpdate = true;
trackRendererSubmodule.meshGenerator.autoUpdate =
GameManager.Instance.trackManager.EnableTrackAutoUpdate;
}
}
#endregion
@@ -79,7 +84,7 @@ namespace Ichni.RhythmGame
public void ManualLateUpdate()
{
if(trackPathSubmodule != null) trackPathSubmodule.refreshedThisFrame = false;
if (trackPathSubmodule != null) trackPathSubmodule.refreshedThisFrame = false;
}
#region [IScheduledElement] Scheduler Interface
@@ -88,7 +93,8 @@ namespace Ichni.RhythmGame
ManualUpdate(songTime);
}
public bool IsScheduledActive => isActiveAndEnabled;
public override bool IsScheduledActive => isActiveAndEnabled;
#endregion
#endregion
@@ -108,6 +114,11 @@ namespace Ichni.RhythmGame
CoreServices.UpdateScheduler.UnregisterTrackSpline(this);
if (parentElement is ElementFolder folder) folder.trackList.Remove(this);
}
public void OnDirtyRefresh(Dictionary<string, bool> flags)
{
Refresh();
}
#endregion
}

View File

@@ -35,6 +35,7 @@ namespace Ichni.RhythmGame
this.path.sampleRate = 8;
this.path.updateMode = SplineComputer.UpdateMode.LateUpdate;
this.path.enabled = false;
SetUpSplineComputer(this.trackSpaceType, this.trackSamplingType);
//闭合路径在PathNode生成时执行在初始化的情况下PathNode数量为0不会执行闭合操作
@@ -109,4 +110,4 @@ namespace Ichni.RhythmGame
}
#endregion
}
}

View File

@@ -52,12 +52,12 @@ namespace Ichni.RhythmGame
this.materialThemeBundleName = materialThemeBundleName;
this.materialName = materialName;
Material mat = ThemeBundleManager.instance.GetObject<Material>(materialThemeBundleName, materialName);
if(mat != null)
if (mat != null)
{
renderMaterial = mat;
meshRenderer.material = renderMaterial;
}
meshRenderer.InitializeShader();
}
@@ -88,7 +88,7 @@ namespace Ichni.RhythmGame
}
return;
}
track.dirtyMarkSubmodule?.MarkDirty();
/*if (forceImmediateRebuild)
{
meshGenerator.RebuildImmediate();

View File

@@ -36,6 +36,7 @@ namespace Ichni.RhythmGame
this.uvMode = MeshGenerator.UVMode.UniformClip;
SetMesh();
this.splineRenderer.enabled = false;
}
#endregion

View File

@@ -35,6 +35,7 @@ namespace Ichni.RhythmGame
this.uvMode = MeshGenerator.UVMode.UniformClip;
SetMesh();
this.pathGenerator.enabled = false;
}
#endregion

View File

@@ -34,6 +34,7 @@ namespace Ichni.RhythmGame
this.surface.uvMode = MeshGenerator.UVMode.UniformClip;
SetMesh();
this.surface.enabled = false;
}
#endregion

View File

@@ -37,6 +37,7 @@ namespace Ichni.RhythmGame
this.tubeGenerator.uvMode = MeshGenerator.UVMode.UniformClip;
SetMesh();
this.tubeGenerator.enabled = false;
}
#endregion

View File

@@ -110,7 +110,7 @@ namespace Ichni.RhythmGame
return Mathf.Clamp01(per);
}
#endregion
#region [] Behavior Overrides
public override void Refresh()
{
@@ -149,7 +149,7 @@ namespace Ichni.RhythmGame
//timeDurationSubmodule 根据下辖Note的时间来设置
}
#endregion
#region [] Behavior Overrides
public override void Refresh()
{

View File

@@ -26,6 +26,8 @@ namespace Ichni.RhythmGame.UI
[Title("Debug")]
public TMP_Text fpsText;
private const float FpsRefreshInterval = 0.25f;
private float _fpsRefreshTimer;
private void Start()
{
@@ -46,7 +48,13 @@ namespace Ichni.RhythmGame.UI
private void Update()
{
fpsText.text = (1.0f / Time.unscaledDeltaTime).ToString("F2");
_fpsRefreshTimer -= Time.unscaledDeltaTime;
if (_fpsRefreshTimer <= 0f && fpsText != null)
{
_fpsRefreshTimer = FpsRefreshInterval;
fpsText.SetText("{0:0.00}", 1.0f / Time.unscaledDeltaTime);
}
if (GameManager.Instance.songPlayer.isPlaying)
{
float songLength = GameManager.Instance.songInformation.songLength;
@@ -64,4 +72,4 @@ namespace Ichni.RhythmGame.UI
comboText.text = currentCombo.ToString("D");
}
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Ichni.RhythmGame;
using Unity.Profiling;
namespace Ichni
{
@@ -41,6 +42,20 @@ namespace Ichni
private const int PhaseStep = 10;
private static readonly UpdatePhase[] AllPhases = (UpdatePhase[])Enum.GetValues(typeof(UpdatePhase));
private static readonly ProfilerMarker[] PhaseMarkers =
{
new ProfilerMarker("Ichni.UpdateScheduler.TimeDuration"),
new ProfilerMarker("Ichni.UpdateScheduler.Animation"),
new ProfilerMarker("Ichni.UpdateScheduler.Apply"),
new ProfilerMarker("Ichni.UpdateScheduler.SplineRebuild"),
new ProfilerMarker("Ichni.UpdateScheduler.TrackCore"),
new ProfilerMarker("Ichni.UpdateScheduler.TrackFollower"),
new ProfilerMarker("Ichni.UpdateScheduler.Note"),
new ProfilerMarker("Ichni.UpdateScheduler.Effect"),
new ProfilerMarker("Ichni.UpdateScheduler.Misc")
};
private static readonly ProfilerMarker DirtyMarkMarker =
new ProfilerMarker("Ichni.UpdateScheduler.DirtyMark");
#endregion
@@ -56,6 +71,7 @@ namespace Ichni
/// <summary>将 UpdatePhase 枚举值转换为数组索引。</summary>
private static int PhaseIndex(UpdatePhase phase) => (int)phase / PhaseStep;
#endregion
#region [ Spline ] Track Spline List
@@ -65,6 +81,13 @@ namespace Ichni
#endregion
#region [Apply Dirty Queues]
private readonly List<IHaveColorSubmodule> _dirtyColorSubmodules = new List<IHaveColorSubmodule>(256);
private readonly List<IHaveTransformSubmodule> _dirtyTransformSubmodules = new List<IHaveTransformSubmodule>(256);
#endregion
#region [] Cached State
/// <summary>TickEarly 中缓存的 songTime供 TickLate 使用</summary>
@@ -121,6 +144,48 @@ namespace Ichni
_trackSplines.Remove(track);
}
public void RegisterDirtyColor(IHaveColorSubmodule target)
{
if (target == null || target.colorSubmodule == null || target.colorSubmodule.queuedForApply)
{
return;
}
target.colorSubmodule.queuedForApply = true;
_dirtyColorSubmodules.Add(target);
}
public void RegisterDirtyTransform(IHaveTransformSubmodule target)
{
if (target == null || target.transformSubmodule == null || target.transformSubmodule.queuedForApply)
{
return;
}
target.transformSubmodule.queuedForApply = true;
_dirtyTransformSubmodules.Add(target);
}
public void UnregisterDirtyColor(IHaveColorSubmodule target)
{
if (target?.colorSubmodule != null)
{
target.colorSubmodule.queuedForApply = false;
}
_dirtyColorSubmodules.Remove(target);
}
public void UnregisterDirtyTransform(IHaveTransformSubmodule target)
{
if (target?.transformSubmodule != null)
{
target.transformSubmodule.queuedForApply = false;
}
_dirtyTransformSubmodules.Remove(target);
}
#endregion
#region [ - ] TickEarly (Update)
@@ -138,13 +203,19 @@ namespace Ichni
_cachedSongTime = songTime;
// ─── Phase 0: TimeDurationLegacy timeDurations 列表)─────────────────
TickTimeDurationLegacy(songTime);
using (PhaseMarkers[PhaseIndex(UpdatePhase.TimeDuration)].Auto())
{
TickTimeDurationLegacy(songTime);
}
// ─── Phase 1: Animation ──────────────────────────────────────────────
TickPhase(UpdatePhase.Animation, songTime);
// ─── Phase 2: ApplyColor + Transform from active lists────────────
TickApplyLegacy();
using (PhaseMarkers[PhaseIndex(UpdatePhase.Apply)].Auto())
{
TickApplyLegacy();
}
}
#endregion
@@ -160,36 +231,49 @@ namespace Ichni
// DirtyMark保持原有 isStarting || isPlaying 条件,与 TickEarly 的 isUpdating 独立判断
if (GameManager.Instance.songPlayer.isStarting || GameManager.Instance.songPlayer.isPlaying)
{
TickDirtyMarkLegacy();
using (DirtyMarkMarker.Auto())
{
TickDirtyMarkLegacy();
}
}
if (!_isUpdating) return;
float songTime = _cachedSongTime;
// ─── Phase 3: SplineRebuild(占位)───────────────────────────────────
// SplineComputer 使用默认 UpdateMode.Update已在 Update 中自行处理。
TickPhase(UpdatePhase.SplineRebuild, songTime);
// ─── Phase 3: SplineRebuild ──────────────────────────────────────────
using (PhaseMarkers[PhaseIndex(UpdatePhase.SplineRebuild)].Auto())
{
TickTrackSplines();
TickPhaseElements(UpdatePhase.SplineRebuild, songTime);
}
// ─── Phase 4: TrackCore ──────────────────────────────────────────────
TickPhase(UpdatePhase.TrackCore, songTime);
// ─── Phase 5: TrackFollower ──────────────────────────────────────────
TickPhase(UpdatePhase.TrackFollower, songTime);
// 清除轨道 refreshedThisFrame 标记TrackManager 保留此单一职责)
GameManager.Instance.trackManager.ManualLateUpdate(songTime);
using (PhaseMarkers[PhaseIndex(UpdatePhase.TrackFollower)].Auto())
{
TickPhaseElements(UpdatePhase.TrackFollower, songTime);
GameManager.Instance.trackManager.ManualLateUpdate(songTime);
}
// ─── Phase 6: Note由 NoteManager 内部驱动)─────────────────────────
TickPhase(UpdatePhase.Note, songTime);
GameManager.Instance.noteManager.ManualUpdate(songTime);
GameManager.Instance.noteManager.ManualLateUpdate(songTime);
using (PhaseMarkers[PhaseIndex(UpdatePhase.Note)].Auto())
{
TickPhaseElements(UpdatePhase.Note, songTime);
GameManager.Instance.noteManager.ManualUpdate(songTime);
GameManager.Instance.noteManager.ManualLateUpdate(songTime);
}
// ─── Phase 7: Effect ─────────────────────────────────────────────────
TickPhase(UpdatePhase.Effect, songTime);
// ─── Phase 8: Misc ───────────────────────────────────────────────────
TickPhase(UpdatePhase.Misc, songTime);
GameManager.Instance.beatmapContainer?.ExecuteLowPriorityActions();
using (PhaseMarkers[PhaseIndex(UpdatePhase.Misc)].Auto())
{
TickPhaseElements(UpdatePhase.Misc, songTime);
GameManager.Instance.beatmapContainer?.ExecuteLowPriorityActions();
}
}
#endregion
@@ -201,6 +285,14 @@ namespace Ichni
/// 倒序遍历防止更新途中元素自行销毁导致越界。
/// </summary>
private void TickPhase(UpdatePhase phase, float songTime)
{
using (PhaseMarkers[PhaseIndex(phase)].Auto())
{
TickPhaseElements(phase, songTime);
}
}
private void TickPhaseElements(UpdatePhase phase, float songTime)
{
var list = _phaseElements[PhaseIndex(phase)];
for (int i = list.Count - 1; i >= 0; i--)
@@ -213,6 +305,27 @@ namespace Ichni
}
}
private void TickTrackSplines()
{
for (int i = _trackSplines.Count - 1; i >= 0; i--)
{
Track track = _trackSplines[i];
if (track == null)
{
_trackSplines.RemoveAt(i);
continue;
}
if (!track.isActiveAndEnabled)
{
continue;
}
track.trackPathSubmodule?.path?.RunManualUpdate();
track.trackRendererSubmodule?.meshGenerator?.RunManualUpdate();
}
}
#endregion
#region [Legacy]
@@ -235,17 +348,38 @@ namespace Ichni
/// </summary>
private void TickApplyLegacy()
{
var activeColorSubmodules = GameManager.Instance.activeColorSubmodules;
var activeTransformSubmodules = GameManager.Instance.activeTransformSubmodules;
for (int i = 0; i < activeColorSubmodules.Count; i++)
int colorCount = _dirtyColorSubmodules.Count;
for (int i = 0; i < colorCount; i++)
{
activeColorSubmodules[i].UpdateColor(true);
IHaveColorSubmodule target = _dirtyColorSubmodules[i];
if (target?.colorSubmodule == null)
{
continue;
}
target.colorSubmodule.queuedForApply = false;
target.UpdateColor(true);
}
if (colorCount > 0)
{
_dirtyColorSubmodules.RemoveRange(0, Math.Min(colorCount, _dirtyColorSubmodules.Count));
}
for (int i = 0; i < activeTransformSubmodules.Count; i++)
int transformCount = _dirtyTransformSubmodules.Count;
for (int i = 0; i < transformCount; i++)
{
activeTransformSubmodules[i].UpdateTransform(true);
IHaveTransformSubmodule target = _dirtyTransformSubmodules[i];
if (target?.transformSubmodule == null)
{
continue;
}
target.transformSubmodule.queuedForApply = false;
target.UpdateTransform(true);
}
if (transformCount > 0)
{
_dirtyTransformSubmodules.RemoveRange(0, Math.Min(transformCount, _dirtyTransformSubmodules.Count));
}
}

View File

@@ -15,19 +15,19 @@ namespace Ichni
public partial class GameManager : Singleton<GameManager>, ISongTimeProvider
{
[FormerlySerializedAs("audioManager")] public SongPlayer songPlayer;
public CameraManager cameraManager;
[FormerlySerializedAs("inputManager")] public NoteJudgeManager noteJudgeManager;
public BackgroundSetter backgroundSetter;
public BackgroundController backgroundController;
public VariablesContainer variablesContainer;
public ProjectLoader projectLoader;
public PlayingRecorder playingRecorder;
public BeatmapContainer beatmapContainer;
public CommandScripts commandScripts;
public ProjectInformation projectInformation;
@@ -37,29 +37,29 @@ namespace Ichni
public AnimationManager animationManager;
public TrackManager trackManager;
public NoteManager noteManager;
public BasePrefabsCollection basePrefabs;
public Dictionary<string, CustomPrefabsCollection> customPrefabs;
public List<IHaveTransformSubmodule> activeTransformSubmodules = new List<IHaveTransformSubmodule>();
public List<IHaveColorSubmodule> activeColorSubmodules = new List<IHaveColorSubmodule>();
public List<IHaveDirtyMarkSubmodule> activeDirtyMarkSubmodules = new List<IHaveDirtyMarkSubmodule>();
public ElementUpdateScheduler updateScheduler;
[Title("UI")]
public Canvas judgeHintCanvas;
public GameUICanvas gameUICanvas;
public GameLoadingCanvas gameLoadingCanvas;
public SummaryPageCanvas summaryPageCanvas;
public float SongTime => songPlayer.isStarting ? 0 :
public float SongTime => songPlayer.isStarting ? 0 :
songPlayer.songTimeSegment - songInformation.offset - (SettingsManager.instance.settingsSaveData.beatmapOffset / 1000f);
public bool IsPlaying => songPlayer != null && songPlayer.isPlaying;
public bool isDebugging;
protected override void Awake()
{
base.Awake();

View File

@@ -17,10 +17,10 @@ namespace Ichni
/// <summary>
/// 当前已加载的 GameScene 后处理管理器。MenuScene 等未创建该对象的场景会返回 <c>null</c>。
/// </summary>
public static PostProcessingManager Instance => instance;
public static new PostProcessingManager Instance => instance;
public static Volume GlobalVolume => instance.globalVolume;
[ShowInInspector]
public Volume globalVolume;
@@ -53,7 +53,7 @@ namespace Ichni
animeBloom.active = bloomQuality != BloomQuality.Off;
if (animeBloom.active)
{
animeBloom.diffusion.value = bloomQuality == BloomQuality.Performance ? 5 : 8;
animeBloom.diffusion.value = bloomQuality == BloomQuality.Performance ? 5 : 6;
}
}
}

View File

@@ -27,6 +27,7 @@ namespace Ichni
public RTPC LowPassFilter;
private uint _playingId;
private AkSegmentInfo _segmentInfo;
public float songTimeSegment = 0;
public float pauseTimeSegment;
private float duration;
@@ -42,6 +43,12 @@ namespace Ichni
isStarting = false;
}
private void OnDestroy()
{
_segmentInfo?.Dispose();
_segmentInfo = null;
}
private void Update()
{
@@ -195,10 +202,10 @@ namespace Ichni
int PlaySegment()
{
AkSegmentInfo segmentInfo = new AkSegmentInfo();
AkUnitySoundEngine.GetPlayingSegmentInfo(_playingId, segmentInfo,true);
_segmentInfo ??= new AkSegmentInfo();
AkUnitySoundEngine.GetPlayingSegmentInfo(_playingId, _segmentInfo,true);
return segmentInfo.iCurrentPosition;
return _segmentInfo.iCurrentPosition;
}
}
}

View File

@@ -1,10 +1,13 @@
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
namespace Ichni.RhythmGame
{
public class TrackManager : MonoBehaviour
{
[Title("Debug")]
public bool EnableTrackAutoUpdate = false;
#region [] Fields
// 这里可以分类存储场上的各类轨道组件
private List<Track> _activeTracks = new List<Track>(50);
@@ -70,7 +73,7 @@ namespace Ichni.RhythmGame
// 执行所有轨道的重置与缓冲清理操作
foreach (var track in _activeTracks)
{
if(track.isActiveAndEnabled) track.ManualLateUpdate();
if (track.isActiveAndEnabled) track.ManualLateUpdate();
}
}
#endregion

View File

@@ -100,7 +100,7 @@ namespace Ichni
public bool enablePostProcessing = true;
/// <summary>
/// Anime Bloom 的质量档位。默认 Standard对应 Diffusion 8
/// Anime Bloom 的质量档位。默认 Standard对应 Diffusion 6
/// </summary>
public BloomQuality bloomQuality = BloomQuality.Standard;

View File

@@ -20,7 +20,7 @@ namespace Ichni.UI
public Button expandButton;
public Button enterStorylineButton;
public Button enterSongSelectionButton;
public ChapterSelectionUnit connectedChapter;
public string chapterName;
@@ -32,7 +32,7 @@ namespace Ichni.UI
public bool isExpanded;
public bool isDuringAnimation;
[Title("闭合内容")]
[Title("闭合内容")]
public Image bottomTip;
public Image upperTip;
public Image leftProgressBarBase;
@@ -45,34 +45,34 @@ namespace Ichni.UI
public RectTransform titleRect;
public TMP_Text titleText;
[Title("展开内容")]
[Title("展开内容")]
public RectTransform expansionBackground;
public Image expansionRipple;
public Material rippleMaterial;
public Sequence rippleSequence;
public RectTransform expansionFunctions;
public RectTransform expansionInfos;
public TMP_Text innerSongProgressText;
public TMP_Text fullComboText;
public TMP_Text allPerfectText;
public TMP_Text beatmapProgressText;
public void Initialize(ChapterSelectionUnit chapter)
{
connectedChapter = chapter;
expansionRipple.material = new Material(rippleMaterial);
RefreshUnlockState();
expandButton.onClick.AddListener(() =>
{
if (isDuringAnimation)
{
return;
}
if (isExpanded)
{
Shrink();
@@ -82,7 +82,7 @@ namespace Ichni.UI
Expand();
}
});
enterStorylineButton.onClick.AddListener(() =>
{
if (isDuringAnimation)
@@ -94,14 +94,14 @@ namespace Ichni.UI
{
return;
}
ChapterSelectionManager.instance.currentChapter = connectedChapter;
AudioManager.SetSwitch(connectedChapter.chapterSwitch);
ChapterSelectionManager.instance.chapterSelectionUIPage.FadeOut();
StoryManager.instance.OpenChapter(connectedChapter.chapterIndex);
StoryManager.instance.storyUIPage.FadeIn();
});
enterSongSelectionButton.onClick.AddListener(() =>
{
if (isDuringAnimation)
@@ -113,7 +113,7 @@ namespace Ichni.UI
{
return;
}
ChapterSelectionManager.instance.currentChapter = connectedChapter;
AudioManager.SetSwitch(connectedChapter.chapterSwitch);
ChapterSelectionManager.instance.chapterSelectionUIPage.FadeOut();
@@ -158,24 +158,24 @@ namespace Ichni.UI
RefreshUnlockState();
return !isLocked;
}
private void Expand()
{
isExpanded = true;
Sequence expandSequence = DOTween.Sequence();
expandSequence.Append(expansionBackground.DOSizeDelta(new Vector2(1147, 826), 0.3f)
expandSequence.Append(expansionBackground.DOSizeDelta(new Vector2(1147, 826), 0.3f).SetEase(Ease.OutExpo)
.OnStart(() =>
{
expansionBackground.gameObject.SetActive(true);
}));
expandSequence.Join(DOTween.To(() => layoutElement.preferredWidth,
expandSequence.Join(DOTween.To(() => layoutElement.preferredWidth,
x => layoutElement.preferredWidth = x, 1147, 0.3f));
expandSequence.Join(avatarMask.rectTransform.DOSizeDelta(new Vector2(1147, 826), 0.3f));
expandSequence.Join(titleRect.DOSizeDelta(new Vector2(0, 100), 0.3f));
expandSequence.Append(expansionInfos.GetComponent<CanvasGroup>().DOFade(1, 0.3f)
.OnStart(()=>
.OnStart(() =>
{
expansionInfos.gameObject.SetActive(true);
}));
@@ -199,23 +199,23 @@ namespace Ichni.UI
rippleSequence.SetLoops(-1);
rippleSequence.Play();
});
expandSequence.Play();
}
private void Shrink()
{
isExpanded = false;
rippleSequence?.Kill(true);
Sequence shrinkSequence = DOTween.Sequence();
shrinkSequence.Append(bottomTip.DOFade(0f, 0.3f));
shrinkSequence.Join(upperTip.DOFade(0f, 0.3f));
shrinkSequence.Join(expansionInfos.GetComponent<CanvasGroup>().DOFade(0, 0.3f)
.OnComplete(()=>
.OnComplete(() =>
{
expansionInfos.gameObject.SetActive(false);
}));
@@ -224,22 +224,22 @@ namespace Ichni.UI
{
expansionFunctions.gameObject.SetActive(false);
}));
shrinkSequence.Join(titleRect.DOSizeDelta(new Vector2(322, 100), 0.3f));
shrinkSequence.Append(expansionBackground.DOSizeDelta(new Vector2(322, 826), 0.3f)
.OnComplete(() =>
{
expansionBackground.gameObject.SetActive(false);
}));
shrinkSequence.Join(DOTween.To(() => layoutElement.preferredWidth,
shrinkSequence.Join(DOTween.To(() => layoutElement.preferredWidth,
x => layoutElement.preferredWidth = x, 322, 0.3f));
shrinkSequence.Join(avatarMask.rectTransform.DOSizeDelta(new Vector2(322, 826), 0.3f));
shrinkSequence.OnStart(() => isDuringAnimation = true);
shrinkSequence.OnComplete(() => isDuringAnimation = false);
shrinkSequence.Play();
}
}

View File

@@ -49,7 +49,7 @@ namespace Ichni.Menu
public Switch vSyncSwitch;
public Dropdown displayModeDropdown;
public Dropdown displayResolutionDropdown;
public override void Initialize()
{
qualityPresetDropdown.SetUp((int)gameSettings.graphicsQualityPreset, QualityPresetOptions,
@@ -67,7 +67,7 @@ namespace Ichni.Menu
gameSettings.renderScaleLevel = renderScaleModifier.GetValue();
SettingsManager.instance.ApplyGraphicSettings();
};
targetFrameModifier.SetUp(gameSettings.targetFrame, 30, "Menu UI/Settings_Target_Frame");
targetFrameModifier.SetMinMax(30, 120);
targetFrameModifier.updateValueAction = () =>