Lesson 1

Writing an Effective Game Brief

๐Ÿ“– 8 min read
By the end of this lesson, you will be able to:
  • Identify the four essential elements of a game brief
  • Write a clear brief that an AI assistant can act on without follow-up questions
  • Recognize and avoid the most common briefing mistakes

What is a game brief?

A game brief is a short written document that defines what you want built. It is the first artifact of any software project. With AI-assisted development, the quality of your brief directly determines the quality of your first working prototype.

The four essential elements

Every effective game brief specifies four things:

  1. Platform โ€” where the game runs (browser, mobile app, desktop, console)
  2. Controls โ€” how the player interacts (keyboard, touch, gamepad, mouse)
  3. Mechanics โ€” what the player can do and what challenges them (jump, shoot, dodge, collect)
  4. Style / Vibe โ€” the look and feel you want (cartoony, realistic, pixel art, premium 3D)
Strong brief example

"Build a browser-based side-scrolling racing game. Player controls a red monster truck using arrow keys (left/right for movement, up for jump, spacebar for nitrous boost). Side profile view with realistic computer graphics. Obstacles include bridges and water holes โ€” falling in water ends the game. Spacebar gives a 2-second speed boost; player has 5 nitrous charges that replenish every 15 seconds. The HUD at the top should look like a premium, million-dollar video game."

This brief specifies all four elements without ambiguity. An AI assistant can begin work immediately and the result will closely match the writer's intent.

Common briefing mistakes

MistakeWhy it failsBetter approach
"Build me a fun game"No platform, no controls, no mechanics, no styleSpecify all four elements
"Make it like [popular game]"Copyright risk + the AI can't actually copy the gameDescribe specific mechanics instead
Writing 5 pages of specifications upfrontYou can't predict every detail before seeing v1Specify v1 essentials, iterate from there
Skipping the visual styleYou get a generic look that disappointsReference a known style or attach an inspiration image

Cost considerations to include

Strong briefs also include a budget instruction. If your project will use paid APIs (image generation, 3D models, music), ask the AI to confirm cost estimates before spending. A simple line like "tell me the approx cost of any API usage before implementing" prevents surprises.

Exercise 1.1
Write a brief for your first game idea. Use the four-element template. Keep it to 5-10 sentences. Don't try to predict every detail โ€” only specify what you're sure about. Save it; you'll use it in Lesson 2.
Lesson 2

Choosing Your Tech Stack

๐Ÿ“– 10 min read
By the end of this lesson, you will be able to:
  • Choose between 2D and 3D engines based on your game's needs
  • Compare the trade-offs of major browser game frameworks
  • Justify why "simplest stack that works" is almost always correct

The first decision: 2D or 3D?

If your game can work as a side-view, top-down, or fixed-angle 2D presentation, choose 2D. 2D games are faster to build, run on weaker devices, look great with the right art, and cost far less to produce.

Only choose 3D if the gameplay requires three-dimensional movement that cannot be faked in 2D. Examples: first-person shooters, free-roaming explorers, racing simulators with corner-turning camera angles.

Comparing browser game frameworks

FrameworkTypeBest forLearning curve
HTML5 Canvas + Vanilla JS2D, no librarySimple games, learning fundamentalsLow
PixiJS2D, WebGL-acceleratedPolished 2D games needing shadersLow-Medium
Phaser2D frameworkGame-specific conveniences (scenes, physics)Medium
Three.js3D, low-levelCustom 3D experiencesHigh
Babylon.js3D, game-focused3D games with built-in featuresHigh

The case for Canvas + Vanilla JS

For your first project, choose HTML5 Canvas with vanilla JavaScript. Here's why:

  • Zero installation. No npm, no webpack, no build configuration. Open an HTML file in any browser and it runs.
  • Universal compatibility. Works on every device with a browser, including old phones and tablets.
  • Instant feedback loop. Edit a file, refresh the browser, see the change. No compile step means faster iteration.
  • Deployment is just file copying. Upload your folder to any web server.
Principle: Premature complexity kills more side projects than insufficient complexity. Start with the simplest stack that can do the job. You can always migrate later if you outgrow it โ€” most projects never do.

When to graduate to a framework

You'll know it's time when:

  • Your game has 20+ entities and managing them is becoming chaotic
  • You need physics that would take hours to write yourself (use Matter.js)
  • You need WebGL effects like bloom, blur, or particle systems at scale (use PixiJS)
  • You're certain about the project's scope and stakes (use Phaser for serious 2D, Three.js for 3D)
Exercise 2.1
Pick three different game concepts (e.g. a clicker, a side-scroller, a 3D space exploration). For each, write down which stack you'd choose and explain in one sentence why.
Lesson 3

