Thursday, March 9, 2023

Mars After Midnight: Gameplay Loop

I just finished a vertical slice of the game and it looks like this:

  1. Work the door to admit martians matching some criteria.
  2. Everyone makes a huge mess of the FREE refreshments, which you have to clean up.
  3. Repeat 1 & 2 a bunch of times until you have enough martians.
  4. Choose the next day's event, where to promote it, and which refreshments to serve.
  5. Repeat from 1.

Everything here is alpha level at best, which means I expect some or all of it to change before final. Still, there was a sale on gifs so let's not waste the opportunity.


Working the Door

There's a knock on the door. Flip the crank up to lift the window and have a look. Press the A button to open the whole door and admit the entrant or close the window to reject them.


loop-WindowOpen
 Hello

Panning around with the D-PAD reveals more of the face, and some satisfying parallax.


loop-WindowParallax
 Parallaxation

I experimented with using the Playdate's accelerometer to control looking around but,

  1. The accelerometer is oriented for holding the device flat on its back. You're looking through a vertical window here so the metaphor doesn't hold. There's a missing axis that precludes vertically-oriented readings.
  2. The screen, while beautiful, is fairly sensitive to viewing angle when it comes to illumination. Tilting it around means the brightness is constantly shifting. It's not a typical viewing angle problem, more of a frontlighting-is-best-from-this-one-angle thing.
  3. I prefer the explicit control of a D-PAD over wobbly gyro controls.

The 3D effect on the window panel is done by pre-scaling the 2D texture in wide strips and drawing the right ones based on some perspective 1/z calculations. Rough but good enough.


loop-Window3d
 Texture strips on the window panel

This window action was my original wafer-thin concept for using the Playdate's crank in a novel way. I threw together the game code and procedural martian generation pretty quickly almost two years (!) ago. Flipping the window open and seeing some random martian is fun. Sometimes even funny. Job done on this part.

Unfortunately the wafer was too thin here. One of those "great where's the rest" concepts that I didn't feel the true shallowness of until it was implemented. Working the door at night on Mars is a cool idea but for what? A bar? Gym? Casino? Store? Hairdresser? And once they're inside, then what? Something cool? Nothing? Maybe more happens at the window? Can you shoot things? I rarely do combat so probably not. The martian faces are procedurally generated, if there's more gaming beyond the window, how far beyond the face do I need to go? Not far please.

Anyways, some potential but still a lot missing. As part of my strategy to just keep trucking on random shit until an idea comes to me, I put together an exterior scene of the bar/gym/restaurant/whatever.


loop-Exterior
 Exterior, just before midnight

To inform the player of their current bouncer rules, I made a big sign and filled it with text.


loop-SignTop
 Big sign, top

"Cyclops Anger Management" sounded good and that was enough to establish that this would be screening for community support sessions -- therapy in this case. I figured there were a lot of ways to make visually interesting criteria to match different kinds of help sessions. Ok, so it's not a bar or a casino, it's a community support center.


loop-SignBot
 Big sign, bottom

At the bottom of the sign, I added "Refreshments Provided" to really pack the place. Who can resist free refreshments? This phrase somehow unfurled in my mind and grew to define the game's core mechanic. What if refreshments are actually provided? What would an angry cyclops on Mars eat, and how?


Refreshments Provided


loop-EatMartian
 Martians eat like this

Martians are slobs and they love their dune bug pie. You let 'em in, they see your nicely prepared spread of delicious refreshments, and they desecrate it. If the food suits them, they'll toss you a few credits.


loop-EatSurvey
 Thanks I guess

Each visitor expects a spotless table, so you'll need to clean it up afterwards to prepare for the next one.


loop-EatReact

loop-EatTentacles
Our tolerant lead

To clean the table, the game uses a relatively simple stack manipulation mechanic with the slight novelty that you can lift two stacks at once but the left and right hands/tentacles move together. The D-PAD adjusts the position, the B button lifts/drops with the left tentacle, and the A button lifts/drops with the right tentacle. These controls take some getting used to.


