Welcome to Combat Design 101. This Article is designed to go over the fundamentals of combat design. In specific, the basics of a melee combat system, as the principals covered in this article do not necessarily apply to projectile combat or shooters in general. Every combat system has its own unique features and nuances, but somewhere at the heart of every good combat game you’ll find the designs that are covered here, whether it’s a 2-D or 3-D fighter like Street Fighter or Soul Calibur or a 3-D beat-em up like God of War. Though this is an article geared toward designers I will go into some detail on a few technical details. The first is a construct known as a State Machine. The state machine is central to any combat system so the first section will be dedicated to describing what a state machine is. If you are already familiar with state machines and are comfortable using them please feel free to skip the first section.
What is a State Machine
State Machines are an idea that exist in many fields of study, and what they are will depend on whether you ask a mathematician, a computer science researcher, or an AI Programmer for games. I find that it is easiest to think about them in practical terms, and also in terms of their usage in video games. State Machines are a method to execute different actions,behavior, or logic depending on a set of conditions. As a simple example you can think of what a game does when the main character is alive versus what it does when it’s dead.When the player is alive the logic is that of moving the character around, performing actions when buttons are pressed etc. When the player is dead the game will run game over logic: play game over music, show a game over screen, and maybe give the player the option to retry when a button is pressed. What I have described is a state machine with two states: Alive and dead and the conditions for being in each state are self evident.
A state machine is composed of two parts: states and transitions which are also sometimes called edges.
They are commonly represented as a graph like the one below.
The yellow circles represent the different states, and the arrows are the transitions. The transitions are the conditions that must be met in order for the state machine to transition from one state into another. Let’s use this graphical representation to go over a more concrete example. Let’s view H2O as a state machine.
So hopefully this will be a little easier to understand. H2O has three states in this state machine corresponding to the three states of matter. The H2O will always be in one of these states and the conditions for transitioning between states are temperature. In order to transition from Ice to Water the temperature must be greater than 0oC. If we were writing code it would be
The other transitions follow suit in the same manner as Ice to Water:
Water → Steam: Temperature > 100oC
Steam → Water: Temperature < 100oC
Water → Ice: Temperature < 0oC
At this point it’s best to point out some important properties of state machines. The first is that transitions between states are directed. This means that they are one way; it is important to understand this because if there is a transition from State A to State B this does not necessarily mean that there is a transition from State B back to State A. We do not have this situation in the above example, but later on we’ll see an example where this is the case. It is also important to note that it’s not necessary for every state to have a transition to all other states. This is actually one of the places where state machines get their power as tools of organization in video games and other applications. In the above example, note that there is no transition from Ice to Steam or from Steam to Ice.This means that the state machine cannot go from being in the Ice state to the Steam state or vice versa without first transitioning into the Water state. This also gives the state machine a lot of organizational power. The above state machine has four transitions, but let’s say the machine is in the ice state, it would be a waste to evaluate all four transitions since there is only one transition that will cause the state machine to leave the ice state (the transition from Ice→ Water). Therefore good implementations of state machines will only evaluate transitions that originate from the current state, which saves processing power as the state machine gets more complex.
So, now knowing what you know about state machines and transitions between states here is an exercise for you. Given the
above state machine and the definitions for the transitions as written, try and figure out what state the state machine
would be in when the temperature is exactly equal to 100oC or
Try to do it without reading ahead. The answer is that it depends on what state the machine is currently in. If water is
the current state then the machine will only transition into steam if the temperature is greater than 100 and since 100 is
not greater than itself the machine will not transition and remain in the water state. Likewise if it is currently in the
steam state it will only transition if the temperature is less than 100 and since 100 is not less than itself no
transition will occur and the machine will remain in the steam state.
A common mistake when thinking about state machines is to take into account all the conditions for transitions all at once. In our H2O example if a person were to think this way they might think that there are bugs in our transitions at the situations Temperature==0 and Temperature==100 because none of the transitions apply to either of those cases. When thinking about state machines though you should always be thinking in terms of the states: What state do I begin in, what state am I currently in, and what states can I transition to? This mentality will lead to the creation of useful and efficient state machines. It will also help with debugging, and keeping your logic clean.
Now that we’ve talked about transitions let’s talk more about the states themselves. The very first thing to know about states is that is that a state machine can only ever be in one and only one state at a time. In our example the H2O can only be ice, water, or steam. With a state machine there is no concept of partially being in one state, no state, or otherwise more than one state. If when creating a state machine you are having trouble figuring out how to make it so that your machine doesn’t need to be in more than one state, then one of two things is probably happening. You are not defining what your state is well enough or a state machine is not the proper tool for the problem you are trying to solve. So, we’ve talked about transitioning between states and being in states, but what’s the point of being in a state? Well the idea is that whatever your state machine applies to will behave differently depending on what state it’s in. H2O doesn’t have particularly interesting state based behaviors, but if it were some kind of game object maybe it would interact with the player differently depending on what state it was in. For example if the player came in contact with the object while it was in the Ice state the player would become frozen and take some damage. If it were in the water state the Player could drink it up and regain health, and if it were in the steam state the player could use it to float into the air. This way a single object can have multiple behaviors, and those behaviors would be clearly defined and the decision as to what behavior to execute is done in a neat and organized way.
Let’s take a look at another example of a state machine, but this time we’ll look at one that was actually used in a game.
It should be easier to see the usefulness of the different states.
The following state machine represents Mario in the original Super Mario Brothers for the Nintendo Entertainment System.
This one’s a little more complicated than the previous example so take a few moments to analyze it. Remember to look at it one state at a time and see what states you can transition to from each state. If you’ll remember Mario always started the game small so let’s take a look at the small state first.
The small state can only transition to the star state and to the big state. Those transitions would look like this.
Small → Star: If (Mario Picks up a Power Star)
Small → Big: If (Mario Picks up a Mushroom OR Mario Picks up a Fire Flower)
The Big State has three Transitions. It can go to small, Fire, or Star. The transitions look like this
Big → Small: If (Mario Hits an Enemy)
Big → Fire: If (Mario Picks up a Fire Flower)
Big → Star: If (Mario Picks up a Power Star)
Here we can already see a difference in behaviors based on State, namely what happens when Mario picks up a Fire Flower. There is no transition from the Small state to the Fire state. This means the designer didn’t want picking up a fire flower to take Mario directly from the Small state to the Fire state, and instead will take Mario to the Big. Only in the Big state will picking up a fire flower move Mario into the Fire State.
The Fire State has two transitions: one to the star state and one to small state. Here it is useful to note that though there is a transition that goes from Big to Fire there is no transition the goes from Fire back to Big. This means that once you’re Fire Mario there is no way to go back to being regular big Mario without becoming small first. At first glance you might think that you can do so by going to Star first but as we’ll see when we get to the Star state the game is not designed to do that. In any case here are the transitions from the Fire state.
Fire → Small: If (Mario Hits an Enemy) Fire → Star: If (Mario Picks up a Power Star)
And finally we have the Star state which has transitions to all the other states, they are as follows.
Star → Small: If (Mario was small when he picked up the Power Star AND Star time has run out)
Star → Big: If (Mario was big when he picked up the Power Star AND Star time has run out)
Star → Fire: If (Mario was fire when he picked up the Power Star AND Star time has run out)
Now the point of using this state machine is that Mario will behave differently depending on what state he is in. In particular what happens when Mario gets hit by an enemy will be different in each state. The behaviors for each state would look like the following.
So as you can see the behavior that is inside the states have dramatic differences between each other. Specifically whether Mario dies, or whether the enemy dies in the case of the star state. Furthermore while in the Fire state Mario can shoot Fireballs.
Now the point of using a state machine, other than being able to represent the behavior with a clean graphical representation, is that despite having four blocks of logic we only run the block that is within the current state. On the technical side having the logic separated in this manner helps with debugging and keeps the logic clean and easy to read.
So now that you’ve learned about state machines I recommend that you try to create one for yourself. No need to bust out a computer and write code or use any kind of software; a piece of paper is just fine. Here’s the exercise: design a state machine for a boss in a 2-D top down shooter. Give him at least three states that he transitions between depending on how much life he has left. Start him off with a simple behavior, but when he gets to 50% health make him get angry and do something different and more challenging to the player. At 25% make him do an even more challenging behavior. Feel free to include things like changing appearances, flashing red, using different weapons, or playing different sounds. If you’re comfortable with that add more states and more complex transitions. You can use things like checking the health or proximity of the player. If you have an idea for a state machine you like better than that by all means try and do that instead.
If you have a mind to implement your own state machine in an actual game or
program it’s important to realize that state machines are a theoretical
construct. As such there is no standard method of implementation for them like
there might be for other programming data structures. A novice or inexperienced
programmer might try to implement a state machine using a series of nested if
statements such that the above Mario example would look like this:
else if(Mario.CurrentState=="Big") then
Though this does properly implement all the features of a state machine, it can get
difficult to read very quickly. Furthermore if you’re not careful you can
create dependencies on scope, local variables, and the like, that can make the
state machine difficult to modify or replace in the future. As a result, in my
opinion, this kind of implementation should be avoided. A slightly
more sophisticated implementation is the use of a switch statement. Not all programming languages and platforms
have them but all the more robust ones do. The Mario State Machine would look
more like this.
This is a lot neater and easier to read. Also, having the logic for each of the states separated not only from the logic that controls the machine but also from the other states is consistent with good programming practices, in particular the idea of modularity. This implementation works for simple state machines and many games have used this method successfully. However this approach can become hard to maintain as you add more and more states, and because each switch statement needs to be tailor made to a given state machine (via state names and corresponding function names) it becomes even more difficult to maintain as you create more and more state machines. Though this can be alleviated with good object oriented programming there are better implementation methods.
How you actually program a state machine is, of course, going to depend on the tool that you’re using. Some platforms like Unreal and Unreal Script have state machines built-in to their scripting language. Others like Java, C#, and even C/C++ have features like delegates and function pointers that make implementing state machines clean and easy. Using these, I believe, is the better and more common way to making a state machine system of your own.If you are not familiar with function pointers or delegates I recommend finding a quick tutorial on the topic.
As an example I will implement a small state machine using the LUA Scripting Language. LUA is well suited for
state machines thanks to its weakly typed variables, and thanks to the fact that functions are first order values.
Let’s walk through the above code shall we. First of all the entire state machine is represented only by its current state
which in stored in memory with the variable
currentState. I have written two functions
StateB(); these are the states of the state machine. To run the
behavior we need only call the current state with the line
currentState(). As you can see at the bottom
of the code there is a while loop that runs infinitely for the simplicity of this example. This while loop is responsible for
running the state machine by running the current state, but what happens when
runs? Well right before the loop we initialized
currentState to equal
This means that when
currentState() is called it runs the code that is located inside of
This is the process of defining the State Machine’s initial state. At some point while running
some condition will become true and
currentState will be set to
and the next time the machine executes,
StateB() will run.There is no need to check what state the machine
is currently in to decide what logic to run, all that is handled by simply calling
and the right function gets used. The states are implemented in their own functions, which keeps them modular and maintainable.
Adding new states is as easy as writing the states themselves, and because the whole control structure of the State Machine is
handled with a single variable (
currentState) creating multiple state machines is trivial.
One final note on implementation, in all of the examples so far the logic for the transitions is written inside the logic for the states themselves. This is how many state machine implementations handle it including Unreal Script. However, there are more advanced implementations that handle the transitions as their own objects and evaluate them separately. In my opinion this implementation is preferable and is more robust, but that requires a much more advanced approach.
|  ||Home||Next >>|