Domain, Server, and HTTPS Setup

๐Ÿ“– 12 min read
By the end of this lesson, you will be able to:
  • Explain how a browser request reaches your web server
  • Set up DNS records to point a domain to a server
  • Configure a web server to serve your game files
  • Add free HTTPS using Let's Encrypt

The four-step web delivery chain

When someone visits yourgame.example.com, four systems hand off:

  1. Registrar (where you bought the domain) tells the world which nameserver controls it
  2. Nameserver (DNS provider) holds the records that say "this name โ†’ this IP address"
  3. Web server (running on a virtual machine at that IP) listens for HTTP requests
  4. Web server config matches the requested hostname to a folder of files

Many beginners confuse steps 2 and 3 โ€” DNS is not on your web server. DNS lives at your registrar or DNS provider. You configure it through their control panel or API.

Adding a DNS record

To make yourgame.example.com point to a server with IP 138.197.145.209, add an A record:

Type:     A
Name:     yourgame
Value:    138.197.145.209
TTL:      3600 (1 hour)

DNS changes can take 5 minutes to an hour to propagate worldwide. Use a tool like dnschecker.org to monitor.

Configuring your web server

If you're using Apache, create a virtual host file telling Apache where to serve files from:

<VirtualHost *:80>
    ServerName yourgame.example.com
    DocumentRoot /var/www/yourgame/public

    <Directory /var/www/yourgame/public>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

Then enable the site and reload Apache:

a2ensite yourgame.example.com.conf
systemctl reload apache2

Adding free HTTPS with Let's Encrypt

HTTPS is required for any modern web app. Let's Encrypt provides free certificates that auto-renew. After DNS propagates, run:

certbot --apache -d yourgame.example.com \
  --non-interactive --agree-tos -m your@email.com \
  --redirect

Certbot does three things: requests a certificate from Let's Encrypt, installs it in your web server config, and sets up automatic renewal. The certificate is valid for 90 days and renews itself.

Cost summary

