Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified presentations/making-games/assets/budgets-legibility.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
95 changes: 95 additions & 0 deletions presentations/making-games/assets/captures.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
{
"sourceSha256": "c2ed572c63a5cf8d4935104a1df49251239306182b9686eb08e66ae6ca688373",
"captures": [
{
"file": "garden-start.png",
"description": "Finished game starts with no planted flowers.",
"displayBackend": "WebGL2",
"sceneBackend": "WebGL shader",
"gpu": "ANGLE (Intel, ANGLE Metal Renderer: Intel(R) Iris(TM) Plus Graphics, Unspecified Version)",
"width": 900,
"height": 900,
"sha256": "7de9abb9d67b6ff1f99b83d8d5d25c6bc2dd04ad5ebde726ff8f81973cc53533"
},
{
"file": "dream-garden.png",
"description": "Populated final game, grown through its bloom handler.",
"displayBackend": "WebGL2",
"sceneBackend": "WebGL shader",
"gpu": "ANGLE (Intel, ANGLE Metal Renderer: Intel(R) Iris(TM) Plus Graphics, Unspecified Version)",
"width": 900,
"height": 900,
"sha256": "30a7e554d2869f491aa9b78408e05299ab9af2b8dcaf4e43ee860708b8245014"
},
{
"file": "shared-rest.png",
"description": "Player one holds A: RELAX, sleeping pose and slower seeds.",
"displayBackend": "WebGL2",
"sceneBackend": "WebGL shader",
"gpu": "ANGLE (Intel, ANGLE Metal Renderer: Intel(R) Iris(TM) Plus Graphics, Unspecified Version)",
"width": 900,
"height": 900,
"sha256": "2eef293f519d23a66ae245ad5fbdaf5bd24b296342df534eba3702a904216ee6"
},
{
"file": "dream-rest.png",
"description": "Actual final Rest scene preserves the planted flowers.",
"displayBackend": "WebGL2",
"sceneBackend": "WebGL shader",
"gpu": "ANGLE (Intel, ANGLE Metal Renderer: Intel(R) Iris(TM) Plus Graphics, Unspecified Version)",
"width": 900,
"height": 900,
"sha256": "f078d873fd75b85fc9d360f68c66177c521d115ab76fb539ef4601a6db90b4bf"
},
{
"file": "first-game.png",
"description": "Actual first steering checkpoint, with only one dreamer.",
"displayBackend": "WebGL2",
"sceneBackend": "WebGL shader",
"gpu": "ANGLE (Intel, ANGLE Metal Renderer: Intel(R) Iris(TM) Plus Graphics, Unspecified Version)",
"width": 900,
"height": 900,
"sha256": "dbc86956d2ed20a8f6f493bb3e7b22153a4ac9d8ae44c6c65f9b0690e594cfd2"
},
{
"file": "tilemaps.png",
"description": "Actual cloud tilemap checkpoint before scrolling.",
"displayBackend": "WebGL2",
"sceneBackend": "WebGL shader",
"gpu": "ANGLE (Intel, ANGLE Metal Renderer: Intel(R) Iris(TM) Plus Graphics, Unspecified Version)",
"width": 900,
"height": 900,
"sha256": "7de9abb9d67b6ff1f99b83d8d5d25c6bc2dd04ad5ebde726ff8f81973cc53533"
},
{
"file": "display-angles.png",
"description": "Geometry example using real Dream Garden assets.",
"displayBackend": "WebGL2",
"sceneBackend": "WebGL shader",
"gpu": "ANGLE (Intel, ANGLE Metal Renderer: Intel(R) Iris(TM) Plus Graphics, Unspecified Version)",
"width": 900,
"height": 900,
"sha256": "a64ed83ee165d56ec6a8af4b020a78f451cbed6eedc49f26d9142bb014d9cf85"
},
{
"file": "display-projections.png",
"description": "Geometry example using real Dream Garden assets.",
"displayBackend": "WebGL2",
"sceneBackend": "WebGL shader",
"gpu": "ANGLE (Intel, ANGLE Metal Renderer: Intel(R) Iris(TM) Plus Graphics, Unspecified Version)",
"width": 900,
"height": 900,
"sha256": "bb3f47930b9b34347ef15f0b65a969d8d0da6bd44f452033388b5b88857a08b8"
},
{
"file": "budgets-legibility.png",
"description": "Geometry example using real Dream Garden assets.",
"displayBackend": "WebGL2",
"sceneBackend": "WebGL shader",
"gpu": "ANGLE (Intel, ANGLE Metal Renderer: Intel(R) Iris(TM) Plus Graphics, Unspecified Version)",
"width": 900,
"height": 900,
"sha256": "4bba014d74818400a154f16fb488c697ef7a779e634aa203c8c172021f4638f1"
}
]
}
Binary file added presentations/making-games/assets/cloud-atlas.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified presentations/making-games/assets/display-angles.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified presentations/making-games/assets/display-projections.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified presentations/making-games/assets/dream-garden.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified presentations/making-games/assets/dream-rest.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified presentations/making-games/assets/first-game.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified presentations/making-games/assets/shared-rest.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified presentations/making-games/assets/tilemaps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 14 additions & 3 deletions presentations/making-games/examples/artwork.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@ Neither Node nor sharp is a game runtime dependency.

