Project Good Robot 20: Gamespace

By Shamus Posted Wednesday Oct 2, 2013

Filed under: Good Robot 53 comments

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:

Playtester: “How do I open the mini-map?”  Shamus: “Just re-compile the project in debug mode, open the console, turn off collision, and then increase the camera distance to a huge number.” Playtester: “I don’t have the source!” Shamus: “The challenge is half the fun!”

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:

This is more enjoyable if you look at it while listening to “The Grid” by Daft Punk. But that’s sort of true of everything.

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:

It’s more confusing than it looks, once you’re zoomed all the way in to gameplay-distance.

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:

gr20_map1.jpg

But what if we get some unlucky rolls and it generates the following?

gr20_map2.jpg

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:

I’m imagining these two spiders are teaming up so they can roll their eyes at me.

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:

gr20_map3.jpg

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:

gr20_map4.jpg

If the flood doesn’t reach other opposite edge it enters panic mode and solves the problem with a sledgehammer:

gr20_map5.jpg

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.

 


From The Archives:
 

53 thoughts on “Project Good Robot 20: Gamespace

  1. Rodyle says:

    I don’t know how much of an issue this is, but instead of sledgehammering through it, would it maybe not be a good idea to try the inverse of the non-navigatable pattern first, to see if that one might work?

    Also: out of interest and I was wondering how you do the robot spawning in some more detail. Do you first create a “swarm budget” and then pick guys until the budget is filled, or do you randomly pick guys (weighted according to class/whatever) and made the random picking algorithm so it statistically should stop somewhere between n/2 and n? I’m going to be DMing a large open-world campaign (West Marches Style soon, so I’ll need a quick and reliable way to create encounters from lists of possible enemies in that area.

      1. Paul Spooner says:

        Thank you.

        To the original question: I’d guess it uses the same “predetermined encounter types” for every level, with the specific robots determined by the level file. Especially since there’s no place for a “weight” to go, and different robots will be more or less effective against different playstyles, a point based system would be much more difficult to implement.

        As a GM, though, you have the ability to judge your party based on their composition, and most importantly based on past encounters. Keep notes of how combat goes, and use those notes when generating new encounters for them.

        1. Rodyle says:

          I usually do, but I feel like doing it a bit differently this time.

          I’m just going to create a world, with lots of dungeons, random areas, small bits of story and whatnot, and let my players explore it as they like. However, I’m not going to bother with making encounters level appropriate this time. I’m making notes for each area for myself about what it contains and the monsters therein and which level the encounters should be balanced around. If they run into an area which is too high level for them at the moment, they’ll have to decide that for themselves and come back later when they’re up to the task.

          Inspiration gotten from:
          http://arsludi.lamemage.com/index.php/78/grand-experiments-west-marches/

        2. Rodyle says:

          On the subject of the game:

          “Especially since there's no place for a “weight” to go, and different robots will be more or less effective against different playstyles, a point based system would be much more difficult to implement.”

          I don’t know. It’s not hard to implement that a swarm has base cost 1, mooks, base cost 2, normal base cost 3 and badass base cost 4 when looking at unit slots, or something like that.
          Also: yes. Some will be more effective than others, but I’d wager that the categories are less about strategies of certain robots and more about health/damage.

          1. Paul Spooner says:

            Oh, I certainly agree that it would be easy enough to implement. I’m just pretty sure that’s not how he’s doing it right now. Hard-coded “encounter groups” sounds like about the right speed for this particular game. Could be wrong though; AFAIK no one has actually seen the source code except Shamus.

      2. Rodyle says:

        Yup. That’s a syntax error for me right there.

  2. Cybron says:

    Seems simple but effective, which is my favorite type of solution.

  3. ZoeM says:

    What size is the game right now? 20-ish levels, or maybe four hours?

    Also, have you given any thought to final value/price point? At four hours, it feels like it should be a $5-$10 Steam title; maybe with a multiplayer/co-op game tacked on (have you considered this? “Hard mode”, basically, with a hotseat/LAN friend?)

    In other words, can you give us a view of your plans for the non-gamedev aspects of this game?

  4. Tizzy says:

    Wait a second…

    Josh is good at finding bugs????…

    None of us would have guessed! ;-)

  5. Abnaxis says:

    How hard would it be to just poke a hole in it instead of sledgehammering?

    Here’s what I’m thinking: Flood from the left. If it doesn’t go through, flood from the right. Randomly pick a square from the rightmost column of the left flood and any square of the right flood, and draw a line between them and just clobber that.

    1. Tizzy says:

      I like the sledgehammering though. It suggest somebody else did the digging for us. It makes sense on so many levels.

      1. swenson says:

        It also would lead to a variety of rather complex and much more open screens (or pages!), as opposed to all of them being complex. That’s a good thing IMO.

        1. Abnaxis says:

          I would agree that not every passage needs to be complex. At the same time, were I working on it, I would prefer to be able to predict when there are open passages and when there aren’t, rather than having it decided by an unpredictable bug in the terrain generator. More stable that way.

          Of course, the right answer for the correct way of doing things is “whatever is more fun,” which can only be determined by play-testing. I’m just tossing in a suggestion in the pot and if it’s easy to implement and improves flow of the game, then that would be dandy.

          1. Decius says:

            A combination of methods might be best: If the floodfill fails, use either the sledgehammer, or invert and upend, or regenerate.

            On the same note, a variety of patterns could combine nicely with a variety of tilesets to make interesting variations in levels; one pattern might be suitable for rocky stuff, while another pattern might lent itself well to interiors, and a third might be suitable for passages internal to a large organism.

      2. Also, there’s nothing wrong with the method. Usually, I don’t advocate for the (almost always completely false) idiom “If it ain’t broke, don’t fix it,” but in this case the brute force solution works, is invisible and is easy to comprehend. There’s really no need to modify it.
        Especially when there are better things to be working on, such as bugs that have come up in play testing.

        1. Abnaxis says:

          There’s nothing wrong with it as tested yet, but the code itself is brittle. If you accidentally plug the wrong numbers into the random number generator, you’ll wind up sledgehammering half the map away. Or you won’t. Or you will every third time.

          To me, it’s better to start with something less drastic than a “*#!@ IT NUKE EVERYTHING!” fall back, especially if you are debating making the game mod-friendly.

    2. Zak McKracken says:

      To make the algorithm even easier: Do a fill from the right, one from the left, and then sledgehammer your way from one to the other. Or draw a randomly contorted path from one to the other, or maybe two. Would have the advantage of keeping more of the original pattern and not having those tunnels start and end at “screen” boundaries.

      I’d think it’d look good if there were some places where most of the randomness remains intact but some others which look as if someone bored a tunnel. So maybe the sledghehammer width might be a random value, too, allowing you to have these large obviously “artificial” corridors but also have more diversity. Sometimes you need to squeeze through, sometimes it’s a large open shooting gallery, and maybe that would make the individual screens’ boundaries less recognizable in the overview map.

  6. Neuro says:

    Concerning the recording, apparently twitch recently made automatic recordings optional and (of course) the default is off.
    You should be able to change this by going to video settings and enabling “Archive Broadcasts”.

    1. It’s quite frustrating – I’ve heard several people say that they suddenly lost a bunch of their livestreams because of the change.

  7. UTAlan says:

    Just want to drop in and say I love this series and can’t wait to get to play at some point!

  8. WillRiker says:

    Argh! The stream happened on my way home from work, so I didn’t notice it yesterday until it had already ended. I was planning on watching the recording today, but no such luck :(

  9. Perhaps “Section” would work as a replacement for “Screen” ?

  10. Lord Nyax says:

    Just so you know, I’m using IE and I can’t see any of the pictures in this post.

    1. Jarenth says:

      Yup, I just tried it and I get the same thing. Here’s what it looks like for me: http://postimg.org/image/tnfgmh8oh/

      1. Shamus says:

        Ah! I think I might have fixed it. I use these shorthand tags for embedding images and I’d malformed the tags. (And then copy-pasted the problem.) Let me know if it’s still a problem.

        1. Jarenth says:

          Works again on my end!

        2. Alexander The 1st says:

          This one here appears to still be shrunk really small in IE:

          http://www.shamusyoung.com/twentysidedtale/images/gr20_level2.jpg

          Specifically, the one with the grid of screens drawn out.

        3. Lord Nyax says:

          Looks fine now! Thanks.

  11. Paul Spooner says:

    Ahh; The simple elegant solutions are so often the best. Love the flood-fill solver.
    I assume you experimented with actual “screen” branching and “screens” of varying sizes? The flood-fill could work on the “screen” level as well, where impassible screens are “black” and passable ones are “white” so to speak. Obviously it would be more work, but it could lend some extra complexity to the level generation without much extra effort.

  12. MikhailBorg says:

    Shamus, I don’t doubt you have addressed this already, so I apologize for the repeated question: what are the chances of this ending up available to OS X users eventually?

    There’s no entitlement here. If the answer is, “Yeah, not realistic right now,” I accept that completely. But I don’t deny that I’d like to hear, “Oh, porting it’s going to be pretty easy when I’m done. Maybe the work of a week or two.”

    Fingers crossed!

    1. mac says:

      Yeah, I’d also love to see an OS X port, but only if it doesn’t come at the cost of time spent on other elements of the game. VMs are pretty good these days, if it’s not available on mac.

    2. Moridin says:

      He’s been trying to keep everything as system agnostic as possible, so the chances of OS X version should be fairly good.

  13. Tim Keating says:

    When refactoring, SublimeText is your friend. Nicest, fastest regular expression search and replace I’ve ever seen. And you can try it out for free with a nagware version. (Of course, real refactoring would be nicer, stupid visual studio…)

    That’s even leaving off the multiple cursor editing, which has saved me hours of my life.

  14. Atarlost says:

    Another option for slightly less brute brute force is to create two functions, plot them, and make the space between them open.

    I’d suggest integrating signed random noise then doing a linear best fit and subtracting that so it runs from the middle of the left to the middle of the right. Then add half your desired average width to one function and subtract it from the other and you’ve got a wandering variable width path from left to right. Minimum width will be the size of your squares for the marching squares algorithm.

    1. ET says:

      This sounds like a nice way to hide the sledgehammering.

  15. swenson says:

    Man, I love that jungle level. Looks so cool, especially with the flashlight. Will there be brighter and darker levels, or will all have the same brightness?

    1. krellen says:

      The build we saw yesterday had varying brightness, including a “dark” level.

  16. Akri says:

    You should label the next entry #15, but not give any acknowledgement to the numbering change.

    1. anaphysik says:

      Naw, Shamus actually already had one, called “Project Good Robot 15: Sorting Algorithms in 6 Minutes.” ;P

  17. Duneyrr says:

    Personally, I like the terms “Card” and “Board” for sections and maps/levels. They are fairly unique and you can reasonably assume what they mean when you’re talking about game spaces.

    1. Slightly off-topic, but this reminded me of Squidi’s game mechanic list, as using “cards” for procedural generation was one of his running themes for a while.

  18. somebodys_kid says:

    Just dropped 25 bucks in your development pot, Shamus.
    There will be another 25 when you release it. It’s looking fantastic. AND it has modding! How cool is that!

  19. Footage lost? Nooooooooooo! I missed it! D:

    Oh well.

    1. Paul Spooner says:

      I feel you man! I was “there”, but I was also at work, so I mostly had the chat popped out and listened to the discussion. Got to see a few seconds here and there of gameplay, but I missed most of that too.

      In the big picture though, I don’t think you “missed” a huge amount. The gameplay was just about what I had imagined from the blog posts. Most of the discussion was things like “Wow, I was almost sure I had fixed that bug.” and “This game looks pretty fun.”
      And it’s true; It DOES look pretty fun.

        1. MrGuy says:

          Pfft. As we all know, nobody here really wants to watch Josh play videogames in real time.

  20. WILL says:

    How about some enemies that are scared of your flashlight? They would try and crash into you but flee when you shine your flashlight in their direction. It’d be some cool crowd management, and would look cool in very dark levels.

    1. MrGuy says:

      Coffee? I love coffee!

    2. David H. says:

      WILL, I had a similar thought: enemies that are the same color as the dark background would be invisible when sneaking up behind you, but would be visible in the cone of the flashlight, which would make them scatter or vulnerable to being zapped.

      The obvious solution is constant spinning and firing, so an enemy that is attracted to player activity (sound of guns, or light of flashlight) might limit that player behavior.

  21. Deoxy says:

    As I’ve said several times in this series, I think you could easily use this game engine you’ve made to make several games, even ones of different genres, and the more you show of it (like above), the more I think that.

    Lighting changes, number/style of enemies changes, and minor variations in the player’s capabilities and/or upgrade choices could shift it easily from bullet hell/standard shooter to survival or even survival horror, to exploration/RPG, to runner-style game (large nasty behind you, for instance), all with the same control scheme and basic engine under the hood.

Thanks for joining the discussion. Be nice, don't post angry, and enjoy yourself. This is supposed to be fun. Your email address will not be published. Required fields are marked*

You can enclose spoilers in <strike> tags like so:
<strike>Darth Vader is Luke's father!</strike>

You can make things italics like this:
Can you imagine having Darth Vader as your <i>father</i>?

You can make things bold like this:
I'm <b>very</b> glad Darth Vader isn't my father.

You can make links like this:
I'm reading about <a href="http://en.wikipedia.org/wiki/Darth_Vader">Darth Vader</a> on Wikipedia!

You can quote someone like this:
Darth Vader said <blockquote>Luke, I am your father.</blockquote>

Leave a Reply to Jarenth Cancel reply

Your email address will not be published.