Monster Simulator
Overview
-
Role: Programmer
-
Genre: 2D Strategy Game
-
Engine: Personal Engine in C++
-
Team Size: Myself
-
Development Time: 2 months
High Concept
Monster Simulator is a 2D battle strategy game in which you will form your monster army to defeat incoming monster enemies. You need to strategize correctly and choose different formations in all different and unique monsters
Core Mechanics
The Behavior Tree
In the game, all monsters are controlled by their behavior tree instances. Therefore, I added the behavior tree feature into my engine that can read XML files to load behavior tree instances. The behavior tree class stores a general behavior tree node pointer called root, and the root contains all nodes in a tree-like structure. There are four different node types: selector, sequence, action, and decorator. Selector and sequence nodes contain a vector of nodes that they can call, and action and decorator nodes contain one or more function pointers that they call to perform executions.
​
The image below demonstrates the action node structure and also its special case called the waiting node. The node stores passed parameters and function pointers and calls the event function in the Run function. Waitnode is a special structure that the whole behavior tree will wait until it completes.
One interesting feature I added to the behavior tree component is that it is frame-independent, which means the behavior tree will not finish one complete run for each frame. The image on the right side shows the node statuses for every behavior tree node. If one node's status is running, then the whole behavior tree waits until that node's status changes to complete. The reason why I designed it this way is that some functions sometimes take longer to execute. For example, the wait function should be frame-independent. Another reason is that this provides better performance as the game won't try to complete all running behavior trees in every frame.
The behavior tree component is completely data-driven. It reads a XML file and constructs a behavior tree structure. The image below is an example of behavior tree XML file. To let decorator and action nodes call the correct functions, every function pointer is stored with a string of function name so that the behavior tree node can call the function using the function name.
The Store System
The store system shown in the demo video is one of the most important systems I implemented in this game. The store system allows players to purchase units, place/sell units, and check unit information. In each level, the store system will show available units and total gold number based on the current level's definition, which allows me to add new levels without changing the store system class.
​
Since all events in the store system are triggered by mouse clicks, it is hard to determine which event should be triggered. The image below illustrates the function in the store system that handles left mouse click. If the mouse position is inside any unit box in the store, then that unit will be selected. If the mouse position is inside the highlight box, then place a unit only if the current gold is greater than the unit price and unit is available.
The Quest System
To give the game more replayable elements, I also designed a dynamic quest system that assigns a random quest in each level. When players enter a new level, the quest system will randomly pick one quest from the quest pool. Then, it checks if the level information passes the quest's prerequisite. If it does not pass, the system will pick a new quest from the pool until it finds a proper quest. Players should be able to see the quest description and requirements in the level, and the quest information is updated real-time: whenever one requirement's data changes, the quest requirement description also changes. This gives players a direct feedback of whether they are following the quest correctly or not. When level completes, the system will also check if the quest completes or not. If the quest completes, it will add a reward to next level. Image below shows an example of a quest in a level.
To check whether the quest is completed or not, the system needs to check three different conditions: before-battle, in-battle and after-battle. Using the quest example above, the prerequisites of this quest are: the level should have at least 2 enemy Goblins, unit Goblin is unlocked, player's gold is enough to purchase at least two Goblins. If these prerequisites are passed, this quest can be assigned as the current quest in this level.
​
During the preparation stage, the system will check whether the player places Goblin or not, and the number on the quest board will update if the player places/sells a Goblin. The before-battle requirement for this quest is that the player must place at least two Goblins when battle starts. The in-battle requirement is that the Goblins must kill at least two enemy Goblins, and the after-battle requirement for this quest is only that the player needs to win the battle. The image below shows the in-battle condition function that checks kill informaiton.
There are five different quest types: place, limit, survive, kill and protect. Each quest has different before-battle, in-battle and after-battle conditions. My original design was to check each quest separately, but soon I realized that I may want some quests that have multiple types, like place and limit at the same time. Therefore, I redesigned my quest system to check all quest conditions no matter what the quest type is. This approach may takes longer to implement and eat a lot of performance, but it helps me and future level designer to design a quest without writing extra code or functions.
​
The image below is an example of a quest in a XML file. All I need is to add a line of place. Then the quest system will know the quest requires a place type and it will handle all the conditions for the place part.
Post Mortem
What Went Well
-
Set up the architecture earlier so that I was able to focus on implementing core gameplay.
-
All the main systems work without severe bugs.
-
Added many interesting and fun units.
-
Designed more than 20 different quests.
-
Data-driven systems make my life much easier.
What Went Wrong
-
Didn't schedule the tasks well, causing a lot of extra tasks added to the schedule.
-
A lot of features that I wanted to add were not implemented.
-
Didn't add audios.
What Needs Work
-
Add more units and quests.
-
Should come up with more general solution when designing a new system.