ItemTypical cost
Domain registration$10-15/year
DNS hosting$0 (included with registrar or DO)
Virtual server (DigitalOcean droplet, etc.)$6-12/month
HTTPS certificate$0 (Let's Encrypt)
Bandwidth (for typical small site)$0 (included in server)
Tip: A single $12/month virtual server can comfortably host 20+ small projects. You don't need a separate server per project.
Exercise 3.1
If you don't yet have a domain, register one ($12). Choose a subdomain name for your first game (e.g. game1.yourdomain.com). Note down the IP of any server you plan to use. You don't need to configure anything yet โ€” just have the pieces ready.
Lesson 4

Generating Game Art with AI

๐Ÿ“– 12 min read
By the end of this lesson, you will be able to:
  • Compare the cost and capabilities of major AI image generators
  • Write image prompts that consistently produce usable results
  • Choose between transparent and opaque backgrounds appropriately
  • Estimate the total art budget for a small game

Why AI image generation for games

Hiring a professional illustrator costs $50โ€“$200 per image with a multi-day turnaround. AI image generation costs $0.05โ€“$0.30 per image and takes 20โ€“60 seconds. For prototypes, indie projects, and learning, AI is the obvious choice.

OpenAI image model pricing (2026)

ModelQualitySizePrice per image
gpt-image-1Low1024ร—1024$0.011
gpt-image-1Medium1024ร—1024$0.042
gpt-image-1Medium1536ร—1024 (wide)~$0.063
gpt-image-1High1536ร—1024 (wide)~$0.25
DALL-E 3Standard1024ร—1024$0.040
DALL-E 3HD1792ร—1024 (wide)$0.120

For game backgrounds, the bolded option (gpt-image-1 medium wide) is the sweet spot โ€” good enough quality for parallax backdrops at low cost.

Anatomy of a strong image prompt

A reliable prompt has six elements:

  1. Style anchor โ€” reference a known visual style ("Sonic Superstars Hill Top Zone", "Studio Ghibli")
  2. Subject โ€” what's in the image
  3. Composition โ€” "wide landscape", "side view", "top 60% blank"
  4. Color palette โ€” "vibrant saturated", "muted earth tones"
  5. Exclusions โ€” "no characters, no vehicles, no text"
  6. Quality words โ€” "premium", "illustrative", "cinematic"
Strong prompt example

"Premium children's book illustration of a bright sunny desert sky with fluffy white clouds and distant rolling purple-blue hills in soft flat vector style. No characters, no vehicles, no foreground. Cheerful color palette: warm peach sky fading to baby blue. Wide landscape composition, flat design, smooth gradients, no text. Style: Sago Mini, Toca Boca, modern children's app illustration."

Handling the transparency problem

AI image models don't reliably produce transparent backgrounds even when asked. Two workarounds:

  1. Solid chroma-key background. Ask for "solid magenta background" and remove the magenta in code at load time.
  2. Composition instruction. Ask for "the top 60% should be blank/transparent" and accept the result.

Budgeting art for a small game

Project sizeImage countCost range
Tiny game (1 background, 2 sprites)~5 with retries$0.30โ€“$0.80
Small game (3-5 backgrounds, UI, sprites)~10-15 with retries$1โ€“$3
Medium game (multiple levels, characters)~30-50 with retries$5โ€“$15
Exercise 4.1
Write three image prompts for your game using all six elements. Compare them side by side to decide which is strongest before any generation happens.
Lesson 5

Organizing Your Code

๐Ÿ“– 10 min read
By the end of this lesson, you will be able to:
  • Apply the "separation of concerns" principle to game code
  • Design a file structure that scales with your project
  • Explain why most small projects don't need build tools

The principle: one file, one responsibility

Each file in your project should do one thing well. When responsibilities are mixed across files, changes ripple unpredictably and bugs hide in unexpected places.

Recommended file structure for a 2D game

your-game/
โ”œโ”€โ”€ index.html                  Page structure, HUD, modals, canvas element
โ”œโ”€โ”€ style.css                   Visual styles for the HUD and overlays
โ”œโ”€โ”€ assets/
โ”‚   โ”œโ”€โ”€ bg-layer1.png           Background art
โ”‚   โ”œโ”€โ”€ bg-layer2.png
โ”‚   โ””โ”€โ”€ music.mp3
โ””โ”€โ”€ src/
    โ”œโ”€โ”€ audio.js                Sound effects and music
    โ”œโ”€โ”€ input.js                Keyboard and touch event handlers
    โ”œโ”€โ”€ parallax.js             Background scrolling
    โ”œโ”€โ”€ terrain.js              Ground/level data
    โ”œโ”€โ”€ particles.js            Visual effects
    โ”œโ”€โ”€ obstacles.js            Hazards and collectibles
    โ”œโ”€โ”€ player.js               Player entity
    โ”œโ”€โ”€ enemy.js                Enemy entities (often extends player)
    โ””โ”€โ”€ game.js                 Main loop, state management

How the modules communicate

Each module exposes its functionality on the global window object as a class or namespace:

// In truck.js:
(function() {
  class Truck { /* ... */ }
  window.Truck = Truck;
})();

// In game.js:
const player = new window.Truck({ x: 100, y: 200 });

The main game.js file orchestrates everything: it instantiates entities, runs the game loop, dispatches events. Individual modules don't reach across to each other directly.

Why no build tools?

Build tools (webpack, Vite, npm, TypeScript compilation) add power but multiply complexity. For most browser games, the trade is not worth it. Plain <script> tags in your HTML are sufficient:

<script src="src/audio.js"></script>
<script src="src/input.js"></script>
<script src="src/player.js"></script>
<script src="src/game.js"></script>

Order matters: load dependencies before the files that use them.

Migration path: If your game grows past ~3000 lines and you start fighting module loading, that's the signal to add Vite or a similar bundler. Until then, the simple approach wins.

Naming conventions

  • Files: lowercase with hyphens or single-word (audio.js, player-controller.js)
  • Classes: PascalCase (Truck, ParticleSystem)
  • Functions: camelCase (updateScore, checkCollisions)
  • Constants: UPPER_SNAKE_CASE (MAX_SPEED, GROUND_Y)
Exercise 5.1
Plan the file structure for your game from Exercise 1.1. List each module you'll need and write one sentence describing what goes in it.
Lesson 6

Player Movement and Physics

๐Ÿ“– 14 min read
By the end of this lesson, you will be able to:
  • Implement variable jump height (tap = short, hold = high)
  • Create procedural rolling terrain with sine waves
  • Tilt a player's body to match terrain slope
  • Apply gravity and ground collision correctly

The basic physics loop

Every frame, three things happen for each moving entity:

  1. Apply forces (gravity, jump impulses, player input)
  2. Update velocity from forces
  3. Update position from velocity
function update(dt) {
  // 1. Forces
  player.vy += GRAVITY;
  if (jumpPressed && player.onGround) {
    player.vy = -JUMP_IMPULSE;
  }

  // 2. Velocity already updated above
  // 3. Position
  player.y += player.vy;
  player.x += player.vx;

  // 4. Resolve collisions
  if (player.y >= groundY) {
    player.y = groundY;
    player.vy = 0;
    player.onGround = true;
  }
}

Variable jump height (a platformer classic)

To make tap = short jump and hold = high jump, apply a continuous upward force while the key is held โ€” up to a maximum duration:

// When jump first pressed:
player.vy = -INITIAL_IMPULSE   // -14
player.jumpHoldTime = 0
player.isJumping = true

// Each frame while key held AND jumpHoldTime < MAX:
if (player.isJumping && jumpHeld && player.jumpHoldTime < 0.45) {
  player.vy -= ADDITIONAL_FORCE   // -0.6 per frame
  player.jumpHoldTime += dt
}

// Always:
player.vy += GRAVITY   // 0.7

The cap (0.45 seconds in this example) prevents the player from holding the button to fly forever. Tune these three values โ€” initial impulse, additional force, max hold โ€” until the jump feels right.

Procedural rolling terrain

Instead of a flat ground line, layer three sine waves to create natural-looking rolling hills:

function groundY(worldX) {
  return baseY - (
    Math.sin(worldX * 0.0035) * 55 +
    Math.sin(worldX * 0.008 + 1.7) * 22 +
    Math.sin(worldX * 0.018 + 3.3) * 8
  );
}

The three layers have different frequencies and offset phases. The result is hills that don't repeat predictably โ€” they feel natural.

Following the terrain

When the player is grounded, their Y position should match the terrain at their X:

const groundHere = groundY(player.x);
if (player.y >= groundHere) {
  player.y = groundHere;
  player.vy = 0;
  player.onGround = true;
}

Body tilt with slope

To make the player visually lean forward going downhill and back going uphill, compute the slope (rise over run) and convert to a rotation angle:

const dx = 5;
const slope = (groundY(player.x + dx) - groundY(player.x - dx)) / (2 * dx);
player.bodyTilt = Math.atan(slope);

When you draw the player, rotate the canvas context by player.bodyTilt before drawing.

Tuning is the whole job. The physics formulas above are simple. What makes a game feel good is finding the right numbers โ€” gravity strength, jump impulse, friction. Plan to spend more time tuning than coding.
Exercise 6.1
Open the live Monster Truck game at mobile-games.marcinmigdal.com/monster-truck-hilltop-rally/play. Notice how the truck tilts on hills and how holding space gives a higher jump. Then look at the source at src/truck.js to see the exact code implementing this lesson.
Lesson 7

Building AI Opponents

๐Ÿ“– 12 min read
By the end of this lesson, you will be able to:
  • Implement rubber-band AI that keeps races feeling close
  • Use adaptive difficulty that responds to player skill
  • Enforce minimum spacing between multiple AI entities
  • Recognize that "game AI" is rules-based, not machine learning

Game AI is rules, not intelligence

When someone says "the game has good AI," they usually mean the rules produce behavior that feels intelligent. Almost no commercial game uses machine learning for opponents. Game AI is finite state machines, decision trees, and tuned numerical rules.

The rubber-banding pattern

In racing games, you want opponents to stay close to the player โ€” exciting races require the outcome to feel uncertain. The technique is called rubber-banding: invisible elastic that pulls opponents toward the player.

function rivalAI(rival, player, idealOffset) {
  // Where we want the rival to be (relative to player)
  const desiredX = player.x - idealOffset;

  // How fast should we move to get there?
  const xDelta = desiredX - rival.x;
  rival.targetVx = player.vx + xDelta * 0.04;

  // Smooth velocity approach (no instant teleport)
  rival.vx += (rival.targetVx - rival.vx) * 0.07;
}

Adaptive difficulty

The interesting part: adjust the rival's behavior based on how well the player is doing.

// Sample player's recent average speed
const recentAvgVx = average(playerSpeedSamplesLast60Frames);

if (recentAvgVx > expectedSpeed * 0.95) {
  // Player is doing well โ€” rival keeps up but stays behind
  rival.targetVx = player.vx * 0.95;
} else if (recentAvgVx < expectedSpeed * 0.5) {
  // Player is struggling โ€” rival slows down to let them catch up (kid mode)
  // OR pulls ahead and threatens to win (adult mode)
  rival.targetVx = mode === 'kid' ? recentAvgVx * 0.8 : expectedSpeed;
}

Multiple rivals โ€” enforce minimum spacing

If you have several AI opponents, they'll naturally cluster at the same target position. Push them apart:

for (const other of allRivals) {
  if (other === this) continue;
  const gap = this.x - other.x;
  if (Math.abs(gap) < MIN_GAP) {
    const push = (MIN_GAP - Math.abs(gap)) * 0.5;
    desiredX += (gap > 0 ? 1 : -1) * push;
  }
}

"Never lose" mode for young players

For games aimed at very young players, you might want to guarantee the player can win. Hard rules near the finish line:

const inFinalStretch = (timeLimit - gameTime) < 5; // last 5 seconds
if (inFinalStretch && mode === 'kid') {
  // Hard cap: rival cannot exceed player's X position
  if (rival.x > player.x - 40) {
    rival.x = player.x - 40;
    rival.vx = Math.min(rival.vx, player.vx * 0.9);
  }
}

Predictive behaviors

AI opponents should react to upcoming obstacles. For a jumping AI:

for (const obstacle of obstacles) {
  if (!obstacle.requiresJump) continue;
  const distance = obstacle.x - rival.x;
  if (distance > 50 && distance < 200 && rival.onGround) {
    rival.jump();
    break;
  }
}
Exercise 7.1
Design the AI behavior for a single enemy in your game. Write the rules in plain English first (not code). Aim for 4-6 rules that cover: idle behavior, player-tracking, attack triggers, retreat conditions.
Lesson 8

Sound Design with the Web Audio API

๐Ÿ“– 10 min read
By the end of this lesson, you will be able to:
  • Choose between synthesized sounds and audio files
  • Synthesize basic game sound effects in code
  • Find legally usable music for your project
  • Add background music with fade in/out

Two routes for game audio

ApproachProsCons
Web Audio API synthesisFree, no file loading, infinite variationsLimited to electronic/synthesized sounds
MP3/OGG audio filesReal instruments, recorded voices, any soundLarger downloads, need to source legally

A common hybrid: synthesize SFX (jump, hit, coin), use MP3 for music.

Synthesizing a jump sound

const ctx = new AudioContext();

function playJump() {
  const osc = ctx.createOscillator();
  const gain = ctx.createGain();

  osc.type = 'sine';
  osc.frequency.setValueAtTime(220, ctx.currentTime);
  osc.frequency.exponentialRampToValueAtTime(680, ctx.currentTime + 0.18);

  gain.gain.setValueAtTime(0.25, ctx.currentTime);
  gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.22);

  osc.connect(gain).connect(ctx.destination);
  osc.start();
  osc.stop(ctx.currentTime + 0.25);
}

