using System.Collections.Generic; using UnityEngine; using BaseGames.Core; using BaseGames.Core.Events; namespace BaseGames.Combat { /// /// 拼刀系统单例服务(架构 06_CombatModule §15)。 /// 常驻 Persistent 场景,由 GameManager 持有。 /// 当玩家与敌人的近战 HitBox 同时激活并相互重叠时触发拼刀:双方均不扣血,各自弹开。 /// [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() != null) { Destroy(gameObject); return; } ServiceLocator.Register(this); Debug.Assert(_config != null, "[ClashResolver] _config 未赋值,请在 Inspector 中指定 ClashConfigSO。", this); } private void LateUpdate() => _processedThisFrame.Clear(); /// /// 由 HitBox.OnTriggerEnter2D 调用。 /// 对同一对 HitBox 每帧只处理一次(HashSet 去重)。 /// 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()?.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(this); } } }