unity-2d-gameplay

v2026.09.24

Unity 2D gameplay patterns — character controllers (kinematic vs dynamic), coyote time, jump buffer, variable jump height, ledge grab, dash, wall slide, hitstop, juice patterns, top-down 8-way movement. USE WHEN: implementing platformer / metroidvania / top-down character controllers, "feel" tuning, ability layers (dash/double jump/wall slide). DO NOT USE FOR: 2D physics fundamentals (use `unity-2d-physics`); animations (use `unity-2d-animation`); cameras (use `unity-2d-cameras`).

GitHub
Install command
npx skhub add claude-dev-suite/unity-2d-gameplay
Markdown
SKILL.md

Unity 2D Gameplay (platformer feel)

Kinematic vs Dynamic for player

Recommended: Kinematic Rigidbody2D for tight platformer feel — you control all motion, no surprise forces, but you still get collision via Rigidbody2D.Cast / MovePosition. Dynamic is fine for physics-driven games (Angry Birds-style).

Platformer character controller — minimum viable

[RequireComponent(typeof(Rigidbody2D))]
public class PlatformerController : MonoBehaviour {
    [Header("Move")]
    [SerializeField] private float moveSpeed = 8f;
    [SerializeField] private float accelGrounded = 80f;
    [SerializeField] private float accelAir = 30f;

    [Header("Jump")]
    [SerializeField] private float jumpVelocity = 14f;
    [SerializeField] private float coyoteTime = 0.1f;
    [SerializeField] private float jumpBuffer = 0.1f;
    [SerializeField] private float lowJumpGravityMul = 2.5f;
    [SerializeField] private float fallGravityMul = 2f;

    [Header("Ground check")]
    [SerializeField] private Transform groundCheck;
    [SerializeField] private Vector2 groundCheckSize = new(0.6f, 0.1f);
    [SerializeField] private LayerMask groundMask;

    private Rigidbody2D _rb;
    private float _inputX;
    private float _coyoteCounter;
    private float _jumpBufferCounter;
    private bool _grounded;
    private float _baseGravity;

    void Awake() {
        _rb = GetComponent<Rigidbody2D>();
        _baseGravity = _rb.gravityScale;
    }

    public void SetInput(float x)  => _inputX = x;
    public void RequestJump()      => _jumpBufferCounter = jumpBuffer;
    public void ReleaseJump()      => _earlyRelease = true;

    private bool _earlyRelease;

    void Update() {
        // ground probe
        _grounded = Physics2D.OverlapBox(groundCheck.position, groundCheckSize, 0f, groundMask);

        // coyote time — leave the ledge but still allowed to jump
        if (_grounded) _coyoteCounter = coyoteTime;
        else           _coyoteCounter -= Time.deltaTime;

        // jump buffer — pressed jump just before landing
        if (_jumpBufferCounter > 0f) _jumpBufferCounter -= Time.deltaTime;

        // execute jump
        if (_jumpBufferCounter > 0f && _coyoteCounter > 0f) {
            var v = _rb.linearVelocity;
            v.y = jumpVelocity;
            _rb.linearVelocity = v;
            _jumpBufferCounter = 0f;
            _coyoteCounter = 0f;
        }

        // variable jump height — release Jump early to cut height
        if (_earlyRelease && _rb.linearVelocity.y > 0f) {
            _rb.gravityScale = _baseGravity * lowJumpGravityMul;
        }
        else if (_rb.linearVelocity.y < 0f) {
            _rb.gravityScale = _baseGravity * fallGravityMul;     // snappier fall
        }
        else {
            _rb.gravityScale = _baseGravity;
        }
        _earlyRelease = false;
    }

    void FixedUpdate() {
        // smooth horizontal — accel toward target
        var v = _rb.linearVelocity;
        float targetX = _inputX * moveSpeed;
        float accel = _grounded ? accelGrounded : accelAir;
        v.x = Mathf.MoveTowards(v.x, targetX, accel * Time.fixedDeltaTime);
        _rb.linearVelocity = v;
    }
}

Tune the four values — coyoteTime, jumpBuffer, lowJumpGravityMul, fallGravityMul — to taste. They're the difference between a frustrating jump and a delicious one.

Coyote time + jump buffer (in plain words)

  • Coyote time — the small grace window after you walk off a ledge during which the game still treats you as grounded for jump purposes. Compensates for human reaction time. Typical: 80–120 ms.
  • Jump buffer — pressing jump shortly before landing still triggers a jump on touchdown. Typical: 80–150 ms.

Both are "feel" features players notice subconsciously by their absence.

Wall slide + wall jump (additions)

bool wallContact = Physics2D.OverlapBox(wallCheck.position, wallCheckSize, 0f, wallMask);
if (wallContact && !_grounded && _rb.linearVelocity.y < 0f) {
    var v = _rb.linearVelocity;
    v.y = Mathf.Max(v.y, -wallSlideSpeed);  // capped fall
    _rb.linearVelocity = v;
}

// wall jump on input + wall contact
if (jumpPressed && wallContact && !_grounded) {
    _rb.linearVelocity = new Vector2(-Mathf.Sign(_inputX) * wallJumpX, wallJumpY);
}

Dash

IEnumerator Dash(Vector2 dir) {
    _isDashing = true;
    _rb.gravityScale = 0f;
    _rb.linearVelocity = dir.normalized * dashSpeed;
    yield return new WaitForSeconds(dashDuration);
    _rb.gravityScale = _baseGravity;
    _isDashing = false;
}

Disable input/jump during dash; restore previous gravity after.

Hitstop / juice

On hit:

  • Freeze time briefly (Time.timeScale = 0 for 50–80 ms, restore).
  • Camera shake via Cinemachine Impulse.
  • Sprite flash white via material property block.
  • Particle pop + SFX with random pitch.

These small frills carry most of the felt impact in 2D combat.

Top-down 8-way movement

Same idea, no jump:

void FixedUpdate() {
    Vector2 target = new Vector2(_inputX, _inputY).normalized * moveSpeed;
    var v = _rb.linearVelocity;
    v = Vector2.MoveTowards(v, target, accel * Time.fixedDeltaTime);
    _rb.linearVelocity = v;
}

Anti-patterns

Anti-patternFix
transform.Translate for playerKinematic Rigidbody2D + linearVelocity
Jump only when _grounded == trueAdd coyote + buffer; check counters
Same gravity on rise and fallMultiply gravity on fall for snappier descent
One-shot dash with no cooldown / iframesAdd cooldown timer + invulnerability flag during dash
Hard freeze without restoringUse coroutine that always restores Time.timeScale

Production checklist

  • Coyote time + jump buffer tuned
  • Variable jump height
  • Different gravity for rise vs fall
  • Wall slide / wall jump (if applicable)
  • Dash with i-frames + cooldown
  • Hitstop on impactful events
  • Camera follow uses Cinemachine Position Composer
  • Pixel Perfect Camera if pixel art
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

skills/gamedev/unity-2d-gameplay

Default branch

main

Latest commit

9496306

Tree SHA

fe4e2f1