The generated sheet contains two dream creatures, golden seeds, four flower
species and sparse garden accents. Dreamers have four poses each. Flowers have
two frames each. Seeds are packed at 9 × 9 pixels: a subdued idle frame and
three brighter glow frames. Flying accents are shrunk inside their 16-pixel
tiles and darkened so the planted flowers remain distinct. `images/steel8x8.png` is copied unchanged from the SDK's
two frames each. The original fourth row is unused in this cloud experiment. Seeds are packed at 9 × 9 pixels: a subdued idle frame and
three brighter glow frames. Flying accents now use the separate cloud atlas described below. `images/steel8x8.png` is copied unchanged from the SDK's
`games/demos/tutorial_game/images/steel8x8.png`.

`make_audio.py` synthesizes the original bloom chime and a 32-second soft loop
Expand All @@ -34,3 +33,15 @@ used.
## Generation prompt

Create a production-ready transparent pixel-art SPRITE ATLAS for an experimental circular arcade game called Dream Garden. Exact layout: EIGHT equal columns and FOUR equal rows, a 2:1 horizontal image. Each cell contains one separate centered sprite with generous transparent padding. No visible grid lines, letters, numbers, labels, background, shadow beneath objects, or scene. Strict low resolution pixel art: chunky crisp square pixels, limited pastel mint/lavender/pink/amber palette with deep navy outlines, no antialiasing style or gradients. Intended to read at 19x19 pixels. ROW ONE all 8 cells: a tiny rounded dream creature with two eyes and soft leafy antennae. Cells 1-4 pink creature, neutral face, leaning left, leaning right, sleeping with closed eyes. Cells 5-8 same creature in mint blue, same four poses. ROW TWO cells 1-4: one floating golden seed/wisp in four subtle animation phases, simple bright core and a little tail; cells 5-8 entirely empty transparent. ROW THREE all 8 cells: four distinct dream flowers, TWO frames per flower with petals open slightly differently. Flower species: pink star blossom, mint fern blossom, lavender moon flower, amber glowing mushroom flower. ROW FOUR all 8 cells: eight tiny sparse dream garden accents on transparency: a mint root curl, a pink petal pair, a lavender crescent, three tiny amber stars, a blue sprout, a mint leaf, lavender bubbles, a single pink curl. All objects completely contained within their individual cells, exact consistent spacing. Cozy strange playful dream imagery for a festival about dreaming and rest. No spacecraft, weapons, robots, metal, UI, logos, or text. Export on genuine transparent background.

## Fluffy cloud experiment

The built-in imagegen tool generated a [separate six-cloud atlas](../assets/cloud-atlas.png).
The exact prompt is included as `sources/cloud-prompt.txt` in the game download.
`node games/demos/dream_garden/sources/prepare-clouds.cjs` packs two variants each
at 16 × 16, 32 × 16 and 48 × 32 pixels, cuts them into adjoining 16-pixel tiles
and produces an 18-frame strip. Binary alpha removes faint surrounding glow;
70% brightness keeps clouds subdued. Clouds use their own palette group.
Larger shapes occupy two cells or a 3 × 2 block, without gaps at cell boundaries.
The packer reverses source cell order to match the polar renderer’s tile
orientation; the map places the resulting frames row by row.
51 changes: 41 additions & 10 deletions presentations/making-games/examples/dream-garden-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The festival's [Dream No Return theme](https://www.sickhouse.nl/festival/dream-n
inspired a garden whose players can choose a slower pace. This is an original
sample game, not a festival commission or an official festival game.

