Files
Cielonos/Assets/Scripts/MainGame/Effects/Feedbacks/Actions/Time/TimeScaleModifierAction.cs
2026-04-18 13:57:19 -04:00

226 lines
7.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// 时间缩放通道的工作模式。
/// </summary>
public enum TimeScaleMode
{
/// <summary>
/// 固定值模式:在 Clip 期间将时间缩放设为固定值。
/// </summary>
Fixed,
/// <summary>
/// 动态曲线模式:根据曲线和 Remap 驱动时间缩放。
/// </summary>
Dynamic
}
/// <summary>
/// 单个时间缩放通道的配置。
/// </summary>
[Serializable]
public class TimeScaleChannel
{
/// <summary>
/// 是否激活此通道。
/// </summary>
[HorizontalGroup("Channel")]
public bool active;
/// <summary>
/// 通道工作模式。
/// </summary>
[ShowIf("active")]
[HorizontalGroup("Channel")]
public TimeScaleMode mode = TimeScaleMode.Fixed;
/// <summary>
/// Fixed 模式下的目标值。
/// </summary>
[ShowIf("@active && mode == TimeScaleMode.Fixed")]
[LabelText("Fixed Value")]
public float fixedValue;
/// <summary>
/// Dynamic 模式下的变化曲线。
/// </summary>
[ShowIf("@active && mode == TimeScaleMode.Dynamic")]
[LabelText("Curve")]
[ShakeCurvePreset]
public AnimationCurve curve = new AnimationCurve(
new Keyframe(0f, 0f),
new Keyframe(0.5f, 1f),
new Keyframe(1f, 0f)
);
/// <summary>
/// 曲线值 0 映射到的实际值。
/// </summary>
[ShowIf("@active && mode == TimeScaleMode.Dynamic")]
[LabelText("Remap Zero")]
[HorizontalGroup("Ramp")]
public float remapZero;
/// <summary>
/// 曲线值 1 映射到的实际值。
/// </summary>
[ShowIf("@active && mode == TimeScaleMode.Dynamic")]
[LabelText("Remap One")]
[HorizontalGroup("Ramp")]
public float remapOne = 1f;
/// <summary>
/// 将此通道的配置转换为事件传输用的 TimeScaleChannelData。
/// </summary>
public TimeScaleChannelData ToChannelData()
{
return new TimeScaleChannelData
{
active = active,
mode = mode,
fixedValue = fixedValue,
curve = curve,
remapZero = remapZero,
remapOne = remapOne
};
}
public float Evaluate(float normalizedTime)
{
if (!active) return 1f;
if (mode == TimeScaleMode.Fixed)
{
return fixedValue;
}
float curveValue = curve?.Evaluate(normalizedTime) ?? 0f;
return Mathf.LerpUnclamped(remapZero, remapOne, curveValue);
}
}
/// <summary>
/// 时间缩放修改器反馈,通过 TimeScaleShakeEvent 触发 TimeScaleShaker。
/// Shaker 负责管理多个并发时间缩放实例的叠加混合和初始值恢复。
///
/// 重要:此 Action 会忽略时间缩放,使用未缩放的 deltaTime 驱动。
/// 当 Time.timeScale == 0 时,此 Action 也会暂停。
/// 不要在包含此 Action 的 Clip 上启用自定义 overrideTimeSettings
/// FeedbackData 的 defaultTimeSettings.useTimeScale 也应保持为 false。
/// </summary>
[Serializable]
[FeedbackActionColor(0.3f, 0.7f, 1.0f)]
public class TimeScaleModifierAction : FeedbackActionBase
{
public override string DisplayName => "Time Scale Modifier";
/// <summary>
/// 忽略时间缩放,使用未缩放的 deltaTime。
/// </summary>
public override bool IgnoreTimeScale => true;
public TimeScaleChannel globalChannel = new TimeScaleChannel { active = true, fixedValue = 0.1f };
public bool advancedSettings = false;
[ShowIf("advancedSettings")]
public TimeScaleChannel playerChannel = new TimeScaleChannel();
[ShowIf("advancedSettings")]
public TimeScaleChannel enemyChannel = new TimeScaleChannel();
[ShowIf("advancedSettings")]
public TimeScaleChannel alliedChannel = new TimeScaleChannel();
[ShowIf("advancedSettings")]
public TimeScaleChannel nonPlayerChannel = new TimeScaleChannel();
public override void OnStart(FeedbackContext context)
{
// 通过事件触发让TimeScaleShaker注册这个实例
TimeScaleShakeEvent.Trigger(
duration: context.duration,
global: globalChannel.ToChannelData(),
player: playerChannel.ToChannelData(),
enemy: enemyChannel.ToChannelData(),
allied: alliedChannel.ToChannelData(),
nonPlayer: nonPlayerChannel.ToChannelData()
);
// 立即执行一次TimeScaleShaker的更新
// 这样在同一帧内TimeScaleModifierAction修改的globalTimeScale就能立即生效
ImmediateApplyTimeScale();
}
/// <summary>
/// 立即应用时间缩放,确保在同一帧内立即生效
/// </summary>
private void ImmediateApplyTimeScale()
{
if (TimeManager.Instance == null) return;
if (globalChannel.active)
{
TimeManager.Instance.globalTimeScale.Value = globalChannel.Evaluate(0);
}
if (playerChannel.active)
{
TimeManager.Instance.playerTimeScale.Value = playerChannel.Evaluate(0);
}
if (enemyChannel.active)
{
TimeManager.Instance.enemyTimeScale.Value = enemyChannel.Evaluate(0);
}
if (alliedChannel.active)
{
TimeManager.Instance.alliedMinionTimeScale.Value = alliedChannel.Evaluate(0);
}
if (nonPlayerChannel.active)
{
TimeManager.Instance.nonPlayerTimeScale.Value = nonPlayerChannel.Evaluate(0);
}
}
public override void OnUpdate(FeedbackContext context, float normalizedTime)
{
// Shaker 自行每帧驱动所有活跃实例。
}
public override void OnEnd(FeedbackContext context)
{
// Shaker 自动管理实例生命周期和初始值恢复。
}
public override void OnInterrupt(FeedbackContext context)
{
TimeScaleShakeEvent.Trigger(0f, stop: true);
}
public override bool Validate(out string error)
{
bool anyActive = globalChannel.active || playerChannel.active ||
enemyChannel.active || alliedChannel.active ||
nonPlayerChannel.active;
if (!anyActive)
{
error = "No time scale channel is active. Enable at least one channel.";
return false;
}
error = null;
return true;
}
}
}