TOOLS GAMES DEVLOG DOCS ABOUT CONTACT
HOME > DOCS > Unity / C# > 2D 플랫포머 캐릭터 이동과 코요테 타임(Coyote Time) · 점프 버퍼링
Unity / C# Beginner Unity 2022.3+ / Unity 6

2D 플랫포머 캐릭터 이동과 코요테 타임(Coyote Time) · 점프 버퍼링

발판 이탈 직후 점프를 보장하는 코요테 타임과 착지 직전 입력을 보존하는 점프 버퍼링, Rigidbody2D 속도 제어를 통한 반응성 높은 2D 플랫포머 이동 구현.

#Unity #2D #Platformer #Movement #Coyote Time #Jump Buffering
핵심 개념완벽한 물리 시뮬레이션보다 중요한 것은 '플레이어의 의도에 즉각 반응하는 조작감'입니다. 발판 끝에서 살짝 떨어진 순간에도 점프를 허용하는 코요테 타임(Coyote Time)과, 공중에서 미리 누른 점프 입력을 착지 즉시 발동시키는 점프 버퍼링(Jump Buffering)은 명작 플랫포머 게임들의 필수 조작 보정 기법입니다.

1. 반응성 극대화 2D 플랫포머 컨트롤러

PlatformerController2D.cs
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D), typeof(Collider2D))]
public class PlatformerController2D : MonoBehaviour
{
    [Header("이동 설정")]
    [SerializeField] private float moveSpeed = 9f;
    [SerializeField] private float acceleration = 50f;
    [SerializeField] private float deceleration = 60f;

    [Header("점프 & 조작 보정")]
    [SerializeField] private float jumpForce = 14f;
    [SerializeField] private float coyoteTimeDuration = 0.12f;
    [SerializeField] private float jumpBufferDuration = 0.1f;
    [SerializeField] private float fallMultiplier = 2.5f;
    [SerializeField] private float lowJumpMultiplier = 2.0f;

    [Header("지면 판정")]
    [SerializeField] private Transform groundCheckPoint;
    [SerializeField] private Vector2 groundCheckSize = new Vector2(0.6f, 0.1f);
    [SerializeField] private LayerMask groundLayer;

    private Rigidbody2D _rb;
    private float _horizontalInput;
    private float _coyoteTimeCounter;
    private float _jumpBufferCounter;
    private bool _isGrounded;

    private void Awake()
    {
        _rb = GetComponent<Rigidbody2D>();
    }

    private void Update()
    {
        // 입력 수집
        _horizontalInput = Input.GetAxisRaw("Horizontal");

        // 지면 판정
        _isGrounded = Physics2D.OverlapBox(groundCheckPoint.position, groundCheckSize, 0f, groundLayer);

        // 코요테 타임 카운터
        if (_isGrounded)
            _coyoteTimeCounter = coyoteTimeDuration;
        else
            _coyoteTimeCounter -= Time.deltaTime;

        // 점프 버퍼링 카운터
        if (Input.GetButtonDown("Jump"))
            _jumpBufferCounter = jumpBufferDuration;
        else
            _jumpBufferCounter -= Time.deltaTime;

        // 점프 실행 조건
        if (_jumpBufferCounter > 0f && _coyoteTimeCounter > 0f)
        {
            ExecuteJump();
        }

        // 가변 점프 높이: 버튼을 일찍 떼면 중력 증가
        ApplyJumpGravityModifiers();
    }

    private void FixedUpdate()
    {
        ApplyHorizontalMovement();
    }

    private void ExecuteJump()
    {
        // Unity 6 linearVelocity (구버전 velocity)
        _rb.linearVelocity = new Vector2(_rb.linearVelocity.x, jumpForce);
        _jumpBufferCounter = 0f;
        _coyoteTimeCounter = 0f;
    }

    private void ApplyHorizontalMovement()
    {
        float targetSpeed = _horizontalInput * moveSpeed;
        float speedDiff = targetSpeed - _rb.linearVelocity.x;
        float accelRate = (Mathf.Abs(targetSpeed) > 0.01f) ? acceleration : deceleration;
        float movement = speedDiff * accelRate * Time.fixedDeltaTime;

        _rb.linearVelocity = new Vector2(_rb.linearVelocity.x + movement, _rb.linearVelocity.y);
    }

    private void ApplyJumpGravityModifiers()
    {
        if (_rb.linearVelocity.y < 0f)
        {
            // 하강 시 더 빠른 속도로 떨어져 묵직한 조작감 제공
            _rb.gravityScale = fallMultiplier;
        }
        else if (_rb.linearVelocity.y > 0f && !Input.GetButton("Jump"))
        {
            // 점프 키를 살짝 탭했을 때 낮은 점프
            _rb.gravityScale = lowJumpMultiplier;
        }
        else
        {
            _rb.gravityScale = 1f;
        }
    }

    private void OnDrawGizmosSelected()
    {
        if (groundCheckPoint == null) return;
        Gizmos.color = Color.green;
        Gizmos.DrawWireCube(groundCheckPoint.position, groundCheckSize);
    }
}

2. 조작감 튜닝 가이드 및 파라미터 매핑

파라미터권장값 범위기능 및 효과
coyoteTimeDuration0.08s ~ 0.15s발판 이탈 후 점프를 인정하는 허용 시간. 0.1초 내외가 가장 자연스러움.
jumpBufferDuration0.10s ~ 0.15s착지 직전 점프 입력을 유지하는 시간. 연타 시 입력 씹힘 방지.
fallMultiplier2.0 ~ 3.0하강 중력 배율. 체공 시간을 단축해 날렵하고 묵직한 착지감 제공.
lowJumpMultiplier1.8 ~ 2.5점프 키를 뗐을 때 적용되는 추가 중력 배율. 숏 점프와 롱 점프 구분.

3. 주의사항 및 실전 팁

  • OverlapBox vs Raycast: 지면 판정 시 단일 레이캐스트는 낭떠러지 모서리에 걸칠 때 지면 미인식 오류가 발생합니다. 가로로 얇은 OverlapBox 또는 2~3개의 복수 레이캐스트를 사용하는 것이 안전합니다.
  • 물리 프레임 동기화: 입력 수집(Input.GetAxisRaw, GetButtonDown)과 타이머 감소는 Update()에서 처리하고, 실제 물리 가속도 전달은 FixedUpdate()에서 수행해야 프레임 저하 시에도 일관된 조작감을 보장합니다.