How Spelunky Creates Amazing Unexpected Situations
From code to chaos
This is The Mechanic, where Alex Wiltshire invites a developer to help him put their game up on blocks and take a wrench to hack out its best feature, just to see how it works.
The arrow trap that shoots the croc man that causes him to telefrag you. Being caught mid-jump by a boomerang that juggles you towards a spike trap, leaving you stunned in front of it until it springs. Shopstorm.
These are not necessarily the noblest events in Spelunky, but they’re surprising, funny, fascinating, and entirely consistent and logical and correct. They might not be exactly your fault, but neither are they, really, the game’s fault. They’re the result of a big reason – the big reason? – why Spelunky is amazing:
THE MECHANIC: How every object in Spelunky has shared fundamental traits
That’s a pretty snappy name. It’s the root of all the interactions between the different items, traps, monsters and other objects that populate its world, and all the fortuitous, hapless, funny, enraging, and wondrous things that can happen as a result.
And it exists primarily for simplicity. "I think at one point I was asked how I could possibly handle all of the cases that come up in Spelunky, like when this item hits that," programmer of Spelunky’s 2013 remake, Andy Hull, tells me. The answer is that every object in the world has default behaviours and properties. For instance, Olmec is actually a glorified push-block. When the spiked balls in Hell are sent flying, they become boulders. The way hawk men, croc men and shopkeepers bounce around when enraged – they share the exact same movement abilities.
"When you’re coding a game you think about what traits things share naturally, right?" says Derek Yu, creator of the original Spelunky. "You want your code to be efficient; you don’t want to be reinventing the wheel for every new thing."
All Spelunky’s richness, therefore, is down to a programmer’s shortcut, and a way of structuring code. And all its objects are either an item or a monster. The main difference is that monsters automatically inherit extra variables for getting around. "They’re pretty similar," says Hull. "It kept our code organised and we didn’t get weird things like push-blocks running around shooting shotguns."
So Olmec, the core (kind of) boss of the game, is an item, not a monster. He’s a push-block, but has special-case properties that make him distinct, such as being able to move independently, not be pushable by the player, to have other push-blocks stacked on his head, and to be able to crush these blocks if he jumps and the ceiling is too close, so he doesn’t get stuck. If you want to see what the code looks like, Yu and Hull gave us access to some of Spelunky remake’s source code, which gives some insight into what makes Olmec and the shopkeeper tick.
As far as the game’s concerned, it just goes down the list of properties to figure out how to resolve any particular situation. Is this a heavy object? A light object? A breakable object? Can it take damage? If something breakable hits a wall, then the thing breaks. If the player whips the ghost, then – well.
"It might be good to mention the ghost, because it’s a case where the default led to a pretty interesting situation," says Yu. The ghost was meant to be invincible, and as far as Yu knows, in his 2008 original, it is. But people figured out how to kill the ghost in the remake, and the way they did this is down to these shared traits.
The ghost appears when you’ve been on a level for over two and a half minutes, if you take the Crystal Skull, or if you destroy too many of Kali’s altars, and it’s a monster with a few special-case properties. Objects don’t collide with it, they don’t deal damage to it, and it has 9999 hit points. "I think we put that in probably just as a failsafe, right, Andy?" says Yu.
"Right, yeah, when I was implementing it, the hitpoints had to be filled in," Hull replies. But he was also aware that there might just be *something* that could damage it, so he didn’t give it one hitpoint. 9999 seemed right.
"It’s definitely not something we thought about very hard," says Yu. "But of course you can never play it safe enough, I guess."
It turned out that the ghost could be damaged by lava. Lava causes any monster to lose 99 hit points when it touches it, an instant kill. One day a player noticed the ghost hit lava and display a hit effect, something it had inherited from being a monster, and the player realised it was taking damage. And so, soon enough came the discovery that by moving the ghost in and out of lava 101 times, it could be killed.
Yu and Hull were happy. "It’s a cool property of the game!" says Yu. "It doesn’t really bother us because it becomes part of the lore, like this supposedly unkillable thing is killable. That someone worked out how to kill it was pretty nifty."
Another example is the Moai, a statue found in the Ice Caves that’s part of the City of Gold route through the game. Players are meant to have the Ankh item and then kill themselves to find themselves resurrected at the Moai with a new item, the Hedjet, and a door having opened in the Moai’s mouth. The Moai itself was meant to be indestructible, and it would be if it wasn’t for the ball and chain.
"It’s crazy, right, because it’s meant to be a punishment!" says Yu. The ball and chain is attached to you if you destroy too many Kali altars, and it makes it hard to move – you either have to drag the ball behind you, or pick it up, meaning you can’t carry something else. But what the ball also does is to tear through blocks if it’s placed on a ledge and you hang below it, a feature that was meant to stop players from getting stuck.
The Moai is actually an image covered by invisible, invincible floor blocks. And what players discovered, namely BaerTaffy and bisnap, is that the ball could destroy them just like any other. "The floors are set to invincible but the ball and chain doesn’t check that at all," says Yu. "We never considered anyone would try to use it on the Moai."
"So the reason it doesn’t check the invincibility flag is that the only other blocks in the game that are invincible are the frame blocks around the level," says Hull. "I knew when I was coding the ball that there was no way to get it on top of the frame with the player below it. So there was no sense to check it. That was the oversight. There was somewhere else with invincible blocks! It just wasn’t something we foresaw – there are so many interactions to consider that it’s pretty much guaranteed that something would fall through the cracks." Still, again, it became part of the game’s lore, and a critical component of the utterly amazing solo eggplant run, and so the only thing Yu and Hull did was to change the graphic for the Moai to indicate the damage the ball does.
Mostly, though, this system of reuse is about the opposite of glitches and bugs – it’s about enabling interesting interactions, and it also made development and testing easier. "We only had to find the things that we didn’t think of that weren’t working in an acceptable way already," says Yu.
"Some people say that it would be better if we handcoded exclusive systems for handling interactions, but the problem is that you’re segregating them from the rest of the mechanics of the game," says Hull. "If it interacts with the rules already, even though it’s slightly not as good, it’s going to have so much more play with the rest of the game. It’s more interesting immediately, which is way more valuable than that extra bit of polish you can get with a separate implementation."
So there are the revolving spiked balls in Hell, which fly off when detached and smash through the level like a boulder – because that’s what they functionally become. Each item is simply defined by an identifying number, so Hull could tell the spike ball that it’s now a boulder; it’d keep its visual properties but suddenly start to interact with the rest of the game as a boulder. The freeze ray is similar – the same projectile (which to the game is an item) comes out of both the gun and the mammoth’s trunk. It even comes into play in the cutscenes, which run entirely in the game engine. In the intro, the character is placed on invisible blocks with a sand graphic in front, while the game simulates a controller input to the right.
"It actually gives us an interesting interaction at the end of the game where you’re shot out of the volcano, you fall on to the ground and you drop the item you were holding because you fell too far. But if you have a parachute it’ll actually deploy and float to the ground." Yu laughs – it’s the best laugh, a crazy infectious giggle.
"And you can keep your item through the rest of the cutscene that way, which is pretty amazing, right?" continues Hull. "It wasn’t something we planned out, but it’s a nice little touch. The game’s still functioning, even in the end when you can’t even control it any more. It reinforces how the whole game works."
"I’m pretty sure we had bugs were you could land in that ending cutscene and you could actually die, because you take fall damage," says Yu.
"The fix was pretty lame," says Hull. "It just gives you an extra life at the beginning of the scene and you’ll lose it when you hit the ground." Yu laughs again.
"And then the blood!" says Hull. "When I did the mummy vomit, the vomit itself is just actually just green blood. We were putting it in, late in development, and I was like, what do we have that already does the thing I need? Well the blood, vomit, same difference. It’s a liquid that can squish around and bounce or whatever, so I just made a new graphic of the blood turned green and made vomit. Of course, later in development, we put in the Kapala that collects blood. We didn’t even realise the interaction existed until the game was out; people were collecting mummy vomit and it counted as blood. I’m like, of course it does. It *is* blood. It just became part of the lore."
"People basically farmed the mummies, getting them stuck on a ledge above them with a hole the vomit could fall through, so they’re just endlessly throwing up on to the player until they had 99 health!" says Yu.
This is why Spelunky is great, but to be clear, Spelunky is far from being the only game to be coded in this way. Dwarf Fortress takes it to a whole extra level, and it also underlies games from Skyrim to GTA V. But there’s something extra special about the way Spelunky works. Perhaps because it’s an action game and also very legible, with clear cause and effect. And partly because its objects are so defined in their properties. When something unexpected happens, everyone really notices it, even Yu. "I think things that surprise even the designers themselves make the world feel that much more real, versus something that was designed."
Derek Yu has written a book about how he made Spelunky that comes out on March 29. You should definitely pre-order it now.
Spelunky's entity code
To give you an idea of how Spelunky understands its objects’ properties, Derek Yu and Andy Hull have given RPS access to part of the remake’s source code, something that hasn’t been revealed before. Here are the defining characteristics of Olmec and the shopkeeper.
Shopkeeper
The ‘tough’ flag denotes that the shopkeeper is impervious to whip attacks. You can also see what governs the fact he has a shotgun and how you can’t pick him up.
case ENT_TYPE_MONS_SHOPKEEPER:
em = create_entity_monster(x, y, type, TEX_MONSTERS, ANIMSET_SHOPKEEPER, MONS_SIZE_SMALL, 0.2f, 0.5f, 0.4f, 10, 1, MONS_WEIGHT_MED, 6);
em->canBeStunned = true;
em->tough = true;
em->isHoldable = false;if (wantedLevel > 0)
{em->state = ENT_STATE_PATROL;
em->heldEntity = (EntityItem *)create_entity(x, y, ENT_TYPE_ITEM_SHOTGUN, true);
em->heldEntity->holder = em;
// em->heldEntity->heldOffset = Vector2(0.25f, -0.35f);
em->hasGun = false;}
else
{em->state = ENT_STATE_IDLE;
em->hasGun = true;
em->passive = true;}
e = (Entity *)em;
break;
Olmec
You can see the flags that tell the game that you can’t web him, hold him, crush him – the rest of his properties are defined by his true nature as a push-block.
case ENT_TYPE_ITEM_OLMEC:
ei = new EntityItem(x, y, 1.0f, 1.0f);
ei->subtype = type;
ei->texture = textureManager->get_texture(TEX_OLMEC);
ei->set_tile(0);
ei->height = 4.00f;
ei->width = 4.00f;
ei->collisionDown = 4.0f/2.0f;
ei->collisionUp = 3.0f/2.0f;
ei->collisionSide = 3.5f/2.0f;
ei->solid = true;
ei->stunned = true;
ei->canExplode = true;
ei->isHoldable = false;
ei->heavy = 0.0f;
ei->collideWithActive = true;
ei->invincible = true;
ei->canExplode = false;
ei->depth = DEPTH_CLIMBABLE – 0.5f;
ei->crushable = false;
ei->isReflective = true;
ei->state = ENT_STATE_RECOVER;
ei->canBeWebbed = false;
//ei->collideWithFloor = false;
//ei->flying = true;
e = (Entity *)ei;
levelManager->add_active_floor(ei);
break;