Stash work

This commit is contained in:
Michal Pikulski
2025-12-02 23:56:13 +01:00
parent bb8d600af2
commit d5ab69d944
100 changed files with 10105 additions and 124 deletions

View File

@@ -0,0 +1,52 @@
using System.Collections;
using Core;
using UnityEngine;
namespace Minigames.FortFight.Projectiles
{
/// <summary>
/// Ceiling Fan projectile - drops straight down when ability is activated.
/// Player taps screen mid-flight to activate drop.
/// </summary>
public class CeilingFanProjectile : ProjectileBase
{
[Header("Ceiling Fan Specific")]
[Tooltip("Speed of downward drop")]
[SerializeField] private float dropSpeed = 20f;
[Tooltip("Delay before dropping")]
[SerializeField] private float dropDelay = 0.2f;
public override void ActivateAbility()
{
base.ActivateAbility();
if (AbilityActivated)
{
Logging.Debug("[CeilingFanProjectile] Ability activated - dropping straight down");
StartCoroutine(DropCoroutine());
}
}
private IEnumerator DropCoroutine()
{
// Stop all velocity
if (rb2D != null)
{
rb2D.linearVelocity = Vector2.zero;
rb2D.angularVelocity = 0f;
}
// Wait brief moment
yield return new WaitForSeconds(dropDelay);
// Drop straight down
if (rb2D != null)
{
rb2D.linearVelocity = Vector2.down * dropSpeed;
Logging.Debug($"[CeilingFanProjectile] Dropping with velocity: {rb2D.linearVelocity}");
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e10ba9bd4bcd40da87ecb3efe5b78467
timeCreated: 1764682337

View File

@@ -0,0 +1,250 @@
using System;
using Core;
using Core.Lifecycle;
using Minigames.FortFight.Fort;
using UnityEngine;
namespace Minigames.FortFight.Projectiles
{
/// <summary>
/// Base class for all projectile types in Fort Fight.
/// Handles physics, collision, and basic damage dealing.
/// Subclasses override ActivateAbility() and OnHit() for unique behaviors.
/// </summary>
[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
/// <summary>
/// Fired when projectile is launched. Parameters: (ProjectileBase projectile)
/// </summary>
public event Action<ProjectileBase> OnLaunched;
/// <summary>
/// Fired when projectile hits something. Parameters: (ProjectileBase projectile, Collider2D hit)
/// </summary>
public event Action<ProjectileBase, Collider2D> OnImpact;
/// <summary>
/// Fired when projectile is destroyed. Parameters: (ProjectileBase projectile)
/// </summary>
public event Action<ProjectileBase> 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<Rigidbody2D>();
projectileCollider = GetComponent<Collider2D>();
if (spriteRenderer == null)
{
spriteRenderer = GetComponent<SpriteRenderer>();
}
// Configure rigidbody
if (rb2D != null)
{
rb2D.mass = mass;
rb2D.gravityScale = 1f;
rb2D.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
}
}
#endregion
#region Launch
/// <summary>
/// Launch the projectile with given direction and force.
/// Called by SlingshotController.
/// </summary>
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
/// <summary>
/// Activate projectile's special ability (mid-flight).
/// Override in subclasses for unique behaviors.
/// Called when player taps screen during flight.
/// </summary>
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<FortBlock>();
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();
}
/// <summary>
/// Called when projectile hits something.
/// Override in subclasses for projectile-specific behavior.
/// </summary>
protected virtual void OnHit(Collider2D hit)
{
// Subclasses override for special behavior
// e.g., Vacuum continues sliding, TrashBag splits, etc.
}
#endregion
#region Effects
/// <summary>
/// Spawn impact particle effect
/// </summary>
protected void SpawnImpactEffect(Vector2 position)
{
if (impactEffectPrefab != null)
{
GameObject effect = Instantiate(impactEffectPrefab, position, Quaternion.identity);
Destroy(effect, 2f); // Auto-cleanup
}
}
#endregion
#region Destruction
/// <summary>
/// Destroy the projectile.
/// Can be overridden by subclasses for delayed destruction.
/// </summary>
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
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 70f37c48406847cdabd5589910220fdf
timeCreated: 1764682302

View File

@@ -0,0 +1,22 @@
using Core;
using UnityEngine;
namespace Minigames.FortFight.Projectiles
{
/// <summary>
/// Standard projectile - no special ability.
/// Moderate damage, standard physics arc.
/// </summary>
public class ToasterProjectile : ProjectileBase
{
// Toaster is the basic projectile - uses base class behavior
// No special ability needed
public override void ActivateAbility()
{
// Toaster has no special ability
Logging.Debug("[ToasterProjectile] Toaster has no special ability");
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6ecf658b5965496abda845de1a28e227
timeCreated: 1764682312

View File

@@ -0,0 +1,120 @@
using Core;
using UnityEngine;
namespace Minigames.FortFight.Projectiles
{
/// <summary>
/// Trash Bag projectile - splits into multiple smaller pieces on impact.
/// Deals AOE damage in a forward cone.
/// </summary>
public class TrashBagProjectile : ProjectileBase
{
[Header("Trash Bag Specific")]
[Tooltip("Number of trash pieces to spawn on impact")]
[SerializeField] private int trashPieceCount = 6;
[Tooltip("Prefab for individual trash pieces")]
[SerializeField] private GameObject trashPiecePrefab;
[Tooltip("Force applied to each trash piece")]
[SerializeField] private float pieceForce = 8f;
[Tooltip("Spread angle for trash pieces (degrees)")]
[SerializeField] private float spreadAngle = 30f;
[Tooltip("Damage dealt by each trash piece")]
[SerializeField] private float pieceDamage = 8f;
protected override void OnHit(Collider2D hit)
{
base.OnHit(hit);
Logging.Debug($"[TrashBagProjectile] Splitting into {trashPieceCount} pieces");
SpawnTrashPieces(hit.transform.position);
}
/// <summary>
/// Spawn multiple trash pieces in a forward cone
/// </summary>
private void SpawnTrashPieces(Vector2 impactPoint)
{
if (trashPiecePrefab == null)
{
Logging.Warning("[TrashBagProjectile] No trash piece prefab assigned!");
return;
}
// Calculate forward direction from velocity
Vector2 forwardDirection = rb2D.linearVelocity.normalized;
if (forwardDirection == Vector2.zero)
{
forwardDirection = LaunchDirection;
}
// Spawn pieces in a cone
for (int i = 0; i < trashPieceCount; i++)
{
// Calculate angle for this piece
float angleOffset = Mathf.Lerp(-spreadAngle, spreadAngle, i / (float)(trashPieceCount - 1));
float angleRadians = Mathf.Atan2(forwardDirection.y, forwardDirection.x) + angleOffset * Mathf.Deg2Rad;
Vector2 pieceDirection = new Vector2(Mathf.Cos(angleRadians), Mathf.Sin(angleRadians));
// Spawn trash piece
GameObject piece = Instantiate(trashPiecePrefab, impactPoint, Quaternion.identity);
// Setup trash piece physics
Rigidbody2D pieceRb = piece.GetComponent<Rigidbody2D>();
if (pieceRb != null)
{
pieceRb.AddForce(pieceDirection * pieceForce, ForceMode2D.Impulse);
}
// Setup trash piece damage (if it has a component)
TrashPiece trashPieceComponent = piece.GetComponent<TrashPiece>();
if (trashPieceComponent != null)
{
trashPieceComponent.Initialize(pieceDamage);
}
// Auto-destroy after 3 seconds
Destroy(piece, 3f);
Logging.Debug($"[TrashBagProjectile] Spawned trash piece {i} in direction {pieceDirection}");
}
}
}
/// <summary>
/// Component for individual trash pieces spawned by TrashBagProjectile.
/// Deals damage on collision.
/// </summary>
public class TrashPiece : MonoBehaviour
{
private float damage;
private bool hasHit = false;
public void Initialize(float pieceDamage)
{
this.damage = pieceDamage;
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (hasHit) return;
hasHit = true;
// Check if hit a fort block
var block = collision.gameObject.GetComponent<Fort.FortBlock>();
if (block != null)
{
block.TakeDamage(damage);
Logging.Debug($"[TrashPiece] Dealt {damage} damage to {block.gameObject.name}");
}
// Destroy this piece
Destroy(gameObject, 0.1f);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b0996e59b91e48f8a542ab9294b11a74
timeCreated: 1764682467

View File

@@ -0,0 +1,82 @@
using System.Collections;
using Core;
using UnityEngine;
namespace Minigames.FortFight.Projectiles
{
/// <summary>
/// Vacuum projectile - high mass, slides along ground after landing.
/// On ground impact: disables gravity, moves horizontally for 2 seconds.
/// </summary>
public class VacuumProjectile : ProjectileBase
{
[Header("Vacuum Specific")]
[Tooltip("Speed when sliding on ground")]
[SerializeField] private float slideSpeed = 10f;
[Tooltip("How long to slide before destroying")]
[SerializeField] private float slideDuration = 2f;
[Tooltip("Layer mask for ground detection")]
[SerializeField] private LayerMask groundLayer = 1; // Default layer
private bool isSliding = false;
protected override void OnHit(Collider2D hit)
{
// Check if hit ground
if (((1 << hit.gameObject.layer) & groundLayer) != 0)
{
Logging.Debug("[VacuumProjectile] Hit ground - starting slide");
StartSliding();
}
// If hit wall or fort block, destroy immediately (handled by base class)
}
/// <summary>
/// Start sliding behavior after hitting ground
/// </summary>
private void StartSliding()
{
if (isSliding) return;
isSliding = true;
// Disable gravity
if (rb2D != null)
{
rb2D.gravityScale = 0f;
// Set velocity to horizontal only (preserve direction)
Vector2 horizontalVelocity = new Vector2(rb2D.linearVelocity.x, 0f).normalized * slideSpeed;
rb2D.linearVelocity = horizontalVelocity;
Logging.Debug($"[VacuumProjectile] Sliding with velocity: {horizontalVelocity}");
}
// Destroy after slide duration
StartCoroutine(SlideCoroutine());
}
private IEnumerator SlideCoroutine()
{
yield return new WaitForSeconds(slideDuration);
Logging.Debug("[VacuumProjectile] Slide duration ended - destroying");
DestroyProjectile();
}
/// <summary>
/// Override to NOT destroy immediately on ground hit
/// </summary>
protected override void DestroyProjectile()
{
// Only destroy if we're done sliding or hit a wall
if (!isSliding || rb2D.linearVelocity.magnitude < 1f)
{
base.DestroyProjectile();
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bb85d181808c411b8bd1335aa7d35257
timeCreated: 1764682326