[Download the complete game](dream-garden.zip). While these changes are under review, use the SDK branch `feat/dream-garden-layers`. Extract the archive at the root of that SDK checkout to create `games/demos/dream_garden/`. The source, packed assets, audio and rebuilding recipes are included, along with `tests/test_dream_garden.py` so the checks below also work when installing into a current SDK checkout. After merging, the same archive works in an updated main checkout.
[Download the complete game](dream-garden.zip). While these changes are under review, use the SDK branch `feat/dream-garden-clouds`. Extract the archive at the root of that SDK checkout to create `games/demos/dream_garden/`. The source, packed assets, audio and rebuilding recipes are included, along with `tests/test_dream_garden.py` so the checks below also work when installing into a current SDK checkout. After merging, the same archive works in an updated main checkout.

[Small first-game starter](first_game.py) · [Back to the slides](../)

Expand All @@ -29,29 +29,32 @@ Windows uses `vs-emu.bat --game demos.dream_garden`.
| --- | --- | --- | --- |
| Begin / join | A | Space | Numpad 0 |
| Drift around the rim | Left / Right | A / D | Left / Right arrows |
| Slow everyone's garden | Hold A | Hold Space | Hold Numpad 0 |
| Slow the seeds | Hold A | Hold Space | Hold Numpad 0 |
| Rest and keep the flowers on screen | B | B | Numpad 1 |
| Toggle the quiet lullaby and bloom sound | X | N | Numpad 2 |
| Return to the launcher | Y / Back / Home | M / 1 / Escape | Numpad 3 / + / Escape |

The garden starts empty. Catch a golden seed to plant a flower at the catching
player’s depth, on the side they last moved toward, with a centre offset of
75% of the player’s width. Each player remembers their own direction while
stopped; opposite inputs cancel without changing it. Before the first movement,
planting defaults to the left.
Flowers drift inward at 8% of seed speed and disappear at the centre.
player’s depth, displaced by 75% of the player’s width. While moving, catches
plant in the current movement direction. After stopping, the first catch uses
that direction, then later catches alternate sides until movement resumes.
Each player has an independent sequence; opposite inputs count as stopping.
Before the first movement, the first catch defaults to the left.
Flowers drift inward at 8% of normal seed speed and disappear at the centre.
Smaller, subdued golden seeds occasionally pulse with a randomly triggered glow.
Missing a seed has no penalty. A second
player joins by steering or pressing A, using an already reserved mint dreamer.
Either player can slow seed movement, the drifting background and flower
animation. New spawn intervals also lengthen, after the pending seed deadline.
Either player can slow the seeds. Clouds, planted flower drift and flower
animation keep their normal pace. New spawn intervals also lengthen, after the pending seed deadline.
Flowers gradually replace the oldest ones when the garden fills up.

There is no death, score, countdown or difficulty ramp. Watching is welcome. Music and bloom sounds start on. X toggles both, showing NOISY or QUIET
for two seconds before the text slides outward past the rim over 450 ms.
Another toggle resets the position and restarts the notice. B stops the music and keeps the current flowers in a still garden.
A starts a new garden from the Rest scene.

![The finished garden begins empty, with a pink dreamer below the clouds](../assets/garden-start.png)

## How the example works