The pattern: create an oscillator (the tone) and a gain node (the volume envelope), connect them, then start and stop the oscillator. Sweep the frequency for "boing" effects; sweep volume down for natural decay.

Standard game sound recipes

EffectWaveFrequency sweepDuration
JumpSine220 โ†’ 680 Hz0.18s
LandSine120 โ†’ 60 Hz0.12s
Coin pickupTwo sines (880 + 1320 Hz)Constant0.15s + 0.04s offset
Hit / bonkSquare180 โ†’ 80 Hz0.08s
Nitrous / hissWhite noise800 โ†’ 3000 Hz bandpass0.5s
Win fanfareTriangle523 โ†’ 1047 Hz arpeggio0.6s

Legal music sources

For background music, never use copyrighted commercial songs. Use one of these instead:

  • freepd.com โ€” CC0, no attribution required
  • incompetech.com โ€” Kevin MacLeod, CC-BY (attribution required)
  • pixabay.com/music โ€” large library, free with credit
  • Original compositions you commission from a musician on Fiverr (~$50-200)

Playing music with fade in/out

const music = document.createElement('audio');
music.src = 'assets/music.mp3';
music.loop = true;
music.volume = 0;
music.play();

// Fade in over ~2 seconds
let v = 0;
const fadeIn = setInterval(() => {
  v = Math.min(v + 0.02, 0.4);
  music.volume = v;
  if (v >= 0.4) clearInterval(fadeIn);
}, 50);
Exercise 8.1
Pick three sound effects your game needs. For each, write down: which approach (synth or file), and if synth, which wave type and frequency. Try the synth code above in a browser console to hear how it sounds.
Lesson 9

