Floating combat text
Problem
You want units to display damage as floating numbers when hit.
Solution
There are many ways to approach this problem. For example, you could use a bitmap font and build an image for each number out of its digits, then use a Sprite
node to display and move it.
However, for this recipe, we’ll use a Label
node (named “FCT”). This will give us the flexibility to change the font, as well as making it easy to display the number as a string - or even other messages such as “miss”.
Start with a Label
node and add a Tween
child. We’ll use the Tween
to animate the movement and the fade-out effect.
Set the label’s Custom Fonts/Font property using your favorite font. In this example, we’re using “Xolonium.ttf” with a font size of 28
and a black outline of 1
pixel. In the menu select “Layout->Center”, and also set Align and Valign to “Center”.
Add a script to the label.
extends Label
func show_value(value, travel, duration, spread, crit=false):
When we spawn the floating text, we’ll call this function to set the parameters:
value
- the number (or string) to displaytravel
- aVector2
representing the direction of travelduration
- how long the text will lastspread
- movement will be randomly spread across this arccrit
- iftrue
, indicates the damage was a “critical” hit
Here’s what the function does:
text = value
var movement = travel.rotated(rand_range(-spread/2, spread/2))
rect_pivot_offset = rect_size / 2
First, assign the value and randomize the movement based on the given spread (+/-90 degrees, for example). Since we may also be animating the scale, we set the rect_pivot_offset
to the center of the control so that it will scale from the center.
$Tween.interpolate_property(self, "rect_position",
rect_position, rect_position + movement,
duration, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
$Tween.interpolate_property(self, "modulate:a",
1.0, 0.0, duration,
Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
Next, we have two properties to interpolate: rect_position
for the movement and modulate.a
for the visibility.
if crit:
modulate = Color(1, 0, 0)
$Tween.interpolate_property(self, "rect_scale",
rect_scale*2, rect_scale,
0.4, Tween.TRANS_BACK, Tween.EASE_IN)
If the hit is a critical, we’ll also change the color and animate the scale for a bigger effect. Note, I’ve hardcoded this to be red, but you should probably make this a configurable value.
$Tween.start()
yield($Tween, "tween_all_completed")
queue_free()
Finally, we start the Tween
and wait for it to finish, then remove the Label.
Floating text manager
Next we’ll crate a small node to manage placing and spawning the floating text. This node will be attached to the game entities that you want to display floating text effects.
This is a Node2D
named “FCTManager”. It contains the following script:
extends Node2D
var FCT = preload("res://FCT.tscn")
export var travel = Vector2(0, -80)
export var duration = 2
export var spread = PI/2
func show_value(value, crit=false):
var fct = FCT.instance()
add_child(fct)
fct.show_value(str(value), travel, duration, spread, crit)
Here you can expose the settings to the Inspector for convenient changes. The show_value()
method here spawns the floating labels and sets their properties.
In your game unit, you’d attach an instance of this node, and position it wherever you want the text to appear. Then add something like the following to the unit’s take_damage()
method:
$FCTManager.show_value(dmg, crit)
Wrapping up
Optimization: in the case where you have a very large number of enemies/bullets, you may experience some performance impact from repeatedly spawning and freeing floating text. In this case, you can spawn a fixed number of text objects in the manager, and show/hide them rather than freeing them at the end of the animation.
Art in this demo by Luis Zuno