using System; using Core; using Core.Lifecycle; using Minigames.FortFight.Fort; using UnityEngine; namespace Minigames.FortFight.Projectiles { /// /// Base class for all projectile types in Fort Fight. /// Handles physics, collision, and basic damage dealing. /// Subclasses override ActivateAbility() and OnHit() for unique behaviors. /// [RequireComponent(typeof(Rigidbody2D), typeof(Collider2D))] public abstract class ProjectileBase : ManagedBehaviour { #region Inspector Properties [Header("Projectile Stats")] [Tooltip("Base damage dealt on impact")] [SerializeField] protected float damage = 20f; [Tooltip("Mass for physics (affects trajectory)")] [SerializeField] protected float mass = 1f; [Header("Visuals")] [Tooltip("Sprite renderer for projectile")] [SerializeField] protected SpriteRenderer spriteRenderer; [Header("Effects")] [Tooltip("Particle effect on impact (optional)")] [SerializeField] protected GameObject impactEffectPrefab; #endregion #region Events /// /// Fired when projectile is launched. Parameters: (ProjectileBase projectile) /// public event Action OnLaunched; /// /// Fired when projectile hits something. Parameters: (ProjectileBase projectile, Collider2D hit) /// public event Action OnImpact; /// /// Fired when projectile is destroyed. Parameters: (ProjectileBase projectile) /// public event Action OnDestroyed; #endregion #region Properties public float Damage => damage; public bool IsLaunched { get; protected set; } public bool AbilityActivated { get; protected set; } public Vector2 LaunchDirection { get; protected set; } public float LaunchForce { get; protected set; } #endregion #region Components protected Rigidbody2D rb2D; protected Collider2D projectileCollider; #endregion #region Lifecycle internal override void OnManagedAwake() { base.OnManagedAwake(); // Cache components rb2D = GetComponent(); projectileCollider = GetComponent(); if (spriteRenderer == null) { spriteRenderer = GetComponent(); } // Configure rigidbody if (rb2D != null) { rb2D.mass = mass; rb2D.gravityScale = 1f; rb2D.collisionDetectionMode = CollisionDetectionMode2D.Continuous; } } #endregion #region Launch /// /// Launch the projectile with given direction and force. /// Called by SlingshotController. /// public virtual void Launch(Vector2 direction, float force) { if (IsLaunched) { Logging.Warning($"[ProjectileBase] {gameObject.name} already launched!"); return; } LaunchDirection = direction.normalized; LaunchForce = force; IsLaunched = true; // Apply physics impulse if (rb2D != null) { rb2D.AddForce(LaunchDirection * LaunchForce, ForceMode2D.Impulse); Logging.Debug($"[ProjectileBase] Launched {gameObject.name} with force {LaunchForce} in direction {LaunchDirection}"); } // Fire event OnLaunched?.Invoke(this); } #endregion #region Ability /// /// Activate projectile's special ability (mid-flight). /// Override in subclasses for unique behaviors. /// Called when player taps screen during flight. /// public virtual void ActivateAbility() { if (!IsLaunched) { Logging.Warning($"[ProjectileBase] Cannot activate ability - projectile not launched yet!"); return; } if (AbilityActivated) { Logging.Warning($"[ProjectileBase] Ability already activated!"); return; } AbilityActivated = true; Logging.Debug($"[ProjectileBase] {gameObject.name} ability activated"); // Subclasses override this for special behavior } #endregion #region Collision private void OnCollisionEnter2D(Collision2D collision) { if (!IsLaunched) return; Logging.Debug($"[ProjectileBase] {gameObject.name} hit {collision.gameObject.name}"); // Check if hit a fort block FortBlock block = collision.gameObject.GetComponent(); if (block != null) { // Deal damage to block block.TakeDamage(damage); Logging.Debug($"[ProjectileBase] Dealt {damage} damage to {block.gameObject.name}"); } // Spawn impact effect SpawnImpactEffect(collision.contacts[0].point); // Fire impact event OnImpact?.Invoke(this, collision.collider); // Call subclass-specific hit behavior OnHit(collision.collider); // Destroy projectile after hit (subclasses can override) DestroyProjectile(); } /// /// Called when projectile hits something. /// Override in subclasses for projectile-specific behavior. /// protected virtual void OnHit(Collider2D hit) { // Subclasses override for special behavior // e.g., Vacuum continues sliding, TrashBag splits, etc. } #endregion #region Effects /// /// Spawn impact particle effect /// protected void SpawnImpactEffect(Vector2 position) { if (impactEffectPrefab != null) { GameObject effect = Instantiate(impactEffectPrefab, position, Quaternion.identity); Destroy(effect, 2f); // Auto-cleanup } } #endregion #region Destruction /// /// Destroy the projectile. /// Can be overridden by subclasses for delayed destruction. /// protected virtual void DestroyProjectile() { Logging.Debug($"[ProjectileBase] Destroying {gameObject.name}"); // Fire destroyed event OnDestroyed?.Invoke(this); // Destroy GameObject Destroy(gameObject); } #endregion #region Debug private void OnDrawGizmos() { if (IsLaunched && Application.isPlaying) { // Draw launch direction Gizmos.color = Color.yellow; Gizmos.DrawLine(transform.position, transform.position + (Vector3)(LaunchDirection * 2f)); } } #endregion } }