Steve Landey

From PoweRL to Power-Q: game jam to product

Power-Q is the pol­ished, pro­duc­tized ver­sion of my Ludum Dare game Pow­eRL. They have ex­actly the same me­chan­ics, but Power-Q has about 30 extra hours of ef­fort in­vested into it. Be­fore I send it out into the world, I want to re­flect upon where those 30 hours went.

Power-Q is in beta on Mac and iOS. You can get the Mac beta on itch.​io, but to get the iOS beta you’ll have to email me.

screenshot

Be­fore:

After:

Game me­chan­ics

Power-Q is a turn-based game played on an 8-col­umn, 6-row grid. The player is a robot with a health bar and a power meter. They can move up, down, left, or right. Each move drains power.

The level con­tains walls, en­e­mies, powerups, and an exit. The over­all goal of the game is to reach the exit 8 times. Each level has more en­e­mies than the last. Powerups in­clude health boosts, power boosts, and am­mu­ni­tion.

There are three kinds of en­e­mies. They move in spe­cific pat­terns (di­ag­o­nals, up/down/left/right every other turn, and knight-style) and sap your health when they hit you. There are also “power drains,” which sap your power and dis­ap­pear if you run over them.

moves diagram

The player has a melee at­tack and a ranged at­tack. The melee at­tack can kill a health-drain­ing enemy in two hits. The ranged at­tack con­sumes am­mu­ni­tion and can kill an enemy in one hit.

Al­though I only pub­lished the Mac ver­sion for Ludum Dare, I wanted to make sure the in­ter­face could work on an iPhone. There are only two kinds of in­ter­ac­tions you can have with the game: you can move (arrow keys on Mac, swipe on iOS) and shoot (click on Mac, dou­ble-tap on iOS).

That’s it, the end! That’s the whole game. Each game is sup­posed to last about five min­utes. I could ex­pand the enemy ros­ter, but I wanted to limit the scope to some­thing I could fin­ish on week­ends be­fore my in­trin­sic mo­ti­va­tion ran out.

Pol­ish­ing the me­chan­ics

I was pretty happy with the me­chan­ics in the con­text of a short game, but I wanted to solve a few prob­lems be­fore send­ing it out to the masses.

Un­reach­able exits and powerups

One of the most com­mon bits of neg­a­tive feed­back from Ludum Dare raters, be­sides crashes, was that the exit was some­times un­reach­able or too close to the player to be a chal­lenge.

I could have solved this by writ­ing a smarter level gen­er­a­tor, but I liked the gen­eral char­ac­ter of the lev­els I was get­ting al­ready, so I just added a flood fill-style reach­a­bil­ity check and had the game re-roll the level if it was no good. Exit place­ment was solved by putting it as far away from the player as pos­si­ble in­stead of any old place.

If you look at the con­sole logs, you’ll see that every level is re-rolled 1-4 times on av­er­age, but it’s re­ally fast, so it won’t mat­ter to play­ers.

Frus­trat­ing tar­get­ing

Shoot­ing fol­lows a line de­ter­mined by Bre­sen­ham’s al­go­rithm. It means a bul­let path fol­lows the yel­low squares:

bresenham exmaple

The al­go­rithm is pop­u­lar and well un­der­stood. It’s con­ve­nient for a pro­gram­mer, but can be frus­trat­ing for play­ers if used for this kind of thing naïvely.

As an ex­am­ple, take a look at this sit­u­a­tion:

bresenham exmaple

Given the way shoot­ing works, the player could rea­son­ably ex­pect to shoot around the cor­ner. And it would work:

bresenham exmaple

But what if the mid­dle two cells were flipped? The player prob­a­bly ex­pects to be able to shoot the enemy.

bresenham exmaple

Oh no! That’s a ter­ri­ble ex­pe­ri­ence! it will fail and the player won’t know why.

I solved this prob­lem by ex­ploit­ing the asym­me­try of Bre­sen­ham’s line al­go­rithm. If you run it from point A to point B, you get a dif­fer­ent set of points than if you run it from point B to point A! By run­ning it both ways every time, al­most all of these weird cases are cov­ered be­cause the re­verse ver­sion uses a slightly dif­fer­ent path from the reg­u­lar ver­sion.

Smarter en­e­mies

The AI in this game is re­ally dumb. The di­ag­o­nal- and knight-mov­ing en­e­mies don’t do any pathfind­ing. They just move to what­ever cell is clos­est to them.

