using UnityEngine;
using UnityEngine.InputSystem;
/// <summary>
/// GAME 1303 Tier 3 — Lets the player climb Ladders. Roach Invasion, Spark of Corruption,
/// Detective Liz (five stories).
///
/// SETUP
/// - Add to the player (PlayerController2D v1.1). Needs a Collider2D on the player
/// that can overlap the Ladder's trigger.
/// - Press Up/Down (Move Y) while overlapping a ladder to grab it. Jump lets go.
/// - Animator: bool "IsClimbing" and float "ClimbSpeed" (0 when holding still, so you
/// can pause the climb animation with Animator speed = ClimbSpeed).
/// </summary>
public class LadderClimber : MonoBehaviour
{
public float climbSpeed = 4f;
[Tooltip("How far the stick must be pushed to grab.")]
public float grabThreshold = 0.5f;
[Tooltip("Jump force when leaving the ladder with Jump.")]
public float jumpOffForce = 10f;
public bool IsClimbing { get; private set; }
private PlayerController2D controller;
private Rigidbody2D rb;
private Animator anim;
private Collider2D myCollider;
private DoubleJump doubleJump;
private Ladder currentLadder;
private static readonly int ClimbHash = Animator.StringToHash("IsClimbing");
private static readonly int ClimbSpeedHash = Animator.StringToHash("ClimbSpeed");
private bool hasBool, hasFloat;
void Awake()
{
controller = GetComponent<PlayerController2D>();
rb = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
myCollider = GetComponent<Collider2D>();
doubleJump = GetComponent<DoubleJump>();
if (anim != null)
foreach (var p in anim.parameters)
{
if (p.nameHash == ClimbHash) hasBool = true;
if (p.nameHash == ClimbSpeedHash) hasFloat = true;
}
}
void OnTriggerEnter2D(Collider2D other)
{
var l = other.GetComponent<Ladder>();
if (l != null) currentLadder = l;
}
void OnTriggerExit2D(Collider2D other)
{
var l = other.GetComponent<Ladder>();
if (l != null && l == currentLadder)
{
if (IsClimbing) StopClimbing();
currentLadder = null;
}
}
void FixedUpdate()
{
float y = controller.MoveVector.y;
if (!IsClimbing)
{
// Grab: overlapping a ladder and pushing up, or pushing down while standing at its top
if (currentLadder != null && controller.InputEnabled && !controller.MovementLocked
&& Mathf.Abs(y) > grabThreshold)
{
// Don't grab downward if we're already on the ground below the ladder
if (y < 0f && controller.IsGrounded && transform.position.y < currentLadder.TopY - 0.5f) return;
StartClimbing();
}
return;
}
// Climbing
float x = currentLadder.snapToCenter ? 0f : controller.MoveInput * controller.moveSpeed * 0.5f;
rb.linearVelocity = new Vector2(x, y * climbSpeed);
if (currentLadder.snapToCenter)
rb.position = new Vector2(Mathf.Lerp(rb.position.x, currentLadder.CenterX, 0.3f), rb.position.y);
if (hasFloat) anim.SetFloat(ClimbSpeedHash, Mathf.Abs(y));
// Reached the ground going down? Let go.
if (y < 0f && controller.IsGrounded) StopClimbing();
// Climbed off the top? Let go (grounded on the top platform)
if (y > 0f && rb.position.y > currentLadder.TopY && controller.IsGrounded) StopClimbing();
}
void OnJump(InputValue v)
{
if (!v.isPressed || !IsClimbing) return;
StopClimbing();
controller.ForceJump(jumpOffForce);
controller.ClearJumpBuffer();
}
void StartClimbing()
{
IsClimbing = true;
controller.MovementLocked = true;
rb.gravityScale = 0f;
rb.linearVelocity = Vector2.zero;
if (currentLadder.topPlatform != null) Physics2D.IgnoreCollision(myCollider, currentLadder.topPlatform, true);
doubleJump?.Refill();
if (hasBool) anim.SetBool(ClimbHash, true);
}
void StopClimbing()
{
IsClimbing = false;
controller.MovementLocked = false;
rb.gravityScale = controller.BaseGravity;
if (currentLadder != null && currentLadder.topPlatform != null)
Physics2D.IgnoreCollision(myCollider, currentLadder.topPlatform, false);
if (hasBool) anim.SetBool(ClimbHash, false);
if (hasFloat) anim.SetFloat(ClimbSpeedHash, 0f);
}
}