2 day 4
Bill Niblock edited this page 2026-03-18 12:55:33 -04:00

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.