Iterating on Feedback

๐Ÿ“– 8 min read
By the end of this lesson, you will be able to:
  • Write feedback that triggers precise fixes rather than guesswork
  • Use screenshots and reference images effectively
  • Prioritize bugs vs polish vs scope additions

The feedback formula

A good feedback message contains three things:

  1. The observation โ€” what you see now
  2. The intent โ€” what should happen instead
  3. Optional: a reference โ€” screenshot, example URL, or sketch
Strong feedback example

"BUG: when I press shift, the speed jumps from 3 to 6 and stays there. Speed should be constant in kid mode. Also, SHIFT shouldn't be the nitrous key โ€” use the spacebar for jump only."

This message names the bug, describes the trigger, and specifies the fix. It can be acted on immediately without follow-up questions.

What makes weak feedback

Weak feedbackWhy it failsBetter version
"It doesn't look right"No information about what's wrong"The trucks look too small relative to the trees"
"Can you fix it?"What's broken?"When I click jump twice fast, the truck floats. Should only jump once per ground touch."
"Make it better"Better in what way?"The coins are too dim against the bright background โ€” add a glow effect or change them to a darker shade."

Using reference images

One good reference image is worth a thousand words of description. If you can find an existing game or app that has the look you want, paste a screenshot with the instruction "make it more like this." The AI will absorb visual style faster than you can describe it.

