MVP of the plane throwing game (#77)
Co-authored-by: Michal Pikulski <michal@foolhardyhorizons.com> Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com> Reviewed-on: #77
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Minigames.Airplane.Interactive
|
||||
{
|
||||
/// <summary>
|
||||
/// Bounces airplanes on collision with configurable bounce multiplier.
|
||||
/// Can be used for trampolines, bounce pads, or elastic surfaces.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Collider2D))]
|
||||
public class AirplaneBouncySurface : MonoBehaviour
|
||||
{
|
||||
[Header("Bounce Configuration")]
|
||||
[SerializeField] private float bounceMultiplier = 1.5f;
|
||||
[SerializeField] private Vector2 bounceDirection = Vector2.up;
|
||||
[SerializeField] private bool useReflection = true;
|
||||
|
||||
[Header("Optional Modifiers")]
|
||||
[SerializeField] private float minBounceVelocity = 2f;
|
||||
[SerializeField] private float maxBounceVelocity = 20f;
|
||||
|
||||
[Header("Visual Feedback (Optional)")]
|
||||
[SerializeField] private Animator bounceAnimator;
|
||||
[SerializeField] private string bounceTrigger = "Bounce";
|
||||
[SerializeField] private AudioSource bounceSound;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showDebugLogs;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Ensure collider is trigger
|
||||
var collider = GetComponent<Collider2D>();
|
||||
if (collider != null)
|
||||
{
|
||||
collider.isTrigger = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
// Check if it's an airplane
|
||||
var airplane = other.GetComponent<Core.AirplaneController>();
|
||||
if (airplane == null || !airplane.IsFlying) return;
|
||||
|
||||
var rb = other.GetComponent<Rigidbody2D>();
|
||||
if (rb == null) return;
|
||||
|
||||
// Calculate bounce velocity
|
||||
Vector2 newVelocity;
|
||||
|
||||
if (useReflection)
|
||||
{
|
||||
// Reflect velocity around bounce direction (normal)
|
||||
Vector2 normal = bounceDirection.normalized;
|
||||
newVelocity = Vector2.Reflect(rb.linearVelocity, normal) * bounceMultiplier;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Direct bounce in specified direction
|
||||
float speed = rb.linearVelocity.magnitude * bounceMultiplier;
|
||||
newVelocity = bounceDirection.normalized * speed;
|
||||
}
|
||||
|
||||
// Clamp velocity
|
||||
float magnitude = newVelocity.magnitude;
|
||||
if (magnitude < minBounceVelocity)
|
||||
{
|
||||
newVelocity = newVelocity.normalized * minBounceVelocity;
|
||||
}
|
||||
else if (magnitude > maxBounceVelocity)
|
||||
{
|
||||
newVelocity = newVelocity.normalized * maxBounceVelocity;
|
||||
}
|
||||
|
||||
// Apply bounce
|
||||
rb.linearVelocity = newVelocity;
|
||||
|
||||
// Visual/audio feedback
|
||||
PlayBounceEffects();
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Debug.Log($"[AirplaneBouncySurface] Bounced {other.name}: velocity={newVelocity}");
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayBounceEffects()
|
||||
{
|
||||
// Trigger animation
|
||||
if (bounceAnimator != null && !string.IsNullOrEmpty(bounceTrigger))
|
||||
{
|
||||
bounceAnimator.SetTrigger(bounceTrigger);
|
||||
}
|
||||
|
||||
// Play sound
|
||||
if (bounceSound != null)
|
||||
{
|
||||
bounceSound.Play();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
// Visualize bounce direction in editor
|
||||
Gizmos.color = Color.cyan;
|
||||
|
||||
var collider = GetComponent<Collider2D>();
|
||||
if (collider != null)
|
||||
{
|
||||
Vector3 center = collider.bounds.center;
|
||||
Vector3 direction = bounceDirection.normalized;
|
||||
|
||||
// Draw arrow showing bounce direction
|
||||
Gizmos.DrawRay(center, direction * 2f);
|
||||
Gizmos.DrawWireSphere(center + direction * 2f, 0.3f);
|
||||
|
||||
// Draw surface bounds
|
||||
Gizmos.DrawWireCube(collider.bounds.center, collider.bounds.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f1ff69bae8e49188f439a8e5cdb7dfc
|
||||
timeCreated: 1765135371
|
||||
@@ -0,0 +1,148 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Minigames.Airplane.Interactive
|
||||
{
|
||||
/// <summary>
|
||||
/// Gravity well that pulls airplanes toward its center.
|
||||
/// Creates challenging "danger zones" that players must avoid or escape from.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Collider2D))]
|
||||
public class AirplaneGravityWell : MonoBehaviour
|
||||
{
|
||||
[Header("Gravity Configuration")]
|
||||
[SerializeField] private float pullStrength = 5f;
|
||||
[SerializeField] private bool useInverseSquare = false;
|
||||
[SerializeField] private float minPullDistance = 0.5f;
|
||||
|
||||
[Header("Optional Modifiers")]
|
||||
[SerializeField] private float maxPullForce = 15f;
|
||||
[SerializeField] private AnimationCurve pullFalloff = AnimationCurve.Linear(0, 1, 1, 0);
|
||||
|
||||
[Header("Visual Feedback (Optional)")]
|
||||
[SerializeField] private ParticleSystem gravityParticles;
|
||||
[SerializeField] private SpriteRenderer centerSprite;
|
||||
[SerializeField] private float rotationSpeed = 90f;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showDebugLogs;
|
||||
[SerializeField] private bool drawDebugLines = true;
|
||||
|
||||
private Vector2 centerPosition;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Ensure collider is trigger
|
||||
var collider = GetComponent<Collider2D>();
|
||||
if (collider != null)
|
||||
{
|
||||
collider.isTrigger = true;
|
||||
}
|
||||
|
||||
centerPosition = transform.position;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Rotate center visual if present
|
||||
if (centerSprite != null)
|
||||
{
|
||||
centerSprite.transform.Rotate(0, 0, rotationSpeed * Time.deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerStay2D(Collider2D other)
|
||||
{
|
||||
// Check if it's an airplane
|
||||
var airplane = other.GetComponent<Core.AirplaneController>();
|
||||
if (airplane == null || !airplane.IsFlying) return;
|
||||
|
||||
var rb = other.GetComponent<Rigidbody2D>();
|
||||
if (rb == null) return;
|
||||
|
||||
// Calculate direction and distance to center
|
||||
Vector2 airplanePos = rb.position;
|
||||
Vector2 toCenter = centerPosition - airplanePos;
|
||||
float distance = toCenter.magnitude;
|
||||
|
||||
// Prevent division by zero
|
||||
if (distance < minPullDistance)
|
||||
{
|
||||
distance = minPullDistance;
|
||||
}
|
||||
|
||||
// Calculate pull force
|
||||
float forceMagnitude;
|
||||
|
||||
if (useInverseSquare)
|
||||
{
|
||||
// Realistic gravity-like force (inverse square law)
|
||||
forceMagnitude = pullStrength / (distance * distance);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Linear falloff based on distance
|
||||
var collider = GetComponent<Collider2D>();
|
||||
float maxDistance = collider != null ? collider.bounds.extents.magnitude : 5f;
|
||||
float normalizedDistance = Mathf.Clamp01(distance / maxDistance);
|
||||
float falloff = pullFalloff.Evaluate(1f - normalizedDistance);
|
||||
forceMagnitude = pullStrength * falloff;
|
||||
}
|
||||
|
||||
// Clamp force
|
||||
forceMagnitude = Mathf.Min(forceMagnitude, maxPullForce);
|
||||
|
||||
// Apply force toward center
|
||||
Vector2 pullForce = toCenter.normalized * forceMagnitude;
|
||||
rb.AddForce(pullForce, ForceMode2D.Force);
|
||||
|
||||
if (showDebugLogs && Time.frameCount % 30 == 0) // Log every 30 frames
|
||||
{
|
||||
Debug.Log($"[AirplaneGravityWell] Pulling {other.name}: force={forceMagnitude:F2}, distance={distance:F2}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
// Visualize gravity well in editor
|
||||
Gizmos.color = new Color(1f, 0f, 1f, 0.3f); // Magenta transparent
|
||||
|
||||
var collider = GetComponent<Collider2D>();
|
||||
if (collider != null)
|
||||
{
|
||||
// Draw zone bounds
|
||||
Gizmos.DrawWireSphere(collider.bounds.center, collider.bounds.extents.magnitude);
|
||||
|
||||
// Draw center point
|
||||
Gizmos.color = Color.magenta;
|
||||
Gizmos.DrawWireSphere(transform.position, 0.5f);
|
||||
|
||||
// Draw pull strength indicator
|
||||
Gizmos.DrawRay(transform.position, Vector3.up * pullStrength * 0.2f);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
if (!drawDebugLines) return;
|
||||
|
||||
// Draw pull force visualization at multiple points
|
||||
var collider = GetComponent<Collider2D>();
|
||||
if (collider == null) return;
|
||||
|
||||
float radius = collider.bounds.extents.magnitude;
|
||||
int samples = 8;
|
||||
|
||||
for (int i = 0; i < samples; i++)
|
||||
{
|
||||
float angle = (i / (float)samples) * 360f * Mathf.Deg2Rad;
|
||||
Vector2 offset = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * radius;
|
||||
Vector3 samplePoint = transform.position + (Vector3)offset;
|
||||
Vector3 direction = (transform.position - samplePoint).normalized;
|
||||
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.DrawLine(samplePoint, samplePoint + direction * 2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f6c5008f2782416095c5b3f5092843a9
|
||||
timeCreated: 1765135424
|
||||
@@ -0,0 +1,122 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Minigames.Airplane.Interactive
|
||||
{
|
||||
/// <summary>
|
||||
/// Speed boost ring that increases airplane velocity when passed through.
|
||||
/// Can be used as collectible power-ups or checkpoint rings.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Collider2D))]
|
||||
public class AirplaneSpeedRing : MonoBehaviour
|
||||
{
|
||||
[Header("Boost Configuration")]
|
||||
[SerializeField] private float velocityMultiplier = 1.5f;
|
||||
[SerializeField] private float boostDuration = 1f;
|
||||
[SerializeField] private bool oneTimeUse = true;
|
||||
|
||||
[Header("Optional Constraints")]
|
||||
[SerializeField] private float minResultSpeed = 5f;
|
||||
[SerializeField] private float maxResultSpeed = 25f;
|
||||
|
||||
[Header("Visual Feedback")]
|
||||
[SerializeField] private GameObject ringVisual;
|
||||
[SerializeField] private ParticleSystem collectEffect;
|
||||
[SerializeField] private AudioSource collectSound;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showDebugLogs;
|
||||
|
||||
private bool hasBeenUsed;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Ensure collider is trigger
|
||||
var collider = GetComponent<Collider2D>();
|
||||
if (collider != null)
|
||||
{
|
||||
collider.isTrigger = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
// Check if already used
|
||||
if (oneTimeUse && hasBeenUsed) return;
|
||||
|
||||
// Check if it's an airplane
|
||||
var airplane = other.GetComponent<Core.AirplaneController>();
|
||||
if (airplane == null || !airplane.IsFlying) return;
|
||||
|
||||
var rb = other.GetComponent<Rigidbody2D>();
|
||||
if (rb == null) return;
|
||||
|
||||
// Apply speed boost
|
||||
Vector2 boostedVelocity = rb.linearVelocity * velocityMultiplier;
|
||||
|
||||
// Clamp to constraints
|
||||
float speed = boostedVelocity.magnitude;
|
||||
if (speed < minResultSpeed)
|
||||
{
|
||||
boostedVelocity = boostedVelocity.normalized * minResultSpeed;
|
||||
}
|
||||
else if (speed > maxResultSpeed)
|
||||
{
|
||||
boostedVelocity = boostedVelocity.normalized * maxResultSpeed;
|
||||
}
|
||||
|
||||
rb.linearVelocity = boostedVelocity;
|
||||
|
||||
// Mark as used
|
||||
hasBeenUsed = true;
|
||||
|
||||
// Trigger effects
|
||||
PlayCollectEffects();
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Debug.Log($"[AirplaneSpeedRing] Boosted {other.name}: velocity={boostedVelocity.magnitude:F1}");
|
||||
}
|
||||
|
||||
// Hide or destroy ring
|
||||
if (oneTimeUse)
|
||||
{
|
||||
if (ringVisual != null)
|
||||
{
|
||||
ringVisual.SetActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject, collectEffect != null ? collectEffect.main.duration : 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayCollectEffects()
|
||||
{
|
||||
// Play particle effect
|
||||
if (collectEffect != null)
|
||||
{
|
||||
collectEffect.Play();
|
||||
}
|
||||
|
||||
// Play sound
|
||||
if (collectSound != null)
|
||||
{
|
||||
collectSound.Play();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
// Visualize ring in editor
|
||||
Gizmos.color = hasBeenUsed ? Color.gray : Color.yellow;
|
||||
|
||||
var collider = GetComponent<Collider2D>();
|
||||
if (collider != null)
|
||||
{
|
||||
Gizmos.DrawWireSphere(collider.bounds.center, collider.bounds.extents.magnitude);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 30fadeee96664cf28e3e2e562c99db26
|
||||
timeCreated: 1765135387
|
||||
@@ -0,0 +1,107 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Minigames.Airplane.Interactive
|
||||
{
|
||||
/// <summary>
|
||||
/// Turbulence zone that applies random chaotic forces to airplanes.
|
||||
/// Creates unpredictable movement, adding challenge to navigation.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Collider2D))]
|
||||
public class AirplaneTurbulenceZone : MonoBehaviour
|
||||
{
|
||||
[Header("Turbulence Configuration")]
|
||||
[SerializeField] private float turbulenceStrength = 3f;
|
||||
[SerializeField] private float changeFrequency = 0.1f;
|
||||
|
||||
[Header("Optional Modifiers")]
|
||||
[SerializeField] private bool preventUpwardForce = false;
|
||||
[SerializeField] private float maxTotalForce = 10f;
|
||||
|
||||
[Header("Visual Feedback (Optional)")]
|
||||
[SerializeField] private ParticleSystem turbulenceParticles;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showDebugLogs;
|
||||
|
||||
private float nextChangeTime;
|
||||
private Vector2 currentTurbulenceDirection;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Ensure collider is trigger
|
||||
var collider = GetComponent<Collider2D>();
|
||||
if (collider != null)
|
||||
{
|
||||
collider.isTrigger = true;
|
||||
}
|
||||
|
||||
// Initialize random direction
|
||||
UpdateTurbulenceDirection();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Change turbulence direction periodically
|
||||
if (Time.time >= nextChangeTime)
|
||||
{
|
||||
UpdateTurbulenceDirection();
|
||||
nextChangeTime = Time.time + changeFrequency;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTurbulenceDirection()
|
||||
{
|
||||
currentTurbulenceDirection = Random.insideUnitCircle.normalized;
|
||||
|
||||
// Prevent upward force if configured
|
||||
if (preventUpwardForce && currentTurbulenceDirection.y > 0)
|
||||
{
|
||||
currentTurbulenceDirection.y = -currentTurbulenceDirection.y;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerStay2D(Collider2D other)
|
||||
{
|
||||
// Check if it's an airplane
|
||||
var airplane = other.GetComponent<Core.AirplaneController>();
|
||||
if (airplane == null || !airplane.IsFlying) return;
|
||||
|
||||
var rb = other.GetComponent<Rigidbody2D>();
|
||||
if (rb == null) return;
|
||||
|
||||
// Apply turbulence force
|
||||
Vector2 turbulenceForce = currentTurbulenceDirection * turbulenceStrength;
|
||||
|
||||
// Clamp total force
|
||||
if (turbulenceForce.magnitude > maxTotalForce)
|
||||
{
|
||||
turbulenceForce = turbulenceForce.normalized * maxTotalForce;
|
||||
}
|
||||
|
||||
rb.AddForce(turbulenceForce, ForceMode2D.Force);
|
||||
|
||||
if (showDebugLogs && Time.frameCount % 30 == 0) // Log every 30 frames to avoid spam
|
||||
{
|
||||
Debug.Log($"[AirplaneTurbulenceZone] Applied turbulence: {turbulenceForce} to {other.name}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
// Visualize turbulence zone in editor
|
||||
Gizmos.color = new Color(1f, 0.5f, 0f, 0.3f); // Orange transparent
|
||||
|
||||
var collider = GetComponent<Collider2D>();
|
||||
if (collider != null)
|
||||
{
|
||||
Gizmos.DrawCube(collider.bounds.center, collider.bounds.size);
|
||||
|
||||
// Draw current direction
|
||||
Gizmos.color = Color.red;
|
||||
Vector3 center = collider.bounds.center;
|
||||
Gizmos.DrawRay(center, (Vector3)currentTurbulenceDirection * 2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae20630c0dc74174ae4d851d97d101c0
|
||||
timeCreated: 1765135403
|
||||
@@ -0,0 +1,70 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Minigames.Airplane.Interactive
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies a constant force to airplanes passing through this zone.
|
||||
/// Can be used for updrafts, downdrafts, or crosswinds.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Collider2D))]
|
||||
public class AirplaneWindZone : MonoBehaviour
|
||||
{
|
||||
[Header("Wind Configuration")]
|
||||
[SerializeField] private Vector2 windForce = new Vector2(0, 5f);
|
||||
[SerializeField] private bool isWorldSpace = true;
|
||||
|
||||
[Header("Visual Feedback (Optional)")]
|
||||
[SerializeField] private ParticleSystem windParticles;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showDebugLogs;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Ensure collider is trigger
|
||||
var collider = GetComponent<Collider2D>();
|
||||
if (collider != null)
|
||||
{
|
||||
collider.isTrigger = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerStay2D(Collider2D other)
|
||||
{
|
||||
// Check if it's an airplane
|
||||
var airplane = other.GetComponent<Core.AirplaneController>();
|
||||
if (airplane == null || !airplane.IsFlying) return;
|
||||
|
||||
// Apply wind force
|
||||
var rb = other.GetComponent<Rigidbody2D>();
|
||||
if (rb != null)
|
||||
{
|
||||
Vector2 force = isWorldSpace ? windForce : transform.TransformDirection(windForce);
|
||||
rb.AddForce(force * Time.fixedDeltaTime, ForceMode2D.Force);
|
||||
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Debug.Log($"[AirplaneWindZone] Applied force: {force} to {other.name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
// Visualize wind direction in editor
|
||||
Gizmos.color = windForce.y > 0 ? Color.green : Color.red;
|
||||
|
||||
var collider = GetComponent<Collider2D>();
|
||||
if (collider != null)
|
||||
{
|
||||
Vector3 center = collider.bounds.center;
|
||||
Vector2 direction = isWorldSpace ? windForce : transform.TransformDirection(windForce);
|
||||
|
||||
// Draw arrow showing wind direction
|
||||
Gizmos.DrawRay(center, direction.normalized * 2f);
|
||||
Gizmos.DrawWireSphere(center + (Vector3)(direction.normalized * 2f), 0.3f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dc3242fd3fe042919496d71933a760a5
|
||||
timeCreated: 1765135354
|
||||
Reference in New Issue
Block a user