Files
2026-05-20 19:47:36 +02:00

336 lines
10 KiB
C#

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<Rigidbody2D>();
col = GetComponent<Collider2D>();
}
private void Awake()
{
if (!body) body = GetComponent<Rigidbody2D>();
if (!col) col = GetComponent<Collider2D>();
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 luft
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;
// Prioritt: 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 zurck"
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 gedrckt wird
if (requireMoveIntoWallForSlide)
{
if (moveX == 0) return;
if (Mathf.Sign(moveX) != wallDir) return;
}
// Nur sliden wenn man nach unten fllt (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 fllt: 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();
// whrend 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);
}
}
}