1 day 2
Bill Niblock edited this page 2026-03-16 09:38:56 -04:00

Day 2: Some Movement

2026-03-15

After some more thought, I've decided to try making this a top-down, twin-stick "shooter." The player will run around an arena, collecting runes and sigils, with which they will cast spells. I haven't ironed out the specifics of spell-casting yet, but I know it will depend on the order of runes/sigils, combined with the number used, combined with a general magic resource, such as mana. When a spell is countered, the counter-speller absorbs the mana intended for the spell. If the player ever has more mana than their capacity, they explode. There may be some lee-way on that, but for now that's the plan.

Setup

I didn't mention it on Day 1, and I want to have some documentation for how I set things up and got started developing.

First off, I'm using the min-love2d-fennel scaffold to get started. I clone that locally, and run the helper script provided: ./min-love2d-fennel/.duplicate/new-game.sh lovejam-2026. I also create the repository on my Forge. Then, in the project directory I git init, and setup a basic .gitignore, namely to ignore the wiki/ directory. I think clone the repository wiki to wiki/. The repository is now setup and configured. Add all the scaffold code, commit the first commit, and push everything to my Forge.

In order to run the game, I need both Fennel and Love. Fennel actually comes packaged in the scaffold code, so I can mark that complete. On Linux (specifically Arch) Love can be installed from the AUR for a nightly version (love-git), or provided from the official source as an AppImage. I installed and compiled the AUR version, which provides a love-git command, and I also pulled the AppImage. I decided to go with the AppImage for now, since it's an offical "numbered" release, 11.5. In order to accommodate the scaffold code, I need a love command, so I move the AppImage into a directory on my $PATH (./local/bin/ in this case), and rename it to "love". I also make sure it has executable permissions.

At this point, in the game directory, I can make run, and the small little demo runs as expected.

Development: Movement

A twin-stick has two components to movement: one for moving the character, and one for aiming the character. For now, using keyboard and mouse, that's keyboard for moving, and mouse for aiming. Setting this up initially was very straight forward: I added some additional conditionals to the :keyreleased function, checking for E/S/D/F (personal preference), and moving a small icon up/left/down/right. To allow for a button being held down, I had to add (love.keyboard.setKeyRepeat true) to wrap.fnl. This helped, but there was a stutter to get started, and still a stuttered-start if I switched to the :keypressed callback. Instead, I opted to make movement a toggle: when a movement key is pressed, enable movement in that direction until that key is released.

  :keypressed 
    (fn keypressed [key code isrepeat]
      ...
      (when (= key "e")
        (set state.move-up true))
      (when (= key "d")
        (set state.move-down true))
      (when (= key "s")
        (set state.move-right true))
      (when (= key "f")
        (set state.move-left true))
      ...
    )

  :keyreleased 
    (fn keyreleased [key code set-mode]
      ...
      (when (= key "e")
        (set state.move-up false))
      (when (= key "d")
        (set state.move-down false))
      (when (= key "s")
        (set state.move-right false))
      (when (= key "f")
        (set state.move-left false))
      ...
    )

The actual movement is handled by different direction functions which get called during the update cycle:

(fn move-left []
  (set state.player.position.x (+ state.player.position.x state.player.speed)))
(fn move-right []
  (set state.player.position.x (- state.player.position.x state.player.speed)))
(fn move-up []
  (set state.player.position.y (- state.player.position.y state.player.speed)))
(fn move-down []
  (set state.player.position.y (+ state.player.position.y state.player.speed)))

:update
  (fn update [dt]
    (when (= state.move-left true) (move-left))
    (when (= state.move-right true) (move-right))
    (when (= state.move-up true) (move-up))
    (when (= state.move-down true) (move-down)))

I know all of this can be refactored to something much cleaner, but that's a project for when the game is done! For now, this gives me the option of changing the player's speed dynamically, and allows for smooth circular movement.

Currently the mouse is just tracking the pointer. I think changing it to aiming from the player's position is an option, but I can also see leaving it completely free.

Arena

Next things I want to tackle are implementing a bounding-box for movement, and establishing a basic arena to move around in. There are great examples and samples from both Love and DragonRuby that I can use to implement this.