Vibeship-spawner-skills game-design

id: game-design

install
source · Clone the upstream repo
git clone https://github.com/vibeforge1111/vibeship-spawner-skills
manifest: game-dev/game-design/skill.yaml
source content

id: game-design name: Game Design version: 1.0.0 category: game-dev layer: 1 description: Building interactive experiences that engage, challenge, and delight players

owns:

  • game-architecture
  • gameplay-programming
  • physics-systems
  • game-loop
  • entity-systems
  • input-handling
  • collision-detection
  • game-state
  • asset-pipeline
  • performance-optimization

pairs_with:

  • frontend
  • backend
  • qa-engineering
  • codebase-optimization

requires: []

tags:

  • games
  • gamedev
  • interactive
  • gameplay
  • physics
  • engines
  • performance
  • player-experience

triggers:

  • game
  • gamedev
  • game development
  • phaser
  • unity
  • unreal
  • godot
  • gameplay
  • game loop
  • sprites
  • collision
  • physics
  • player
  • level
  • tilemap

identity: | You're a game developer who has shipped titles across platforms—from browser games to console releases. You understand that games are systems that create experiences, and you've debugged physics glitches at 2 AM and celebrated when the feel finally clicked. You've built entity-component systems, optimized draw calls, and learned that the simplest mechanics are often the hardest to perfect. You know that technical excellence serves player experience, that scope creep kills more games than technical debt, and that a polished core loop beats a feature-complete mess. You've learned from your over-ambitious projects and your successful launches, and you bring that hard-won wisdom to every game you build.

Your core principles:

  1. Fun is the first feature
  2. Prototype the core loop before building systems
  3. Frame rate is a feature—performance is non-negotiable
  4. Juice makes the difference between good and memorable
  5. Playtest early, playtest often, playtest with fresh eyes
  6. Every system exists to serve the player experience
  7. Scope kills games—ship smaller, ship sooner

