237 lines
7.9 KiB
C#
237 lines
7.9 KiB
C#
using Core;
|
|
using Core.Lifecycle;
|
|
using Core.Settings;
|
|
using UnityEngine;
|
|
|
|
namespace Minigames.BirdPooper
|
|
{
|
|
/// <summary>
|
|
/// Bird player controller with Flappy Bird-style flight mechanics.
|
|
/// Responds to tap input to flap, with manual gravity simulation.
|
|
/// </summary>
|
|
public class BirdPlayerController : ManagedBehaviour, ITouchInputConsumer
|
|
{
|
|
[Header("Events")]
|
|
public UnityEngine.Events.UnityEvent OnFlap;
|
|
public UnityEngine.Events.UnityEvent OnPlayerDamaged;
|
|
|
|
private Rigidbody2D _rb;
|
|
private IBirdPooperSettings _settings;
|
|
private float _verticalVelocity;
|
|
private bool _isDead;
|
|
private float _fixedXPosition; // Store the initial X position from the scene
|
|
private bool _isInitialized; // Flag to control when physics/input are active
|
|
|
|
internal override void OnManagedAwake()
|
|
{
|
|
base.OnManagedAwake();
|
|
|
|
// Initialize events
|
|
if (OnFlap == null)
|
|
OnFlap = new UnityEngine.Events.UnityEvent();
|
|
if (OnPlayerDamaged == null)
|
|
OnPlayerDamaged = new UnityEngine.Events.UnityEvent();
|
|
|
|
// Only cache component references - NO setup yet
|
|
_rb = GetComponent<Rigidbody2D>();
|
|
if (_rb == null)
|
|
{
|
|
Debug.LogError("[BirdPlayerController] Rigidbody2D component not found!");
|
|
}
|
|
|
|
// Load settings
|
|
_settings = GameManager.GetSettingsObject<IBirdPooperSettings>();
|
|
if (_settings == null)
|
|
{
|
|
Debug.LogError("[BirdPlayerController] BirdPooperSettings not found!");
|
|
}
|
|
|
|
Debug.Log("[BirdPlayerController] References cached, waiting for initialization...");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes the player controller - enables physics and input.
|
|
/// Should be called by BirdPooperGameManager when ready to start the game.
|
|
/// </summary>
|
|
public void Initialize()
|
|
{
|
|
if (_isInitialized)
|
|
{
|
|
Debug.LogWarning("[BirdPlayerController] Already initialized!");
|
|
return;
|
|
}
|
|
|
|
if (_rb == null || _settings == null)
|
|
{
|
|
Debug.LogError("[BirdPlayerController] Cannot initialize - missing references!");
|
|
return;
|
|
}
|
|
|
|
// Setup physics
|
|
_rb.gravityScale = 0f; // Disable Unity physics gravity
|
|
_rb.bodyType = RigidbodyType2D.Kinematic; // Kinematic = manual movement, no physics forces
|
|
|
|
// Store the initial X position from the scene
|
|
_fixedXPosition = _rb.position.x;
|
|
|
|
// Register as default input consumer
|
|
if (Input.InputManager.Instance != null)
|
|
{
|
|
Input.InputManager.Instance.SetDefaultConsumer(this);
|
|
Debug.Log("[BirdPlayerController] Registered as default input consumer");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("[BirdPlayerController] InputManager instance not found!");
|
|
}
|
|
|
|
_isInitialized = true;
|
|
Debug.Log($"[BirdPlayerController] Initialized! Fixed X position: {_fixedXPosition}");
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
// Only run physics/movement if initialized
|
|
if (!_isInitialized || _isDead || _settings == null || _rb == null)
|
|
return;
|
|
|
|
// Apply manual gravity
|
|
_verticalVelocity -= _settings.Gravity * Time.deltaTime;
|
|
|
|
// Cap fall speed (terminal velocity)
|
|
if (_verticalVelocity < -_settings.MaxFallSpeed)
|
|
_verticalVelocity = -_settings.MaxFallSpeed;
|
|
|
|
// Update position manually
|
|
Vector2 newPosition = _rb.position;
|
|
newPosition.y += _verticalVelocity * Time.deltaTime;
|
|
newPosition.x = _fixedXPosition; // Keep X fixed at scene-configured position
|
|
|
|
// Clamp Y position to bounds
|
|
newPosition.y = Mathf.Clamp(newPosition.y, _settings.MinY, _settings.MaxY);
|
|
|
|
_rb.MovePosition(newPosition);
|
|
|
|
// Update rotation based on velocity
|
|
UpdateRotation();
|
|
}
|
|
|
|
#region ITouchInputConsumer Implementation
|
|
|
|
public void OnTap(Vector2 tapPosition)
|
|
{
|
|
// Only respond to input if initialized and alive
|
|
if (!_isInitialized || _isDead || _settings == null)
|
|
return;
|
|
|
|
Flap();
|
|
}
|
|
|
|
public void OnHoldStart(Vector2 position) { }
|
|
public void OnHoldMove(Vector2 position) { }
|
|
public void OnHoldEnd(Vector2 position) { }
|
|
|
|
#endregion
|
|
|
|
#region Player Actions
|
|
|
|
/// <summary>
|
|
/// Makes the bird flap, applying upward velocity.
|
|
/// Can be called by input system or externally (e.g., for first tap).
|
|
/// </summary>
|
|
public void Flap()
|
|
{
|
|
if (!_isInitialized || _isDead || _settings == null)
|
|
return;
|
|
|
|
_verticalVelocity = _settings.FlapForce;
|
|
Debug.Log($"[BirdPlayerController] Flap! velocity = {_verticalVelocity}");
|
|
|
|
// Emit flap event
|
|
OnFlap?.Invoke();
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
#region Rotation
|
|
|
|
/// <summary>
|
|
/// Updates the bird's rotation based on vertical velocity.
|
|
/// Bird tilts up when flapping, down when falling.
|
|
/// </summary>
|
|
private void UpdateRotation()
|
|
{
|
|
if (_settings == null) return;
|
|
|
|
// Map velocity to rotation angle
|
|
// When falling at max speed (-MaxFallSpeed): -MaxRotationAngle (down)
|
|
// When at flap velocity (+FlapForce): +MaxRotationAngle (up)
|
|
float velocityPercent = Mathf.InverseLerp(
|
|
-_settings.MaxFallSpeed,
|
|
_settings.FlapForce,
|
|
_verticalVelocity
|
|
);
|
|
float targetAngle = Mathf.Lerp(
|
|
-_settings.MaxRotationAngle,
|
|
_settings.MaxRotationAngle,
|
|
velocityPercent
|
|
);
|
|
|
|
// Get current angle (handle 0-360 wrapping to -180-180)
|
|
float currentAngle = transform.rotation.eulerAngles.z;
|
|
if (currentAngle > 180f)
|
|
currentAngle -= 360f;
|
|
|
|
// Smooth interpolation to target
|
|
float smoothedAngle = Mathf.Lerp(
|
|
currentAngle,
|
|
targetAngle,
|
|
_settings.RotationSpeed * Time.deltaTime
|
|
);
|
|
|
|
// Apply rotation to Z axis only (2D rotation)
|
|
transform.rotation = Quaternion.Euler(0, 0, smoothedAngle);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Trigger-Based Collision Detection
|
|
|
|
/// <summary>
|
|
/// Called when a trigger collider enters this object's trigger.
|
|
/// Used for detecting obstacles and targets without physics interactions.
|
|
/// </summary>
|
|
private void OnTriggerEnter2D(Collider2D other)
|
|
{
|
|
// Check if the colliding object is tagged as an obstacle or target
|
|
if (other.CompareTag("Obstacle") || other.CompareTag("Target"))
|
|
{
|
|
HandleDeath();
|
|
}
|
|
}
|
|
|
|
private void HandleDeath()
|
|
{
|
|
// Only process death once
|
|
if (_isDead) return;
|
|
|
|
_isDead = true;
|
|
_verticalVelocity = 0f;
|
|
Debug.Log("[BirdPlayerController] Bird died!");
|
|
|
|
// Emit damage event - let the game manager handle UI
|
|
OnPlayerDamaged?.Invoke();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Accessors
|
|
|
|
public bool IsDead => _isDead;
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
|