71 lines
2.7 KiB
C#
71 lines
2.7 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using BaseGames.Core;
|
||
using BaseGames.Core.Events;
|
||
|
||
namespace BaseGames.Combat
|
||
{
|
||
/// <summary>
|
||
/// 拼刀系统单例服务(架构 06_CombatModule §15)。
|
||
/// 常驻 Persistent 场景,由 GameManager 持有。
|
||
/// 当玩家与敌人的近战 HitBox 同时激活并相互重叠时触发拼刀:双方均不扣血,各自弹开。
|
||
/// </summary>
|
||
[DefaultExecutionOrder(-500)]
|
||
public class ClashResolver : MonoBehaviour, IClashService
|
||
{
|
||
[SerializeField] private VoidEventChannelSO _onNailClash; // EVT_NailClash
|
||
[SerializeField] private ClashConfigSO _config;
|
||
|
||
// 防止同一碰撞在同帧被双方 HitBox 各触发一次(去重)
|
||
// Key = (min(idA,idB), max(idA,idB)),顺序无关且无 XOR 哈希碰撞风险
|
||
private readonly HashSet<(int, int)> _processedThisFrame = new();
|
||
|
||
private void Awake()
|
||
{
|
||
if (ServiceLocator.GetOrDefault<IClashService>() != null)
|
||
{
|
||
Destroy(gameObject);
|
||
return;
|
||
}
|
||
ServiceLocator.Register<IClashService>(this);
|
||
Debug.Assert(_config != null, "[ClashResolver] _config 未赋值,请在 Inspector 中指定 ClashConfigSO。", this);
|
||
}
|
||
|
||
private void LateUpdate() => _processedThisFrame.Clear();
|
||
|
||
/// <summary>
|
||
/// 由 HitBox.OnTriggerEnter2D 调用。
|
||
/// 对同一对 HitBox 每帧只处理一次(HashSet 去重)。
|
||
/// </summary>
|
||
public void ResolveClash(HitBox hitBoxA, HitBox hitBoxB)
|
||
{
|
||
int idA = hitBoxA.GetInstanceID();
|
||
int idB = hitBoxB.GetInstanceID();
|
||
(int, int) key = (System.Math.Min(idA, idB), System.Math.Max(idA, idB));
|
||
if (!_processedThisFrame.Add(key)) return;
|
||
|
||
// 1. 拼刀冻帧(比普通命中的 2 帧更短,强化拼刀手感)
|
||
ServiceLocator.GetOrDefault<IHitStopService>()?.FreezeFrames(_config.ClashFreezeFrames);
|
||
|
||
// 2. 双方弹开
|
||
ApplyClashKnockback(hitBoxA.OwnerRigidbody, hitBoxB.transform.position);
|
||
ApplyClashKnockback(hitBoxB.OwnerRigidbody, hitBoxA.transform.position);
|
||
|
||
// 3. 广播事件(VFX / Audio / CameraImpulse 订阅)
|
||
_onNailClash?.Raise();
|
||
}
|
||
|
||
private void ApplyClashKnockback(Rigidbody2D rb, Vector2 oppositePos)
|
||
{
|
||
if (rb == null) return;
|
||
Vector2 dir = ((Vector2)rb.transform.position - oppositePos).normalized;
|
||
rb.AddForce(dir * _config.ClashKnockbackForce, ForceMode2D.Impulse);
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
ServiceLocator.Unregister<IClashService>(this);
|
||
}
|
||
}
|
||
}
|