Good news and bad news: Yesterday we had an impromptu hangout where Josh played the latest build of Good Robot and streamed the game for all to see. It was supposed to be a simple test: I wanted to see Josh play the game to hunt for bugs and gather gameplay feedback. But then I Tweeted a link to the stream and the internet showed up.
Friends joined in and we had a little party where Josh played through all of the available content for Good Robot. It was funny and fun, although the best part of the show was when Josh got to the end and began using the console to spawn clouds of enemies in an effort to break the game.
The bad news is that Twitch.tv didn’t save the footage. We don’t know why. So the event is gone forever. Sorry.
The crowd feedback was invaluable, and Josh’s skill at finding bugs was… irritating. (But also valuable.)
I’m kind of surprised at how many people are interested in modding or changing the game. It’s already pretty mod-able. I mean, this is the level data for level 9:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
[Map9] Title=Chapter Five Subtitle=The Robot Factory StoryPoints=29 30 31 WallColor=000 LightColor=169 BacklightColor=024 MusicTrack=jc_bad_robot.ogg Coins=960 Tileset=4 Stars=0 Screens=16 SwarmSize=15 Patterns=grid shafts grid RobotSwarm=cutter1 cutter2 bomber1 shooter1 launcher1 RobotMooks=cutter2 shooter2 sniper1 launcher2 RobotNormal=cutter3 launcher2 sniper2 RobotBadass=sniper3 bomber2 launcher3
The robots are similarly defined by setting variables in text files. The artwork for all robots and items comes from a single PNG file. I did this for convenience during development, but since people are so eager to mess with it I might spend a little extra time to expose a few more systems and variables to modders.
Anyway. Let’s talk about gamespace.
It’s really odd that we’re all the way up to entry 20 and I still haven’t talked about where the levels come from or how they’re generated. (Actually 19, since there was no 15.) But all of my past projects were focused almost entirely on generating gamespace, and I guess I was itching to talk about some other stuff. Well, we’ve done that. So let’s talk level generation.
This is the most primitive level generation I’ve done in any of these projects. Working in 2D is like that.
So the gameworld is divided into a grid of regions. For some idiotic reason I named these things “screens”. You can see this in the level info above on line 12 where it says “Screens=16”. I think “pages” would have been a better name, but I’m not sure it’s worth re-naming them now. (The name is dumb, but is it confusing enough to be a bother? Meh.) At a high level, the game world looks like this:
This is one of the early-game levels. Here is the same area, but some hack has drawn lines to roughly show the screen boundaries:
The first star marks the level entry, and the second star marks an open arena suitable for a boss fight. Everything between those two probably represents about 5 minutes of game space. Here is a similar view of a place somewhere around mid-game:
There are a number of different patterns we use for generating individual screens. (Now that I’m writing about it, the name “screens” is starting to annoy me more. Nothing will reveal bad design better than trying to explain the design to someone else.) Some patterns just cut straight-edged shafts. Others use value noise to generate tunnels.
I’ve found that a little complexity goes a long way. Just a couple of side-tunnels or an occasional cul-de-sac is all it takes to make the world feel like a baffling maze. I keep the tunnels linear in the early game and then gradually introduce the more complex ones as the game progresses.
The trick with using noise is that there aren’t any guarantees. If I make a pattern that uses value noise to fill in the the screen with solid mass at the bottom and gradually becoming more porous towards the top, then that might make something like this:
But what if we get some unlucky rolls and it generates the following?
That’s bad. You can’t get from the left side of the screen to the right side, which means the player would be walled off. Even if 99% of the generated screens are fine, the player is going to pass through hundreds of screens throughout the course of the game. The odds of them hitting a bad screen are actually pretty good. Worse, we don’t automatically know if the screen is passable or not.
Now, you might suggest using the Minecraft solution and letting the player solve the problem by blasting through walls. The problem with that solution is this:
Levels are covered in shaggy grass, drooping vines, piled snow, hanging icicles, stalactites and stalagmites. If I blow away a chunk of ground, it would be goofy to have it reveal more grass or for a fresh batch of hanging vines to appear to replace the old. I am not eager to mess with that. Right now it assembles tiles from a few base shapes, flipping them as needed and juggling several different alternate versions of each one so the user doesn’t see the repetition or mirroring. I have base tiles and alternate tiles that are used to assemble the final tiles. Allowing the user to tunnel would require alternate alternate tiles and base tiles. The map system would need to keep track of what bits were made of which materials, and the different tiles would need to seamlessly blend.
It would be double the artwork and more than double the coding complexity. I wouldn’t mind, but I strongly suspect that tunneling through solid rock isn’t the most fun activity in the world. I say this as a die-hard Minecraft fan. Unless I’m going to embed gold ore, diamonds, and hidden caverns in the walls, tunneling is just busywork. And the player could end up doing a lot of it.
Going back to our unfortunate screen:
What if the player misunderstands and begins tunneling in the wrong spot? They could end up hacking through a lot of solid mass, wondering where the rest of this videogame is. I suppose I could embed blocks of XP in the wall, but that might signal to players that this is a game about digging and they’ll spend hours hacking away at solid mass. This would kill the flow of the game and turn it into a horrible boring Terraria riff.
I’m wary of any course of action that’s going to double my workload while at the same time running completely counter to the core of the game. I love mining games, and maybe there’s another sandbox game that could be made with this, but for now I think it’s best to just solve this walling-off problem for the user.
My solution is about the most brute-force method you can come up with. I do a flood fill on one edge:
If the flood doesn’t reach other opposite edge it enters panic mode and solves the problem with a sledgehammer:
It just obliterates a horizontal block of points to make a shaft. Harmless, really. To the user it probably won’t even look at all that out-of-place. It would just be a single section that was a little less confusing than the previous bit.
Once the walls are done, it passes over the screen and looks for places that are touched by the floodfill that are also dead-ends. (Surrounded on three sides.) It puts robot spawners there.
When placing enemies, it basically acts like a tabletop GM. Referring back to our level file:
13 14 15 16 17 18
SwarmSize=15 Patterns=grid shafts grid RobotSwarm=cutter1 cutter2 bomber1 shooter1 launcher1 RobotMooks=cutter2 shooter2 sniper1 launcher2 RobotNormal=cutter3 launcher2 sniper2 RobotBadass=sniper3 bomber2 launcher3
These lines tell us what bots should be used in what situation. It then chooses from a list of predetermined encounter types. Some examples:
1) A swarm of the lowest class of robot. (Line 13 tells us the size of the largest possible swarm on this level. It spawns between n and n/2 in a swarm.)
2) A medium-sized group of mooks and a couple of normal bots.
3) A couple of normal bots and one badass.
4) A swarm and a badass.
5) You get the idea.
Then it drops the robots down on the spawn points we worked out above.
It’s a pretty simple system, but it yields varied and interesting results.
I’m seriously considering renaming screens now. That means renaming the dang file, too, since the class is in screen.cpp. Grrr. This is actually something I did back on day 2 of the project, long before I was sure I was going anywhere with it.
The Best of 2017
My picks for what was important, awesome, or worth talking about in 2017.
The product of fandom run unchecked, this novel began as a short story and grew into something of a cult hit.
Let's ruin everyone's fun by listing all the ways in which zombies can't work, couldn't happen, and don't make sense.
Zenimax vs. Facebook
This series explores the troubled history of VR and the strange lawsuit between Zenimax publishing and Facebook.
The Truth About Piracy
What are publishers doing to fight piracy and why is it all wrong?