Good combos in video games are insanely difficult to architect. When people notice that something is wrong theyāll say things likeĀ āThe combat is floatyā,Ā āControls are unresponsiveā, or my least favoriteĀ āCombat is brokenā. Here Iām going to cover our custom combo system in Unity and the core programming principles behind it.
To date we have around 23 unique combat nodes for Coal (as pictured above). The resulting graph for maintaining it is pretty easy to use and magically gets optimized when everything boots up. That said letās talk about how I failed miserably trying to build this thing 3 times before I came up with the current solution.
Originally I tried to put all my graph information into an individual file. After just three attacks I had 700 lines that I couldnāt maintain. After that I tried finite state machines (FSMs), problem is they werenāt easily editable and lacked the hooks I needed for meta data. When that didnāt work I moved onto behavior trees (BTs), hoping I could make it work. What I ended up with was around 100 nodes for around 14 attacks. It was better than all previous solutions, but still not what we needed.
After that I came up with what I call a Node Graph Planner. Itās a combination of an FSM and BT that allows me to magically traverse my tree to create simple maintainable flowing combos.
Full screen print of the finalized node graph planner above.
Architecture of a Node Graph Planner
I broke up my Node Graph Planner into several different pieces for a data driven design. Iāll try to explain what each piece is and how it works.
Action NodeĀ - Each individual node on the graph. These are used to indicate a particular attack is ongoing.
Parent Container Node - These guys are used to group individual actions. They also allow attached transitions to automatically be passed to all children. This is how we pass uppercuts to all grounded combos and slams to all aerial combos.
TransitionsĀ - The little blocks you see at the bottom of each graph node. These determine if a new node should be triggered or not. Each one takes a customizable input monitor. See inputs for exactly how those work. Transitions also support a delay which is extremely important for timed attacks.
Actions - Each node has an action that is executed by it. These determine what actually happens to the player and how they go about attacking enemies.Ā
Action Sequencer - To rapidly prototype and create new simple attack animations / actions I rolled a custom sequencer. It supports some really cool functionality like physics overrides, camera shakes, and other nifty tools.
Inputs - It should be noted that for the transitions I create a custom input library that wraps Unityās 3rd party plugin Rewired. This way Iām now able to rapidly prototype different kinds of input monitoring beyond basic button up and down commands.
Each action follows a basic lifecycle similar to the following numbered list. In addition to the following I made receive damage and hit hooks available so I could adjust attacks according to collected combat information. Allows us to only shake the screen when hitting enemies, only float in the air if actually hitting an enemy, and many other interactive environment applications.
Buffer Monitoring - Monitors and stores the next valid attack before itās enabled
Update - Continue until buffer is enabled or attack is complete
Attack Ends (also triggers on cancel)
The short answer isĀ āTOO DAMN LONG!ā We knew this combo editor had to be rapidly customizable for new attacks and able to handle any kind of special move we threw at it (uppercuts, slams, dash attacks, ect).
Since this is such an important game element that weād need to adjust rapidly based on user feedback too. That said it took 200 hours according to toggle.com to write this 4th iteration. Quite a few hours were dumped into this, but I think our users are going to love the fluidity and control it gives them over combat. This was the last major tool I had to write so Iām looking forward to assembling all the alpha libraries next so I can start rolling more content again.