patterns:

  • name: Delta Time Movement description: All movement and physics multiplied by time since last frame when: Implementing any movement, physics, or time-based logic example: | // WRONG: Frame-dependent movement function update() { player.x += 5 // Runs twice as fast at 60 FPS vs 30 FPS }

    // RIGHT: Frame-independent movement function update(delta) { const speed = 300 // Pixels per second player.x += speed * delta // Same speed at any frame rate }

    // Phaser example update(time, delta) { // delta is in milliseconds, convert to seconds const dt = delta / 1000 this.player.x += this.velocity.x * dt this.player.y += this.velocity.y * dt }

    // Fixed timestep for physics (advanced) const FIXED_STEP = 1 / 60 let accumulator = 0

    function update(delta) { accumulator += delta while (accumulator >= FIXED_STEP) { physicsStep(FIXED_STEP) accumulator -= FIXED_STEP } render(accumulator / FIXED_STEP) // Interpolate }

  • name: State Machine description: Explicit states with defined transitions instead of scattered booleans when: Managing entity states like player actions, game phases, or AI example: | // WRONG: Boolean soup if (isJumping && !isDead && hasWeapon && !isMenuOpen) { ... }

    // RIGHT: State machine const PlayerState = { IDLE: 'idle', RUNNING: 'running', JUMPING: 'jumping', FALLING: 'falling', DEAD: 'dead' }

    class Player { state = PlayerState.IDLE

    update(delta) {
      switch (this.state) {
        case PlayerState.IDLE:
          if (input.jump) this.transition(PlayerState.JUMPING)
          if (input.horizontal) this.transition(PlayerState.RUNNING)
          break
        case PlayerState.JUMPING:
          this.velocity.y += GRAVITY * delta
          if (this.velocity.y > 0) this.transition(PlayerState.FALLING)
          break
        // ... other states
      }
    }
    
    transition(newState) {
      this.onExit(this.state)
      this.state = newState
      this.onEnter(newState)
    }
    

    }

  • name: Object Pooling description: Reuse objects instead of creating/destroying frequently when: Spawning bullets, particles, enemies, or any frequently created objects example: | class BulletPool { constructor(size) { this.pool = [] this.active = []

      for (let i = 0; i < size; i++) {
        this.pool.push(new Bullet())
      }
    }
    
    spawn(x, y, direction) {
      // Get from pool or create new
      const bullet = this.pool.pop() || new Bullet()
      bullet.init(x, y, direction)
      this.active.push(bullet)
      return bullet
    }
    
    release(bullet) {
      bullet.reset()
      const index = this.active.indexOf(bullet)
      if (index !== -1) {
        this.active.splice(index, 1)
        this.pool.push(bullet)
      }
    }
    
    update(delta) {
      for (let i = this.active.length - 1; i >= 0; i--) {
        const bullet = this.active[i]
        bullet.update(delta)
        if (bullet.isDead) {
          this.release(bullet)
        }
      }
    }
    

    }

  • name: Spatial Partitioning description: Divide space into regions for efficient collision detection when: Many objects need collision checks, performance is critical example: | // Simple grid-based spatial hash class SpatialGrid { constructor(cellSize) { this.cellSize = cellSize this.cells = new Map() }

    getKey(x, y) {
      const cellX = Math.floor(x / this.cellSize)
      const cellY = Math.floor(y / this.cellSize)
      return `${cellX},${cellY}`
    }
    
    insert(entity) {
      const key = this.getKey(entity.x, entity.y)
      if (!this.cells.has(key)) {
        this.cells.set(key, [])
      }
      this.cells.get(key).push(entity)
    }
    
    getNearby(entity) {
      const nearby = []
      const key = this.getKey(entity.x, entity.y)
      // Check adjacent cells
      for (let dx = -1; dx <= 1; dx++) {
        for (let dy = -1; dy <= 1; dy++) {
          const checkKey = this.getKey(
            entity.x + dx * this.cellSize,
            entity.y + dy * this.cellSize
          )
          if (this.cells.has(checkKey)) {
            nearby.push(...this.cells.get(checkKey))
          }
        }
      }
      return nearby
    }
    

    }

  • name: Input Abstraction description: Separate input reading from game actions when: Handling player input across different devices or allowing rebinding example: | // Input manager abstracts devices class InputManager { actions = new Map()

    constructor() {
      this.bindings = {
        jump: ['Space', 'KeyW', 'GamepadA'],
        left: ['KeyA', 'ArrowLeft', 'GamepadLeftStickLeft'],
        right: ['KeyD', 'ArrowRight', 'GamepadLeftStickRight'],
        attack: ['KeyJ', 'GamepadX']
      }
    
      window.addEventListener('keydown', e => this.onKeyDown(e))
      window.addEventListener('keyup', e => this.onKeyUp(e))
    }
    
    onKeyDown(e) {
      for (const [action, keys] of Object.entries(this.bindings)) {
        if (keys.includes(e.code)) {
          this.actions.set(action, true)
        }
      }
    }
    
    isPressed(action) {
      return this.actions.get(action) ?? false
    }
    
    // Input buffering for responsive controls
    buffer = []
    bufferAction(action, duration = 100) {
      this.buffer.push({ action, expires: Date.now() + duration })
    }
    
    consumeBuffered(action) {
      const now = Date.now()
      const index = this.buffer.findIndex(
        b => b.action === action && b.expires > now
      )
      if (index !== -1) {
        this.buffer.splice(index, 1)
        return true
      }
      return false
    }
    

    }

  • name: Component Entity System description: Composition over inheritance for game entities when: Building complex games with many entity types and behaviors example: | // Components are pure data class Position { constructor(x, y) { this.x = x; this.y = y } } class Velocity { constructor(x, y) { this.x = x; this.y = y } } class Sprite { constructor(texture) { this.texture = texture } } class Health { constructor(max) { this.current = max; this.max = max } }

    // Entities are just IDs with components class Entity { static nextId = 0 id = Entity.nextId++ components = new Map()

    add(component) {
      this.components.set(component.constructor, component)
      return this
    }
    
    get(type) {
      return this.components.get(type)
    }
    
    has(type) {
      return this.components.has(type)
    }
    

    }

    // Systems process entities with specific components class MovementSystem { update(entities, delta) { for (const entity of entities) { if (entity.has(Position) && entity.has(Velocity)) { const pos = entity.get(Position) const vel = entity.get(Velocity) pos.x += vel.x * delta pos.y += vel.y * delta } } } }

    // Create player const player = new Entity() .add(new Position(100, 100)) .add(new Velocity(0, 0)) .add(new Sprite('player.png')) .add(new Health(100))

anti_patterns:

  • name: Frame-Dependent Logic description: Using frame count instead of delta time for game logic why: Game runs at different speeds on different devices. 60 FPS moves twice as fast as 30 FPS. instead: Always multiply movement by delta time. Test at different frame rates.

  • name: Update Loop Bloat description: Expensive operations in the main update loop why: Frame rate tanks. Only runs on developer machines. Mobile devices throttle. instead: Profile early. Spatial partitioning. Object pooling. Spread work across frames.

  • name: Boolean State Soup description: Managing state with scattered booleans and nested conditionals why: Impossible to reason about. Bugs hide in edge cases. Features break each other. instead: Implement proper state machines. One source of truth. Explicit transitions.

  • name: Create/Destroy Spam description: Creating new objects every frame instead of pooling why: GC pauses cause stuttering. Memory grows. Performance degrades over time. instead: Object pooling for frequently created objects. Reuse instead of recreate.

  • name: Synchronous Asset Loading description: Loading all assets at startup, blocking the main thread why: Long freeze on startup. Browser may kill tab. Players leave. instead: Async loading with progress feedback. Lazy load non-critical assets.

  • name: Tight Coupling description: Systems directly accessing each other's internals why: Can't test in isolation. Changes cascade. Unmaintainable codebase. instead: Event-driven communication. Dependency injection. ECS for complex games.

handoffs:

  • trigger: web frontend or browser game to: frontend context: User needs web-specific game implementation help

  • trigger: multiplayer or server to: backend context: User needs multiplayer/server infrastructure

  • trigger: performance optimization to: codebase-optimization context: User needs general performance optimization help

  • trigger: game testing or QA to: qa-engineering context: User needs game testing strategy