Creature Combat Arena Tutorial
Welcome to my Creature Combat Arena coding tutorial from a game I recently started working on. This is the basic framework for multiple characters fighting each other which is designed to be flexible and able to create unique characters and items quickly.
Our game will consist of a single plane with walls that is the game world. Characters (monsters, players, npc, whatever) and Items (potions, spells, weapons, etc) are spawned into the game world when the game is started. The characters behavior and stats will be defined in XML as will the items.
The XML script below is used to define characters.
This XML holds a collection of Characters. These could be even further sorted if they are bipedal humanoids or quadruped animals. For right now though, we create all of our characters defining the values with whatever we want. So the “Characters” contains many child “Character” that have stats such as Damage, Defense, Speed, etc. These will all be parsed into a string that our code can read, creating characters and having them do behaviors based on the XML tags. The same is true for the items.
The above code is what parses the XML and creates a collection of agents. Each agents has its own property (just like items), and they are defined in the class Character. These need to match up with the XML tags and if they do not then they need to be defined above as an XMLAttribute.
For the code above, The CharacterContainer holds the Characters array which will contain all of the Character nodes as defined with XML tags above.
The Item script is very similar it just also has an enum type defined and is serializable, so these items can show up in the inspector even though this script is not a mono behavior. This is really nice for displaying the inventory items and their attributes on the game object.
Now that all of the XML for all of our monsters and items are all assigned, we need to actually spawn them based off these variables.
Our Spawner class below will take care of loading it all into the game world.
The Spawner iterates through our whole collection of characters that were parsed in through the XML. They are placed at a random position (this could be defined in XML also). The characters are made up of multiple scripts: a character base class, health, death, and inventory. All of these variables for those scripts are set here.
Variables like search radius will be used by the base class for finding enemies; health will be filled into health variable on the agent base which is a reference to its health script. Then health will be evaluated, and so on for the other variables. If the agent is spawned with gear, that gear will be evaluated as well. In the agent base class, there is a dictionary containing many booleans that are defined with strings.
These strings are then called to switch the booleans on and off resulting in behavior unique to that character, such as search type and personality type (aggressive, defensive, etc). The Character Base Class is the core logic handler for our characters and is what brings life to our game.
Character base first gets references to every script it will need. There are many ways to do this. I just did not want to have a manager game object with a ton of scripts on it, so I just have the manager scripts non-monobehaviours and instance them. If I only need one instance of that script, I could use a singleton pattern which is useful for global constants. Now, all of the booleans are loaded into our dictionary. These could be split up into their own dictionary for the sake of organization. The bools right now handle movement and attack strategies that are set in the XML, activated when parsed, and can be toggled during run-time to change behaviors. This class could also be derived to create custom behavior for different types of characters. In Update, we make sure that if their health is above a threshold then the character moves and attacks. If it is below the threshold, then it retreats. Wander allows the character to attack and follows its search pattern at the same time; where other personality type like aggression cause the agent to stop its search pattern once an enemy is found and focus on it until dead. When the character runs out of health it is dead. Lets look more at each script making up our agent. Health and Die are very simple. Health just has a number that goes up and down and could control the rate at which that happens for something like recharging health or invincibility. Die destroys the game object and could reset game values and clean up. It is up to the agent base to decide what to do when health is at zero, not the health script, since dying could result in resurrection,being removed from the game world, etc. Each agent will have a simple Inventory for now like the code below.
Inventory just has a list of items that are set up in the XML. Since this script is a mono-behavior and the inventory variable is public and since we made the item class serializable from before we can see all the items the agent has in the inspector which is really useful for debugging and designing*. We also have basic functions for adding and checking items. There could easily be more functions like drop item or destroy item.
The searching and combat are a little trickier. The Movement Manager script below is an example.
The MovementManager script creates an instance of itself when its Instance function is called which our Character Base does. This allows all of these functions to be easily accessible. In its constructor, MovementManager gets a reference to two other scripts that handle specific functionality that are not MonoBehaviours.
MovementManager is made up mainly of a state machine that takes in the game object. It calls it so it can move and calculate movement by using types of search patterns. This script allows us to search by doing nothing, randomly picking points in sequence, move to a single point and stop, orbit a point, spiral a point, follow waypoints, or just ignore all searching an auto find nearest agent and move towards it.
The point of this script is to handle movement related tasks. Searching is separated into its own script that can search for a friend or enemy. This can easily be expanded to search for groups, home bases, landmarks, etc.
There is also a PositionCalc script which can be used to calculate a random point in the world bounds among other functions. Combat is also handled on its own and has functions like attack enemy and can have attack base, attack friend, etc. Combat handles damage calculation as well and search ranges.
Lastly, there is a retreat script not pictured. It has the same state machine pattern as MovementManager. It allows the agent to retreat to friend, retreat to enemy (suicide basically), or retreat to base.
To complete the protoype, lets focus a little on visuals and spruce things up a bit since cubes are not all too interesting. We are going to visualize damage and health, separate from the damage and health calculation script. When an attack happens, we are going to show it by drawing a ray and changing its color. Each color could eventually mean certain types of weapons. Once combat involves more than just a distance check and starts having different types of projectiles, we could have the projectiles change health when they hit the agent base. As health goes down, lets also gradually change the color of the cube from green to red and add some color to our different items (spheres) so we can know whats what.
That is it; we have an amazing prototype!! There are many ways to code things in my experience and each way I learn was better than the one I was doing before. I hope to always be learning, so feel free to let me know any comments on the tutorial. Hope you enjoyed it!!