Skip to content

Commit 509ef74

Browse files
committed
feat(maps): add map format spec + MVP-1 vertical shooter
- docs/map-format.md: v1 spec, decision log, MVP plan - examples/maplib.bas: runtime lib over the level convention (MapTileBg/Fg, MapTileSolid, MapRectHitsSolid, MapPixelW/H) - examples/shooter/: walls.png, player.png, enemy-sprites.png, tank.png, missiles.png, level1.bas (10x100 world, 13 enemies, boss trigger), shooter.bas (auto vertical scroll, A/D move, SPACE fire, AABB collision vs solid tiles + enemies) Shooter validates the format end-to-end: - tile flag (MAP_COLL list) for solid obstacles - object layer with type/kind/x/y/w/h dispatched by engine - camera scroll_dir/speed pulled from MAP_CAM_* - layered render (bg tilemap + sprite stamps at z=10/12/20) Known parser quirk worked around: rgc-basic FUNCTION bodies choke on UDF-call-inside-IF-condition followed by another statement, so MapRectHitsSolid inlines the solid-list scan. File a separate bug for that.
1 parent 5acee87 commit 509ef74

11 files changed

Lines changed: 828 additions & 0 deletions

File tree

docs/map-format.md

Lines changed: 334 additions & 0 deletions
Large diffs are not rendered by default.

examples/maplib.bas

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
' ============================================================
2+
' maplib.bas — RGC-BASIC level/map convention + helpers (MVP v0)
3+
'
4+
' This is the runtime side of docs/map-format.md. v0 doesn't parse
5+
' JSON yet — levels are authored as .bas files that #INCLUDE this
6+
' lib, fill the MAP_* globals listed below, then call game code.
7+
' When the JSON parser ships (v1.1), MapLoadJson(path$) will
8+
' populate the same globals — game code stays unchanged.
9+
'
10+
' Required globals each level .bas must populate:
11+
' MAP_W, MAP_H cells
12+
' MAP_TILE_W, MAP_TILE_H pixels (usually 32, 32)
13+
' MAP_BG(MAP_W * MAP_H - 1) background tile layer; 0 = blank
14+
' MAP_FG(MAP_W * MAP_H - 1) foreground tile layer; 0 = blank
15+
' MAP_COLL_COUNT how many tile ids are solid
16+
' MAP_COLL(MAP_COLL_COUNT - 1) the solid tile ids
17+
' MAP_OBJ_COUNT number of objects in the obj layer
18+
' MAP_OBJ_TYPE$(N) MAP_OBJ_KIND$(N)
19+
' MAP_OBJ_X(N) MAP_OBJ_Y(N) MAP_OBJ_W(N) MAP_OBJ_H(N) MAP_OBJ_ID(N)
20+
' (props are level-engine specific; reuse MAP_OBJ_PROP_*$ if you
21+
' want, or store in side arrays in the level file)
22+
' MAP_CAM_START_X, MAP_CAM_START_Y pixels
23+
' MAP_CAM_SCROLL_DIR$ "up"|"down"|"left"|"right"|"free"
24+
' MAP_CAM_SPEED_PX_PER_FRAME per-frame scroll @ 60fps
25+
'
26+
' Coordinate convention: top-left origin, +X right, +Y down.
27+
' Tile coords are integers (col, row); object coords are pixels.
28+
' ============================================================
29+
30+
' Tile id on the bg layer. 0 if (col,row) outside map.
31+
FUNCTION MapTileBg(col, row)
32+
IF col < 0 THEN RETURN 0
33+
IF col >= MAP_W THEN RETURN 0
34+
IF row < 0 THEN RETURN 0
35+
IF row >= MAP_H THEN RETURN 0
36+
RETURN MAP_BG(row * MAP_W + col)
37+
END FUNCTION
38+
39+
' Tile id on the fg layer. 0 if (col,row) outside map.
40+
FUNCTION MapTileFg(col, row)
41+
IF col < 0 THEN RETURN 0
42+
IF col >= MAP_W THEN RETURN 0
43+
IF row < 0 THEN RETURN 0
44+
IF row >= MAP_H THEN RETURN 0
45+
RETURN MAP_FG(row * MAP_W + col)
46+
END FUNCTION
47+
48+
' 1 if tileId appears in the solid list, else 0.
49+
FUNCTION MapTileSolid(tileId)
50+
IF tileId <= 0 THEN RETURN 0
51+
MTSI = 0
52+
WHILE MTSI < MAP_COLL_COUNT
53+
IF MAP_COLL(MTSI) = tileId THEN RETURN 1
54+
MTSI = MTSI + 1
55+
WEND
56+
RETURN 0
57+
END FUNCTION
58+
59+
' Tile id at world pixel (px, py) on the bg layer. 0 if outside map.
60+
FUNCTION MapTileAtPx(px, py)
61+
RETURN MapTileBg(px \ MAP_TILE_W, py \ MAP_TILE_H)
62+
END FUNCTION
63+
64+
FUNCTION MapTileSolidAtPx(px, py)
65+
RETURN MapTileSolid(MapTileAtPx(px, py))
66+
END FUNCTION
67+
68+
' AABB-vs-solid-bg-tiles. Returns 1 if any cell the rect overlaps is
69+
' a solid tile; useful for projectile / player collision.
70+
FUNCTION MapRectHitsSolid(rx, ry, rw, rh)
71+
HC1 = rx \ MAP_TILE_W
72+
HC2 = (rx + rw - 1) \ MAP_TILE_W
73+
HR1 = ry \ MAP_TILE_H
74+
HR2 = (ry + rh - 1) \ MAP_TILE_H
75+
HR = HR1
76+
WHILE HR <= HR2
77+
HC = HC1
78+
WHILE HC <= HC2
79+
HTID = 0
80+
IF HC >= 0 AND HC < MAP_W AND HR >= 0 AND HR < MAP_H THEN
81+
HTID = MAP_BG(HR * MAP_W + HC)
82+
END IF
83+
IF HTID > 0 THEN
84+
HSOL = 0
85+
HSI = 0
86+
WHILE HSI < MAP_COLL_COUNT
87+
IF MAP_COLL(HSI) = HTID THEN HSOL = 1
88+
HSI = HSI + 1
89+
WEND
90+
IF HSOL = 1 THEN RETURN 1
91+
END IF
92+
HC = HC + 1
93+
WEND
94+
HR = HR + 1
95+
WEND
96+
RETURN 0
97+
END FUNCTION
98+
99+
' World pixel size (helpers so callers don't recompute).
100+
FUNCTION MapPixelW()
101+
RETURN MAP_W * MAP_TILE_W
102+
END FUNCTION
103+
104+
FUNCTION MapPixelH()
105+
RETURN MAP_H * MAP_TILE_H
106+
END FUNCTION

