Skip to content
Development
Skill

/endless-runner

Endless runner architecture — procedural chunk spawning, lane-based or free movement, obstacle patterns, speed ramping, coin/collectible systems, distance scoring.

From plugin
everything-claude-unity
2442 skills20 agents27 commands
Install
$ npx -y skills add XeldarAlz/everything-claude-unity --skill endless-runner --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/endless-runner

Context preview

The summary Claude sees to decide when to auto-load this skill.

Endless runner architecture — procedural chunk spawning, lane-based or free movement, obstacle patterns, speed ramping, coin/collectible systems, distance scoring.

SKILL.md

endless-runner.SKILL.md
name: endless-runner
description: "Endless runner architecture — procedural chunk spawning, lane-based or free movement, obstacle patterns, speed ramping, coin/collectible systems, distance scoring."
globs: ["**/Runner*.cs", "**/Endless*.cs", "**/Chunk*.cs", "**/Obstacle*.cs", "**/Lane*.cs"]

Endless Runner Patterns

Chunk-Based Level Generation

public sealed class ChunkSpawner : MonoBehaviour
{
    [SerializeField] private GameObject[] _chunkPrefabs;
    [SerializeField] private float _chunkLength = 20f;
    [SerializeField] private int _activeChunkCount = 5;
    [SerializeField] private Transform _player;

    private readonly Queue<GameObject> _activeChunks = new();
    private float _spawnZ;
    private ObjectPool<GameObject>[] _chunkPools;

    private void Awake()
    {
        _spawnZ = 0f;
        // Initialize pools for each chunk type
    }

    private void Update()
    {
        float playerZ = _player.position.z;
        float despawnZ = playerZ - _chunkLength;

        // Recycle chunks behind player
        while (_activeChunks.Count > 0)
        {
            GameObject oldest = _activeChunks.Peek();
            if (oldest.transform.position.z < despawnZ)
            {
                _activeChunks.Dequeue();
                oldest.SetActive(false); // return to pool
            }
            else break;
        }

        // Spawn chunks ahead
        float spawnThreshold = playerZ + _chunkLength * _activeChunkCount;
        while (_spawnZ < spawnThreshold)
        {
            SpawnChunk();
        }
    }

    private void SpawnChunk()
    {
        int index = Random.Range(0, _chunkPrefabs.Length);
        GameObject chunk = GetFromPool(index);
        chunk.transform.position = new Vector3(0f, 0f, _spawnZ);
        chunk.SetActive(true);
        _activeChunks.Enqueue(chunk);
        _spawnZ += _chunkLength;
    }

    private GameObject GetFromPool(int index)
    {
        // Use ObjectPool<T> or custom pool
        return Instantiate(_chunkPrefabs[index]); // placeholder — use pool
    }
}

Lane-Based Movement (3-Lane)

public sealed class LaneRunner : MonoBehaviour
{
    [Header("Lanes")]
    [SerializeField] private float _laneWidth = 2.5f;
    [SerializeField] private float _laneSwitchSpeed = 15f;

    [Header("Jump")]
    [SerializeField] private float _jumpForce = 10f;
    [SerializeField] private float _gravity = -30f;

    [Header("Slide")]
    [SerializeField] private float _slideDuration = 0.5f;

    private int _currentLane; // -1, 0, 1
    private float _targetX;
    private float _verticalVelocity;
    private bool _isGrounded = true;
    private bool _isSliding;
    private CharacterController _controller;

    private void Awake()
    {
        _controller = GetComponent<CharacterController>();
        _currentLane = 0;
    }

    public void SwitchLane(int direction) // -1 left, +1 right
    {
        _currentLane = Mathf.Clamp(_currentLane + direction, -1, 1);
        _targetX = _currentLane * _laneWidth;
    }

    public void Jump()
    {
        if (!_isGrounded) return;
        _verticalVelocity = _jumpForce;
        _isGrounded = false;
    }

    public void Slide()
    {
        if (_isSliding) return;
        StartCoroutine(SlideCoroutine());
    }

    private IEnumerator SlideCoroutine()
    {
        _isSliding = true;
        _controller.height = 0.5f;
        _controller.center = new Vector3(0f, 0.25f, 0f);

        yield return new WaitForSeconds(_slideDuration);

        _controller.height = 2f;
        _controller.center = new Vector3(0f, 1f, 0f);
        _isSliding = false;
    }

    private void Update()
    {
        // Lateral movement
        float currentX = transform.position.x;
        float newX = Mathf.MoveTowards(currentX, _targetX, _laneSwitchSpeed * Time.deltaTime);

        // Vertical
        if (_isGrounded && _verticalVelocity < 0f)
        {
            _verticalVelocity = -1f;
        }
        _verticalVelocity += _gravity * Time.deltaTime;

        Vector3 move = new Vector3(newX - currentX, _verticalVelocity * Time.deltaTime, 0f);
        _controller.Move(move);

        _isGrounded = _controller.isGrounded;
    }
}

Touch Input Mapping

public sealed class RunnerInput : MonoBehaviour
{
    [SerializeField] private LaneRunner _runner;
    [SerializeField] private float _swipeThreshold = 50f;

    private Vector2 _touchStart;

    private void Update()
    {
        if (UnityEngine.InputSystem.Touchscreen.current == null) return;

        UnityEngine.InputSystem.Controls.TouchControl touch =
            UnityEngine.InputSystem.Touchscreen.current.primaryTouch;

        if (touch.press.wasPressedThisFrame)
        {
            _touchStart = touch.position.ReadValue();
        }

        if (touch.press.wasReleasedThisFrame)
        {
            Vector2 delta = touch.position.ReadValue() - _touchStart;

            if (delta.magnitude > _swipeThreshold)
            {
                if (Mathf.Abs(delta.x) > Mathf.Abs(delta.y))
                {
                    _runner.SwitchLane(delta.x > 0f ? 1 : -1);
                }
                else if (delta.y > 0f)
                {
                    _runner.Jump();
                }
                else
                {
                    _runner.Slide();
                }
            }
        }
    }
}

Speed Ramping

public sealed class SpeedManager : MonoBehaviour
{
    [SerializeField] private float _startSpeed = 8f;
    [SerializeField] private float _maxSpeed = 25f;
    [SerializeField] private float _accelerationPerSecond = 0.1f;

    private float _currentSpeed;
    private float _playTime;

    public float CurrentSpeed => _currentSpeed;

    private void Update()
    {
        _playTime += Time.deltaTime;
        _currentSpeed = Mathf.Min(_startSpeed + _accelerationPerSecond * _playTime, _maxSpeed);
    }

    public void ResetSpeed()
    {
        _playTime = 0f;
        _cu
Read more
Ships witheverything-claude-unity

The ultimate Claude Code toolkit for Unity game development. A production-ready, plug-and-play system that gives Claude Code deep Unity expertise — from writing performant C# to building scenes, profiling performance, and triggering iOS/Android builds — all

Get the whole plugin

Other skills on everything-claude-unity.