后处理+FEEL完全改进
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using Sirenix.OdinInspector;
|
||||
using SLSFramework.General;
|
||||
using SLSFramework.WwiseAssistance;
|
||||
using SLSUtilities.FunctionalAnimation;
|
||||
using UniRx;
|
||||
@@ -220,4 +221,24 @@ namespace Cielonos.MainGame.Characters
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class CharacterBase
|
||||
{
|
||||
public Vector2 GetNormalizedScreenPosition(Camera cam = null)
|
||||
{
|
||||
if (this is Player player)
|
||||
{
|
||||
cam ??= player.viewSc.playerCamera;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cam == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cam), "Camera must be provided for non-player characters.");
|
||||
}
|
||||
}
|
||||
|
||||
return SpaceConverter.WorldPointToNormalizedScreenPoint(flexibleCenterPoint.position, cam);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,7 +105,7 @@ namespace Cielonos.MainGame.Characters
|
||||
|
||||
private void BoneShakeLateUpdate()
|
||||
{
|
||||
float dt = Time.deltaTime;
|
||||
float dt = owner.selfTimeSm.DeltaTime;
|
||||
|
||||
for (int i = activeShakes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
@@ -267,7 +267,7 @@ namespace Cielonos.MainGame.Characters
|
||||
}
|
||||
else
|
||||
{
|
||||
animator.CrossFade("Empty", 0f, fullBodyActionIndex, 0);
|
||||
animator.CrossFade("Empty", 0.1f, fullBodyActionIndex, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace Cielonos.MainGame.Characters
|
||||
{
|
||||
foreach (var feedbackUnit in feedbacks.Values)
|
||||
{
|
||||
float timeScaleMultiplier = owner.selfTimeSm.TimeScale;
|
||||
feedbackUnit.feedback.ExternalTimeScale = timeScaleMultiplier;
|
||||
feedbackUnit.Update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +141,9 @@ namespace Cielonos.MainGame.Characters
|
||||
{
|
||||
sourceItem.audioContainer.PlaySoundFX("PerfectBlock");
|
||||
sourceItem.feedbackSc["PerfectBlock"]?.Play();
|
||||
sourceItem.blockData.InstantiateBlockEffect(perfectEffectName, blockEffectPosition, Quaternion.identity);
|
||||
GameObject pObj = sourceItem.blockData.InstantiateBlockEffect(perfectEffectName, sourceCharacter, blockEffectPosition, Quaternion.identity);
|
||||
pObj.GetComponent<ParticleSystem>().Simulate(0.1f, true, true);
|
||||
pObj.GetComponent<ParticleSystem>().Play();//TODO: 增加起点时间参数
|
||||
}
|
||||
|
||||
onPerfectBlock?.Invoke(attackArea);
|
||||
@@ -153,7 +155,7 @@ namespace Cielonos.MainGame.Characters
|
||||
{
|
||||
sourceItem.audioContainer.PlaySoundFX("NormalBlock");
|
||||
sourceItem.feedbackSc["NormalBlock"]?.Play();
|
||||
sourceItem.blockData.InstantiateBlockEffect(normalEffectName, blockEffectPosition, Quaternion.identity);
|
||||
sourceItem.blockData.InstantiateBlockEffect(normalEffectName, sourceCharacter, blockEffectPosition, Quaternion.identity);
|
||||
}
|
||||
|
||||
onNormalBlock?.Invoke(attackArea);
|
||||
|
||||
@@ -142,23 +142,23 @@ namespace Cielonos.MainGame.Characters
|
||||
|
||||
public void PerfectDodge()
|
||||
{
|
||||
onPerfectDodge?.Invoke();
|
||||
|
||||
if (sourceItem == null)
|
||||
{
|
||||
sourceCharacter.feedbackSc["PerfectDodge"].Play();
|
||||
Debug.Log("Perfect Dodge!");
|
||||
}
|
||||
|
||||
onPerfectDodge?.Invoke();
|
||||
}
|
||||
|
||||
public void NormalDodge()
|
||||
{
|
||||
onNormalDodge?.Invoke();
|
||||
|
||||
if (sourceItem == null)
|
||||
{
|
||||
sourceCharacter.feedbackSc["NormalDodge"].Play();
|
||||
}
|
||||
|
||||
onNormalDodge?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,6 +136,11 @@ namespace Cielonos.MainGame.Characters
|
||||
|
||||
foreach (Material mat in baseRenderMaterials)
|
||||
{
|
||||
if (!mat.HasProperty("_RimParams"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Tweener rimTween = mat.DOVector(new Vector4(1, 1, 4, 1), "_RimParams", 0.5f)
|
||||
.From(new Vector4(0, 1, 4, 1))
|
||||
.OnPlay(() =>
|
||||
@@ -152,33 +157,6 @@ namespace Cielonos.MainGame.Characters
|
||||
}
|
||||
|
||||
getHitBlinkTween.Play();
|
||||
|
||||
/*getHitBlinkTween.OnPlay(() =>
|
||||
{
|
||||
effectContainers["GetHitBlink"].SetActive(true);
|
||||
});
|
||||
|
||||
foreach (Material mat in effectRenderMaterials["GetHitBlink"])
|
||||
{
|
||||
TweenerCore<Color, Color, ColorOptions> matTween = mat.DOColor(Color.white * 0.5f, "_EmissionColor", 0.05f)
|
||||
.OnStart(() => mat.EnableKeyword("_EMISSION"))
|
||||
.From(Color.black)
|
||||
.SetEase(Ease.OutQuad)
|
||||
.OnComplete(() =>
|
||||
{
|
||||
mat.SetColor("_EmissionColor", Color.black);
|
||||
mat.DisableKeyword("_EMISSION");
|
||||
});
|
||||
getHitBlinkTween.Join(matTween);
|
||||
}
|
||||
|
||||
getHitBlinkTween.SetLoops(2, LoopType.Yoyo);
|
||||
getHitBlinkTween.OnComplete(() =>
|
||||
{
|
||||
effectContainers["GetHitBlink"].SetActive(false);
|
||||
});*/
|
||||
|
||||
getHitBlinkTween.Play();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,107 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Cielonos.MainGame.Characters;
|
||||
using UniRx;
|
||||
using UnityEngine;
|
||||
using SLSFramework.General;
|
||||
|
||||
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;
|
||||
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;
|
||||
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
public SelfTimeSubmodule(CharacterBase entity) : base(entity)
|
||||
{
|
||||
timeScaleCoefficient = new FloatReactiveProperty(1);
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
if (entity.animationSc != null)
|
||||
{
|
||||
timeScaleCoefficient.Subscribe(x =>
|
||||
timeScaleObservable.Subscribe(timeScale =>
|
||||
{
|
||||
entity.animationSc.fullBodyFuncAnimSm.currentPlaySpeedMultiplier = x;
|
||||
entity.animationSc.fullBodyFuncAnimSm.currentPlaySpeedMultiplier = timeScale;
|
||||
});
|
||||
}
|
||||
|
||||
if (entity.animationSc.animator != null)
|
||||
{
|
||||
timeScaleCoefficient.Subscribe(x =>
|
||||
timeScaleObservable.Subscribe(timeScale =>
|
||||
{
|
||||
entity.animationSc.animator.speed = x;
|
||||
entity.animationSc.animator.speed = timeScale;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -59,6 +131,26 @@ namespace Cielonos.MainGame.Characters
|
||||
() => onComplete?.Invoke() // 4. 流结束时(TakeWhile 返回 false)执行 Action
|
||||
).AddTo(owner); // 5. 绑定生命周期到角色,防止内存泄漏
|
||||
}
|
||||
|
||||
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. 绑定生命周期到角色,防止内存泄漏
|
||||
}
|
||||
}
|
||||
|
||||
public partial class SelfTimeSubmodule
|
||||
@@ -84,7 +176,7 @@ namespace Cielonos.MainGame.Characters
|
||||
hitStopDisposable?.Dispose();
|
||||
|
||||
// 2. 设置当前的缩放倍率
|
||||
timeScaleCoefficient.Value = targetScale;
|
||||
localTimeScale.Value = targetScale;
|
||||
|
||||
// 3. 开启计时器
|
||||
// 注意:这里使用 Scheduler.MainThread,它是基于 Time.time (全局时间) 的。
|
||||
@@ -95,7 +187,7 @@ namespace Cielonos.MainGame.Characters
|
||||
.Subscribe(_ =>
|
||||
{
|
||||
// 计时结束,恢复为 1
|
||||
timeScaleCoefficient.Value = 1f;
|
||||
localTimeScale.Value = 1f;
|
||||
hitStopDisposable = null;
|
||||
})
|
||||
.AddTo(owner); // 安全性:如果角色在顿帧期间死亡/销毁,自动取消计时器
|
||||
@@ -105,17 +197,16 @@ namespace Cielonos.MainGame.Characters
|
||||
/// 使用曲线动态修改本地时间流速
|
||||
/// </summary>
|
||||
/// <param name="duration">持续时间(秒)</param>
|
||||
/// <param name="start">曲线值为0时对应的时间倍率(通常是初始值)</param>
|
||||
/// <param name="peak">曲线值为1时对应的时间倍率(通常是极值)</param>
|
||||
/// <param name="curve">时间变化曲线(归一化:X轴0~1,Y轴通常0~1)。如果为null,则使用默认的“先升后降”抛物线。</param>
|
||||
public void ModifyTimeScale(float duration, float start, float peak, AnimationCurve curve = null)
|
||||
/// <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)
|
||||
{
|
||||
// 1. 清理旧的计时器
|
||||
hitStopDisposable?.Dispose();
|
||||
|
||||
// 2. 处理默认曲线逻辑
|
||||
curve ??= DefaultParabola;
|
||||
|
||||
|
||||
AnimationCurve curve = Ease.GetCurve(easeType);
|
||||
|
||||
// 3. 记录开始时的累计时间
|
||||
float timer = 0f;
|
||||
|
||||
@@ -125,30 +216,27 @@ namespace Cielonos.MainGame.Characters
|
||||
.Subscribe(
|
||||
_ =>
|
||||
{
|
||||
// 累加时间 (使用 Time.deltaTime 以响应全局暂停)
|
||||
timer += Time.deltaTime;
|
||||
// 累加时间
|
||||
timer += DeltaTime;
|
||||
|
||||
// 计算归一化进度 (0.0 ~ 1.0)
|
||||
float progress = Mathf.Clamp01(timer / duration);
|
||||
// 计算归一化时间(0 到 1)
|
||||
float normalizedTime = Mathf.Clamp01(timer / duration);
|
||||
|
||||
// 核心逻辑:
|
||||
// A. 从曲线获取当前的“强度” (Y轴值)
|
||||
float curveValue = curve.Evaluate(progress);
|
||||
// 根据曲线获取当前的插值系数
|
||||
float curveValue = curve.Evaluate(normalizedTime);
|
||||
|
||||
// B. 在 start 和 peak 之间根据强度进行插值
|
||||
// 当 curveValue = 0 时,结果为 start
|
||||
// 当 curveValue = 1 时,结果为 peak
|
||||
float currentScale = Mathf.Lerp(start, peak, curveValue);
|
||||
// 计算当前的时间缩放值
|
||||
float currentScale = curveValue * (oneValue - zeroValue) + zeroValue;
|
||||
|
||||
// C. 应用到响应式属性
|
||||
timeScaleCoefficient.Value = currentScale;
|
||||
// 应用到 timeScaleCoefficient
|
||||
localTimeScale.Value = currentScale;
|
||||
},
|
||||
() =>
|
||||
{
|
||||
// 5. 计时结束后的收尾工作
|
||||
// 通常为了安全,结束后我们会强制恢复到 1.0 (正常速度)
|
||||
// 或者你可以恢复到 start,视具体需求而定
|
||||
timeScaleCoefficient.Value = 1f;
|
||||
localTimeScale.Value = 1f;
|
||||
hitStopDisposable = null;
|
||||
}
|
||||
)
|
||||
@@ -159,7 +247,7 @@ namespace Cielonos.MainGame.Characters
|
||||
public void ResetTimeScale()
|
||||
{
|
||||
hitStopDisposable?.Dispose();
|
||||
timeScaleCoefficient.Value = 1f;
|
||||
localTimeScale.Value = 1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user