Developing Games
When asked to build a game or game system, follow this decision tree:
- Identify the platform/engine (explicit or inferred from context: web → HTML5/Canvas/Phaser, Python → Pygame, mentions of Unity/Godot/Unreal → that engine, no constraint → pick the simplest fit for the genre).
- Identify the genre (platformer, top-down shooter, puzzle, RPG, roguelike, etc.) — genre determines core loop, data structures, and physics needs.
- Scaffold with a proper game loop first, before any feature code:
JavaScript// Fixed timestep game loop (prevents physics inconsistency across frame rates) const FPS = 60; const STEP = 1000 / FPS; let lastTime = 0, accumulator = 0; function gameLoop(timestamp) { accumulator += timestamp - lastTime; lastTime = timestamp; while (accumulator >= STEP) { update(STEP / 1000); // deterministic, delta in seconds accumulator -= STEP; } render(accumulator / STEP); // interpolation factor for smooth rendering requestAnimationFrame(gameLoop); }
- Build systems in this strict order: loop → input → entity/state model → physics/collision → rendering → audio → UI → polish (juice). Never start with graphics/art code.
Progress checklist for any non-trivial game feature or full game:
- Step 1: Define the core loop (what does the player do every 5 seconds? every 5 minutes?)
- Step 2: Choose architecture pattern (ECS for many entity types, OOP state machine for simple games, FSM per-entity for AI/player states)
- Step 3: Implement game loop with fixed timestep
- Step 4: Implement input handling (abstracted from action logic — map keys to actions, not actions to keys)
- Step 5: Implement entity/world model and update logic
- Step 6: Implement collision detection appropriate to need (AABB for simple, spatial hashing/quadtree if >100 entities)
- Step 7: Implement rendering (separate from update logic — never mutate state in render)
- Step 8: Add game feel/juice (screen shake, hit-stop, particle feedback, easing) — do this LAST
- Step 9: Profile and optimize (object pooling, avoid GC pressure in hot loops)
- Step 10: Playtest-driven tuning (numbers like jump height, enemy speed are never right on first guess)
Architecture decision guide
| Situation | Pattern |
|---|---|
| Few entity types, simple game | Classes + inheritance or simple composition |
| Many entity types sharing partial behavior (position, health, AI...) | Entity-Component-System (ECS) |
| Player/enemy with distinct behavior modes (idle/attack/stun) | Finite State Machine per entity |
| Complex branching AI | Behavior Tree |
| Game-wide modes (menu/playing/paused/gameover) | Global State Machine / Scene Manager |
Core systems reference
Collision (AABB, the default for 2D):
JavaScriptfunction aabbCollide(a, b) { return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y; }
Object pooling (avoid new in hot loops — biggest perf killer in JS/C# games):
JavaScriptclass Pool { constructor(factory, size) { this.factory = factory; this.pool = Array.from({length: size}, factory); this.active = []; } spawn(...args) { const obj = this.pool.pop() || this.factory(); obj.init(...args); this.active.push(obj); return obj; } release(obj) { this.active.splice(this.active.indexOf(obj), 1); this.pool.push(obj); } }
Input abstraction (decouple keys from actions):
JavaScriptconst keyMap = { ArrowLeft: 'moveLeft', ArrowRight: 'moveRight', Space: 'jump' }; const actions = new Set(); window.addEventListener('keydown', e => keyMap[e.code] && actions.add(keyMap[e.code])); window.addEventListener('keyup', e => keyMap[e.code] && actions.delete(keyMap[e.code])); // game logic checks actions.has('jump'), never e.code directly
Example 1: Input: "اعملي لعبة Platformer بسيطة بالـ HTML5 Canvas فيها قفز وجاذبية" Output: Full single-file HTML5 game with: fixed-timestep loop, player entity with velocity/acceleration-based movement, gravity constant applied per-frame (not per-tick-dependent), AABB collision against tile array, coyote-time jump buffering (small window after leaving platform where jump still registers — critical for "feel"), camera follow with lerp smoothing, and a tile-based level defined as a 2D array for easy editing.
Example 2:
Input: "عايز نظام Inventory في Unity بـ C#"
Output: ScriptableObject-based item definitions (data decoupled from instances), Inventory MonoBehaviour using List<InventorySlot> with add/remove/stack logic, UnityEvent or C# event (OnInventoryChanged) to decouple UI from logic (UI subscribes, never polls), and a separate InventoryUI that rebuilds slots only on the event — not every frame.
Example 3:
Input: "اللعبة بتلاج (lag) لما العدد بتاع الأعداء يزيد"
Output: Diagnose first — is it collision (O(n²) pairwise checks → fix with spatial hashing/quadtree), GC pressure (allocating objects/arrays per frame → fix with pooling), or draw calls (too many individual sprite draws → fix with batching/sprite atlases). Never guess; profile first (performance.now() timestamps around suspect sections, or engine profiler).
- Separate update from render. Update mutates state; render only reads state. Never do
if (keyPressed) sprite.draw()logic mixed into physics. - Delta-time everything. Never hardcode
position += 5per frame — alwaysposition += speed * deltaTime, or use fixed timestep for determinism. - Data-driven design. Enemy stats, level layouts, item definitions belong in data (JSON, ScriptableObjects, config objects), not hardcoded in logic classes.
- "Juice" comes last but matters most for feel. Screen shake, squash/stretch, particle bursts, sound on impact — these make a mechanically identical game feel 10x better. Add after core mechanics are solid.
- Coyote time + input buffering for platformers. Players expect forgiveness: jump still works ~100ms after leaving a ledge, and jump input is buffered ~100ms before landing.
- Pool anything spawned repeatedly (bullets, particles, enemies) — avoid garbage collection stutter.
- Test with placeholder art (gray boxes) first. Never block mechanics work on final art/assets.
- Keep camera logic separate from game logic. Camera follows/shakes independently of entity state.
- Frame-rate-dependent movement (
x += 5without deltaTime) — breaks on different hardware/refresh rates. - Checking collisions every entity-pair naively (O(n²)) once entity count grows — use spatial partitioning early if count can exceed ~50-100.
- Mixing rendering code into game logic — makes testing and porting impossible.
- Hardcoding tunable values (jump force, damage, speed) deep in logic instead of exposing them as easily-tweakable constants/data — playtesting requires fast iteration on numbers.
- Allocating new objects/arrays inside the game loop — causes GC pauses/stutter, especially in JS and C#.
- Starting with art/animation before core mechanics are fun — a game must be fun as gray boxes before it's worth skinning.
- Polling input state deep inside nested logic instead of abstracting to an action layer — makes rebinding and multi-input support painful later.
- Ignoring fixed timestep for physics — variable timestep physics causes different outcomes across machines (critical bug for multiplayer/replays).