using UnityEngine; using BaseGames.Core; using BaseGames.Core.Pool; namespace BaseGames.Combat { /// /// 抛射物基类。子类通过重写 设定初速度。 /// 依赖 子组件进行碰撞伤害检测。 /// [RequireComponent(typeof(Rigidbody2D), typeof(HitBox))] public abstract class Projectile : MonoBehaviour { [HideInInspector] public DamageInfo DamageInfo; [HideInInspector] public Vector2 Direction; protected ProjectileConfigSO _config; protected Rigidbody2D _rb; protected HitBox _hitBox; protected float _aliveTimer; // Lifetime 在 Initialize 时缓存,避免 Update 每帧访问 SO 成员并做 null check private float _lifetime = float.MaxValue; // 命中预算:剩余可命中次数(<=0 的 _maxHits 表示无限穿透) private int _maxHits = 1; private int _hitsRemaining; // 弹反帧标记:弹反命中不计入命中预算(避免反射后立即被回收) private bool _justReflected; private PooledObject _pooledObject; protected virtual void Awake() { _rb = GetComponent(); _hitBox = GetComponent(); _pooledObject = GetComponent(); // 订阅命中确认:按命中预算决定何时回收(穿透 / 命中即消失) _hitBox.OnHitConfirmed += HandleHitConfirmed; } /// 从对象池取出后的初始化入口。 public virtual void Initialize(ProjectileConfigSO config, DamageInfo damageInfo, Vector2 direction, int ownerLayer = 0) { _config = config; _lifetime = config.Lifetime; DamageInfo = damageInfo; Direction = direction.normalized; _aliveTimer = 0f; _maxHits = config.MaxHits; _hitsRemaining = config.MaxHits; _justReflected = false; SetFactionLayer(ownerLayer); _hitBox.Activate(config.DamageSource); OnInitialized(); } /// 根据发射方所在 Layer 切换到对应的 PlayerProjectile / EnemyProjectile 层。 private void SetFactionLayer(int ownerLayer) { int playerLayer = LayerMask.NameToLayer("Player"); int playerProjLayer = LayerMask.NameToLayer("PlayerProjectile"); int enemyProjLayer = LayerMask.NameToLayer("EnemyProjectile"); if (playerProjLayer < 0 || enemyProjLayer < 0) return; // Layer 尚未创建,保留现有层 gameObject.layer = (ownerLayer == playerLayer) ? playerProjLayer : enemyProjLayer; } /// /// 弹反:将投射物阵营从 EnemyProjectile 切换为 PlayerProjectile。 /// 反转飞行方向,并重置 HitBox 命中记录使其能够命中新目标(敌人)。 /// 由 HurtBox.ReceiveDamage() 在弹反成功后调用。 /// public virtual void ReflectAsPlayerProjectile() { int playerProjLayer = LayerMask.NameToLayer("PlayerProjectile"); if (playerProjLayer < 0) return; gameObject.layer = playerProjLayer; Direction = -Direction; _rb.velocity = -_rb.velocity; // 重置 HitBox 命中记录,确保反射后可命中新目标 _hitBox.Deactivate(); _hitBox.Activate(_config?.DamageSource); // 反射后重置命中预算,并跳过本次(弹反)命中的扣减,避免反射后立即被回收 _hitsRemaining = _maxHits; _justReflected = true; } /// HitBox 命中确认回调:按命中预算决定是否回收(穿透 / 命中即消失)。 private void HandleHitConfirmed(DamageInfo _) { if (_maxHits <= 0) return; // 无限穿透:只靠寿命回收 if (_justReflected) { _justReflected = false; return; } // 弹反命中不计入预算 if (--_hitsRemaining <= 0) ReturnToPool(); } protected virtual void OnInitialized() { } protected virtual void Update() { _aliveTimer += Time.deltaTime; if (_aliveTimer >= _lifetime) ReturnToPool(); } /// 停用并归还对象池。 protected void ReturnToPool() { _hitBox.Deactivate(); gameObject.SetActive(false); if (_pooledObject != null && _config != null) ServiceLocator.GetOrDefault()?.Despawn(_config.PoolKey, _pooledObject); } protected virtual void OnDisable() { _aliveTimer = 0f; _lifetime = float.MaxValue; // 归还对象池后重置,防止未初始化时自毁 } } }