Iteration vs scope additions

Two different requests to keep separate:

  • Iteration โ€” refining something that already exists ("the coin is too small")
  • Scope addition โ€” asking for something new ("add 3 more trucks")

Bundle iteration into one message; introduce scope additions one at a time. This keeps the AI's attention focused.

Speed beats perfection. Don't compose perfect feedback. Type the observation, intent, and any reference quickly โ€” three sentences, send. The AI will ask for clarification if needed. Speed of iteration is more valuable than precision of each message.
Exercise 9.1
Play the Monster Truck game and write feedback as if you were the project owner. Find at least 3 things to improve. Use the observation + intent format for each.
Lesson 10

Planning Project Costs

๐Ÿ“– 8 min read
By the end of this lesson, you will be able to:
  • Estimate the cost of common project types
  • Identify which costs are one-time vs recurring
  • Use AI capabilities tactically to stay under budget

One-time vs recurring costs

ItemTypeTypical cost
Domain registrationAnnual$10-15/year
Virtual serverMonthly$6-12/month
HTTPS certificateFree$0 (Let's Encrypt)
AI image generationPer-image$0.04-0.25
AI 3D model generation (Meshy.ai)Per-model$0.50-2
AI assistant subscriptionMonthly$20/month (Claude Pro)
Music (royalty-free)Free or one-time$0-$50

Project-type cost estimates

Project typeOne-time costRecurring
Static landing page$0$0 (use GitHub Pages)
Simple 2D browser game$1-5 (images)$0 if hosted free
2D game with custom art$5-15~$6/month server
2.5D fighting game$10-30 (DALL-E) or $50-200 (Meshy)~$6/month server
Full 3D adventure game$200-1000 (3D models)~$12/month server
AI chatbot for business$0 dev$2-50/month API based on usage
Multiplayer game (small scale)$0 dev$12-50/month server + DB

Strategies to stay under budget

  • Use medium quality, not HD. For prototype art, gpt-image-1 medium is 4x cheaper than high and looks great.
  • Use procedural art where possible. Anything geometric (UI icons, simple sprites) can be drawn in code at $0.
  • Cache and reuse images. Pull one strong asset, use it across multiple game elements.
  • Start with free tools. Mixamo for 3D character animations, freepd.com for music, Web Audio for SFX.
  • Set a hard ceiling. Tell the AI "spend no more than $0.50 on this." Limits force creative reuse.
For most learners: If you already have a domain and a server, building a polished small game costs $1-3 in AI image fees. This is the cheapest creative medium in human history.
Exercise 10.1
Estimate the total cost for your game from Exercise 1.1. Include AI image costs, any music, and assume you already have a server. Set a hard budget for v1 โ€” say $5 โ€” and design around it.
Lesson 11

Deploying Your Game

๐Ÿ“– 6 min read
By the end of this lesson, you will be able to:
  • Choose a deployment method matched to your project size
  • Upload your game files to a web server
  • Test the deployed version before sharing

Three deployment options

OptionCostBest for
GitHub PagesFreeStatic games, public source
Netlify / VercelFree tierStatic or serverless, fast CDN
Your own server (DigitalOcean, Linode)$6-12/moMultiple projects, full control, server-side features

Deploying to your own server

If you already have a server set up from Lesson 3, deployment is just copying files:

# From your local machine:
scp -r ./my-game/* user@your-server-ip:/var/www/my-game/public/

# Set proper permissions on the server:
ssh user@your-server-ip
chown -R www-data:www-data /var/www/my-game/
chmod -R 755 /var/www/my-game/public/

Deploying to GitHub Pages (free)

  1. Create a new GitHub repository
  2. Upload your game folder contents
  3. Go to Settings โ†’ Pages โ†’ enable Pages from the main branch
  4. Your game is live at https://username.github.io/repo-name/

Pre-launch checklist

  • โœ… Test in Chrome, Firefox, and Safari
  • โœ… Test on a phone (or use Chrome DevTools mobile emulator)
  • โœ… Verify all asset URLs work (no file:// or local paths)
  • โœ… Verify the HTTPS certificate is valid (no browser warnings)
  • โœ… Hard-refresh the deployed version (Ctrl+Shift+R) โ€” your browser may have cached the old version

Sharing your game

Once live, share the URL. Real feedback from real users is the only reliable way to know if your game is good. Watch someone play it without explaining anything โ€” their confusion shows you what to improve next.

You finished the course. The next step is to build your own game. Start small โ€” use the Cookie Clicker course or one of the practice projects as a template. Ship something working. Iterate. The skills compound quickly.
Final exercise
Build a tiny version of your game from Exercise 1.1. Deploy it. Share the URL with one friend. Use their reaction to write your next 3 feedback items.
Lesson 12

Making Your Game Mobile-Friendly

๐Ÿ“– 10 min read
By the end of this lesson, you will be able to:
  • Add on-screen touch buttons for mobile players
  • Detect touch devices and conditionally show controls
  • Make your layout responsive across screen sizes
  • Handle touch events correctly (avoiding common pitfalls)

Why mobile matters

Over half of casual game players are on phones. If your game requires a keyboard, you exclude that audience entirely. Adding touch support takes about an hour and unlocks 5ร— the potential players.

Two layers of touch support

  1. Tap anywhere on canvas = jump (simplest, works without UI changes)
  2. Dedicated on-screen buttons (better UX, supports continuous actions like "hold to accelerate")

Implement both. The on-canvas tap is a safety net; the dedicated buttons are the primary interface.

Canvas tap handler

const canvas = document.getElementById('gameCanvas');

const tapHandler = (e) => {
  // Don't fire if the touch landed on a UI button
  if (e.target && e.target.tagName === 'BUTTON') return;
  e.preventDefault();
  Input.jumpQueued = true;
};

canvas.addEventListener('touchstart', tapHandler, { passive: false });
canvas.addEventListener('mousedown', tapHandler);  // works for desktop too

The { passive: false } option lets us call preventDefault(), which stops the browser from also scrolling or zooming on the tap.

On-screen buttons (HTML)

<div id="touch-controls">
  <button id="btn-gas">
    <span class="touch-icon">โšก</span>
    <span class="touch-label">GAS</span>
  </button>
  <button id="btn-jump">
    <span class="touch-icon">โฌ†</span>
    <span class="touch-label">JUMP</span>
  </button>
</div>

Held-button pattern (for "hold to accelerate")

const gasBtn = document.getElementById('btn-gas');

gasBtn.addEventListener('touchstart', (e) => {
  e.preventDefault();
  Input.touchGasHeld = true;
});

gasBtn.addEventListener('touchend', (e) => {
  e.preventDefault();
  Input.touchGasHeld = false;
});

// Also handle touchcancel โ€” fires when the OS interrupts (incoming call, etc.)
gasBtn.addEventListener('touchcancel', () => {
  Input.touchGasHeld = false;
});

// Then in your game logic:
isAccelerating() {
  return this.keys['ArrowRight'] || this.touchGasHeld;
}

CSS: only show buttons on touch devices

/* Hide by default (desktop) */
#touch-controls { display: none; }

/* Show on touch-only devices */
@media (hover: none) and (pointer: coarse) {
  #touch-controls { display: flex; }
}

The (hover: none) and (pointer: coarse) queries together reliably detect touch-primary devices without false positives on hybrid laptops.

Button styling for thumbs

  • Minimum 80ร—80px tap target (60ร—60 is acceptable, smaller is hostile)
  • Position in bottom corners โ€” natural thumb reach when holding a phone
  • Semi-transparent backgrounds so they don't block the game view
  • Use :active CSS state to give immediate visual feedback on tap
  • Set touch-action: manipulation to disable double-tap-to-zoom on the buttons

Responsive layout

Make your canvas scale to fill the screen while maintaining aspect ratio:

#game-container {
  width: min(100vw, calc(100vh * 16 / 9));
  height: min(100vh, calc(100vw * 9 / 16));
  aspect-ratio: 16 / 9;
}

