Breaking the System: Recreating the entire first level of Doom in Twine.

Hey everyone, Nick here!

Over the weekend I took some time to bring to life a game idea that I've had sitting in the back of my mind for a very long time. It's something that I had wanted to save for this year's 7DFS jam (a week-long game jam where everyone makes first person shooters), but when I recently found out that there wouldn't be a 7DFPS this year I decided to just go ahead and make it.

I made a made a Twine version of the first level of Doom. It's called Doom.txt and you can play it here.

If you're not familiar with it, Twine is a game engine specifically made for building interactive stories. You write a series of passages and can link them together, usually giving the player some options for branching narratives, but generally it's pretty straightforward and the story progresses in a linear fashion. I'd never used it before, but to its credit it was remarkably easy to learn the basics. The problem was that what I needed to do was about as far from the basics of Twine as you could probably go: I needed to make a full combat system.

I knew that I obviously couldn't make real-time shooting work, but I did at least want to give the combat system a bit of depth. What I eventually decided on was that in combat you would have two options: stand still and shoot a lot, or move around to dodge enemy attacks while shooting a bit. If you just shot, you would deal full damage to one enemy and receive full damage from all enemies, but if you chose to dodge you would deal half damage to one enemy but receive half damage from all enemies. The idea was that this would simulate a lot of the difficulty of controlling the original Doom with the keyboard only, back before using the mouse and keyboard simultaneously was the standard way to control PC games. On top of all of that, I also needed to create an approximation of how the armor system in Doom works, and I needed to make the system adaptable enough that you could fight and take damage from multiple different types of enemy at once.

Part of the challenge here was that I wanted to see if I could do all of this using only the basic Twine commands. If you look at the outline below, you'll see that there are some statements (bottom-left and top-middle) that have no connections to anything else. These are my functions. Twine has a command called "Display" that lets you pull in the text from another passage without exiting the current passage that you are in. What I discovered though, is that you can create passages that display no text, and instead simply contain the formulas that you use to calculate changes. So, if I set a variable, "Display" the calculation, then check the variable again within the same original passage, all of the proper calculations will have been done.

The full outline of Doom.txt

For example, here's the entire look for firing your gun at an imp. (there's also a diagram below)

  • When you select the "Fire" passage from the combat page, it first sets the dodging variable to false, then calls the "Shoot Imp" passage.
  • This then calculates how much damage you deal by grabbing the damage of your weapon (which is defined at the very beginning of the game) and then checking to see if you are dodging to know if it should halve the damage.
  • The system checks how many imps there are in the room and applies the damage to the correct one. If the damage exceeds the imp's health, it removes one from the total and prints a message that the imp has died.
  • The "Fire" passage then continues. It checks to see if there are any enemies left to counter-attack, and if so, runs the "Shot by Imp" passage.
  • This sets calculates how much damage is being dealt by multiplying the imp's damage by the number of imps in the room. If there were multiple different types of enemies in the room, the "Fire" option would run a different "Shot by" passage for each different type of enemy.
  • The "Shot by" passage then runs a general "Take Damage" passage that can be called for any type of damage source.
  • The "Take Damage" function checks armor and dodging to calculate how much damage you take.
  • The "Fire" passage then reaches it's final line, which just displays the combat options again, but this time the variables have all been updated to reflect the new state of the player and enemies.

If all of that just looks like regular scripting to you, that's because it pretty much is. Twine has all of the basic calculations that you need, but normally lacks the ability to organize them into functions. Setting a damage variable and then telling Twine to (Display: "Take Damage") is exactly the same as writing TakeDamage(takenDMG) in C#.

It just goes to show you how much can be done with even simple tools when you start to think outside the box.