Levels from files
In Tile maps the program built its level square by square. A real game
draws its levels in a map editor and saves each one as a file, and the
program only has to load it. Your game’s folder has one: level.map.json,
the courtyard from my Adventures of Sir Blocksley demo, drawn with the
tiles in greyscale.png.
One line
A map has layers of its own, and CREATE MAP puts them on the screen’s.
It takes the file, which of the map’s layers to show, and with
!! layer_to the screen layers they go on, one each. A map’s layers count
up from 0, the lowest. This map has two, and this puts them on screen
layers 1 and 2, which leaves screen layer 0 under them for anything else.
The words go on layer 3, over the top, as in Tile maps:
SCREEN 320, 240
CREATE MAP "level.map.json", 0...1 !! layer_to(1...2)
SET LAYER 3
PRINT "the level is", mapwidth(1), "tiles across and", mapheight(1), "down"
the level is 20 tiles across and 23 down
The file says everything else: which tile set to use and how big a tile is,
so there’s no LOADIMG and no TILESIZE. mapwidth and mapheight say
how big a map is, in tiles, given the screen layer it’s on. This one is
bigger than the screen, so you’d SET SCROLL down it the way you did in
Tile maps.
One map layer goes on one screen layer: CREATE MAP "level.map.json", 1 !! layer_to(3) puts the map’s layer 1 on screen layer 3. Leave the map’s layers
out and I start from 0, as many as layer_to has room for:
CREATE MAP "level.map.json" !! layer_to(1...2) is the same as the line
above.
From 1.x: That’s
CREATE MAP("level.map.json", 0 TO 1) AS LAYER 1 TO 2.
What’s in the file
The file is JSON, and loadjson reads any JSON file into maps and lists.
Here’s the level’s:
SCREEN 320, 240
level = loadjson("level.map.json")
PRINT "tiles from", level.tileset, "and", level.tileWidth, "pixels square"
PRINT level.mapWidth, "by", level.mapHeight
FOR layer = level.layers
PRINT "a", layer.layerType, "layer called", layer.name
END FOR
tiles from greyscale.png and 32 pixels square
20 by 23
a tile layer called Layer 1
a tile layer called Layer 2
a metadata layer called Metadata
Two layers of tiles, and one more of metadata, which is the interesting
one. It isn’t drawn. Each square of it holds a bit of text that says what’s
there: "P" where the player starts, "C" for a coin, "X" for the exit,
whatever your game needs. The map editor lets you write on it, and the game
reads it back.
Crash-tan’s Tip:
loadjsonis for any data your game keeps in files: a list of levels, the enemies in each, what a shop sells. Build the file by hand or with a tool, and read it with one line.
Starting from the file
A TILEMAP can start from a file too. BASEMAP says which, and then the
map’s size, tile set and layers all come from it. Each layer that says
!! into(grid) fills that grid with the file’s tiles, so your program can
look at the level square by square:
SCREEN 320, 240
ground = (23, 20):INTEGER
walls = (23, 20):INTEGER
TILEMAP "level"
BASEMAP "level.map.json"
TILES 0 !! name("ground") into(ground)
TILES 1 !! name("walls") into(walls)
END TILEMAP
CREATE MAP "level", 0...1 !! layer_to(1...2)
count = 0
FOR row = 0 ... 22
FOR column = 0 ... 19
IF walls(row, column) <> 0 THEN count = count + 1
NEXT
NEXT
SET LAYER 3
PRINT count, "squares have a wall on them"
PRINT "the top left is", ground(0, 0), "in the grid and", maptile(0, 0, 1), "on the map"
121 squares have a wall on them
the top left is 10 in the grid and 9 on the map
The grids are 23 rows of 20, the size of the map. A TILEMAP’s layers go on
the screen the same way a file’s do, and when it has more than one TILES
layer, each needs a name, which picks it too: CREATE MAP "level", "ground" !! layer_to(1). maptile(x, y, 1) asks about the map on screen layer 1,
where its layer 0 went. The grid counts tiles from 1, with 0 for none, the
same as the grids in Tile maps, while maptile counts them from 0.
Markers
The courtyard’s metadata layer is already written on. "P" is where Sir
Blocksley starts, "K" is the king, "B" is someone standing about, "*"
runs along every wall, and a number marks each way out.
mapmeta(x, y) reads the marker on one square of the map on the layer
you’re drawing on, so SET LAYER one the map is on, and ask before going up
to print. mapdata goes through
a whole region and gives every square back, each with its x, its y and
its meta, so finding the start and the people is one loop:
SCREEN 320, 240
CREATE MAP "level.map.json", 0...1 !! layer_to(1...2)
SET LAYER 1
start = mapmeta(9, 8)
cells = mapdata((0, 0) -> (19, 22))
SET LAYER 3
PRINT "on (9, 8):", start
FOR cell = cells
IF cell.meta = "P" THEN PRINT "start at", cell.x, cell.y
IF cell.meta = "K" THEN PRINT "the king at", cell.x, cell.y
IF cell.meta = "B" THEN PRINT "someone at", cell.x, cell.y
END FOR
on (9, 8): P
someone at 3 4
the king at 9 5
someone at 15 6
start at 9 8
Your program can put markers down too. A META layer that says
!! from(grid) is drawn from a grid of text you keep, the same way TILES
is drawn from a grid of numbers:
SCREEN 320, 240
markers = (23, 20):STRING
markers(8, 9) = "P"
markers(7, 3) = "C"
markers(3, 14) = "C"
TILEMAP "level"
BASEMAP "level.map.json"
META "markers" !! from(markers)
END TILEMAP
CREATE MAP "level", 0...1 !! layer_to(1...2)
SET LAYER 1
cells = mapdata((0, 0) -> (19, 22))
start = mapmeta(9, 8)
gone = mapmeta(0, 1)
SET LAYER 3
FOR cell = cells
IF cell.meta = "C" THEN PRINT "a coin at", cell.x, cell.y
END FOR
PRINT "on (9, 8):", start
PRINT "on (0, 1):", gone
a coin at 14 3
a coin at 3 7
on (9, 8): P
on (0, 1):
Your grid takes the place of the file’s markers: (0, 1) had a "3" on it,
and now it has nothing. That’s why the grid puts "P" back.
Change the grid and UPDATE MAP shows the change, for markers as for tiles:
when a coin’s picked up, rub out its "C".
Crash-tan’s Tip: Most squares have no marker, so look for the ones you want.
mapdatagives back every square in the region, marker or not.
Which square is that?
A sprite’s place, or the mouse’s, is in pixels, and the map is in squares. Four functions turn one into the other, and the difference between them is scrolling.
maptilex(x)andmaptiley(y)are for a place on the screen, likemousexandmousey. They add on how far the map is scrolled.mapworldtilex(x)andmapworldtiley(y)are for a place in the level, like a sprite’sxandy. They don’t.
SCREEN 320, 240
CREATE MAP "level.map.json", 0...1 !! layer_to(1...2)
FOR layer = 1 ... 2
SET LAYER layer
SET SCROLL 0, 160
NEXT
SET LAYER 1
on_screen = maptiley(20)
in_level = mapworldtiley(20)
SET LAYER 3
PRINT "20 pixels down the screen is row", on_screen
PRINT "20 pixels down the level is row", in_level
20 pixels down the screen is row 5
20 pixels down the level is row 0
With the map scrolled 160 pixels, five rows of 32, the top of the screen shows row 5 of the level.
A walk for coins
Here’s all of it together: the level from the file, the start and the coins
from the markers, and the hero walking about picking them up. When the hero
stands on a square marked "C", the marker’s rubbed out and the coin goes.
SCREEN 640, 480
LOADIMG "hero.png", "hero"
MEMORYBUFFER "coin", 10, 10
CIRCLE (5, 5), 4, 14, 14
END MEMORYBUFFER
// The start and the coins, as the map editor would mark them.
markers = (23, 20):STRING
markers(8, 9) = "P"
FOR spot = [(3, 7), (14, 3), (9, 9), (15, 8), (3, 2)]
markers(spot.y, spot.x) = "C"
END FOR
TILEMAP "level"
BASEMAP "level.map.json"
META "markers" !! from(markers)
END TILEMAP
CREATE MAP "level", 0...1 !! layer_to(1...2)
SET LAYER 1
// A coin sprite on every "C". coin_at remembers which sprite is where.
coin_at = (23, 20):INTEGER
coins = 0
FOR cell = mapdata((0, 0) -> (19, 14))
IF cell.meta = "C"
id = reserve_sprite()
SET SPRITE id !! image("coin") x(cell.x * 32 + 11) y(cell.y * 32 + 11)
!! layer(2)
SHOW SPRITE id
coin_at(cell.y, cell.x) = id
coins = coins + 1
ELSE IF cell.meta = "P"
x = cell.x * 32 + 16
y = cell.y * 32 + 16
END IF
END FOR
hero = reserve_sprite()
SET SPRITE hero !! image("hero") x(x) y(y)
!! origin((0.5, 0.5)) layer(2)
SHOW SPRITE hero
found = 0
DO WHILE found < coins
IF COOLDOWN(1)
IF keydown("Left") AND x > 8 THEN x = x - 2
IF keydown("Right") AND x < 632 THEN x = x + 2
IF keydown("Up") AND y > 8 THEN y = y - 2
IF keydown("Down") AND y < 472 THEN y = y + 2
SET SPRITE hero !! x(x) y(y)
column = mapworldtilex(x)
row = mapworldtiley(y)
IF mapmeta(column, row) = "C"
markers(row, column) = ""
UPDATE MAP "level"
DISABLE SPRITE coin_at(row, column)
found = found + 1
END IF
END IF
LOOP
SOUND 660, 0.3
Click the screen and walk with the arrow keys. The level isn’t scrolled, so
mapworldtilex and maptilex would agree here; use the world ones for a
sprite anyway, and the game keeps working once the camera starts to move.