feat(feedback): 命中/受击反馈支持固定位置与击中位置两种生成模式

DamageInfo 新增运行时字段 HitPoint:HitBox 结算时写入精确接触点
(ClosestPoint),HurtBox.ReceiveDamage 以解析后命中点兜底盖戳,
陷阱/环境等直调路径同样可用。IFeedbackPlayer.PlayHit/PlayTakeHit
增加命中点参数,全链路(武器实例→PlayerCombat→HurtState→
EnemyFeedback)透传。各 Feedback 组件命中/受击槽位新增
FeedbackPositionMode(Fixed=反馈链编排的固定位置 /
HitPoint=传入命中点,启用位置选项的模块在该点生成),由编排
反馈链的开发者在 Inspector 按表现选择;武器命中默认 HitPoint
(火花贴点),角色级默认 Fixed(震屏/振动与位置无关)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 11:48:37 +08:00
parent 5ccab35948
commit 09bdda970a
10 changed files with 93 additions and 29 deletions

View File

@@ -1,3 +1,6 @@
using UnityEngine;
using MoreMountains.Feedbacks;
namespace BaseGames.Feedback
{
/// <summary>
@@ -8,15 +11,22 @@ namespace BaseGames.Feedback
public interface IFeedbackPlayer
{
// ── 命中 ──────────────────────────────────────────────────────────────────
/// <summary>对目标造成命中时播放对应力度的反馈(摄像机震屏 + 控制器振动等)。</summary>
void PlayHit(HitWeight weight);
/// <summary>
/// 对目标造成命中时播放对应力度的反馈(摄像机震屏 + 控制器振动等)。
/// hitPoint = 命中点世界坐标DamageInfo.HitPoint
/// 实际生成位置取决于实现方各槽位的 FeedbackPositionMode 配置。
/// </summary>
void PlayHit(HitWeight weight, Vector3 hitPoint);
/// <summary>成功弹反Parry时播放反馈。</summary>
void PlayParrySuccess();
// ── 受伤 / 死亡 ──────────────────────────────────────────────────────────
/// <summary>角色受到伤害时播放反馈(闪白 + 轻微震屏等)。</summary>
void PlayTakeHit();
/// <summary>
/// 角色受到伤害时播放反馈(闪白 + 轻微震屏等)。
/// hitPoint = 受击点世界坐标DamageInfo.HitPoint
/// </summary>
void PlayTakeHit(Vector3 hitPoint);
/// <summary>角色死亡时播放反馈(慢动作 + 震屏 + 音效)。</summary>
void PlayDeath();
@@ -53,6 +63,34 @@ namespace BaseGames.Feedback
/// <summary>命中力度。</summary>
public enum HitWeight { Light, Medium, Heavy }
/// <summary>
/// 反馈生成位置模式。反馈链本身由开发者在 Inspector 编排,
/// 此模式只决定播放时是否把命中点传入反馈链。
/// </summary>
public enum FeedbackPositionMode
{
/// <summary>固定位置:按反馈链各模块编排时设定的位置播放(不传入命中点)。</summary>
Fixed,
/// <summary>击中位置:把命中点传入反馈链,启用了播放位置选项的模块在命中点生成。</summary>
HitPoint,
}
/// <summary>
/// 反馈播放辅助:统一"按位置模式播放 MMF_Player"的实现,供各 IFeedbackPlayer 实现复用。
/// </summary>
public static class FeedbackPlayback
{
/// <summary>按位置模式播放。HitPoint 模式将命中点传给反馈链(仅启用位置选项的模块生效)。</summary>
public static void Play(MMF_Player player, FeedbackPositionMode mode, Vector3 hitPoint)
{
if (player == null) return;
if (mode == FeedbackPositionMode.HitPoint)
player.PlayFeedbacks(hitPoint);
else
player.PlayFeedbacks();
}
}
/// <summary>
/// 空对象模式实现:所有方法均为空操作,用于测试和不需要反馈的实体。
/// </summary>
@@ -60,9 +98,9 @@ namespace BaseGames.Feedback
{
public static readonly NullFeedbackPlayer Instance = new NullFeedbackPlayer();
public void PlayHit(HitWeight weight) { }
public void PlayHit(HitWeight weight, Vector3 hitPoint) { }
public void PlayParrySuccess() { }
public void PlayTakeHit() { }
public void PlayTakeHit(Vector3 hitPoint) { }
public void PlayDeath() { }
public void PlayHeal() { }
public void PlayLandImpact() { }
@@ -74,4 +112,3 @@ namespace BaseGames.Feedback
public void PlayFormSwitch(int formIndex) { }
}
}

View File

@@ -15,10 +15,14 @@ namespace BaseGames.Feedback
[SerializeField] private MMF_Player _onHitLight;
[SerializeField] private MMF_Player _onHitMedium;
[SerializeField] private MMF_Player _onHitHeavy;
[Tooltip("命中反馈生成位置Fixed = 反馈链编排的固定位置HitPoint = 在命中点生成")]
[SerializeField] private FeedbackPositionMode _hitPositionMode = FeedbackPositionMode.Fixed;
[SerializeField] private MMF_Player _onParrySuccess;
[Header("受伤 / 死亡反馈")]
[SerializeField] private MMF_Player _onTakeHit;
[Tooltip("受击反馈生成位置Fixed = 反馈链编排的固定位置HitPoint = 在受击点生成")]
[SerializeField] private FeedbackPositionMode _takeHitPositionMode = FeedbackPositionMode.Fixed;
[SerializeField] private MMF_Player _onDeath;
[Header("恢复反馈")]
@@ -53,7 +57,7 @@ namespace BaseGames.Feedback
}
// ── IFeedbackPlayer ──────────────────────────────────────────────────────
public void PlayHit(HitWeight weight)
public void PlayHit(HitWeight weight, Vector3 hitPoint)
{
var player = weight switch
{
@@ -62,11 +66,12 @@ namespace BaseGames.Feedback
HitWeight.Heavy => _onHitHeavy,
_ => _onHitLight,
};
player?.PlayFeedbacks();
FeedbackPlayback.Play(player, _hitPositionMode, hitPoint);
}
public void PlayParrySuccess() => _onParrySuccess?.PlayFeedbacks();
public void PlayTakeHit() => _onTakeHit?.PlayFeedbacks();
public void PlayTakeHit(Vector3 hitPoint)
=> FeedbackPlayback.Play(_onTakeHit, _takeHitPositionMode, hitPoint);
public void PlayDeath() => _onDeath?.PlayFeedbacks();
public void PlayHeal() => _onHeal?.PlayFeedbacks();
public void PlayLandImpact() => _onLandImpact?.PlayFeedbacks();