`code/dream_garden.py` contains the whole game. `main()` returns Title, which
Expand All @@ -63,7 +66,11 @@ The second player costs one reserved sprite even before joining.
The Garden graph uses **42 sprites, 3 tilemaps, 3 layers and 5 image strips**.
The flower pool counts toward that cost even when its slots are hidden. The
lower layer draws flowers first and players above them. The middle layer draws
smaller, darker flying accents in 16-pixel tiles, followed by 9-pixel seeds.
six subdued fluffy cloud shapes, followed by 9-pixel golden seeds. Clouds
occupy 1 × 1, 2 × 1 or 3 × 2 tile cells, packed at 16 × 16, 32 × 16
and 48 × 32 pixels. Clouds keep scrolling at 0.45 depth units per tick. Seeds move 15% slower
by default (0.3825), and slow to 0.102 while relaxing. Seeds always paint
above clouds, including when their shapes overlap.
The top HUD shows RELAX while A is held, then slides it out on release,
plus temporary audio feedback.
Both flips keep the top text upright.
Expand All @@ -74,6 +81,30 @@ base and never go into the rotor ROM. The sample follows the repository's GPLv3
license. [Artwork sources and rebuilding](artwork.html) record the new art and
original sound synthesis. The general SDK tutorial remains a separate example.

## Build this garden step by step

Each stage is a complete, cumulative source file inside the download at
`code/checkpoints/`. The slides explain the changes and launch the matching
version in the web emulator. The last stage matches `code/dream_garden.py` exactly.

| Stage | Adds |
| --- | --- |
| 01-steering | One pink dreamer moving around the rim |
| 02-poses | Leaning and sleeping poses |
| 03-pools | Reserved seed and flower slots |
| 04-timer | Periodic random seed spawning |
| 05-flowers | Collisions, directional planting and inward flower drift |
| 06-tiles | Fluffy clouds in the middle layer, behind seeds |
| 07-scroll | Steady scrolling clouds, slower outward seeds |
| 08-hud | Seed-only RELAX and a label that slides out |
| 09-scenes | Title and voluntary rest, preserving flowers |
| 10-shared | A second player, independent planting sequences and random seed glow |
| 11-audio | Default-on lullaby and bloom sounds, timed NOISY/QUIET notices |

A new garden starts without flowers. The planting direction follows movement,
then alternates after stopping. Every stage keeps the same packed artwork,
with unused strips available for the later steps.

## Checks

From the SDK root:
Expand Down
Binary file modified presentations/making-games/examples/dream-garden.zip
Binary file not shown.
62 changes: 41 additions & 21 deletions presentations/making-games/examples/dream_garden.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Dream Garden: a shared, unhurried circular garden for Overkill 2026.

