Tilemaps are grids of integers. Each integer is a tile ID, 0 for empty, anything else for solid or typed terrain. Store the map in a script-lifetime fixed array.
Map Setup
# 20 x 15 grid, 300 tiles total
@map_w :: script = 20
@map_h :: script = 15
@tile_size :: script = 32
@tiles :: script = array_fixed(300, 0)Reading and Writing Tiles
defp tile_at(tx :: int, ty :: int) :: int do
if tx < 0 do return 1 end
if ty < 0 do return 1 end
if tx >= @map_w do return 1 end
if ty >= @map_h do return 1 end
return @tiles.get(ty * @map_w + tx)
end
defp set_tile(tx :: int, ty :: int, id :: int) do
if tx >= 0 and ty >= 0 and tx < @map_w and ty < @map_h do
@tiles.set(ty * @map_w + tx, id)
end
endOut-of-bounds reads return 1 (solid), so the map is implicitly walled.
World-to-Tile Coordinates
defp world_to_tile_x(wx :: float) :: int do
return to_int(wx) / @tile_size
end
defp world_to_tile_y(wy :: float) :: int do
return to_int(wy) / @tile_size
endRendering the Map
Only draw tiles within the camera viewport:
defp draw_map(cam_x :: float, cam_y :: float) do
ts :: frame = to_float(@tile_size)
start_tx :: frame = to_int(cam_x) / @tile_size
start_ty :: frame = to_int(cam_y) / @tile_size
sw :: frame = to_float(screen_width())
sh :: frame = to_float(screen_height())
tiles_x :: frame = to_int(sw / ts) + 2
tiles_y :: frame = to_int(sh / ts) + 2
ty = 0
while ty < tiles_y do
tx = 0
while tx < tiles_x do
wx :: frame = to_float((start_tx + tx) * @tile_size)
wy :: frame = to_float((start_ty + ty) * @tile_size)
id :: frame = tile_at(start_tx + tx, start_ty + ty)
if id == 1 do
draw_rectangle(wx - cam_x, wy - cam_y, ts, ts, :dark_gray)
end
tx = tx + 1
end
ty = ty + 1
end
endTile Collision
Check the four corners of an AABB against the tile grid:
defp tile_solid(wx :: float, wy :: float) :: int do
tx :: frame = world_to_tile_x(wx)
ty :: frame = world_to_tile_y(wy)
return tile_at(tx, ty)
end
defp aabb_in_solid(x :: float, y :: float, w :: float, h :: float) :: int do
# check all four corners
if tile_solid(x, y ) == 1 do return 1 end
if tile_solid(x + w - 1.0, y ) == 1 do return 1 end
if tile_solid(x, y + h - 1.0) == 1 do return 1 end
if tile_solid(x + w - 1.0, y + h - 1.0) == 1 do return 1 end
return 0
endSwept Movement
Move in X and Y separately so the player slides along walls:
defp move_and_slide(x :: float, y :: float,
vx :: float, vy :: float,
w :: float, h :: float,
dt :: float) :: float do
# try X
nx :: frame = x + vx * dt
if aabb_in_solid(nx, y, w, h) == 1 do
nx = x
end
# try Y
ny :: frame = y + vy * dt
if aabb_in_solid(nx, ny, w, h) == 1 do
ny = y
end
# return as encoded pair, caller unpacks
return nx
endFor a clean API, split into separate move_x / move_y helpers so each can return a single float without needing tuples.
Loading a Map from Data
Hard-code a small level as a flat array of ints:
defp load_level() do
data :: frame = array_fixed(300, 0)
# perimeter walls
i = 0
while i < @map_w do
set_tile(i, 0, 1)
set_tile(i, @map_h - 1, 1)
i = i + 1
end
i = 0
while i < @map_h do
set_tile(0, i, 1)
set_tile(@map_w - 1, i, 1)
i = i + 1
end
# platforms
set_tile(5, 10, 1)
set_tile(6, 10, 1)
set_tile(7, 10, 1)
set_tile(12, 7, 1)
set_tile(13, 7, 1)
end