public class Movement : MonoBehaviour
{
public float moveSpeed = 5f;
public float jumpForce = 10f;
public float airControl = 0.5f; // How much control in air (0-1)
public float rotationSpeed = 5f; // How fast to rotate back to upright
public LayerMask groundLayer; // Set this to your ground/circle layers
public float groundCheckDistance = 0.2f;
private GrapplingHook grapple;
private Rigidbody2D rb;
private Vector2 groundNormal = Vector2.up;
private bool isGrounded;
void Awake()
{
rb = GetComponent<Rigidbody2D>();
grapple = GetComponent<GrapplingHook>();
}
void Update()
{
CheckGroundNormal();
HandleRotation();
float moveInput = Input.GetAxis("Horizontal");
if (!grapple.isGrappling)
{
// Only override velocity when there's input
// This preserves swing momentum when you let go
if (Mathf.Abs(moveInput) > 0.01f)
{
// Use air control multiplier for smoother transitions
float control = IsGrounded() ? 1f : airControl;
rb.linearVelocity = new Vector2(
moveInput * moveSpeed * control,
rb.linearVelocity.y
);
}
}
if (Input.GetButtonDown("Jump") && IsGrounded())
{
RaycastHit2D hit = Physics2D.Raycast(rb.position, -transform.up, groundCheckDistance, groundLayer);
if (hit.collider != null)
{
// Jump perpendicular to surface
rb.linearVelocity = hit.normal * jumpForce;
}
else
{
// Fallback to normal jump
rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce);
}
}
}
void CheckGroundNormal()
{
// Find nearby ground using OverlapCircle
Collider2D nearbyGround = Physics2D.OverlapCircle(rb.position, groundCheckDistance, groundLayer);
if (nearbyGround != null)
{
// Get the closest point on the surface to the player
Vector2 closestPoint = nearbyGround.ClosestPoint(rb.position);
// The normal points FROM the surface TO the player
groundNormal = ((Vector2)rb.position - closestPoint).normalized;
isGrounded = true;
}
else
{
groundNormal = Vector2.up;
isGrounded = false;
}
}
void HandleRotation()
{
if (IsGrounded())
{
// Rotate to align with the surface
float targetAngle = Mathf.Atan2(groundNormal.x, groundNormal.y) * Mathf.Rad2Deg;
float currentAngle = transform.eulerAngles.z;
float newAngle = Mathf.LerpAngle(currentAngle, targetAngle, rotationSpeed * Time.deltaTime);
transform.rotation = Quaternion.Euler(0, 0, newAngle);
}
else
{
// When in air, smoothly rotate back to upright (0 degrees)
float currentAngle = transform.eulerAngles.z;
float newAngle = Mathf.LerpAngle(currentAngle, 0, rotationSpeed * Time.deltaTime);
transform.rotation = Quaternion.Euler(0, 0, newAngle);
}
}
bool IsGrounded()
{
float extraHeight = 0.1f;
RaycastHit2D hit = Physics2D.Raycast(rb.position, Vector2.down, extraHeight);
return hit.collider != null;
}
}