A series of events;
An important part of any game is level design and the game play loop of the level. One of the reasons I chose Quake / Darkplaces is because of how mod-friendly the map making process is...and how well documented it is. Back in my high school days, before learning to code, I had dabbled with making maps for Quake II. iD Tech games all generally used a tool called GTKRadiant for level creation, this was an all-in-one map maker for their games. The tool allowed users to created the full 3D spaces of a Quake level, place map objects like weapons, monsters, and player start zones. It also is where the logic of a map was created. These logic map objects were under sections called trigger_ and func_. They are responsible for making doors that move, elevators, changing maps, etc.
There’s an old joke about iD games using the map design called a ‘monster closet.’ A monster closet is a part of the map where, behind a seemingly normal piece of wall, a monster or two is just sitting there. When the player gets close, or triggers a specific event, the wall opens up surprising the player and dropping monsters on them. This is a joke because if you look at the wall, it doesn’t look like it should logically have any space behind it and what exactly were the monsters doing behind the wall? Taking a smoke break?
For this article, let’s focus on the how of the monster closet. The user in GTKRadiant would create a space behind the wall. They would place monster entities into that space. Next they’d turn the ‘normal’ wall piece into a door. The door, like the monsters, is another entity. Rather than relying on a specified model though, the door entity uses the map geometry its assigned as its model. Then the map designer would create another map object; trigger_once, point it to the ‘wall’. The Quake C would then say, “when the player entity touches trigger_once, open the door.” In this regard, we can say that the map file holds a lot of the game state and logic.
I believed that at least for map design and creation, Quake was a good move. Creating custom entities that the editor can place was fairly straightforward. GTKRadiant uses a text file to define all the possible map entities that can be placed before compiling the map into a format that Quake can read. By adding my own battleMETAL objects to this text file, I found it was easier to create maps specifically for battleMETAL - including the really cool aspects like Nav Points and Objectives ( we’ll get to those in a bit). Unlike AI which I covered previously, the map logic schema for Quake is surprisingly flexible. The map editor GTKRadiant uses a series of ‘definition’ files containing text, usually called entities.def. This file contains the definitions for entities to be placed on the map.
The definitions must link to function names in the Quake C code. For example, the definition for an enemy AI looks like this in the .def file:
/*QUAKED unit_human_mech_sniper (1 0 0) (-14 -14 -20) (14 14 20) <spawn flags>
<description text here>
*/
The “QUAKED” is the head tag for an entity definition, the next piece is the function name “unit_human_mech_sniper” which should be defined in the Quake C code exactly the same wording. From what I can tell, the map compiles the entities and converts the function name either to a reference or compares the function name to all the functions in the code to find a match. Once the game finds the right function, it creates an entity and then calls this function on that entity. Whatever code is written in unit_human_mech_sniper is then run immediately.
The next series of arguments are as follows: color in GTKRadiant, and the bounding box size. The color is for color coding the entity in the map editor, making it easier to tell entities apart from each other. As for bounding box size, that is also shown in the editor, but will also be passed to the engine when the entity is spawned. Quake C code can override the bounding boxes if it is needed. Spawnflags are an important piece of data for both the editor and the engine. Flags are a data storage concept used in general coding. They hold a series of “yes or no” sets in a single variable usually by using binary math. For our purposes here, every entity has a spawnflags field which can set up certain “yes or no” choices when the entity is created. The exact wording is up to the coder, so one entity might have DROP_TO_FLOOR for a spawnflag while another has START_INACTIVE. The map editor will render these as checkboxes.
Finally there’s a the descriptor section, it is here that normal text will be rendered in the map editor but has no relation to code in the game. This section is for any relevant info about creating, placing , and using a map entity. Altogether this system is fairly easy to understand and extend for modding. Debugging is nice as well, if engine can’t parse the entity from the map, it simply removes the entry and logs the result into the console.
Now that we have context for the maps, we can do quick overview of the game logic itself. Every gameplay instance in Quake takes place on one of these maps files. The map is loaded up when the server switches to the desired map file. In Quake C this before the Main() function is run, the main is the entry function for the Quake C. StartFrame() is any code you want to run before the engine runs all other code in that slice of server time. From here the game instance if open-ended, where the ‘end’ of a map can be determined in a few different ways. Vanilla Quake used round timers, kill counts, and map entities to end a game instance and load the next map. For battleMETAL I needed something a bit more sophisticated, seeing as how the game is descended from different DNA.
battleMETAL is inspired by mech sims, in which part of the simulation was of more authentic military scenarios. This meant that the player is given a set number of ‘objectives’ to achieve before the scenario is ended. The objectives sometimes were destroying things, but other times it was just visiting a location on the map or protecting a base of buildings from an attacking enemy. So, to achieve a similar setup as I created some custom map objects - the Objective, and the Nav Point. I also extended the game engine to load in a text file called a “mission file.” The world map entity would have a variable that pointed to a specified mission file that was loaded to the player’s view when they connect to a map. The code I wrote also sends a unique objective ID for each objective on the map. This is linked to a list of objectives in the mission file. Together, they communicate objectives to the player both during the briefing and game play.
Of all the things I found to be difficult with battleMETAL, this was surprisingly not one of those things. The configuration of GTKRadiant is fairly well documented and extending the functionality of making maps for my specific game turned out to not be a lovecraftian nightmare, unlike other parts of the project….well except for those 3D terrain meshes…<sigh>.
A hobby blog covering topics like comics, video games, video game development, and tabletop gaming
Monday, December 31, 2018
Wednesday, December 12, 2018
battleMETAL - Why that was a bad idea, a terrible idea - Part 5 - AI deux
Ah, that army...well played.
Now that we have some context about how the Quake AI code works, we can look into why it was a no-go for battleMETAL. The first hiccup is how battleMETAL builds its AI entities. Like the player, AI entities are built using the same factory pattern code. The entity is actually a bunch of entities strung together - things like torsos, arms….legs. battleMETAL units are not animated as a complete distinct model. Because they are collection of parts, each part has its own animation. This breaks the original Quake C architecture and its reliance on the frame macros (as was explained in the last post).
The unit piece with the most animation was the leg entity for mechs. However, the animations for the legs are not authoritative to other entity states. Therefore most code hooked up to leg frame animations is not decisive, but rather cosmetic; reacting to game events as they happen. Just having this one attached entity threw off the need for using Quake frame macros. A different solution was needed, and that was using a state machine. State machines are abstract computer designs that describe a series of ‘states’ for an arbitrary object. An AI object is a good candidate for state machines, where the state in the state machine could be a set of behavior or actions that the AI performs.
Mind you, this was after a few other attempts to split the difference with Quake’s original AI implementation. The original code also had a set of generic AI behaviors that most monsters would invoke in about the same sequence. There existed functions like ai_walk, ai_run and so on, these were called on a per-frame basis and packed into a frame macro for the monsters. Additionally, other functions were used by the AI during gameplay to assist in calculating attacks, spotting the player, and moving around defined node entities.
All of this existing functionality was put off to the side, to be reworked into the new AI design. Using a state machine, I broke down almost all the possible actions that an AI could take into 2 categories - states, and actions. I then declared a state to be a collection of actions. I credit my cousin Eamonn with the idea of making actions were atomic as possible ie each action as a function call would only do 1 specific thing. For example, the walk state would be the following actions:
Scan for enemy
Move to patrol node
And that would be just the walk state. If the AI spotted a valid target in Scan For Enemy, the the AI would transition to the Run state. The code marks each state with an integer, 0 = Walk, 1 = Run. Each entity then has a series of member function pointers to each state like mech.st_walk(). When the AI is created, the code tells the AI which version of of st_walk to use. In the case of mech units, they’d be assigned the mech_walk() function.
Then, the AI keeps track of which state it is in by having a variable called .attack_state, when the AI executes its code every frame it looks up which function to run by using its state variable. Once this paradigm was implemented, I took the idea to the next step - organizing collections of states into ‘behaviors’ or ‘unit types’. Tanks and Mechs would clearly need their own sets of state functions because of how different these units are. Using this approach I found it was easy to implement even more unit types like turrets and non-combat vehicles. Each behavior set, although using the same list of actions has their own logical flow for states, and changing the state they are on. The result is a fairly flexible and open-ended design for adding AI and its behavior.
There are still some limits due to the foundation of this code being on Quake. A big one is AI movement. The Quake C uses an engine function to actually execute the movement of bots and no physics operations are carried out, this results in stiff movement of bots that doesn’t really look natural. This also results in bots running more slowly during gameplay, their per-frame updates are less because of how proportionally expensive it is for the game engine to move bots. In the end I’m ok with the outcome of the AI, despite its limitations I was able to add a few diverse unit types to the game. This diversity helped make the game more fun and engaging to play.
Now that we have some context about how the Quake AI code works, we can look into why it was a no-go for battleMETAL. The first hiccup is how battleMETAL builds its AI entities. Like the player, AI entities are built using the same factory pattern code. The entity is actually a bunch of entities strung together - things like torsos, arms….legs. battleMETAL units are not animated as a complete distinct model. Because they are collection of parts, each part has its own animation. This breaks the original Quake C architecture and its reliance on the frame macros (as was explained in the last post).
The unit piece with the most animation was the leg entity for mechs. However, the animations for the legs are not authoritative to other entity states. Therefore most code hooked up to leg frame animations is not decisive, but rather cosmetic; reacting to game events as they happen. Just having this one attached entity threw off the need for using Quake frame macros. A different solution was needed, and that was using a state machine. State machines are abstract computer designs that describe a series of ‘states’ for an arbitrary object. An AI object is a good candidate for state machines, where the state in the state machine could be a set of behavior or actions that the AI performs.
Mind you, this was after a few other attempts to split the difference with Quake’s original AI implementation. The original code also had a set of generic AI behaviors that most monsters would invoke in about the same sequence. There existed functions like ai_walk, ai_run and so on, these were called on a per-frame basis and packed into a frame macro for the monsters. Additionally, other functions were used by the AI during gameplay to assist in calculating attacks, spotting the player, and moving around defined node entities.
All of this existing functionality was put off to the side, to be reworked into the new AI design. Using a state machine, I broke down almost all the possible actions that an AI could take into 2 categories - states, and actions. I then declared a state to be a collection of actions. I credit my cousin Eamonn with the idea of making actions were atomic as possible ie each action as a function call would only do 1 specific thing. For example, the walk state would be the following actions:
Scan for enemy
Move to patrol node
And that would be just the walk state. If the AI spotted a valid target in Scan For Enemy, the the AI would transition to the Run state. The code marks each state with an integer, 0 = Walk, 1 = Run. Each entity then has a series of member function pointers to each state like mech.st_walk(). When the AI is created, the code tells the AI which version of of st_walk to use. In the case of mech units, they’d be assigned the mech_walk() function.
Then, the AI keeps track of which state it is in by having a variable called .attack_state, when the AI executes its code every frame it looks up which function to run by using its state variable. Once this paradigm was implemented, I took the idea to the next step - organizing collections of states into ‘behaviors’ or ‘unit types’. Tanks and Mechs would clearly need their own sets of state functions because of how different these units are. Using this approach I found it was easy to implement even more unit types like turrets and non-combat vehicles. Each behavior set, although using the same list of actions has their own logical flow for states, and changing the state they are on. The result is a fairly flexible and open-ended design for adding AI and its behavior.
There are still some limits due to the foundation of this code being on Quake. A big one is AI movement. The Quake C uses an engine function to actually execute the movement of bots and no physics operations are carried out, this results in stiff movement of bots that doesn’t really look natural. This also results in bots running more slowly during gameplay, their per-frame updates are less because of how proportionally expensive it is for the game engine to move bots. In the end I’m ok with the outcome of the AI, despite its limitations I was able to add a few diverse unit types to the game. This diversity helped make the game more fun and engaging to play.
Monday, December 10, 2018
battleMETAL - Why that was a bad idea, a terrible idea - Part 4 - AI
Oh yeah? You and what army?
Another tarpit that consumed many months of dev time during this project was the AI. Quake was never known for its AI, which was only ever serviceable. The problem was thinking that the AI was extensible for the needs of battleMETAL. Quake AI is good enough for ‘basic’ FPS games. The code available allows for AI that can follow defined paths, attack and ‘fight’ with the player, and some limited hunting routines. All of this code is laid out in a few generic functions available to all AI and any monster-specific code is defined in that monster’s code file. Quake monsters are fairly capable of engaging the player in the tighter, more confined environments that Quake uses (big surprise). However I estimated that expanding level geometry size would not have had a great impact on the AI code, not realizing just how tightly engineered the AI code was.
The first wrinkle encountered was one part of how the AI code is implemented in Quake itself. Quake C functionality sits atop the C code of the engine, with limited access to engine-level code (by design). This equates to the Quake C doing the bulk of the AI work here. This means that AI execution is a tad slower due to the nature of Quake C being a parsed language versus running straight C code. I accepted these limitations due to lack of foresight and with the mistaken assumption that I could reuse a lot of the original code.
First hurdle turned out to be understanding the way the AI code was written. For example, in the m_grunt we see the following function:
void() army_stand1 [ $stand1, army_stand2 ] { ai_stand(); };
For me, that looked really wierd, and I had not encountered similar code in the project other than some simple animations for sprites. Let’s break this one down; what we’re looking at is a code macro. Code macros are like syntactic sugar, which is lingo for ‘shorthand’. Much like other abstract languages, such as math, have shorthand notations when writing large segments and code can also have shorthand. Most of the code shorthand is usually already baked into the language syntax itself, such as var foo = a_value. This is shorthand for the computer assigning the value a_value to the variable foo.
But what if you found yourself writing the same code many times, and would rather reduce this code to a simpler syntax? This is where macros come in. Macros can be user-defined, and customized according to the needs of the programmer. For Quake C, iD created the macro I mentioned earlier, for use with animations being synchronized to game code. Originally the animation frame rate was tied to the game’s frame rate, mostly because computing power was very limited back in 1996. It didn’t matter that the animation was tied to the game’s update rate because usually that update rate didn’t surpass 20FPS on most systems. The second part was tying game code to the animations themselves; what should happen when the Frame X of the animation is played?
So looking back to the example macro, this is what it means
Army_stand1 is a function definition.
The [ and ] are part of the Frame macro, this has 2 arguments, the frame number of the current frame and the next function to go to.
$stand1 is the frame number of the first frame of the ‘stand’ animation.
Army_stand2 is the name of the function that should be executed next after this function( army_stand1 )
The { and } then define the function body for army_stand1, what code should be executed on this frame.
When dealing with models that have dozens of frames and multiple sets of animations, this is a clever way of organizing the overall flow of the entity’s animation and code. Knowing the schema for the macro, you can now read through a model’s game logic quickly and easily. There is a cost however.
The macro is more rigid than writing the code out yourself every time. What I mean by this is that the frame macro is laser-focused on solving the animation / code synchronization issue. What happens when I don’t need any code to run on a frame of animation? I still need to define the macro for that frame. This creates a problem at-scale when model animations start reaching the dozens or hundreds of frames. That was the main reason why I, once understanding what the macro does, ditched it in favor of my own approach.
I won’t go into super detail about all the attempts to bring the AI to life - there were 9 major runs of work to arrive at the solution I went with, spread across 3 distinct solutions. The 3rd solution was what ended up working, and even then I have ways it could have been improved. This topic will continue into at least a second post so I can discuss the working solution...
Another tarpit that consumed many months of dev time during this project was the AI. Quake was never known for its AI, which was only ever serviceable. The problem was thinking that the AI was extensible for the needs of battleMETAL. Quake AI is good enough for ‘basic’ FPS games. The code available allows for AI that can follow defined paths, attack and ‘fight’ with the player, and some limited hunting routines. All of this code is laid out in a few generic functions available to all AI and any monster-specific code is defined in that monster’s code file. Quake monsters are fairly capable of engaging the player in the tighter, more confined environments that Quake uses (big surprise). However I estimated that expanding level geometry size would not have had a great impact on the AI code, not realizing just how tightly engineered the AI code was.
The first wrinkle encountered was one part of how the AI code is implemented in Quake itself. Quake C functionality sits atop the C code of the engine, with limited access to engine-level code (by design). This equates to the Quake C doing the bulk of the AI work here. This means that AI execution is a tad slower due to the nature of Quake C being a parsed language versus running straight C code. I accepted these limitations due to lack of foresight and with the mistaken assumption that I could reuse a lot of the original code.
First hurdle turned out to be understanding the way the AI code was written. For example, in the m_grunt we see the following function:
void() army_stand1 [ $stand1, army_stand2 ] { ai_stand(); };
For me, that looked really wierd, and I had not encountered similar code in the project other than some simple animations for sprites. Let’s break this one down; what we’re looking at is a code macro. Code macros are like syntactic sugar, which is lingo for ‘shorthand’. Much like other abstract languages, such as math, have shorthand notations when writing large segments and code can also have shorthand. Most of the code shorthand is usually already baked into the language syntax itself, such as var foo = a_value. This is shorthand for the computer assigning the value a_value to the variable foo.
But what if you found yourself writing the same code many times, and would rather reduce this code to a simpler syntax? This is where macros come in. Macros can be user-defined, and customized according to the needs of the programmer. For Quake C, iD created the macro I mentioned earlier, for use with animations being synchronized to game code. Originally the animation frame rate was tied to the game’s frame rate, mostly because computing power was very limited back in 1996. It didn’t matter that the animation was tied to the game’s update rate because usually that update rate didn’t surpass 20FPS on most systems. The second part was tying game code to the animations themselves; what should happen when the Frame X of the animation is played?
So looking back to the example macro, this is what it means
Army_stand1 is a function definition.
The [ and ] are part of the Frame macro, this has 2 arguments, the frame number of the current frame and the next function to go to.
$stand1 is the frame number of the first frame of the ‘stand’ animation.
Army_stand2 is the name of the function that should be executed next after this function( army_stand1 )
The { and } then define the function body for army_stand1, what code should be executed on this frame.
When dealing with models that have dozens of frames and multiple sets of animations, this is a clever way of organizing the overall flow of the entity’s animation and code. Knowing the schema for the macro, you can now read through a model’s game logic quickly and easily. There is a cost however.
The macro is more rigid than writing the code out yourself every time. What I mean by this is that the frame macro is laser-focused on solving the animation / code synchronization issue. What happens when I don’t need any code to run on a frame of animation? I still need to define the macro for that frame. This creates a problem at-scale when model animations start reaching the dozens or hundreds of frames. That was the main reason why I, once understanding what the macro does, ditched it in favor of my own approach.
I won’t go into super detail about all the attempts to bring the AI to life - there were 9 major runs of work to arrive at the solution I went with, spread across 3 distinct solutions. The 3rd solution was what ended up working, and even then I have ways it could have been improved. This topic will continue into at least a second post so I can discuss the working solution...
Monday, August 27, 2018
battleMETAL - Why that was a bad idea, a terrible idea - Part 3 - Collision Systems
About those multi-part models
Collision systems a core part of any video game engine. From the first version of Pong up to the latest games on any platform, these systems are responsible for processing impacts between game objects in the game space. In general, like most computer code, collision systems have to be built at some point because a computer does not intrinsically know when two ‘objects’ have hit each other. The complexity of these systems can vary wildly depending on how detailed the game wants to get for collisions and how complicated the game itself is. Quake’s collision system is fairly simple for a 3D FPS.
The collision system for Quake starts with a series of boxes, or specifically, axis-aligned bounding boxes (AABB). Woah there’s some jargon, AABB simply means that the collision model for a game object is a box (technically a rectangle because box proportions can be altered). The axis-aligned part means that this bounding box does not rotate. The model can change angles all it wants, but in the eyes of the collision system, it never rotates - this is an important distinction.
Here we see Quake Guy and his AABB. Whenever Quake Guy hits another entity, or is hit by entity, the game doesn’t check the Quake Guy model for this collision. Instead, the game sees if Quake Guy’s AABB was contacted; which is mathematically simpler and therefore a faster calculation. The downsides to simple AABB is the lack of detail in collision detection, this can be best seen when Quake Guy is standing on a staircase or slope. Notice how only a few corners of the AABB are touching the ground?
Let’s make one quick jump back to the previous post about models, specifically the models that make up a mech in battleMETAL. These entities use a move type called movetype_follow which tells the game to always keep the entity’s center as an offset from a code-defined center in game space. There’s one important detail here, entities with movetype_follow cannot have a collision AABB. As a restriction, this exists because the use-case for movetype_follow is for cosmetic models put on the parent entity - weapons, clothes, etc. For battleMETAL, this means that the various mech parts that are seen on-screen don’t actually have a link to the collision system. Hm, but battleMETAL requires a feature where mechs various parts can be blown off the mech.
The solution implemented is a bit obtuse to manages to satisfy the requirement and still work. The player seen on screen is a heavy composite of several code pieces and models. To start, the player doesn’t actually have a model bound to its entity structure. Rather, the visual components - the mech piece models - are all separate entities using the movetype_follow functionality to appear in the correct location during gameplay. From the perspective of the collisions system however, these pieces are ‘wrapped’ inside the player’s main AABB. When a player is struck by a weapon, the following is executed in the code:
- Determine the gamespace location of the hit, this is a 3D vector of X Y Z
- Query the gamespace location of every mech piece that the player-being-hit owns.
- Find the closest mech piece to the hit origin.
- Closest mech piece becomes the area that is ‘hit’.
There are some quirks to the written algorithm as well.
- Ignores the volume of each model piece on the mech
- An imprecision thats hard to detect
- Effects on mech design
Tuesday, August 14, 2018
battleMETAL - Why that was a bad idea, a terrible idea - Part 2 - Model Mishaps
Compromises were made
Models are a good enough place to start about things that went awry. The most complicated models so far in battleMETAL are the star units themselves, the metals. The game requires that certain game units have multiple hit locations that can be destroyed before the entire unit is killed. In the case of the mechs, the locations are the center torso, left and right torsos, left and right arms, and the legs. 5 locations total for every mech unit. To better communicate damage to players, it was decided that arms and torsos should be ‘blown off’ when the health of those areas reach zero. This translates to each mech piece its own disconnect mesh, like a lego piece.
Most FPS games don’t deal with complex vehicles, they are scaled to people-sized players and monsters fighting. The models for each of this units is usually 1 solid, animated mesh. Despite complexity of the model, the fact that model is 1 contiguous piece of geometry makes working with them fairly easy. Having five sub-models that make up one complete mech model gets heavy very quickly. It increases the number of files per game unit, makes coding for game units a bit more complex. More modern game engines can handle this complication using techniques like bone-based modeling. Quake was made slightly before skeletal animation became an industry standard. Half-Life is credited with bringing skeletal animation to the mainstream and Half-Life’s engine is a heavily modified version of the Quake 1 engine.
Darkplaces does allow for skeletal animation but it comes with some severe catches. The quake c code for Darkplaces can utilize bones but only on the client-side. Now why is this a problem? I think I mentioned client-side prediction as an issue in previous posts. The snag with this approach is that the bones are only rendered on the client’s side of the game code. If the bones are only on the client then the location in game-space of those bones becomes an issue due to discrepancies between server and client game states. If Darkplaces came with out-of-the-box client-side prediction for programmers to work with, then this might be less technical debt. As it stands, client-side prediction was left up to the individual coder for some...reason...and not every programmer is up to the task of solving a such a complex issue.
The other possible option for skeletal animation is found in the Quake 3 model format called md3. This model format was made for Quake 3, itself a multiplayer-centric, fast-paced, arena FPS from late 1999. Quake 3 also made an attempt to fix client-side issues of skeletal animation but John Carmack went about it in his own unique way. MD3 models can be given little ‘tag’ triangles (a triangle being made up of 3 vertices in the model). Each ‘tag’ would then be given a unique name within the model for later reference. The technical debt of this approach is how tags are organized. When making a model, the tags have to be made, pointing in the desired direction of orientation for the mesh that will be attached there. The tags themselves are not saved when the model is exported to md3, thus any subsequent work on the mesh will require remaking the tags in their specific location.
For Quake 3 models that only ever had 1 or 2 tags per model, the ‘tag’ method was an ok solution. battleMETAL however, breaks this through complexity of the models in the game. The solution I settled on isn’t exactly performant for multiplayer gaming (thankfully battleMETAL is more a single-player game), but it does work. Remember how every model is an entity in quake c? Entities can be given specified ‘movement’ types to reflect, in the engine, how the entity is supposed to move. Player entities have MOVETYPE_WALK which is for handling player movement and physics. Darkplaces expands this functionality by adding a new MOVETYPE_FOLLOW. This movement mode is for objects that cannot collide with anything, and is for entities that, well, follow another entity.
General use-case for movetype_follow in the context of Quake is things like, having the gun model that a player is holding reflected externally by having a gun entity set to movetype_follow. Using this, battleMETAL treats all mech model pieces as separate entities with the movetype_follow. The locations of the pieces are set in relation to the controlling player’s origin point. In this way, the requirement of having hit locations, and visible damage have been satisfied without resorting to time consuming or costly programming quirks. The code uses the Factory Pattern to turn a mech data file into a mech, including the construction of the model component entities.
battleMETAL was able to use some animation for the mech models in the end. The leg models for every mech are animated in the model creation program and exported to the md3 format when animations are complete. The quake c code then controls the playback of those animations. Although this means animations are comparatively ‘stiff’ when put next to modern games, they are sufficient for the project.
Models are a good enough place to start about things that went awry. The most complicated models so far in battleMETAL are the star units themselves, the metals. The game requires that certain game units have multiple hit locations that can be destroyed before the entire unit is killed. In the case of the mechs, the locations are the center torso, left and right torsos, left and right arms, and the legs. 5 locations total for every mech unit. To better communicate damage to players, it was decided that arms and torsos should be ‘blown off’ when the health of those areas reach zero. This translates to each mech piece its own disconnect mesh, like a lego piece.
Most FPS games don’t deal with complex vehicles, they are scaled to people-sized players and monsters fighting. The models for each of this units is usually 1 solid, animated mesh. Despite complexity of the model, the fact that model is 1 contiguous piece of geometry makes working with them fairly easy. Having five sub-models that make up one complete mech model gets heavy very quickly. It increases the number of files per game unit, makes coding for game units a bit more complex. More modern game engines can handle this complication using techniques like bone-based modeling. Quake was made slightly before skeletal animation became an industry standard. Half-Life is credited with bringing skeletal animation to the mainstream and Half-Life’s engine is a heavily modified version of the Quake 1 engine.
Darkplaces does allow for skeletal animation but it comes with some severe catches. The quake c code for Darkplaces can utilize bones but only on the client-side. Now why is this a problem? I think I mentioned client-side prediction as an issue in previous posts. The snag with this approach is that the bones are only rendered on the client’s side of the game code. If the bones are only on the client then the location in game-space of those bones becomes an issue due to discrepancies between server and client game states. If Darkplaces came with out-of-the-box client-side prediction for programmers to work with, then this might be less technical debt. As it stands, client-side prediction was left up to the individual coder for some...reason...and not every programmer is up to the task of solving a such a complex issue.
The other possible option for skeletal animation is found in the Quake 3 model format called md3. This model format was made for Quake 3, itself a multiplayer-centric, fast-paced, arena FPS from late 1999. Quake 3 also made an attempt to fix client-side issues of skeletal animation but John Carmack went about it in his own unique way. MD3 models can be given little ‘tag’ triangles (a triangle being made up of 3 vertices in the model). Each ‘tag’ would then be given a unique name within the model for later reference. The technical debt of this approach is how tags are organized. When making a model, the tags have to be made, pointing in the desired direction of orientation for the mesh that will be attached there. The tags themselves are not saved when the model is exported to md3, thus any subsequent work on the mesh will require remaking the tags in their specific location.
For Quake 3 models that only ever had 1 or 2 tags per model, the ‘tag’ method was an ok solution. battleMETAL however, breaks this through complexity of the models in the game. The solution I settled on isn’t exactly performant for multiplayer gaming (thankfully battleMETAL is more a single-player game), but it does work. Remember how every model is an entity in quake c? Entities can be given specified ‘movement’ types to reflect, in the engine, how the entity is supposed to move. Player entities have MOVETYPE_WALK which is for handling player movement and physics. Darkplaces expands this functionality by adding a new MOVETYPE_FOLLOW. This movement mode is for objects that cannot collide with anything, and is for entities that, well, follow another entity.
General use-case for movetype_follow in the context of Quake is things like, having the gun model that a player is holding reflected externally by having a gun entity set to movetype_follow. Using this, battleMETAL treats all mech model pieces as separate entities with the movetype_follow. The locations of the pieces are set in relation to the controlling player’s origin point. In this way, the requirement of having hit locations, and visible damage have been satisfied without resorting to time consuming or costly programming quirks. The code uses the Factory Pattern to turn a mech data file into a mech, including the construction of the model component entities.
battleMETAL was able to use some animation for the mech models in the end. The leg models for every mech are animated in the model creation program and exported to the md3 format when animations are complete. The quake c code then controls the playback of those animations. Although this means animations are comparatively ‘stiff’ when put next to modern games, they are sufficient for the project.
Monday, August 6, 2018
battleMETAL - Quake/Darkplaces was a bad idea - Part 1
This was a terrible idea
Late again with the blog post, ach, about 2 weeks out from moving to a new location. This part is a quick overview detailing all the fun to be had pointing out why Quake / Darkplaces was a terrible choice. A feeling that I had upon reaching spring 2018, having been working on battleMETAL since July 2016, was that of exhaustion. Any strengths that the Darkplaces engine might have offered turned out to not really offset the Technical Debt that was incurred by the requirements of the game.
A great example of this situation was level creation. For original Quake, the best way to make levels is to use one of a few tools specifically dedicated to this task. Tools like GTKRadiant, QUARK, or TrenchBroom. These tools facilitate level creation by providing a means for designers to create the terrain for a level, place objects such as lights, monsters, player start point, select textures for the levels, and modify lighting. The level building tool then compiles all this information into a .bsp file that Quake loads when entering a level. Mapping for Quake is a well documented process, the advantage of being a popular and easy-to-mod game from 1996. There was just one big hitch in the process - Quake can’t really do outdoor terrain, cue the record-scratch sound.
See, in 1996 - and even well into the late 90’s and very early 2000’s, outdoor terrain was hard to achieve. Computer power was more limited despite the advances since 1993’s DooM game. Many games achieved a feeling of outdoor space through camera tricks, data storage techniques, or even art / design aesthetics. The method that Quake uses for creating level terrain is called Binary Space Partition or BSP. We’re not going to go deep into that particular set of computer programming, rather here’s the gist: the game (Quake) creates walls, floors, doors, ceilings, ramps, stairs, etc out of chunks of 3d space called brushes. When the map is compiled, the compiler takes the interior space of these brushes and compiles them into one large mesh of vertices. For computers in the 90’s, this was a level format that was easy to render without killing frame rate.
The Quake level design tools allow the user to create, reshape, change size, change textures of all brushes in a map. However, as we’ve seen with these old 90’s games, the brush-based system of level making results in very….chunky world aesthetics. There are ways to generate ‘realistic’ terrain with this brush method, but it results in map files that take several minutes or hours to compile. For battleMETAL a work around was found, by treating the terrain mesh as 3d model like any other 3d model, the game now had a way for realistic terrain. The sacrifice was that this terrain model does not show up in the map editors available, leaving the user to guess where to place other objects on the map. By requiring more accurate terrain models for the game, battleMETAL lost the ability to leverage Quake level makers which is a large trade-off indeed. This next part of the blog series will be just as the subtitle describes; why that (choosing Quake / Darkplaces ) was a bad idea.
Late again with the blog post, ach, about 2 weeks out from moving to a new location. This part is a quick overview detailing all the fun to be had pointing out why Quake / Darkplaces was a terrible choice. A feeling that I had upon reaching spring 2018, having been working on battleMETAL since July 2016, was that of exhaustion. Any strengths that the Darkplaces engine might have offered turned out to not really offset the Technical Debt that was incurred by the requirements of the game.
A great example of this situation was level creation. For original Quake, the best way to make levels is to use one of a few tools specifically dedicated to this task. Tools like GTKRadiant, QUARK, or TrenchBroom. These tools facilitate level creation by providing a means for designers to create the terrain for a level, place objects such as lights, monsters, player start point, select textures for the levels, and modify lighting. The level building tool then compiles all this information into a .bsp file that Quake loads when entering a level. Mapping for Quake is a well documented process, the advantage of being a popular and easy-to-mod game from 1996. There was just one big hitch in the process - Quake can’t really do outdoor terrain, cue the record-scratch sound.
See, in 1996 - and even well into the late 90’s and very early 2000’s, outdoor terrain was hard to achieve. Computer power was more limited despite the advances since 1993’s DooM game. Many games achieved a feeling of outdoor space through camera tricks, data storage techniques, or even art / design aesthetics. The method that Quake uses for creating level terrain is called Binary Space Partition or BSP. We’re not going to go deep into that particular set of computer programming, rather here’s the gist: the game (Quake) creates walls, floors, doors, ceilings, ramps, stairs, etc out of chunks of 3d space called brushes. When the map is compiled, the compiler takes the interior space of these brushes and compiles them into one large mesh of vertices. For computers in the 90’s, this was a level format that was easy to render without killing frame rate.
The Quake level design tools allow the user to create, reshape, change size, change textures of all brushes in a map. However, as we’ve seen with these old 90’s games, the brush-based system of level making results in very….chunky world aesthetics. There are ways to generate ‘realistic’ terrain with this brush method, but it results in map files that take several minutes or hours to compile. For battleMETAL a work around was found, by treating the terrain mesh as 3d model like any other 3d model, the game now had a way for realistic terrain. The sacrifice was that this terrain model does not show up in the map editors available, leaving the user to guess where to place other objects on the map. By requiring more accurate terrain models for the game, battleMETAL lost the ability to leverage Quake level makers which is a large trade-off indeed. This next part of the blog series will be just as the subtitle describes; why that (choosing Quake / Darkplaces ) was a bad idea.
Monday, July 16, 2018
battleMETAL - Why Quake / Darkplaces - Part 7 - Input Layer
It's a bit more tricky than one thinks
One very important aspect of game engines is reading player input. Kind of hard to play a game if a player can’t actually control anything, right? Avoiding a more technical conversation of engine frames, processor cycles, and the like, game input is important because it needs to be timely. When picking game engines to use, input configuration is also a specific feature to compare. Quake has a few points towards itself in the area of game input, making configuration and custom behavior easy to create and maintain. Starting at the top level, Quake has multiple ways for a player to assign keys and bindings for behavior.
The 2 primary ways to change key bindings are using a console command ‘bind X key <action>’ and the second is modifying the .cfg config file. Using the bind console command, a player can change key bindings at anytime during gameplay. This is great for players and programmers. The config file is just another format of console commands, rather then being used in the console, the commands are loaded from a text file on start up or whenever the user enters the ‘reloadconfig’ command.
In the Quake C code, there exists functions to get the action bound to any given key and a way to programmatically set a keybinding. This allows the programmer to determine what keys are being used, and how, as well as make easier the task of custom key actions. Both the server and the client (remember Client-side Quake C?) can intercept key button presses for even more custom code or to consume a key press so that it does not go on to the other end of the network. A fun extension to the Quake engine provided by Darkplaces are key bindings for controllers and joysticks. Controllers and Joystick bindings aren’t a priority but if possible to implement, it’d be a welcome addition to the types of players of battleMETAL.
Custom, game-specific action code can read for player input within 2 ‘channels’ so-to-speak of input. There are impulse commands, and button commands. Impulse commands are used for one-off key presses. The engine can only read 1 impulse per frame - wait, that sounds limited? Ah ha, says the game engine, consider this: Although the game engine can only read 1 impulse per frame, the entire engine is running anywhere between 60 frames per second or higher on modern systems. This frame rate is high enough where the player will never notice that the engine is only reading 1 impulse per frame! Impulses are stored in a variable on the player’s entity under the name ‘impulse’. The engine runs the function, ImpulseCommands() on every player on the every frame. The programmer puts their custom code into that function, allowing for custom code to be bound to specific impulses.
The second piece are ‘buttons.’ These are slightly different than impulses. The state of a Button is not cleared every frame, whereas impulses are. So if the player is holding down the ‘forward’ button, then every frame knows the player is holding down that button. This difference is important because Buttons are used for constant-on commands such as moving forward, or jumping, or attacking. Another key difference between Buttons and impulses is that there are only a set number of ‘Buttons’ that the engine can recognize. These aren’t buttons on your keyboard, but a specific set of buttons numbered 0 to 9 internal to the engine. Quake generally uses ‘button0’ as the ‘fire weapon’ button, and this is generally bound to Mouse1 - your left mouse button.
In summary, Quake and by extension Darkplaces, have a tight handle on player input. Allowing for both player and programmer a level of flexibility not seen in most other engines. Out of the box, Quake’s input system is understandable by both player and programmer. Of all the parts of battleMETAL that had to be coded, the input system was one of the easiest.
And this wraps of the 'Why' of using Quake/Darkplaces, in the next segment we will see why that was a really bad idea in the long run!
One very important aspect of game engines is reading player input. Kind of hard to play a game if a player can’t actually control anything, right? Avoiding a more technical conversation of engine frames, processor cycles, and the like, game input is important because it needs to be timely. When picking game engines to use, input configuration is also a specific feature to compare. Quake has a few points towards itself in the area of game input, making configuration and custom behavior easy to create and maintain. Starting at the top level, Quake has multiple ways for a player to assign keys and bindings for behavior.
The 2 primary ways to change key bindings are using a console command ‘bind X key <action>’ and the second is modifying the .cfg config file. Using the bind console command, a player can change key bindings at anytime during gameplay. This is great for players and programmers. The config file is just another format of console commands, rather then being used in the console, the commands are loaded from a text file on start up or whenever the user enters the ‘reloadconfig’ command.
In the Quake C code, there exists functions to get the action bound to any given key and a way to programmatically set a keybinding. This allows the programmer to determine what keys are being used, and how, as well as make easier the task of custom key actions. Both the server and the client (remember Client-side Quake C?) can intercept key button presses for even more custom code or to consume a key press so that it does not go on to the other end of the network. A fun extension to the Quake engine provided by Darkplaces are key bindings for controllers and joysticks. Controllers and Joystick bindings aren’t a priority but if possible to implement, it’d be a welcome addition to the types of players of battleMETAL.
Custom, game-specific action code can read for player input within 2 ‘channels’ so-to-speak of input. There are impulse commands, and button commands. Impulse commands are used for one-off key presses. The engine can only read 1 impulse per frame - wait, that sounds limited? Ah ha, says the game engine, consider this: Although the game engine can only read 1 impulse per frame, the entire engine is running anywhere between 60 frames per second or higher on modern systems. This frame rate is high enough where the player will never notice that the engine is only reading 1 impulse per frame! Impulses are stored in a variable on the player’s entity under the name ‘impulse’. The engine runs the function, ImpulseCommands() on every player on the every frame. The programmer puts their custom code into that function, allowing for custom code to be bound to specific impulses.
The second piece are ‘buttons.’ These are slightly different than impulses. The state of a Button is not cleared every frame, whereas impulses are. So if the player is holding down the ‘forward’ button, then every frame knows the player is holding down that button. This difference is important because Buttons are used for constant-on commands such as moving forward, or jumping, or attacking. Another key difference between Buttons and impulses is that there are only a set number of ‘Buttons’ that the engine can recognize. These aren’t buttons on your keyboard, but a specific set of buttons numbered 0 to 9 internal to the engine. Quake generally uses ‘button0’ as the ‘fire weapon’ button, and this is generally bound to Mouse1 - your left mouse button.
In summary, Quake and by extension Darkplaces, have a tight handle on player input. Allowing for both player and programmer a level of flexibility not seen in most other engines. Out of the box, Quake’s input system is understandable by both player and programmer. Of all the parts of battleMETAL that had to be coded, the input system was one of the easiest.
And this wraps of the 'Why' of using Quake/Darkplaces, in the next segment we will see why that was a really bad idea in the long run!
Subscribe to:
Posts (Atom)


