设置扩展
This commit is contained in:
@@ -32,6 +32,47 @@ namespace Ichni.RhythmGame
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds the list of elements whose active state is driven by a finite time duration.
|
||||
/// This preserves the existing runtime filter, while avoiding a LINQ allocation during
|
||||
/// every project load.
|
||||
/// </summary>
|
||||
public void RebuildRuntimeTimeDurations()
|
||||
{
|
||||
List<TimeDurationSubmodule> runtimeTimeDurations = GameManager.Instance.timeDurations;
|
||||
runtimeTimeDurations.Clear();
|
||||
|
||||
float songLength = GameManager.Instance.songInformation.songLength;
|
||||
for (int i = 0; i < gameElementList.Count; i++)
|
||||
{
|
||||
if (gameElementList[i] is not IHaveTimeDurationSubmodule timeDurationHost)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
TimeDurationSubmodule timeDuration = timeDurationHost.timeDurationSubmodule;
|
||||
if (timeDuration != null &&
|
||||
(timeDuration.startTime > 0f || timeDuration.endTime < songLength))
|
||||
{
|
||||
runtimeTimeDurations.Add(timeDuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Immediately applies the active state for the registered time-duration elements.
|
||||
/// Used at loading-state boundaries so the first rendered frame already matches the
|
||||
/// current song timeline.
|
||||
/// </summary>
|
||||
public void SyncRuntimeTimeDurationStates(float songTime)
|
||||
{
|
||||
List<TimeDurationSubmodule> runtimeTimeDurations = GameManager.Instance.timeDurations;
|
||||
for (int i = 0; i < runtimeTimeDurations.Count; i++)
|
||||
{
|
||||
runtimeTimeDurations[i]?.DetectAndSwitchActiveState(songTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUpInspector()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
@@ -43,4 +84,4 @@ namespace Ichni.RhythmGame
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
113
Assets/Scripts/Game/Base/VFXObject.cs
Normal file
113
Assets/Scripts/Game/Base/VFXObject.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System.Collections.Generic;
|
||||
using Sirenix.OdinInspector;
|
||||
using SLSUtilities.LeanPoolAssistance;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni
|
||||
{
|
||||
/// <summary>
|
||||
/// 面向对象池特效的通用质量控制组件。
|
||||
///
|
||||
/// 挂载在由 LeanPool 生成与回收的 VFX 根节点上。每次 <see cref="OnSpawn"/> 时会先恢复受管理子对象的
|
||||
/// Prefab 初始激活状态,再依据当前 <see cref="VfxQuality"/> 关闭非必要的视觉子树。
|
||||
/// 设置切换仅影响之后新生成的 VFX,不会中断正在播放的效果。
|
||||
/// </summary>
|
||||
public class VFXObject : PooledObject
|
||||
{
|
||||
[Title("VFX Quality")]
|
||||
[Tooltip("Medium 与 Low 都会关闭的次要视觉子树。应放入高频、长寿命或大面积覆盖的装饰层。")]
|
||||
public List<GameObject> disableAtMediumAndLower = new List<GameObject>();
|
||||
|
||||
[Tooltip("仅 Low 会额外关闭的视觉子树。Medium 仍保留这些效果以维持基本演出层次。")]
|
||||
public List<GameObject> disableAtLowOnly = new List<GameObject>();
|
||||
|
||||
// 仅缓存两个质量列表中出现过的对象。没有被列出的子对象永远不受本组件控制。
|
||||
private readonly Dictionary<GameObject, bool> originalActiveStates = new Dictionary<GameObject, bool>();
|
||||
private bool qualityObjectsCached;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
CacheOriginalActiveStates();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对象从 LeanPool 取出时执行一次质量裁剪。
|
||||
/// 先恢复原始状态可避免同一个对象先以 Low 生成、回收后再以 High 生成时仍错误保持隐藏。
|
||||
/// </summary>
|
||||
public override void OnSpawn()
|
||||
{
|
||||
// LeanPool 的 Broadcast IPoolable 模式或错误的重复通知不应重新裁剪一个已播放的效果。
|
||||
if (spawnExecuted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CacheOriginalActiveStates();
|
||||
RestoreOriginalActiveStates();
|
||||
ApplyCurrentQuality();
|
||||
base.OnSpawn();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取生成瞬间的 VFX Quality 并关闭对应的完整视觉子树。
|
||||
/// 未初始化 SettingsManager 的编辑器预览或独立加载流程默认使用 High,保持作者配置的完整视觉。
|
||||
/// </summary>
|
||||
private void ApplyCurrentQuality()
|
||||
{
|
||||
VfxQuality currentQuality = SettingsManager.instance?.settingsSaveData?.vfxQuality ?? VfxQuality.High;
|
||||
|
||||
if (currentQuality <= VfxQuality.Medium)
|
||||
{
|
||||
SetObjectsActive(disableAtMediumAndLower, false);
|
||||
}
|
||||
|
||||
if (currentQuality == VfxQuality.Low)
|
||||
{
|
||||
SetObjectsActive(disableAtLowOnly, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录 Quality 列表对象在 Prefab 中的初始激活状态。
|
||||
/// 这样即使某个对象本来就由作者设为 inactive,也不会在下一次 Spawn 时被强制打开。
|
||||
/// </summary>
|
||||
private void CacheOriginalActiveStates()
|
||||
{
|
||||
if (qualityObjectsCached)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CacheOriginalActiveStates(disableAtMediumAndLower);
|
||||
CacheOriginalActiveStates(disableAtLowOnly);
|
||||
qualityObjectsCached = true;
|
||||
}
|
||||
|
||||
private void CacheOriginalActiveStates(List<GameObject> qualityObjects)
|
||||
{
|
||||
foreach (GameObject qualityObject in qualityObjects)
|
||||
{
|
||||
if (!originalActiveStates.ContainsKey(qualityObject))
|
||||
{
|
||||
originalActiveStates.Add(qualityObject, qualityObject.activeSelf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RestoreOriginalActiveStates()
|
||||
{
|
||||
foreach (KeyValuePair<GameObject, bool> originalState in originalActiveStates)
|
||||
{
|
||||
originalState.Key.SetActive(originalState.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetObjectsActive(List<GameObject> qualityObjects, bool isActive)
|
||||
{
|
||||
foreach (GameObject qualityObject in qualityObjects)
|
||||
{
|
||||
qualityObject.SetActive(isActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Game/Base/VFXObject.cs.meta
Normal file
2
Assets/Scripts/Game/Base/VFXObject.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 922e341faf5784c41b3b0466e0468972
|
||||
@@ -68,6 +68,12 @@ namespace Ichni.RhythmGame
|
||||
|
||||
public override void Execute()
|
||||
{
|
||||
// 玩家关闭游戏内音乐特效时,不再覆盖 SettingsManager 已归零的 RTPC。
|
||||
if (!SettingsManager.instance.settingsSaveData.enableInGameAudioEffects)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 在 10Hz(无效果)与 peak Hz(最强截止)之间基于曲线插值
|
||||
float intensity = intensityCurve != null ? intensityCurve.Evaluate(effectProgressPercent) : 0f;
|
||||
float currentFreq = Mathf.Lerp(MinFrequency, peak, intensity);
|
||||
|
||||
@@ -66,6 +66,12 @@ namespace Ichni.RhythmGame
|
||||
|
||||
public override void Execute()
|
||||
{
|
||||
// 玩家关闭游戏内音乐特效时,不再覆盖 SettingsManager 已归零的 RTPC。
|
||||
if (!SettingsManager.instance.settingsSaveData.enableInGameAudioEffects)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 在 22000Hz(无效果)与 bottom Hz(最强截止)之间基于曲线插值
|
||||
float intensity = intensityCurve != null ? intensityCurve.Evaluate(effectProgressPercent) : 0f;
|
||||
float currentFreq = Mathf.Lerp(MaxFrequency, bottom, intensity);
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace Ichni.RhythmGame
|
||||
|
||||
if (!GameManager.Instance.songPlayer.isUpdating || isFinalJudged) return true;
|
||||
|
||||
if (SettingsManager.instance.gameSettings.autoPlay && isFirstJudged) ExecuteProcessJudge();
|
||||
if (SettingsManager.instance.settingsSaveData.autoPlay && isFirstJudged) ExecuteProcessJudge();
|
||||
|
||||
if (isHolding)
|
||||
{
|
||||
@@ -109,7 +109,7 @@ namespace Ichni.RhythmGame
|
||||
|
||||
UpdateHoldEffects();
|
||||
|
||||
if (SettingsManager.instance.gameSettings.autoPlay && !isFirstJudged && currentSongTime >= exactJudgeTime)
|
||||
if (SettingsManager.instance.settingsSaveData.autoPlay && !isFirstJudged && currentSongTime >= exactJudgeTime)
|
||||
{
|
||||
ExecuteStartJudge(currentSongTime);
|
||||
}
|
||||
@@ -227,7 +227,7 @@ namespace Ichni.RhythmGame
|
||||
|
||||
protected override void SetJudgeArea()
|
||||
{
|
||||
if (!SettingsManager.instance.gameSettings.debugMode || NoteJudgeSubmodule == null) return;
|
||||
if (!SettingsManager.instance.settingsSaveData.debugMode || NoteJudgeSubmodule == null) return;
|
||||
|
||||
if (isDuringJudging && !isFirstJudged)
|
||||
{
|
||||
|
||||
@@ -201,7 +201,7 @@ namespace Ichni.RhythmGame
|
||||
}
|
||||
|
||||
// AutoPlay
|
||||
if (SettingsManager.instance.gameSettings.autoPlay && !isFirstJudged && currentSongTime >= exactJudgeTime)
|
||||
if (SettingsManager.instance.settingsSaveData.autoPlay && !isFirstJudged && currentSongTime >= exactJudgeTime)
|
||||
{
|
||||
ExecuteStartJudge(currentSongTime);
|
||||
}
|
||||
@@ -239,7 +239,7 @@ namespace Ichni.RhythmGame
|
||||
|
||||
protected virtual void SetJudgeArea()
|
||||
{
|
||||
if (!SettingsManager.instance.gameSettings.debugMode || NoteJudgeSubmodule?.judgeUnitList == null) return;
|
||||
if (!SettingsManager.instance.settingsSaveData.debugMode || NoteJudgeSubmodule?.judgeUnitList == null) return;
|
||||
|
||||
if (isDuringJudging && !isFirstJudged)
|
||||
{
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Ichni.RhythmGame
|
||||
|
||||
if(trackRendererSubmodule != null)
|
||||
{
|
||||
trackRendererSubmodule.meshGenerator.autoUpdate = false;
|
||||
trackRendererSubmodule.meshGenerator.autoUpdate = true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -83,7 +83,7 @@ namespace Ichni.RhythmGame
|
||||
}
|
||||
|
||||
#region [IScheduledElement] Scheduler Interface
|
||||
public void ScheduledUpdate(UpdatePhase phase, float songTime)
|
||||
public override void ScheduledUpdate(UpdatePhase phase, float songTime)
|
||||
{
|
||||
ManualUpdate(songTime);
|
||||
}
|
||||
|
||||
@@ -89,10 +89,10 @@ namespace Ichni.RhythmGame
|
||||
return;
|
||||
}
|
||||
|
||||
if (forceImmediateRebuild)
|
||||
/*if (forceImmediateRebuild)
|
||||
{
|
||||
meshGenerator.RebuildImmediate();
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
public void RequestMeshRebuild()
|
||||
|
||||
Reference in New Issue
Block a user