using System; using System.Collections; using System.Collections.Generic; using Unity.VisualScripting; using UnityEngine; using UnityEngine.InputSystem; public class PlayerMovement : MonoBehaviour { public Rigidbody2D rb; public Animator animator; bool isFacingRight = true; public ParticleSystem smokeFx; BoxCollider2D playerCollider; [Header("Movement")] public float moveSpeed = 5f; float horizontalMovement; [Header("Jumping")] public float jumpPower = 10f; public int maxJumps = 2; int jumpsRemaining; [Header("Dashing")] public float dashSpeed = 20f; public float dashDuration = 0.3f; public float dashCooldown = 0.5f; public float dashIframeBuffer = 0.15f; // Buffer time for dash invincibility frames bool isDashing; bool canDash = true; TrailRenderer dashTrail; [Header("GroundCheck")] public Transform groundCheckPos; public Vector2 groundCheckSize = new Vector2(0.5f, 0.05f); public LayerMask groundLayer; bool isGrounded; bool isOnPlatform; [Header("Gravity")] public float baseGravity = 2f; public float maxFallSpeed = 18f; public float fallSpeedMultiplier = 2f; [Header("WallCheck")] public Transform wallCheckPos; public Vector2 wallCheckSize = new Vector2(0.5f, 0.05f); public LayerMask wallLayer; [Header("WallMovement")] public float wallSlideSpeed = 2f; bool isWallSliding = false; // Wall jumping bool isWallJumping; float wallJumpDirection; float wallJumpTime = 0.5f; float wallJumpTimer; public Vector2 wallJumpPower = new Vector2(5f, 10f); private void Start() { dashTrail = GetComponent(); playerCollider = GetComponent(); } void Update() { if (isDashing) // Items in update were in wrong order so dashing was being canceled before it was applied { return; // Skip movement if dashing } GroundCheck(); ProcessGravity(); ProcessWallSlide(); ProcessWallJump(); if (!isWallJumping) { rb.linearVelocity = new Vector2(horizontalMovement * moveSpeed, rb.linearVelocity.y); Flip(); } animator.SetFloat("yVelocity", rb.linearVelocity.y); animator.SetFloat("magnitude", rb.linearVelocity.magnitude); animator.SetBool("isWallSliding", isWallSliding); } public void Move(InputAction.CallbackContext context) { horizontalMovement = context.ReadValue().x; } public void Jump(InputAction.CallbackContext context) { if (jumpsRemaining > 0) { if (context.performed) { // Hold down jump for full power rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpPower); jumpsRemaining--; JumpFx(); } else if (context.canceled) { // Half jump when light tap rb.linearVelocity = new Vector2(rb.linearVelocity.x, rb.linearVelocity.y * 0.5f); jumpsRemaining--; JumpFx(); } } // Wall jump if (context.performed & wallJumpTimer > 0f) { isWallJumping = true; rb.linearVelocity = new Vector2(wallJumpDirection * wallJumpPower.x, wallJumpPower.y); // Jump away from the wall wallJumpTimer = 0f; // Reset wall jump timer JumpFx(); if (transform.localScale.x != wallJumpDirection) { // Flip character if jumping away from the wall isFacingRight = !isFacingRight; Vector3 scale = transform.localScale; scale.x *= -1; transform.localScale = scale; } Invoke (nameof(CancelWallJump), wallJumpTime + 0.1f); // Cancel wall jump after 0.5 seconds + 0.1 seconds } } private void JumpFx() { animator.SetTrigger("jump"); smokeFx.Play(); } public void Dash(InputAction.CallbackContext context) { if(context.performed && canDash) { StartCoroutine(DashCoroutine()); } } private IEnumerator DashCoroutine() { Physics2D.IgnoreLayerCollision(6, 11, true); isDashing = true; canDash = false; dashTrail.emitting = true; float dashDirection = isFacingRight ? 1f : -1f; float dashTime = 0f; Vector2 dashVelocity = new Vector2(dashDirection * dashSpeed, 0); while (dashTime < dashDuration) { // Check for wall in dash direction RaycastHit2D hit = Physics2D.Raycast(transform.position, Vector2.right * dashDirection, 0.5f, wallLayer); if (hit.collider != null) { break; // Stop dash if wall hit } rb.MovePosition(rb.position + dashVelocity * Time.fixedDeltaTime); dashTime += Time.fixedDeltaTime; yield return new WaitForFixedUpdate(); } isDashing = false; dashTrail.emitting = false; yield return new WaitForSeconds(dashIframeBuffer); Physics2D.IgnoreLayerCollision(6, 11, false); yield return new WaitForSeconds(dashCooldown); canDash = true; } public void Drop(InputAction.CallbackContext context) { if (context.performed && isGrounded && isOnPlatform && playerCollider.enabled) { StartCoroutine(DisablePlayerCollider(0.25f)); // Disable collider for 0.25 seconds to drop through platform } } private IEnumerator DisablePlayerCollider(float disableTime) { playerCollider.enabled = false; yield return new WaitForSeconds(disableTime); playerCollider.enabled = true; } private void GroundCheck() { if (Physics2D.OverlapBox(groundCheckPos.position, groundCheckSize, 0, groundLayer)) { jumpsRemaining = maxJumps; isGrounded = true; } else { isGrounded = false; } } void OnCollisionStay2D(Collision2D collision) { if (collision.gameObject.layer == LayerMask.NameToLayer("Platforms")) { isOnPlatform = true; isGrounded = true; jumpsRemaining = maxJumps; } } private void OnCollisionEnter2D(Collision2D collision) { if (collision.gameObject.layer == LayerMask.NameToLayer("Platforms")) { isOnPlatform = true; } } private void OnCollisionExit2D(Collision2D collision) { if (collision.gameObject.layer == LayerMask.NameToLayer("Platforms")) { isOnPlatform = false; } } private bool WallCheck() { return Physics2D.OverlapBox(wallCheckPos.position, wallCheckSize, 0, wallLayer); } private void ProcessGravity() { if (rb.linearVelocity.y < 0) { rb.gravityScale = baseGravity * fallSpeedMultiplier; // Fall faster when falling rb.linearVelocity = new Vector2(rb.linearVelocity.x, Mathf.Max(rb.linearVelocity.y, -maxFallSpeed)); // Cap fall speed } else { rb.gravityScale = baseGravity; } } private void ProcessWallSlide() { if (!isGrounded & WallCheck() & horizontalMovement != 0) { isWallSliding = true; rb.linearVelocity = new Vector2(rb.linearVelocity.x, Mathf.Max(rb.linearVelocity.y, -wallSlideSpeed)); // Caps fall rate } else { isWallSliding = false; } } private void ProcessWallJump() { if (isWallSliding) { isWallJumping = false; // Reset wall jump when sliding wallJumpDirection = -transform.localScale.x; // Set direction based on facing wallJumpTimer = wallJumpTime; CancelInvoke(nameof(CancelWallJump)); // Cancel any previous wall jump cancellation } else if (wallJumpTimer > 0f) { wallJumpTimer -= Time.deltaTime; } } private void CancelWallJump() { isWallJumping = false; } public void ResetJumps() { jumpsRemaining = maxJumps; } private void Flip() { if (isFacingRight && horizontalMovement < 0 || !isFacingRight && horizontalMovement > 0) { isFacingRight = !isFacingRight; Vector3 scale = transform.localScale; scale.x *= -1; transform.localScale = scale; if (rb.linearVelocity.y == 0) { smokeFx.Play(); } } } void OnDrawGizmos() { // Visualise ground and wall checks Gizmos.color = Color.red; Gizmos.DrawWireCube(groundCheckPos.position, groundCheckSize); Gizmos.color = Color.blue; Gizmos.DrawWireCube(wallCheckPos.position, wallCheckSize); } }