loop-EatClean
 Cleaning up the stacks a bit

There are a few basic stacking rules like plates must be aligned, and narrower items can't support wider ones, but we're not quite Tower Of Hanoi here. Each mess only takes a few seconds to clean up.

To remind you this is a Playdate game, there's also a crank-controlled table sweeper to suck up the crumbs. All the items on the table have to be lifted at once to get a full sweep.


loop-EatSweep
 Sweeping crumbs

Implementing this table cleaning part was mostly straightforward, with a handful of edge cases. Design-wise it's just stacking stuff so there aren't especially complex mechanics that need to be carefully explained or aligned with player expectations. Still:

  • The tentacles move in discrete steps, which feels a little odd at first but this isn't a full physics simulation so I think it's ok.
  • It's not always perfectly clear which item you'll pick up with A/B -- there's no highlight, plate stacks are pretty tight vertically, and the tentacles are wiggly. It's not hard to get used to but I may address this later.

Also, I was a little stuck on how to illustrate the "clean" state that you need to restore the table to, and ended up with two imperfect ways:


loop-EatMarks
 Marked spots on the table.

The table has marks to indicate where items should be placed: open circle for plates, closed circle for pie, triangle for knife, square for note. Never explained in-game, not great.


loop-EatThought
 Thought bubble

A thought bubble that appears after idling for a few seconds to show the proper arrangement. Also not great. I should probably just stick a note on the back wall instead.

Things were a little trickier with performance. I've got more about this below but the brief version here is that some of the drawing functions were written in C (most of the game is in Lua) in order to maintain 30fps.


All Full

After admitting enough martians, feeding them, and cleaning up their mess, the help session fills up and kicks off. Since you're not a licensed therapist whatever happens in the meeting room is out of sight. Someone else handles that I guess.


loop-SessionStart
 Do not enter

When the session finishes, you can see the results. First, martians that were helped by the topic will pay out a bonus. A happy two-eyed martian won't get much out of "Cyclops Anger Management" for instance, so it's important to admit the right ones from the start.


loop-SessionExit
 Post-session bonuses

Next, your progress is shown in the context of the entire settlement.


loop-TomorrowStart
 Sobering progress so far


Planning for Tomorrow


loop-TomorrowPlan
 Planning

At this point you need to lay out the plan for tomorrow. Choose the topic, where to promote the session, and which food to serve. Each choice is made against a map of the settlement, showing martians that need help.

1 // Session Topic


loop-TomorrowSession
 Choosing session topic

Most topics are free to host but a few require paid accessories. I haven't worked out the exact details for all the sessions and accessories. One example is a "Night Glowers" topic, which requires installing a switch to cut the lights and check if the martians are appropriately glowing or not.


loop-TomorrowLight
 Light switch accessory

Accessories are activated with the B button while working the door, and are only available if the session requires it. Theoretically. I haven't implemented any yet.

2 // Regional Promotion


loop-TomorrowPromo

After selecting a topic, you'll need to promote it in the areas where affected martians live. The settlement on Mars is split into large districts, where the price of advertising varies slightly. Failing to promote in a district that contains your target group means they won't know about the session and won't show up tomorrow.

3 // Refreshments


loop-TomorrowFood
 Food selections

And finally, you need to order the refreshments. Some foods are more popular than others in certain areas. Different foods need to be handled and cleaned in slightly different ways. Soup, for example, may spill if moved too quickly. Martians will pay higher refreshment bonuses for foods they like.

4 // Checkout

Once the planning is done, you pay and the day ends.


loop-TomorrowPay
 Checkout

If working the door was the catchy-but-shallow concept to get this project going, and cleaning the table was adding meat to the core loop, this planning phase is intended to tie everything together into a complete game with high-level goals, narrative, and gradual progress. Maybe it'll work.

I had a hell of a time designing the presentation and flow for this planning phase. Even now it's probably not final. The combination of unfamiliar task, small screen size, long text list navigation, and map display is all a bit much. Tilting the map back in 3D gave me enough space to show a list below, and I tried to whittle the displayed information down to the minimum. Part of me thinks I'm still trying to do too much here.


