2025-12-17 04:19:38 -05:00
|
|
|
|
using System;
|
2026-03-20 12:07:44 -04:00
|
|
|
|
using System.Collections.Generic;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
using UniRx;
|
|
|
|
|
|
using UnityEngine;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
using SLSUtilities.General;
|
2025-11-25 08:19:33 -05:00
|
|
|
|
|
|
|
|
|
|
namespace Cielonos.MainGame.Characters
|
|
|
|
|
|
{
|
2025-12-17 04:19:38 -05:00
|
|
|
|
public partial class SelfTimeSubmodule : SubmoduleBase<CharacterBase>
|
2025-11-25 08:19:33 -05:00
|
|
|
|
{
|
2025-12-22 18:36:29 -05:00
|
|
|
|
private TimeManager timeManager => TimeManager.Instance;
|
|
|
|
|
|
private IObservable<float> timeScaleObservable;
|
|
|
|
|
|
|
|
|
|
|
|
public FloatReactiveProperty localTimeScale;
|
|
|
|
|
|
private float globalTimeScale => timeManager.globalTimeScale.Value;
|
|
|
|
|
|
private float playerTimeScale => timeManager.playerTimeScale.Value;
|
|
|
|
|
|
private float alliedMinionTimeScale => timeManager.alliedMinionTimeScale.Value;
|
|
|
|
|
|
private float enemyTimeScale => timeManager.enemyTimeScale.Value;
|
|
|
|
|
|
private float nonPlayerTimeScale => timeManager.nonPlayerTimeScale.Value;
|
|
|
|
|
|
|
2026-05-26 00:21:27 -04:00
|
|
|
|
public Dictionary<string, CooldownTimer> coolDownTimers = new Dictionary<string, CooldownTimer>();
|
2025-12-22 18:36:29 -05:00
|
|
|
|
|
|
|
|
|
|
public float TimeScale => owner.fraction switch
|
|
|
|
|
|
{
|
|
|
|
|
|
Fraction.Player => localTimeScale.Value * globalTimeScale * playerTimeScale,
|
|
|
|
|
|
Fraction.AlliedMinion => localTimeScale.Value * globalTimeScale * alliedMinionTimeScale * nonPlayerTimeScale,
|
|
|
|
|
|
Fraction.Enemy => localTimeScale.Value * globalTimeScale * enemyTimeScale * nonPlayerTimeScale,
|
|
|
|
|
|
Fraction.Neutral => localTimeScale.Value * globalTimeScale * nonPlayerTimeScale,
|
|
|
|
|
|
_ => localTimeScale.Value * globalTimeScale
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
public float DeltaTime => owner.fraction switch
|
|
|
|
|
|
{
|
|
|
|
|
|
Fraction.Player => Time.deltaTime * localTimeScale.Value * globalTimeScale * playerTimeScale,
|
|
|
|
|
|
Fraction.AlliedMinion => Time.deltaTime * localTimeScale.Value * globalTimeScale * alliedMinionTimeScale * nonPlayerTimeScale,
|
|
|
|
|
|
Fraction.Enemy => Time.deltaTime * localTimeScale.Value * globalTimeScale * enemyTimeScale * nonPlayerTimeScale,
|
|
|
|
|
|
Fraction.Neutral => Time.deltaTime * localTimeScale.Value * globalTimeScale * nonPlayerTimeScale,
|
|
|
|
|
|
_ => Time.deltaTime * localTimeScale.Value * globalTimeScale
|
|
|
|
|
|
};
|
2025-11-25 08:19:33 -05:00
|
|
|
|
|
|
|
|
|
|
public SelfTimeSubmodule(CharacterBase entity) : base(entity)
|
|
|
|
|
|
{
|
2025-12-22 18:36:29 -05:00
|
|
|
|
localTimeScale = new FloatReactiveProperty(1);
|
|
|
|
|
|
|
|
|
|
|
|
switch (owner.fraction)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Fraction.Player:
|
|
|
|
|
|
// 依赖: local, global, player
|
|
|
|
|
|
timeScaleObservable =
|
|
|
|
|
|
localTimeScale.CombineLatest(
|
|
|
|
|
|
timeManager.globalTimeScale,
|
|
|
|
|
|
timeManager.playerTimeScale,
|
|
|
|
|
|
(local, global, player) => local * global * player
|
|
|
|
|
|
);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case Fraction.AlliedMinion:
|
|
|
|
|
|
// 依赖: local, global, alliedMinion, nonPlayer
|
|
|
|
|
|
timeScaleObservable =
|
|
|
|
|
|
localTimeScale.CombineLatest(
|
|
|
|
|
|
timeManager.globalTimeScale,
|
|
|
|
|
|
timeManager.alliedMinionTimeScale,
|
|
|
|
|
|
timeManager.nonPlayerTimeScale,
|
|
|
|
|
|
(local, global, minion, nonPlayer) => local * global * minion * nonPlayer
|
|
|
|
|
|
);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case Fraction.Enemy:
|
|
|
|
|
|
// 依赖: local, global, enemy, nonPlayer
|
|
|
|
|
|
timeScaleObservable =
|
|
|
|
|
|
localTimeScale.CombineLatest(
|
|
|
|
|
|
timeManager.globalTimeScale,
|
|
|
|
|
|
timeManager.enemyTimeScale,
|
|
|
|
|
|
timeManager.nonPlayerTimeScale,
|
|
|
|
|
|
(local, global, enemy, nonPlayer) => local * global * enemy * nonPlayer
|
|
|
|
|
|
);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case Fraction.Neutral:
|
|
|
|
|
|
// 依赖: local, global, nonPlayer
|
|
|
|
|
|
timeScaleObservable =
|
|
|
|
|
|
localTimeScale.CombineLatest(
|
|
|
|
|
|
timeManager.globalTimeScale,
|
|
|
|
|
|
timeManager.nonPlayerTimeScale,
|
|
|
|
|
|
(local, global, nonPlayer) => local * global * nonPlayer
|
|
|
|
|
|
);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new ArgumentOutOfRangeException();
|
|
|
|
|
|
}
|
2026-01-03 18:19:39 -05:00
|
|
|
|
}
|
2025-12-17 04:19:38 -05:00
|
|
|
|
|
2026-01-03 18:19:39 -05:00
|
|
|
|
public void SetUp(CharacterBase entity)
|
|
|
|
|
|
{
|
2025-12-17 04:19:38 -05:00
|
|
|
|
if (entity.animationSc != null)
|
|
|
|
|
|
{
|
2025-12-22 18:36:29 -05:00
|
|
|
|
timeScaleObservable.Subscribe(timeScale =>
|
2025-12-17 04:19:38 -05:00
|
|
|
|
{
|
2025-12-22 18:36:29 -05:00
|
|
|
|
entity.animationSc.fullBodyFuncAnimSm.currentPlaySpeedMultiplier = timeScale;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
}).AddTo(entity);
|
2025-12-17 04:19:38 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-25 08:19:33 -05:00
|
|
|
|
if (entity.animationSc.animator != null)
|
|
|
|
|
|
{
|
2025-12-22 18:36:29 -05:00
|
|
|
|
timeScaleObservable.Subscribe(timeScale =>
|
2025-12-17 04:19:38 -05:00
|
|
|
|
{
|
2025-12-22 18:36:29 -05:00
|
|
|
|
entity.animationSc.animator.speed = timeScale;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
}).AddTo(entity);
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-20 12:07:44 -04:00
|
|
|
|
|
|
|
|
|
|
public void Update()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 这里不需要每帧计算 TimeScale,因为它是通过 ReactiveProperty 自动更新的
|
|
|
|
|
|
// 但是我们需要更新所有 Timer 的 currentTime
|
|
|
|
|
|
foreach (var timer in coolDownTimers.Values)
|
|
|
|
|
|
{
|
|
|
|
|
|
timer.Update(DeltaTime);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|
2025-12-17 04:19:38 -05:00
|
|
|
|
|
|
|
|
|
|
public partial class SelfTimeSubmodule
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 添加一个基于本地时间(Local DeltaTime)的计时器
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public IDisposable AddLocalTimer(float duration, Action onComplete, Action onUpdate = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 用于记录累积时间
|
|
|
|
|
|
float accumulatedTime = 0f;
|
|
|
|
|
|
|
|
|
|
|
|
return Observable.EveryUpdate()
|
|
|
|
|
|
.Select(_ => DeltaTime) // 1. 获取每帧的真实 DeltaTime
|
|
|
|
|
|
.TakeWhile(dt =>
|
|
|
|
|
|
{
|
|
|
|
|
|
// 2. 累加时间
|
|
|
|
|
|
accumulatedTime += dt;
|
|
|
|
|
|
// 3. 如果累积时间小于总时长,继续流;否则停止流并触发 OnCompleted
|
|
|
|
|
|
return accumulatedTime < duration;
|
|
|
|
|
|
})
|
|
|
|
|
|
.Subscribe(
|
|
|
|
|
|
_ => onUpdate?.Invoke(), // 每帧更新时执行 Action
|
|
|
|
|
|
() => onComplete?.Invoke() // 4. 流结束时(TakeWhile 返回 false)执行 Action
|
|
|
|
|
|
).AddTo(owner); // 5. 绑定生命周期到角色,防止内存泄漏
|
|
|
|
|
|
}
|
2025-12-22 18:36:29 -05:00
|
|
|
|
|
|
|
|
|
|
public IDisposable AddGlobalTimer(float duration, Action onComplete, Action onUpdate = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 用于记录累积时间
|
|
|
|
|
|
float accumulatedTime = 0f;
|
|
|
|
|
|
|
|
|
|
|
|
return Observable.EveryUpdate()
|
|
|
|
|
|
.Select(_ => Time.deltaTime) // 1. 获取每帧的全局 DeltaTime
|
|
|
|
|
|
.TakeWhile(dt =>
|
|
|
|
|
|
{
|
|
|
|
|
|
// 2. 累加时间
|
|
|
|
|
|
accumulatedTime += dt;
|
|
|
|
|
|
// 3. 如果累积时间小于总时长,继续流;否则停止流并触发 OnCompleted
|
|
|
|
|
|
return accumulatedTime < duration;
|
|
|
|
|
|
})
|
|
|
|
|
|
.Subscribe(
|
|
|
|
|
|
_ => onUpdate?.Invoke(), // 每帧更新时执行 Action
|
|
|
|
|
|
() => onComplete?.Invoke() // 4. 流结束时(TakeWhile 返回 false)执行 Action
|
|
|
|
|
|
).AddTo(owner); // 5. 绑定生命周期到角色,防止内存泄漏
|
|
|
|
|
|
}
|
2025-12-17 04:19:38 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public partial class SelfTimeSubmodule
|
|
|
|
|
|
{
|
|
|
|
|
|
// 缓存一个默认的抛物线曲线,避免每次 null 时都 new 一个
|
|
|
|
|
|
// 形状:(0,0) -> (0.5, 1) -> (1, 0)
|
|
|
|
|
|
private static readonly AnimationCurve DefaultParabola = new AnimationCurve(
|
|
|
|
|
|
new Keyframe(0f, 0f),
|
|
|
|
|
|
new Keyframe(0.5f, 1f),
|
|
|
|
|
|
new Keyframe(1f, 0f)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
private IDisposable hitStopDisposable;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 应用顿帧(Hit Stop)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="duration">持续时间(秒,基于全局游戏时间)</param>
|
|
|
|
|
|
/// <param name="targetScale">目标缩放倍率(通常为 0 或 0.1)</param>
|
|
|
|
|
|
public void ModifyTimeScale(float duration, float targetScale = 0f)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1. 如果之前有正在进行的顿帧,先取消它(防止旧的恢复逻辑覆盖新的设置)
|
|
|
|
|
|
hitStopDisposable?.Dispose();
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 设置当前的缩放倍率
|
2025-12-22 18:36:29 -05:00
|
|
|
|
localTimeScale.Value = targetScale;
|
2025-12-17 04:19:38 -05:00
|
|
|
|
|
|
|
|
|
|
// 3. 开启计时器
|
|
|
|
|
|
// 注意:这里使用 Scheduler.MainThread,它是基于 Time.time (全局时间) 的。
|
|
|
|
|
|
// 这意味着:
|
|
|
|
|
|
// - 它会受到 Time.timeScale (全局暂停) 的影响(符合预期,游戏暂停时顿帧也该暂停)。
|
|
|
|
|
|
// - 它 *不会* 受到 timeScaleCoefficient (我们自己改的本地时间) 的影响(关键!)。
|
|
|
|
|
|
hitStopDisposable = Observable.Timer(TimeSpan.FromSeconds(duration), Scheduler.MainThread)
|
|
|
|
|
|
.Subscribe(_ =>
|
|
|
|
|
|
{
|
|
|
|
|
|
// 计时结束,恢复为 1
|
2025-12-22 18:36:29 -05:00
|
|
|
|
localTimeScale.Value = 1f;
|
2025-12-17 04:19:38 -05:00
|
|
|
|
hitStopDisposable = null;
|
|
|
|
|
|
})
|
|
|
|
|
|
.AddTo(owner); // 安全性:如果角色在顿帧期间死亡/销毁,自动取消计时器
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 使用曲线动态修改本地时间流速
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="duration">持续时间(秒)</param>
|
2025-12-22 18:36:29 -05:00
|
|
|
|
/// <param name="zeroValue">曲线起点值</param>
|
|
|
|
|
|
/// <param name="oneValue">曲线终点值</param>
|
|
|
|
|
|
/// <param name="easeType">插值曲线(null 则使用默认的 EaseInOut (从0到1) 曲线)</param>
|
|
|
|
|
|
public void ModifyTimeScale(float duration, EaseType easeType, float zeroValue = 0, float oneValue = 1)
|
2025-12-17 04:19:38 -05:00
|
|
|
|
{
|
|
|
|
|
|
// 1. 清理旧的计时器
|
|
|
|
|
|
hitStopDisposable?.Dispose();
|
2025-12-22 18:36:29 -05:00
|
|
|
|
|
|
|
|
|
|
AnimationCurve curve = Ease.GetCurve(easeType);
|
|
|
|
|
|
|
2025-12-17 04:19:38 -05:00
|
|
|
|
// 3. 记录开始时的累计时间
|
|
|
|
|
|
float timer = 0f;
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 开启每帧更新的流
|
|
|
|
|
|
hitStopDisposable = Observable.EveryUpdate()
|
|
|
|
|
|
.TakeWhile(_ => timer < duration) // 当时间超过 duration 时结束流
|
|
|
|
|
|
.Subscribe(
|
|
|
|
|
|
_ =>
|
|
|
|
|
|
{
|
2025-12-22 18:36:29 -05:00
|
|
|
|
// 累加时间
|
|
|
|
|
|
timer += DeltaTime;
|
2025-12-17 04:19:38 -05:00
|
|
|
|
|
2025-12-22 18:36:29 -05:00
|
|
|
|
// 计算归一化时间(0 到 1)
|
|
|
|
|
|
float normalizedTime = Mathf.Clamp01(timer / duration);
|
2025-12-17 04:19:38 -05:00
|
|
|
|
|
2025-12-22 18:36:29 -05:00
|
|
|
|
// 根据曲线获取当前的插值系数
|
|
|
|
|
|
float curveValue = curve.Evaluate(normalizedTime);
|
2025-12-17 04:19:38 -05:00
|
|
|
|
|
2025-12-22 18:36:29 -05:00
|
|
|
|
// 计算当前的时间缩放值
|
|
|
|
|
|
float currentScale = curveValue * (oneValue - zeroValue) + zeroValue;
|
2025-12-17 04:19:38 -05:00
|
|
|
|
|
2025-12-22 18:36:29 -05:00
|
|
|
|
// 应用到 timeScaleCoefficient
|
|
|
|
|
|
localTimeScale.Value = currentScale;
|
2025-12-17 04:19:38 -05:00
|
|
|
|
},
|
|
|
|
|
|
() =>
|
|
|
|
|
|
{
|
|
|
|
|
|
// 5. 计时结束后的收尾工作
|
|
|
|
|
|
// 通常为了安全,结束后我们会强制恢复到 1.0 (正常速度)
|
|
|
|
|
|
// 或者你可以恢复到 start,视具体需求而定
|
2025-12-22 18:36:29 -05:00
|
|
|
|
localTimeScale.Value = 1f;
|
2025-12-17 04:19:38 -05:00
|
|
|
|
hitStopDisposable = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
.AddTo(owner); // 绑定生命周期
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 可选:提供一个强制恢复的方法,用于因为某些逻辑需要立刻打断顿帧时调用
|
|
|
|
|
|
public void ResetTimeScale()
|
|
|
|
|
|
{
|
|
|
|
|
|
hitStopDisposable?.Dispose();
|
2025-12-22 18:36:29 -05:00
|
|
|
|
localTimeScale.Value = 1f;
|
2025-12-17 04:19:38 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-20 12:07:44 -04:00
|
|
|
|
|
2026-05-26 00:21:27 -04:00
|
|
|
|
public class CooldownTimer : Timer
|
2026-03-20 12:07:44 -04:00
|
|
|
|
{
|
|
|
|
|
|
public float originalDuration; // 可选:记录最初设置的持续时间,方便重置时使用
|
|
|
|
|
|
|
2026-05-26 00:21:27 -04:00
|
|
|
|
public CooldownTimer(float duration) : base(duration)
|
2026-03-20 12:07:44 -04:00
|
|
|
|
{
|
|
|
|
|
|
this.originalDuration = duration;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 重置计时器,可以选择新的持续时间(如果不提供则使用原始持续时间)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="newDuration"></param>
|
2026-05-26 00:21:27 -04:00
|
|
|
|
public override void Reset(float newDuration = -1f)
|
2026-03-20 12:07:44 -04:00
|
|
|
|
{
|
|
|
|
|
|
currentTime = 0f;
|
|
|
|
|
|
duration = newDuration >= 0f ? newDuration : originalDuration;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-25 08:19:33 -05:00
|
|
|
|
}
|