Table of Contents
Day 4: Arena
2026-03-17
My primary objective for today was to get some kind of arena setup. I'm happy to say I did that.
Arena: Collision
I had mentioned the past few days I wanted a viewpoint that stopped at an edge, but the player could continue a bit further. I've implement that, probably very standard approach, rather straight-forwardly.
(fn canmove [dx dy]
(var (new_camera_x new_camera_y new_player_x new_player_y)
(values
(- state.camera.position.x dx)
(- state.camera.position.y dy)
(+ state.player.position.x dx)
(+ state.player.position.y dy)))
(when (not (collide new_player_x new_player_y))
(if (and (< arena.l (- new_player_x boundry.w)) (> arena.r (+ new_player_x boundry.w)))
(do
(set state.camera.position.x new_camera_x)
(set state.player.position.x new_player_x))
(if (and (< arena.l new_player_x) (> arena.r (+ new_player_x player.w)))
(set state.player.position.x new_player_x)))
(if (and (< arena.t (- new_player_y boundry.h)) (> arena.b (+ new_player_y boundry.h)))
(do
(set state.camera.position.y new_camera_y)
(set state.player.position.y new_player_y))
(if (and (< arena.t new_player_y) (> arena.b (+ new_player_y player.h)))
(set state.player.position.y new_player_y)))))
Here, I establish a new set of positions, one for the player, and one for the camera, which in this case is actually the values used for moving the world; they're used as translation values during the draw phase. Then, I first check if the player will collide with anything. I have a table of rectangles which represent obstacles, and the player's new position is checked against that:
(fn collide [px py]
(var break false)
(each [k v (ipairs arena.c) &until (= break true)]
(when (and (< px (+ (. v 1) (. v 3)))
(< (. v 1) (+ px player.w))
(< py (+ (. v 2) (. v 4)))
(< (. v 2) (+ py player.h)))
(set break true)))
break)
If the player won't collide, I check if the "boundry" box around the player will stay within the arena boundaries:
; Check X-axis
(if (and (< arena.l (- new_player_x boundry.w)) (> arena.r (+ new_player_x boundry.w)))
; Check Y-axis
(if (and (< arena.t (- new_player_y boundry.h)) (> arena.b (+ new_player_y boundry.h)))
If that's true, then I move the camera and the player (notice, the player and camera move in different directions!). If that's not true, I then check if the player will stay within the arena boundaries in a similar fashion, and if that's true, I move only the player.
As with everything I've made during this jam, I'm sure there is better code for this problem. Maybe I'll figure it out someday!
Arena: Decorations
This update also adds some charm to the arena. And by "charm" I mean "my sad attempts at art." A series of simple tiles were created for the walls and corners of the arena, as well as the floor. I randomly pick among the options each time the arena is generated, which is currently each time the game is started.
One important thing I finally internalized is that most of the drawing commands work around the top-left corner. For example, when drawing an image that is rotated, it will (by default) be rotated around the top-left corner. It's possible to give an origin offset in the command, as I do for the floor tiling.
Wall Example
(local arena-walls [
(love.image.newImageData "assets/tiles/arena_wall/wall_1.png")
(love.image.newImageData "assets/tiles/arena_wall/wall_2.png")
(love.image.newImageData "assets/tiles/arena_wall/wall_3.png")])
; Left Wall
(for [i 1 10]
(love.graphics.draw (love.graphics.newImage (. arena-walls (love.math.random 3))) 50 (+ 50 (* (- i 1) 100)) 1.571))
Floor Example
(local arena-floors [
(love.image.newImageData "assets/tiles/arena_floor/floor_1.png")
(love.image.newImageData "assets/tiles/arena_floor/floor_2.png")
(love.image.newImageData "assets/tiles/arena_floor/floor_3.png")])
; Arena Floor
(for [i 1 10]
(for [j 1 10]
(love.graphics.draw
; Choose randomly from the 3 loaded tiles
(love.graphics.newImage (. arena-floors (love.math.random 3))) ;
; X and Y coordinates; 100x100 tiles, 10 times
(+ 100 (* (- i 1) 100))
(+ 100 (* (- j 1) 100))
; Rotate randomly 0/360, 90, or 80
(* (/ (love.math.random 4) 2) 3.141)
; Scale Factor (these must be included to
set the next values). Defaults; 1.
1
1
; Origin Offset, from top-left 0:0. The
tiles are 100x100, thus 50.
50
50)))
Arena: Finishing Touches
At this point, all that remains for the arena is to fill it with more collision objects (ideally based on the tile), and to add the spawn/pick-up locations for the various runes. Speaking of...
Spell Casting
The next objective is to build some basic spell casting. While my design docs plan for 4 runes, I'll work with just one and build up from there. Even before any runes, I need some basics: health, essence threshold, and a basic attack.