Programming in Lua

Coding this game in Lua has been an experience. This is the first time I've used Lua and I can't say I'm a huge fan. It has some nice stuff like php-style do-everything collections, first-class functions, and... well I'm struggling to finish a triumvirate here but I guess I can say that the 1-indexing isn't that bad. Wait no, I remember a proper third: coroutines are great and enable a kind of concurrent programming that can work well to replace state machines and other finicky patterns.

I could get along better with Lua if it was just a little more performant. I really miss things like true consts, macros, inlines, and other basic performance-focused features. Overall it feels very much like a good scripting extension for a core compiled system and using it to build an entire game, even at the relatively high level I'm working at, is beyond the spec.

A few other complaints:

  • Lua documentation is weird. The official docs are written like a wordy book with chapters and stuff and I still haven't found a clear, clinical, versioned language+API reference for the thing.
  • Features that are convenient are often slow, or have poorly-defined performance. Iterating a table, for instance, can be done in multiple ways and the easy Lua way is almost always slower.
  • I use Visual Studio Code for everything and the Lua step-debugging doesn't work right for me. Back to print() debugging.
  • The usual problems with dynamic languages: no static type-checking, no compiler to catch basic errors, plus an extra dash of wonky/expensive error handling.

Luckily, the Playdate SDK also includes robust C support. If something's too slow in Lua, it's relatively straightforward to move that logic to C. A few of the things I'm doing in C:

// Line-drawing

The Playdate API has built-in line-drawing support but since I'm filling the screen with lines in some cases, I wrote my own slightly faster functions in C. This is used for the wiggly fonts and pictograms everywhere.

// Bezier spline code

The tentacles use some auto-tangent calculation code I've dragged around with me over the years and this is the exact kind of thing you don't want to do in Lua.

// Optimized image drawing

1-bit is nice for saving memory but a huge pain in the ass for image manipulation. Every pixel write to a buffer also requires a read, so there's a much higher cost to even simple draw operations. Calculating bit offsets can be painful too. Playdate already has great image drawing functions but I added a few optimized ones for my specific use cases.

  • Pre-shifted image plotting. This works like the old ZX Spectrum trick of saving 8 pre-shifted copies of each image so it can be pasted quickly at the byte-level instead of the bit-level. Trading memory for speed.
  • Sweeper rendering. Drawing the table sweeper requires enough math, lines, and fills that just porting the logic to C is much, much faster.
  • Tentacle rendering. This uses the bezier code and pre-shifted filled-circle images to get the tentacles drawing at a decent speed.
  • Shadow rendering. The tabletop shadows are specific enough -- spaced horizontal black lines -- that custom drawing functions in C gave me a few frame speedup on hardware.

// Silhouette rendering

When working with the Playdate hardware, an early surprise was trying to scale/rotate/transform images in realtime. Basically, you can't -- it's too slow. In almost every case it's better to precompute the transformed image offline or during loads.

When admitting a martian, I really wanted them to enter the room, walking in from outside. To save me from having to make more art, this is shown from the back, in silhouette. The scene is drawn with depth so they need to start large and shrink while walking into the screen.


loop-SilWelcome
 Welcome

Doing this the naive way, by drawing the images scaled and rotated is either a slide show or requires gobs of pre-compute time and memory. My solution was to divide the silhouette into 8x8 patches and rotate/scale the position of each patch instead of each pixel. I call it a tarp and it works ok.


loop-SilPatches
 Visualizing the tarp patches

Rotating too much will expose seams and scaling down too much looks pretty rough. Overlapping the patches slightly and keeping the scale within safe ranges gives a nice organic look that's even better imo than proper transformations in this narrow case.


Timeline

At this point I need to deal with some long-pending maintenance for Papers, Please and Obra Dinn. Once that's handled I'll get back on this to fill out all the session topics, accessories, and foods -- only one topic and one food is currently implemented. I estimate at least a few months of work and my estimates are always a joke.



from Hacker News https://ift.tt/eYuhiKa

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.