Files
Cielonos/Assets/Scripts/MainGame/Characters/Base/Submodules/SelfTimeSubmodule.cs
SoulliesOfficial d15957c719 更新
2025-12-17 04:19:38 -05:00

166 lines
6.9 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 System.Collections;
using System.Collections.Generic;
using Cielonos.MainGame.Characters;
using UniRx;
using UnityEngine;
namespace Cielonos.MainGame.Characters
{
public partial class SelfTimeSubmodule : SubmoduleBase<CharacterBase>
{
public FloatReactiveProperty timeScaleCoefficient;
public float TimeScale => timeScaleCoefficient.Value * Time.timeScale;
public float DeltaTime => timeScaleCoefficient.Value * Time.deltaTime;
public SelfTimeSubmodule(CharacterBase entity) : base(entity)
{
timeScaleCoefficient = new FloatReactiveProperty(1);
if (entity.animationSc != null)
{
timeScaleCoefficient.Subscribe(x =>
{
entity.animationSc.fullBodyFuncAnimSm.currentPlaySpeedMultiplier = x;
});
}
if (entity.animationSc.animator != null)
{
timeScaleCoefficient.Subscribe(x =>
{
entity.animationSc.animator.speed = x;
});
}
}
}
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. 绑定生命周期到角色,防止内存泄漏
}
}
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. 设置当前的缩放倍率
timeScaleCoefficient.Value = targetScale;
// 3. 开启计时器
// 注意:这里使用 Scheduler.MainThread它是基于 Time.time (全局时间) 的。
// 这意味着:
// - 它会受到 Time.timeScale (全局暂停) 的影响(符合预期,游戏暂停时顿帧也该暂停)。
// - 它 *不会* 受到 timeScaleCoefficient (我们自己改的本地时间) 的影响(关键!)。
hitStopDisposable = Observable.Timer(TimeSpan.FromSeconds(duration), Scheduler.MainThread)
.Subscribe(_ =>
{
// 计时结束,恢复为 1
timeScaleCoefficient.Value = 1f;
hitStopDisposable = null;
})
.AddTo(owner); // 安全性:如果角色在顿帧期间死亡/销毁,自动取消计时器
}
/// <summary>
/// 使用曲线动态修改本地时间流速
/// </summary>
/// <param name="duration">持续时间(秒)</param>
/// <param name="start">曲线值为0时对应的时间倍率通常是初始值</param>
/// <param name="peak">曲线值为1时对应的时间倍率通常是极值</param>
/// <param name="curve">时间变化曲线归一化X轴0~1Y轴通常0~1。如果为null则使用默认的“先升后降”抛物线。</param>
public void ModifyTimeScale(float duration, float start, float peak, AnimationCurve curve = null)
{
// 1. 清理旧的计时器
hitStopDisposable?.Dispose();
// 2. 处理默认曲线逻辑
curve ??= DefaultParabola;
// 3. 记录开始时的累计时间
float timer = 0f;
// 4. 开启每帧更新的流
hitStopDisposable = Observable.EveryUpdate()
.TakeWhile(_ => timer < duration) // 当时间超过 duration 时结束流
.Subscribe(
_ =>
{
// 累加时间 (使用 Time.deltaTime 以响应全局暂停)
timer += Time.deltaTime;
// 计算归一化进度 (0.0 ~ 1.0)
float progress = Mathf.Clamp01(timer / duration);
// 核心逻辑:
// A. 从曲线获取当前的“强度” (Y轴值)
float curveValue = curve.Evaluate(progress);
// B. 在 start 和 peak 之间根据强度进行插值
// 当 curveValue = 0 时,结果为 start
// 当 curveValue = 1 时,结果为 peak
float currentScale = Mathf.Lerp(start, peak, curveValue);
// C. 应用到响应式属性
timeScaleCoefficient.Value = currentScale;
},
() =>
{
// 5. 计时结束后的收尾工作
// 通常为了安全,结束后我们会强制恢复到 1.0 (正常速度)
// 或者你可以恢复到 start视具体需求而定
timeScaleCoefficient.Value = 1f;
hitStopDisposable = null;
}
)
.AddTo(owner); // 绑定生命周期
}
// 可选:提供一个强制恢复的方法,用于因为某些逻辑需要立刻打断顿帧时调用
public void ResetTimeScale()
{
hitStopDisposable?.Dispose();
timeScaleCoefficient.Value = 1f;
}
}
}