Ludum Dare 53 Postmortem

Another Ludum Dare has come and gone, which means its time to write a postmortem about how it went this time around! This time I made a game called "Messenger Boy", its a short top down story about passing letters between 2 scientists on the verge of a great discovery. You can play and review it here! There are a couple disjointed things I've been thinking about, so get ready for some blogging whiplash. Before that though, here's some quick stuff.

Lets Make a Textbox

Before the jam I was working on another tic80 game which required a simple textbox, and with Messenger Boy I wanted to expand it a bit. The goal was to create a component that could be dropped in any project. I started by converting this pico8 cart to Janet pretty much 1 to 1, but built from that. The biggest change is that I did not want to have to hardcode in newlines in my script. So I wrote a function that split text into an array based on the textboxs line length. It looks like this

(def LINE_LENGTH (- 240 30)) # 240 is tic80 screen width

(defn split-into-lines [text fixed scale smallfont]
  (var  on-line-2? false)
  (var lines @["" ""])
  (var final @[])

  (each word (string/split " " text)
	(let [potential-l0 (string/trim (string (lines 0) " " word))
      	potential-l1 (string/trim (string (lines 1) " " word))

      	width-potential-l0 (cam/print potential-l0 0 -100 0 fixed scale smallfont)
      	width-potential-l1 (cam/print potential-l1 0 -100 0 fixed scale smallfont)]
    	# space on first line
    	(and (<= width-potential-l0 LINE_LENGTH)
         	(not on-line-2?))
    	(put lines 0 potential-l0)

    	# space on second line
    	(<= width-potential-l1 LINE_LENGTH)
    	(do (set on-line-2? true)
        	(put lines 1 potential-l1))

    	# no space on either line, push them to final
    	(do (array/push final (string/format "%s\n%s" ;lines))
        	(set lines @[word ""])
        	(set on-line-2? false)))))

  # need # one final push
  (array/push final (string/format "%s\n%s" ;lines)))

It ... works. But I honestly think theres a cleaner and more "lispy" way of doing this. Ah well

Here's the rest of the textbox code. Without getting into too much detail, the idea is to treat the "text" field like a stack, displaying the top of the queue and popping as you progress. The actual text displayed is truncated by a "char" index which creates the scrolling

(defn textbox-update [self dt]
  (when (not (:dead? self))
  	# if the message has not been processed until it's last character:
  	(< (self :char) (length (array/peek (self :text))))
  	(do (+= (self :cur) (self :speed))
      	(when (> (self :cur) 0.9)
        	(++ (self :char))
        	(put self :cur 0)

        	# play voice sfx if given
        	(when-let [voice (self :voice)]
          	(tic80/sfx voice "G-6" -1 2))))

  	#  else if already on the last message character and we can progress
  	(:progress? self)
    	(array/pop (self :text))
    	(put self :cur 0)
    	(put self :char 0)))))

(defn textbox-draw [self]
  (when-let [has-text (not (:dead? self))
         	{:pos pos :box box :colors colors
          	:text text :char char} self]

	# background
	(tic80/rect (pos :x) (pos :y)
            	(box :width) (box :height)
            	(colors :background))

	# border
	(tic80/rectb (pos :x) (pos :y)
             	(box :width) (box :height)
             	(colors :border))

	# actual text
	(tic80/print (string/slice (array/peek text) 0 char)
             	(+ (pos :x) 4)
                 	(+ (pos :y) 4)
                	(colors :text))))

(defn create-textbox [long-text &named voice progress-fn speed fixed scale smallfont]
  (default voice nil)
  (default progress-fn (fn [self] (tic80/btnp A)))
  (default speed 0.9)
  (default fixed false)
  (default scale 1)
  (default smallfont false)

  @{:pos {:x 10 :y 110}
	:box {:width (+ LINE_LENGTH 5) :height 21}
	:colors {:background 0 :border 4 :text 5}

	# split into 2 line chunks, then treat the text like a stack
	# ie index 0 should be displayed
	:text (reverse (split-into-lines long-text fixed scale smallfont))

	:voice voice
	:cur 0 # buffer used to progressively show characters on the text box.
	:char 0 # current character to be drawn on the text box.
	:speed speed
	:dead? (fn [{:text text}] (empty? text))
	:progress? progress-fn
	:update textbox-update
	:draw textbox-draw})

Eventually I want to add support for text formatting and fancy effects.. Im sure that will require a complete rewrite.

Receiving Feedback

Feedback of any sort makes me uncomfortable. Ive found myself avoiding watching people play my games, or even not letting them play the game at all. I think a big part of it is just being self conscious, but I dont want that to stop me!

I think like any muscle, interacting with feedback is something I need to work at. My plan is to try really hard to genuinely read all the comments on my game. I also want to go out of my way to watch others play Messenger Boy. I wont take the controller away, or explain mechanics, or complain about bugs as excuses. Just let them play, and try and learn what works and what doesnt.

Post Jam Changes

Speaking of feedback, Ive already got enough to have some ideas on how to improve Messenger Boy! You cant make changes during the review part of Ludum Dare, so I'll wait to release these changes until after thats over. Both of my changes have to do with the 2 main mechanics of the game, jumping and sprinting.

First off is jumping. In general people seemed to really like the feel of how this worked.. with one exception. It is extremely easy to get stuck in the fences youre jumping over! The technical reason for this is that collision in Messenger Boy happens a priori, which means we calculate where the character is going and prevent it from moving into other solid bodies. If you end up inside another solid body, theres no logic to move you out! I could add this logic, but an easier solution I think will be to change the fences collision from a box to a single line. If you by some chance end up on the line Ill just shift the character off by one.

Next up is sprinting. People seemed to have a hard time with this. I think one big problem with it is that theres no way to stop! My current plan is to make it so holding the A button causes you to run, otherwise youll walk like normal. I will probably adjust the friction a bit to improve the "feel" of stuff.

Another thing I just plain forgot about was adding particles. They really add a lot of "juice", and I have some I can easily add. Im thinking stuff like dust particles and leaves blowing in the wind.

Other Things Which Dont Warrant a Header