Or, The Toughest Lua Code Ever Written
“The toughest Lua code ever written? REALLY?”
Well it was the toughest code ever written... by me (Dave).
Eating out of a fridge is easy, right?
It's something we all do, every day, or late at night,
it's something we all look forward to.
If you're like me, you just stand there with the fridge door open for long periods,
wondering what to do next. Wondering which item is missing from within.
Well you, my friend, have a lot in common with the Sheeple.
Insulted? Don't be, because this author does too.
We're all Sheeple at some point in our lives, no shame in that.
Yet, this easily digestable concept was probably the hardest thing I've ever done.
It took me well over a month to figure out how to eat out of the fridge with two hands.
Wait... I didn't take a month to eat out of the fridge!
That would be moldy! Yuk!!
No - it was the hardest thing I've ever coded.
Code. Kinda sounds like mold. Moving on...
Positioning and animating all of these sprites was a relatively complicated task, but we did generalize it to two NPCs, meaning adding further NPC skins will be easy. Ihor Motuz, the pixel artist responsible for these adorable Sheeple, drew the sprites in Photoshop and delivered them in PNGs. GIMP, ImageMagick, and TexturePacker were used to manage and arrange all of these assets.
The fridge is full of fruits (sprites) that can be of any kind - we're only working with apples and oranges for now but they could be of any object type. The fridge holds the list of fruits inside and arranges the drawing order. The NPC requests a fruit from the fridge and then the sprite is removed from the fridge list and animated towards the appropriate NPC hand (left or right). Once the fruit has synced up to the hand, it is positioned to the hand offset coordinates, per frame, and finally when the fruit reaches the mouth eat frame, it disappears from the game world. Crumb rectangles are then animated falling from the mouth. Counters are kept and updated such as number of each kind of fruit in the fridge, and how many fruits the NPC has left to eat in order to convert. Dialog bubbles are popped up (and removed) if a new kind of fruit is delivered from the fridge. Multiple NPCs can be awoken from the same fridge, but due to the compounded complexity we make them wait their turn. If there are multiple NPCs bound to the same fridge (there may be 0..F fridges per level) then the amount of fruits needed to convert are divided by the number of NPCs bound to the fridge.
Our trailer level-win (starting at 1:20) culminates in a Sheeple eating from the fridge:
There is a barrel of tasks that had to be taken care of before getting to this point. Our development style tends to build components in the same order they'll be experienced. This approach keeps a carrot dangling in front of us. We could write another article on this stuff if we wanted. We don't want to.
- fridge: design, animations, open/close, matrix pressure point, inventory gui
- player interaction: throwing fruits into the fridge
Fridge Action Possibility Checklist:
Each of these are possibilities or steps in the Sheeple's interaction with the Fridge:
-- deciding to look in the fridge
-- opening, deciding if likes the contents
-- eat package contents (yes)
-- tossing away fridge contents w/physics (yes)
-- tossing away package, chips explode (yes)
-- dialog bubbles (yes)
-- eating hand-by-hand, each item from fridge (yes, hard!)
-- multiple sheeple gfx (guy/girl)
-- conversion, or no conversion? needs more food to convert?
-- multiple conversions from same fridge? (yes)
-- multiple sheeple from each fridge? (no - wait turns)
-- patchman throws into fridge while eating (yes)
-- sheeple is pushed away while eating (yes)
-- sheeple is distracted by patchman while eating (yes)
-- sheeple becomes brainwashed while eating (yes)
A sample page of notes taken to help organize each fridge frame.
A breakdown of one of these sub-actions, eating hand-by-hand:
- check every frame whether or not:
-- fridge is open and still have items?
-- still in proximity to fridge?
-- eat fridge desire hasnt been override? (ex: sees player)
- break down the fridge eating action into 9 steps:
-- ROUND (A) first three steps are to receive the first fruit in right hand
-- ROUND (B) steps four to six are to eat the first fruit in right hand, and receive second fruit in left hand
-- ROUND (C) steps seven to nine are to eat the second fruit in left hand, and receive a third fruit in right hand
--- any of these steps can be aborted due to in-game events (make sure to clean up pointers+memory if so, or we have a nasty bug to trace down)
As a poor starving developer I wish I could eat code...
Click to see the code for the sub-action listed above.
Too many arms!
(Especially While Famished in Front a Fridge Full of Food)
I was asked by someone infinitely cooler than I to expound on a previous statement I had made in the last Sheeple-related development diary. Name drop: Christian Nutt. Here's what I had said:
"It's harder to do AI when you're in a realtime physics world, where all sorts of things can happen at any time. By contrast a turn-based tile-bound system doesn't have the same concerns. One of the toughest things for me was just having the sheeple wait. Wait between actions, pause, to look like they're contemplating... instead of just doing everything immediately whenever they want to. It's also hard to explain why this was hard."
I'm suprised I didn't get any flak for the first two sentences, but apparently turn-based tile-bound developers were too busy lounging in easy riches to care. When I coded the AI initially, I just had them immediately perform any action that came to the forefronts of their little ai brains. Adding in a slowdown factor was tricky.
Two solutions were implemented:
1) Default Variable Delay Timer Between New Solutions
-- NEXT SOLUTION DELAY TIMER
if not sheeple.solution then
-- can do another step?
if not (Engine.step > sheeple.next_solution) then
else -- DEFAULT NEXT SOLUTION TIME:
sheeple.next_solution = Engine.step + 24
-- additionally, can change 24 to another number if necessary
2) Explicit Wait Actions in Plan Lists
-- EXPLICIT WAIT TIMER
-- purposely hack in a wait time, such as the 'ponder' action
elseif solution == 'point' then
sheeple.plans = 'open_fridge'
sheeple.plans = 'ponder'
sheeple.plans = 'check_fridge'
sheeple.plan = 1
Granted, there are plenty of software systems more complex than this. Yet this is really just a small piece of a larger game, that includes growing plants, realtime physics, stalking/hunting enemy ai, character animations, isometric walls and doors, multi-platform/multi-ui support, and on and on. Little of this was spelled out ahead of time, it was baked up on the fly, a sort of uncharted territory, and pounded in by mechanical keyboard whenever the chance arose.
So, wouldn't you concede, Dear Naysayer, that this task of eating out of the fridge was hard after all? And it might explain why the build-up for our trailer level-win culminates in a fridge-eating scene: