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; // 归还对象池后重置,防止未初始化时自毁
}
}
}