Godot Version
4.2.1
Question
Hello! I’m building a new fighting game in Godot, and I want to know how to create complex animation tree for it. Here’s some problems:
- Flipping character based on his position relative to second character (they must be facing each other all time). Controls in my game will work like in Mortal Kombat, distinguishing different hands and legs. You see, when my character is facing right, his fastest punch will be from left hand, slower and more damaging - from right, but when I flip my character it should be on the contrary: fastest from right hand and slower from left. Same with legs.
- Handling complex inputs. When every attack is mapped to a single button, all works fine, but when it’s for example not like 1, 2 or 3, but <-, R1+R2, or even <-, → R1+R2. How to make it so when you start making complex inputs on ← joystick motion it will start going back, but if you complete complex input animation will smoothly and fast switch to some special attack animation?
- Hurt animations. What will work best: bunch of hurt animations? Ragdoll simulation? Combined? How to do hurt animations properly?
1 can be done by scaling the mesh on a axis by negative one, this will “flip” the model. Though some unique asymmetrical character designs will look odd, like an eyepatch moving from the left eye to the right.
3 should be actual hurt animations, ragdolls generally have no place in fightning games.
2 needs some work done before we can really help. I think it will depend on the kinds of inputs you want.
There’s a decent page on Godot Docs for you to get started on using Animation Trees.
I hope you know that what you’re looking to create is not trivial. Building a system that recognizes specific input combinations is harder than it looks on the surface. Your animation system also needs to be custom made as it should dynamically configure the animation tree based on a character’s moveset; it is not realistic to manually configure each tree based on a character’s moveset.
I don’t mean to discourage you but there’s a lot of hidden complexity.
Is it easier and fits if I design different trees for different characters and simply make a ton of connections to match each situation?
Well no matter what you’ll want to buffer inputs, something to store the last 8 or so inputs and when they where pressed
class ComboInput:
var time: int
var action: StringName
var combo_inputs: Array[ComboInput] = []
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventAction:
var new_input := ComboInput.new()
new_input.time = Time.get_ticks_msec()
new_input.action = event.action
combo_inputs.append(new_input)
Looks good, but how to distinguish between combo and non-combo inputs? What if player wants to combo, but basic stro forward?
P.s. I got 1 idea, but I don’t know how to implement it in code.
The idea is that every single input is applied with some delay, for example 0.3 seconds, during which you can press next input, and then it will register it as a combo input, otherwise it will just apply that input and clear combo array/dictionary
I believe most fighting games allow you to cancel basic attacks into combo moves. A 0.3 second delay is massive, and you want to avoid any delay outside of anticipation frames for gameplay balancing. The example I provided keeps track of when the buttons were pressed, so for example a A + B combo could check if the actions were pressed within 166 milliseconds (~10 frames)
func check_ab_combo() -> bool:
var end := combo_inputs.size() - 1
# comparing the last two actions
var buttons_match: bool = combo_inputs[end].action == "B" and combo_inputs[end - 1].action == "A"
var buttons_match_flipped: bool == combo_inputs[end].action == "A" and combo_inputs[end - 1].action == "B"
var timing_match: bool = (combo_inputs[end].time - combo_inputs[end - 1].time) < 166
return timing_match and (buttons_match or buttons_match_flipped)
This example function could be made a little more modular. Appending movement commands usually rounds the direction to the nearest octagon corner, so a hold command might look for “BACK” with a high time difference, then “FORWARD”
Another improvement would be to use a “ring buffer” instead of appending and/or removing every ComboInput.
Actually it’s very good idea, I’ll try to make it better, for example, make it return a dictionary of combo input, like [“forward”, “back”, “attack”].