This sizes the container to the largest 16:9 rectangle that fits in the viewport, regardless of phone or tablet dimensions.

Test the basics

  • Tap a button โ†’ does the action fire?
  • Hold a button โ†’ does it stay active?
  • Rotate the phone โ†’ does the layout still work?
  • Open and close on-screen keyboard (focus an input) โ†’ does the canvas resize correctly?
  • Try in airplane mode (after first load) โ†’ service worker should serve from cache
Common mistake: Forgetting e.preventDefault() on touch handlers. Without it, mobile browsers will double-fire (touch + synthesized click) and your game receives every jump twice.
Exercise 12.1
Open the live Monster Truck game on your phone: mobile-games.marcinmigdal.com/monster-truck-hilltop-rally/play/. Notice the JUMP and GAS buttons that appear. Try playing with just your thumbs. Notice if anything feels awkward โ€” that becomes your tuning list.
Lesson 13

Installing on Phone: PWA & APK

๐Ÿ“– 10 min read
By the end of this lesson, you will be able to:
  • Convert your browser game into a Progressive Web App (PWA)
  • Add an app manifest and service worker for offline support
  • Generate an installable .apk file using PWABuilder
  • Decide between PWA-only and APK distribution

What is a PWA?

A Progressive Web App is a regular website with three additional ingredients:

  1. manifest.json โ€” tells the browser the app's name, icon, color, and how to display it
  2. service worker โ€” a background script that caches files for offline use
  3. HTTPS โ€” required (you already have this from Lesson 3)