View File

@@ -14,6 +14,8 @@ namespace BaseGames.Feedback
[SerializeField] private MMF_Player _onHitLight;
[SerializeField] private MMF_Player _onHitMedium;
[SerializeField] private MMF_Player _onHitHeavy;
[Tooltip("命中反馈生成位置Fixed = 反馈链编排的固定位置HitPoint = 在命中点生成(命中火花等贴点表现)")]
[SerializeField] private FeedbackPositionMode _hitPositionMode = FeedbackPositionMode.HitPoint;
[Header("攻击破风")]
[SerializeField] private MMF_Player _onAttackWhoosh;
@@ -30,7 +32,7 @@ namespace BaseGames.Feedback
// ── IFeedbackPlayer 实现 ──────────────────────────────────────────────
public void PlayHit(HitWeight weight)
public void PlayHit(HitWeight weight, Vector3 hitPoint)
{
var player = weight switch
{
@@ -38,7 +40,7 @@ namespace BaseGames.Feedback
HitWeight.Heavy => _onHitHeavy,
_ => _onHitMedium,
};
player?.PlayFeedbacks();
FeedbackPlayback.Play(player, _hitPositionMode, hitPoint);
}
public void PlayAttackWhoosh() => _onAttackWhoosh?.PlayFeedbacks();
@@ -52,7 +54,7 @@ namespace BaseGames.Feedback
// ── 武器上不适用的反馈,空实现 ────────────────────────────────────────
public void PlayParrySuccess() { }
public void PlayTakeHit() { }
public void PlayTakeHit(Vector3 hitPoint){ }
public void PlayDeath() { }
public void PlayHeal() { }
public void PlayLandImpact() { }