examples/shooter/enemy-sprites.png

1.99 KB
Loading

examples/shooter/level1.bas

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
' ============================================================
2+
' shooter/level1.bas — MVP-1 vertical shooter, stage 1
3+
'
4+
' Pre-baked level data for the maplib.bas convention. Sets all
5+
' required MAP_* globals and the engine-specific MAP_OBJ_*$/numeric
6+
' arrays before the main game loop in shooter.bas runs.
7+
'
8+
' Tileset: walls.png (256x128, 8x4 grid of 32x32 tiles).
9+
' 1..32 = decoration / floor / debris.
10+
' 13, 20 = solid obstacles (block player + projectiles).
11+
'
12+
' Object list: player spawn at bottom-centre, ~10 enemies sprinkled
13+
' up the map, one boss trigger at the top.
14+
' ============================================================
15+
16+
MAP_W = 10
17+
MAP_H = 100
18+
MAP_TILE_W = 32
19+
MAP_TILE_H = 32
20+
21+
DIM MAP_BG(MAP_W * MAP_H - 1)
22+
DIM MAP_FG(MAP_W * MAP_H - 1)
23+
24+
' Floor every cell with tile 1 — gives the bg something visible.
25+
FOR L1I = 0 TO MAP_W * MAP_H - 1
26+
MAP_BG(L1I) = 1
27+
MAP_FG(L1I) = 0
28+
NEXT L1I
29+
30+
' Scatter solid obstacles (tile ids 13 and 20). Skip the bottom 4
31+
' rows so the player spawn has clear airspace to start.
32+
FOR L1ROW = 4 TO MAP_H - 4 STEP 5
33+
L1C = (L1ROW * 7) MOD MAP_W
34+
MAP_BG(L1ROW * MAP_W + L1C) = 13
35+
L1C2 = (L1C + 5) MOD MAP_W
36+
MAP_BG(L1ROW * MAP_W + L1C2) = 20
37+
NEXT L1ROW
38+
39+
' Solid tile id list.
40+
MAP_COLL_COUNT = 2
41+
DIM MAP_COLL(MAP_COLL_COUNT - 1)
42+
MAP_COLL(0) = 13
43+
MAP_COLL(1) = 20
44+
45+
' Object list: index 0 = player spawn, then enemies, then boss trigger.
46+
' KIND: "player" | "ship" | "tank" | "boss"
47+
MAP_OBJ_COUNT = 14
48+
DIM MAP_OBJ_TYPE$(MAP_OBJ_COUNT - 1)
49+
DIM MAP_OBJ_KIND$(MAP_OBJ_COUNT - 1)
50+
DIM MAP_OBJ_X(MAP_OBJ_COUNT - 1)
51+
DIM MAP_OBJ_Y(MAP_OBJ_COUNT - 1)
52+
DIM MAP_OBJ_W(MAP_OBJ_COUNT - 1)
53+
DIM MAP_OBJ_H(MAP_OBJ_COUNT - 1)
54+
DIM MAP_OBJ_ID(MAP_OBJ_COUNT - 1)
55+
56+
' Player spawn (bottom-centre of map; 32px above world bottom).
57+
MAP_OBJ_TYPE$(0) = "spawn" : MAP_OBJ_KIND$(0) = "player"
58+
MAP_OBJ_X(0) = 144 : MAP_OBJ_Y(0) = MAP_H * MAP_TILE_H - 64
59+
MAP_OBJ_W(0) = 32 : MAP_OBJ_H(0) = 32 : MAP_OBJ_ID(0) = 1
60+
61+
' Enemies — Y values count up the map (camera starts at bottom and
62+
' scrolls up, so smaller Y = encountered later).
63+
MAP_OBJ_TYPE$(1) = "enemy" : MAP_OBJ_KIND$(1) = "ship" : MAP_OBJ_X(1) = 32 : MAP_OBJ_Y(1) = 2700 : MAP_OBJ_W(1) = 32 : MAP_OBJ_H(1) = 32 : MAP_OBJ_ID(1) = 2
64+
MAP_OBJ_TYPE$(2) = "enemy" : MAP_OBJ_KIND$(2) = "ship" : MAP_OBJ_X(2) = 256 : MAP_OBJ_Y(2) = 2500 : MAP_OBJ_W(2) = 32 : MAP_OBJ_H(2) = 32 : MAP_OBJ_ID(2) = 3
65+
MAP_OBJ_TYPE$(3) = "enemy" : MAP_OBJ_KIND$(3) = "ship" : MAP_OBJ_X(3) = 96 : MAP_OBJ_Y(3) = 2300 : MAP_OBJ_W(3) = 32 : MAP_OBJ_H(3) = 32 : MAP_OBJ_ID(3) = 4
66+
MAP_OBJ_TYPE$(4) = "enemy" : MAP_OBJ_KIND$(4) = "tank" : MAP_OBJ_X(4) = 192 : MAP_OBJ_Y(4) = 2100 : MAP_OBJ_W(4) = 32 : MAP_OBJ_H(4) = 32 : MAP_OBJ_ID(4) = 5
67+
MAP_OBJ_TYPE$(5) = "enemy" : MAP_OBJ_KIND$(5) = "ship" : MAP_OBJ_X(5) = 32 : MAP_OBJ_Y(5) = 1900 : MAP_OBJ_W(5) = 32 : MAP_OBJ_H(5) = 32 : MAP_OBJ_ID(5) = 6
68+
MAP_OBJ_TYPE$(6) = "enemy" : MAP_OBJ_KIND$(6) = "ship" : MAP_OBJ_X(6) = 288 : MAP_OBJ_Y(6) = 1700 : MAP_OBJ_W(6) = 32 : MAP_OBJ_H(6) = 32 : MAP_OBJ_ID(6) = 7
69+
MAP_OBJ_TYPE$(7) = "enemy" : MAP_OBJ_KIND$(7) = "ship" : MAP_OBJ_X(7) = 160 : MAP_OBJ_Y(7) = 1500 : MAP_OBJ_W(7) = 32 : MAP_OBJ_H(7) = 32 : MAP_OBJ_ID(7) = 8
70+
MAP_OBJ_TYPE$(8) = "enemy" : MAP_OBJ_KIND$(8) = "tank" : MAP_OBJ_X(8) = 64 : MAP_OBJ_Y(8) = 1300 : MAP_OBJ_W(8) = 32 : MAP_OBJ_H(8) = 32 : MAP_OBJ_ID(8) = 9
71+
MAP_OBJ_TYPE$(9) = "enemy" : MAP_OBJ_KIND$(9) = "ship" : MAP_OBJ_X(9) = 224 : MAP_OBJ_Y(9) = 1100 : MAP_OBJ_W(9) = 32 : MAP_OBJ_H(9) = 32 : MAP_OBJ_ID(9) = 10
72+
MAP_OBJ_TYPE$(10) = "enemy" : MAP_OBJ_KIND$(10) = "ship" : MAP_OBJ_X(10) = 128 : MAP_OBJ_Y(10) = 900 : MAP_OBJ_W(10) = 32 : MAP_OBJ_H(10) = 32 : MAP_OBJ_ID(10) = 11
73+
MAP_OBJ_TYPE$(11) = "enemy" : MAP_OBJ_KIND$(11) = "ship" : MAP_OBJ_X(11) = 32 : MAP_OBJ_Y(11) = 700 : MAP_OBJ_W(11) = 32 : MAP_OBJ_H(11) = 32 : MAP_OBJ_ID(11) = 12
74+
MAP_OBJ_TYPE$(12) = "enemy" : MAP_OBJ_KIND$(12) = "ship" : MAP_OBJ_X(12) = 288 : MAP_OBJ_Y(12) = 500 : MAP_OBJ_W(12) = 32 : MAP_OBJ_H(12) = 32 : MAP_OBJ_ID(12) = 13
75+
76+
' Boss trigger: a marker the engine notices when the camera nears Y=64.
77+
MAP_OBJ_TYPE$(13) = "trigger" : MAP_OBJ_KIND$(13) = "boss" : MAP_OBJ_X(13) = 0 : MAP_OBJ_Y(13) = 64 : MAP_OBJ_W(13) = 320 : MAP_OBJ_H(13) = 8 : MAP_OBJ_ID(13) = 14
78+
79+
' Camera setup — vertical shooter. Start at the bottom of the world,
80+
' scroll up by 1 px per frame (matches gfx_world_demo pacing).
81+
MAP_CAM_START_X = 0
82+
MAP_CAM_START_Y = MAP_H * MAP_TILE_H - 200
83+
MAP_CAM_SCROLL_DIR$ = "up"
84+
MAP_CAM_SPEED_PX_PER_FRAME = 1

examples/shooter/manifest.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
enemy-sprites.png 128x32 cellw=32 cellh=32 enemy ship, enemy with jet flame, hit, exploded
2+
missiles.png 128x32 cellw=16 cellh=32 various enemy and player missiles/lasers
3+
player.png 32x32 cellw=32 cellh=32 player space ship
4+
tank.png 32x32 cellw=32 cellh=32 enemy tank
5+
walls.png 256x128 cellw=32 cellh=32 map tiles various
6+
7+
All tiles are decorative apart from tile 20 and tile 13 for now which we can call obstacles until I create more
8+
10x100 map initially
9+
screen 320x200 but with a HUD either at top or bottom of screen
10+
pacing of gfx_world_demo.bas is fine (shooter mvp does not require horizontal scrolling)
11+
12+
13+
14+

examples/shooter/map.bin

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
 
2+
3+

4+

5+

6+

7+

8+


examples/shooter/missiles.png

732 Bytes
Loading

examples/shooter/player.png

444 Bytes
Loading

0 commit comments

Comments
 (0)