With those three pieces, your game becomes installable from any browser. Users tap "Add to Home Screen" and it appears as an app icon โ€” opens fullscreen, no browser chrome, indistinguishable from a native app to the user.

Writing manifest.json

{
  "name": "Monster Truck Hill Top Rally",
  "short_name": "Monster Truck",
  "description": "A premium kid-friendly browser racing game.",
  "start_url": "./",
  "scope": "./",
  "display": "fullscreen",
  "orientation": "landscape",
  "background_color": "#0a3d4f",
  "theme_color": "#0a3d4f",
  "categories": ["games", "kids"],
  "icons": [
    { "src": "icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" }
  ]
}

Save as manifest.json in your public folder. Link it in your HTML:

<link rel="manifest" href="manifest.json">
<meta name="theme-color" content="#0a3d4f">
<link rel="apple-touch-icon" href="icon-192.png">

Required: app icons

You need at least two PNG icons: 192ร—192 and 512ร—512. Generate them via AI (one prompt, $0.01 with gpt-image-1 low quality), then resize. The 512px version should be designed to look good both as a square and a circle (for adaptive icons on Android).

Writing the service worker

// sw.js
const CACHE = 'my-game-v1';
const FILES = [
  './',
  'index.html',
  'style.css',
  'src/game.js',
  'assets/bg.png',
  'assets/music.mp3'
];

self.addEventListener('install', (e) => {
  e.waitUntil(caches.open(CACHE).then(c => c.addAll(FILES)));
});

self.addEventListener('fetch', (e) => {
  e.respondWith(
    caches.match(e.request).then(cached =>
      cached || fetch(e.request)
    )
  );
});

Register the service worker from your HTML:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('sw.js');
  });
}

Testing installation

  1. Open the game URL in Chrome on Android (or Safari on iPhone)
  2. Chrome shows an "Install app" prompt OR a menu option in โ‹ฎ โ†’ "Install app"
  3. iOS Safari: tap Share โฌ† โ†’ "Add to Home Screen"
  4. The app appears on your home screen โ€” launches fullscreen, runs offline after first load

Generating a real .apk file (PWABuilder route)

For Google Play Store submission or sideloading via APK file, use Microsoft's free PWABuilder:

  1. Visit pwabuilder.com
  2. Paste your PWA URL
  3. PWABuilder analyzes your manifest and service worker, scores readiness
  4. Click "Package For Stores" โ†’ Android
  5. Download the .apk file (small โ€” a few MB, contains a WebView wrapper around your URL)
  6. Sideload by emailing the APK to yourself and tapping it (enable "Install from Unknown Sources" first), OR submit to Play Store

PWA vs APK โ€” which to choose?

ApproachProsCons
PWA onlyFree, instant install, no app store, updates push automaticallyiOS treats it differently from "real" apps (some limitations)
PWA + APK sideloadReal .apk file for sharing/distributionUsers must enable Unknown Sources, no auto-updates
PWA + Play StoreDiscoverable on Play Store, auto-updates$25 one-time Google fee, review process, must use Trusted Web Activities

App Store (iOS)

Apple does not allow PWAs to be submitted to the App Store as PWAs. To publish on iOS, you would need to wrap your web app with Capacitor or React Native โ€” significantly more work. For most indie projects, "Add to Home Screen" on iOS is sufficient.

Recommendation: Ship as a PWA first. Track installs via your service worker's install event. Only invest in APK / app store distribution if you have evidence the audience wants a "real app."
Exercise 13.1
Install the Monster Truck game on your phone via "Add to Home Screen." Then visit pwabuilder.com, paste the game URL, and review the readiness score. Note which manifest fields PWABuilder suggests improving โ€” this becomes your polish checklist for your own PWA.
๐Ÿ”“

Advanced Prompts REGISTERED ONLY

Copy-paste these prompts into your AI assistant to add advanced features to your game. Each one is a real prompt that was used to extend this exact Monster Truck game.

๐ŸŽ“ Course complete

You've built, deployed, and installed a browser game. Earn your certificate or continue with another course.

๐Ÿ… Get Certificate Browse all courses Practice projects