The dumb AI is mostly fine, but for the “tur­tle” (orig­i­nally an ac­tual tur­tle, now a kind of blobby thing), which moves one cell up/down/left/right every other turn, it was too much of a lim­i­ta­tion. So I added pathfind­ing just to the tur­tle. It will move to­ward the player even if it has to go around some walls, in­stead of get­ting stuck in cor­ners like an idiot.

Scor­ing

Typ­i­cal play­ers will die a lot be­fore beat­ing the game, and I don’t want them to feel like they aren’t pro­gress­ing. A sim­ple high score counter seemed like a good so­lu­tion. I made each enemy worth one point and dis­played the high­est score on the title screen.

Art

I de­vel­oped the game using ASCII char­ac­ters for most of Ludum Dare. Mid­way through, I turned the en­e­mies into emoji. As the dead­line ap­proached, it didn’t feel right to use Apple’s beau­ti­ful high-res­o­lu­tion an­i­mal il­lus­tra­tions as my game art, so I did some bad trac­ings in Pix­el­ma­tor and shipped it.

screenshot from jam

I im­me­di­ately re­gret­ted all of my art choices. The whole thing looked like crap to me. So I down­loaded Piskel and started on some 16x16 pixel art style sprites. The re­sult was a lot more charm­ing! I pack­aged it up into a post-compo build with some crash fixes a cou­ple days after the jam ended.

screenshot from just after the

Pol­ish­ing the art

As I con­verted more of my art to the new style, I be­came frus­trated with Piskel’s UI. I bought Aseprite in­stead and my pixel art pro­duc­tiv­ity sky­rock­eted. I re­duced my color palette and put the fin­ish­ing touches on the sprites. As some­one with­out any art train­ing, I found cre­at­ing the color palette to be a big­ger chal­lenge than I would have ex­pected, though I sus­pect if I had started with Aseprite in­stead of Piskel I would have been more con­sis­tent from the be­gin­ning and had an eas­ier time of it.

My game-over screens were just black voids with text in the mid­dle of them, so as soon as I was done with the sprites, I did some sim­ple il­lus­tra­tions for the three pos­si­ble endgame con­di­tions: a win, a loss due to power, and a loss due to health.

One enemy, for­merly known as the tur­tle, moves every other turn. It was im­pos­si­ble to know at a glance whether it was about to move or not. I re­placed the sin­gle tur­tle frame with a blobby thing that flips be­tween two frames.

I’m leav­ing a lot out here. There were prob­a­bly a hun­dred more lit­tle things I did to the art.

screenshot from final game

Music and sound

Music pro­duc­tion for 48-hour game jams re­quires a care­ful bal­ance be­tween time spent and dope­ness. My ini­tial music pass had a pretty good set of sounds and melodies, but I re­arranged it a lit­tle bit for the final re­lease. I might still write a sec­ond track.

To get an extra push over the mu­si­cal pol­ish line, I put Goos­eN­inja’s space-themed load­ing track over the title screen. If this game makes any money, I’ll be kick­ing some of it over to them.

My ap­proach to sound was to open Logic Pro X and bonk around on pre­sets until it sounded video gamey. It worked great! I didn’t change much on the sound front, but the ranged at­tack sound was a lit­tle clipped and unim­pres­sive. I re­placed it with two sounds: one for the player fir­ing and one for the im­pact of the bul­let. The fir­ing sound is a drum ma­chine hi hat and the im­pact is a kick drum.

I added graph­i­cal tog­gles for sound and music, be­cause smart­phone users are often lis­ten­ing to pod­casts or music while they play games. I made sure the game wouldn’t in­ter­rupt any al­ready-play­ing audio.

My Ludum Dare score for audio was a lot lower than I thought it would be, so I worry that I haven’t done enough for the audio of this game. My last game, Rogue Base­ment, scored much higher and didn’t even have sound ef­fects! What it did have, though, was four tracks of music and cool lo­ca­tion-based tran­si­tions be­tween them. Power-Q doesn’t have the same sense of space, and I’m not sure I’ll be able to make the music as dis­tinc­tive as it was in Rogue Base­ment no mat­ter what I do, so I’m not wor­ry­ing about it for now.

Help screen

For the Ludum Dare build, I did what most peo­ple do: slap some in­struc­tion text on the title screen. For the final ver­sion, I moved the in­struc­tions to a sep­a­rate screen.

Be­fore:

help screen before

After:

help screen after

Por­trait mode

One of my core be­liefs as an iOS gamer and de­vel­oper is one that ap­par­ently isn’t shared by game de­vel­op­ers who make oth­er­wise great games:

