using UnityEngine; [RequireComponent(typeof(Rigidbody2D))] public class CelesteStyleController2D : MonoBehaviour { [Header("References")] [SerializeField] private Rigidbody2D body; [SerializeField] private Collider2D col; [SerializeField] private Transform groundCheck; [SerializeField] private Transform wallCheck; [SerializeField] private LayerMask groundLayer; [Header("Run")] [SerializeField] private float maxRunSpeed = 9f; [SerializeField] private float runAcceleration = 70f; [SerializeField] private float runDeceleration = 80f; [SerializeField] private float airAccelerationMultiplier = 0.65f; [Header("Jump")] [SerializeField] private float jumpVelocity = 14f; [SerializeField] private float coyoteTime = 0.12f; [SerializeField] private float jumpBufferTime = 0.12f; [SerializeField] private float jumpCutMultiplier = 0.5f; // wenn Jump losgelassen wird [Header("Gravity Feel")] [SerializeField] private float fallGravityMultiplier = 1.6f; // schneller fallen [SerializeField] private float lowJumpGravityMultiplier = 1.3f; // wenn Jump nicht gehalten wird [Header("Checks")] [SerializeField] private Vector2 groundCheckSize = new Vector2(0.6f, 0.12f); [SerializeField] private float wallCheckDistance = 0.35f; [Header("Wall Slide & Wall Jump")] [SerializeField] private bool enableWallSlide = true; [SerializeField] private float wallSlideMaxSpeed = 3.5f; [SerializeField] private float wallJumpX = 10f; [SerializeField] private float wallJumpY = 14f; [SerializeField] private float wallJumpInputLock = 0.15f; // kurz kein normaler Move nach Walljump [SerializeField] private bool requireMoveIntoWallForSlide = true; // Celeste-like: zum Sliden Richtung Wand halten [Header("Dash (optional)")] [SerializeField] private bool enableDash = true; [SerializeField] private float dashSpeed = 18f; [SerializeField] private float dashTime = 0.16f; [SerializeField] private float dashCooldown = 0.05f; // kleine Pause nach Dash [SerializeField] private int airDashes = 1; // input private float moveX; private bool jumpPressed; private bool jumpHeld; private bool dashPressed; // state private bool grounded; private bool onWallLeft; private bool onWallRight; private bool wallSliding; private int wallDir; // -1 links, +1 rechts private float coyoteCounter; private float jumpBufferCounter; private float inputLockCounter; // dash state private bool isDashing; private float dashCounter; private float dashCooldownCounter; private int dashesLeft; private float baseGravity; private int facingDir = 1; // 1 = rechts, -1 = links private void Reset() { body = GetComponent(); col = GetComponent(); } private void Awake() { if (!body) body = GetComponent(); if (!col) col = GetComponent(); baseGravity = body.gravityScale; dashesLeft = airDashes; } private void Update() { ReadInput(); UpdateChecks(); UpdateTimers(); HandleFlip(); // Jump buffer aktualisieren if (jumpPressed) jumpBufferCounter = jumpBufferTime; // Dash if (enableDash && dashPressed && !isDashing && dashCooldownCounter <= 0f && dashesLeft > 0) { StartDash(); } } private void FixedUpdate() { if (isDashing) { dashCounter -= Time.fixedDeltaTime; if (dashCounter <= 0f) EndDash(); return; } if (dashCooldownCounter > 0f) dashCooldownCounter -= Time.fixedDeltaTime; ApplyHorizontalMovement(); ApplyWallSlide(); TryConsumeBufferedJump(); ApplyBetterGravity(); } // ---------------- INPUT & CHECKS ---------------- private void ReadInput() { moveX = Input.GetAxisRaw("Horizontal"); jumpPressed = Input.GetButtonDown("Jump"); jumpHeld = Input.GetButton("Jump"); dashPressed = Input.GetKeyDown(KeyCode.LeftShift) || Input.GetKeyDown(KeyCode.X); // frei anpassbar } private void UpdateChecks() { // Ground check (OverlapBox) grounded = Physics2D.OverlapBox(groundCheck.position, groundCheckSize, 0f, groundLayer); // Wall check (Raycasts) onWallLeft = Physics2D.Raycast(wallCheck.position, Vector2.left, wallCheckDistance, groundLayer); onWallRight = Physics2D.Raycast(wallCheck.position, Vector2.right, wallCheckDistance, groundLayer); wallDir = onWallRight ? 1 : (onWallLeft ? -1 : 0); // Dash reset am Boden if (grounded) dashesLeft = airDashes; } private void UpdateTimers() { // Coyote Time if (grounded) coyoteCounter = coyoteTime; else coyoteCounter -= Time.deltaTime; // Jump Buffer if (jumpBufferCounter > 0f) jumpBufferCounter -= Time.deltaTime; // Input Lock nach Walljump if (inputLockCounter > 0f) inputLockCounter -= Time.deltaTime; } private void HandleFlip() { // Facing nur wenn Spieler wirklich l�uft if (moveX != 0 && !isDashing) { facingDir = (moveX > 0) ? 1 : -1; // Simple Flip: Scale X (alternativ SpriteRenderer.flipX) Vector3 s = transform.localScale; s.x = Mathf.Abs(s.x) * facingDir; transform.localScale = s; } } // ---------------- MOVEMENT ---------------- private void ApplyHorizontalMovement() { if (inputLockCounter > 0f) return; float targetSpeed = moveX * maxRunSpeed; float accel = (Mathf.Abs(targetSpeed) > 0.01f) ? runAcceleration : runDeceleration; if (!grounded) accel *= airAccelerationMultiplier; float speedDiff = targetSpeed - body.linearVelocity.x; float movement = speedDiff * accel; body.AddForce(Vector2.right * movement); // Clamp, damit du nicht "durch AddForce" zu schnell wirst float clampedX = Mathf.Clamp(body.linearVelocity.x, -maxRunSpeed, maxRunSpeed); body.linearVelocity = new Vector2(clampedX, body.linearVelocity.y); } // ---------------- JUMP LOGIC ---------------- private void TryConsumeBufferedJump() { if (jumpBufferCounter <= 0f) return; // Priorit�t: Wenn wallSliding ? Walljump, sonst normal Jump (Coyote) if (wallSliding) { DoWallJump(); jumpBufferCounter = 0f; return; } if (coyoteCounter > 0f) { DoJump(); jumpBufferCounter = 0f; } } private void DoJump() { // konsistent: setze Y-Velocity (Celeste-like) body.linearVelocity = new Vector2(body.linearVelocity.x, jumpVelocity); coyoteCounter = 0f; } private void DoWallJump() { // weg von Wand: wenn Wand rechts (+1), spring nach links (-1) Vector2 v = new Vector2(-wallDir * wallJumpX, wallJumpY); body.linearVelocity = Vector2.zero; body.linearVelocity = v; // kurzer Lock gegen Input "zieht dich zur�ck" inputLockCounter = wallJumpInputLock; // Nach Walljump gilt Sprung als verbraucht, aber du kannst dashen coyoteCounter = 0f; } // ---------------- WALL SLIDE ---------------- private void ApplyWallSlide() { wallSliding = false; if (!enableWallSlide) return; if (grounded) return; if (wallDir == 0) return; // Optional: nur sliden, wenn Richtung zur Wand gedr�ckt wird if (requireMoveIntoWallForSlide) { if (moveX == 0) return; if (Mathf.Sign(moveX) != wallDir) return; } // Nur sliden wenn man nach unten f�llt (Celeste-feel) if (body.linearVelocity.y < 0f) { wallSliding = true; float newY = Mathf.Max(body.linearVelocity.y, -wallSlideMaxSpeed); body.linearVelocity = new Vector2(body.linearVelocity.x, newY); } } // ---------------- GRAVITY FEEL ---------------- private void ApplyBetterGravity() { // Wenn man f�llt: schneller if (body.linearVelocity.y < 0f) { body.linearVelocity += Vector2.up * Physics2D.gravity.y * (fallGravityMultiplier - 1f) * Time.fixedDeltaTime; } // Wenn man aufsteigt, aber Jump nicht mehr gehalten wird: Jump "cut" else if (body.linearVelocity.y > 0f && !jumpHeld) { body.linearVelocity += Vector2.up * Physics2D.gravity.y * (lowJumpGravityMultiplier - 1f) * Time.fixedDeltaTime; } // Optional: sofortiges jump cut beim Loslassen (noch snappier) if (!jumpHeld && body.linearVelocity.y > 0f) { body.linearVelocity = new Vector2(body.linearVelocity.x, body.linearVelocity.y * (1f - (1f - jumpCutMultiplier) * 0.02f)); } } // ---------------- DASH ---------------- private void StartDash() { isDashing = true; dashCounter = dashTime; dashCooldownCounter = 0f; dashesLeft--; // Dash direction: Input, sonst facing Vector2 dir = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")); if (dir.sqrMagnitude < 0.01f) dir = new Vector2(facingDir, 0f); dir.Normalize(); // w�hrend Dash keine Gravity body.gravityScale = 0f; body.linearVelocity = dir * dashSpeed; } private void EndDash() { isDashing = false; dashCooldownCounter = dashCooldown; body.gravityScale = baseGravity; // Optional: kleiner "carry", damit dash nicht abrupt stoppt body.linearVelocity = new Vector2(body.linearVelocity.x * 0.9f, body.linearVelocity.y); } // ---------------- GIZMOS ---------------- private void OnDrawGizmosSelected() { if (groundCheck) { Gizmos.color = Color.green; Gizmos.DrawWireCube(groundCheck.position, groundCheckSize); } if (wallCheck) { Gizmos.color = Color.cyan; Gizmos.DrawLine(wallCheck.position, wallCheck.position + Vector3.left * wallCheckDistance); Gizmos.DrawLine(wallCheck.position, wallCheck.position + Vector3.right * wallCheckDistance); } } }