LEFT/RIGHT drift, hold A to slow the whole garden, B rests, X toggles audio.
LEFT/RIGHT drift, hold A to slow the seeds, B rests, X toggles audio.
A second controller can join at any time. Watching is also a way to play.
"""

Expand All @@ -13,11 +13,16 @@
AWAKE, LEFT_POSE, RIGHT_POSE, ASLEEP = range(4)
DRIFT_SPEED = 0.45
SLOW_SPEED = 0.12
CLOUD_SPEED = DRIFT_SPEED
DRIFT_SPAWN_MS = 1800
SLOW_SPAWN_MS = 4000
SEED_DEPTH = 160
FLOWER_COUNT = 24
PATTERN_ROWS = 6
PATTERN_ROWS = 12
SEED_SPEED_RATIO = 0.85
# (column, row, first tile, width in cells, height in cells)
CLOUD_PATCHES = ((1, 0, 0, 1, 1), (7, 1, 1, 2, 1), (11, 4, 3, 3, 2),
(5, 6, 9, 1, 1), (13, 8, 10, 2, 1), (1, 9, 12, 3, 2))
FLOWER_DRIFT_RATIO = 0.08
FLOWER_DEPTH = 255
GLOW_FRAMES = (1, 2, 3, 2, 1, 0)
Expand All @@ -34,15 +39,16 @@ def slide_text_out(label, start_y, started):
return started


def garden_tiles(world):
ground = world.tilemap("garden.png", columns=16, rows=16,
view_width=256, view_height=160)
for row in range(16):
for col in range(16):
band = row % PATTERN_ROWS
ground[col, row] = ((col + band) % 8
if (col + band * 3) % 5 == 0
else vs2.EMPTY_TILE)
def cloud_tiles(air):
ground = air.tilemap("clouds.png", columns=16, rows=22,
view_width=256, view_height=160)
for base_row in range(0, ground.rows, PATTERN_ROWS):
for col, row, first, width, height in CLOUD_PATCHES:
for dy in range(height):
target_row = base_row + row + dy
if target_row < ground.rows:
for dx in range(width):
ground[col + dx, target_row] = first + dy * width + dx
return ground


Expand All @@ -63,7 +69,7 @@ def build(self):
self.world = self.layer("world", projection=vs2.TUNNEL)
self.air = self.layer("air", projection=vs2.TUNNEL)
self.hud = self.layer("hud", projection=vs2.HUD)
self.ground = garden_tiles(self.air)
self.ground = cloud_tiles(self.air)
self.flowers = self.world.sprite_pool(
"flowers.png", count=FLOWER_COUNT, on_empty=vs2.RECYCLE)
self.seeds = self.air.sprite_pool("seed.png", count=16)
Expand All @@ -85,9 +91,13 @@ def build(self):
self.slow = False
self.sound_on = True
vs2.audio.music("lullaby", loop=True)
# Start toward LEFT; stopping keeps each player’s last steering direction.
# Idle catches alternate; movement resets the first idle planting side.
self.last_direction1 = 1
self.last_direction2 = 1
self.plant_side1 = 1
self.plant_side2 = 1
self.moving1 = False
self.moving2 = False
self.seed_glows = {}
self.speed = DRIFT_SPEED
self.spawn_ms = DRIFT_SPAWN_MS
Expand All @@ -96,11 +106,16 @@ def build(self):
def steer(self, dreamer, controller, base_frame, sleeping):
direction = controller.held(LEFT) - controller.held(RIGHT)
dreamer.x = (dreamer.x + direction) % vs2.display.width
if direction:
if dreamer is self.friend:
if dreamer is self.friend:
self.moving2 = bool(direction)
if direction:
self.last_direction2 = direction
else:
self.plant_side2 = direction
else:
self.moving1 = bool(direction)
if direction:
self.last_direction1 = direction
self.plant_side1 = direction
pose = (ASLEEP if sleeping else
LEFT_POSE if direction > 0 else
RIGHT_POSE if direction < 0 else AWAKE)
Expand Down Expand Up @@ -139,8 +154,8 @@ def update(self):
self.mood_exit = slide_text_out(self.mood, 1, self.mood_exit)
self.speed = SLOW_SPEED if slow else DRIFT_SPEED
self.spawn_ms = SLOW_SPAWN_MS if slow else DRIFT_SPAWN_MS
self.dream_time = (self.dream_time + self.speed) % 96
self.scroll = (self.scroll + self.speed) % (
self.dream_time = (self.dream_time + CLOUD_SPEED) % 96
self.scroll = (self.scroll + CLOUD_SPEED) % (
PATTERN_ROWS * self.ground.tile_height)
self.ground.view_y = self.scroll
phase = int(self.dream_time // 6) % 2
Expand All @@ -149,7 +164,7 @@ def update(self):
for flower in self.flowers:
flower.frame = (flower.frame // 2) * 2 + phase
for flower in self.flowers:
flower.y += self.speed * FLOWER_DRIFT_RATIO
flower.y += CLOUD_SPEED * SEED_SPEED_RATIO * FLOWER_DRIFT_RATIO
if flower.y >= FLOWER_DEPTH:
self.flowers.despawn(flower)
self.move_seeds()
Expand All @@ -174,18 +189,23 @@ def update(self):
self.sound_notice_until = ticks_add(ticks_ms(), 2000)

def bloom(self, seed, player):
side = self.last_direction2 if player is self.friend else self.last_direction1
side = self.plant_side2 if player is self.friend else self.plant_side1
centre = player.x + player.width // 2
self.flowers.spawn(x=(centre + side * player.width * 0.75
- self.flower_half) % vs2.display.width, y=player.y,
frame=randrange(4) * 2 + self.flower_phase)
if player is self.friend:
if not self.moving2:
self.plant_side2 = -side
elif not self.moving1:
self.plant_side1 = -side
self.retire_seed(seed)
if self.sound_on:
vs2.audio.sound("bloom")

def move_seeds(self):
for seed in self.seeds:
seed.y -= self.speed
seed.y -= self.speed * SEED_SPEED_RATIO
self.glow_seed(seed)
if seed.overlaps(self.dreamer):
self.bloom(seed, self.dreamer)
Expand Down
Loading
Loading