If your iOS game can be played with one hand, it should sup­port por­trait mode.

I can’t be­lieve I even have to bring this up, but for frack’s sake, 868-HACK can only be played in land­scape mode! I’ve de­vel­oped a com­fort­able but ab­surd grip that lets me see the screen and move, but there is ab­solutely no rea­son why that game could not let you ro­tate that lit­tle glass rec­tan­gle 90 de­grees and keep all the text read­able.

Given my pas­sion for this be­lief, as soon as Ludum Dare was over, I began to think about how I’d go about sup­port­ing por­trait mode in Power-Q.

In terms of just the in­ter­face, the so­lu­tion was sim­ple. The 8x6 grid was de­signed to com­fort­ably fit on an iPhone 5S with­out scrolling, so in por­trait mode, it would just be­come a 6x8 grid! The map would phys­i­cally stay in the same place in both ro­ta­tions; only your per­spec­tive of the ob­jects would change. The re­source bars and la­bels would be at the bot­tom of the screen in­stead of the left.

In prac­tice, this feels a bit weird when you see the ro­ta­tion in per­son, but when you’re play­ing locked in one ro­ta­tion it doesn’t mat­ter at all and just works.

help screen after

But in the code, it was night­mar­ish. I had to move a lot of lay­out code around and make sure every sprite was in the right place at all times. And be­cause the map was just ro­tated 90º, I also had to un-ro­tate all the sprites in­di­vid­u­ally and do a lit­tle bit of funky co­or­di­nate space trans­la­tion math to de­tect grid touches cor­rectly. Now every sin­gle screen in the game has two com­pletely sep­a­rate lay­outs that have to be in­di­vid­u­ally tested.

So I guess I lied when I said there was “no rea­son” why 868-HACK couldn’t sup­port por­trait mode. It would be a lot of work. But I swear on Steve Job’s grave that I would pay $10 for an in-app pur­chase that en­ables it.

Now that I’ve baked the por­trait/land­scape du­al­ity into the game, I think I’ll just do it for every iOS game I make going for­ward. The pat­terns are easy to un­der­stand and im­ple­ment. In the fu­ture, though, I’m going to just make games with scrolling or square maps so I don’t have to ro­tate the play­ing field.

Sav­ing and load­ing

Power-Q is a sim­ple game with­out an ex­plicit save sys­tem, but on iOS it’s im­por­tant not to lose the player’s game when the app is kicked out of RAM.

That means my hacky, often thought­less Ludum Dare code had to be refac­tored a lot. The main issue was that the map gen­er­a­tor gave you a fully con­nected ob­ject graph with lots of ref­er­ences be­tween ob­jects, in­stead of a flat data struc­ture that was easy to se­ri­al­ize.

There was no short­cut, so I just dove in. It took about 500 lines of new code, but it worked the very first time I ran it. (I have a lot of prac­tice writ­ing se­ri­al­iza­tion code from work­ing on Hip­munk!)

Pow­erRL/Power-Q uses an en­tity-com­po­nent sys­tem. The main change I made was to add se­ri­al­iza­tion code to every com­po­nent pos­si­ble. I also in­tro­duced new com­po­nents whose only job is to hold in­for­ma­tion used to re­con­struct more com­plex com­po­nents when the game is loaded from a file.

Here’s what the save file looks like. It’s writ­ten at the be­gin­ning of each level and loaded when the app launches.

Take­aways

Swift, SpriteKit, and Game­playKit are re­ally, re­ally good for rapid pro­to­typ­ing and gen­eral 2D game de­vel­op­ment. I’ll never pro­duce a Win­dows or An­droid port, but hav­ing fun is more im­por­tant to me.

Good-look­ing pixel art is much eas­ier for me to cre­ate than vec­tor art. I’ll just start with that style in fu­ture game jams.

It helps to think about sav­ing and load­ing from the be­gin­ning, to avoid work­ing with bad as­sump­tions about how scene con­struc­tion and map cre­ation should work.

Re-rolling bad pro­ce­du­rally gen­er­ated lev­els is much sim­pler than writ­ing fancy al­go­rithms to make them good in the first place, as long as it’s cheap to do so.

Full changelog

  • Com­pletely new art in every screen
  • Por­trait mode sup­ported on phones
  • UI lay­out tweaks in every screen
  • Help screen
  • No lev­els with un­reach­able exits or powerups
  • Music is slightly bet­ter
  • Dif­fer­ent sounds for shoot­ing
  • Game saves at the start of each level, loads on launch