+6 votes

I'm trying to display damage on an enemy with values in 3d space like a number popping up over the enemy head when hit. I Know its really simple in 2D but any ideas of how this would be done in 3d?

in Engine by (270 points)

3 Answers

+6 votes

You probably want a scene just for that; call it "billboard_label" or something, than copy the code available on the GUI_in_3D demo. Basically, in this scene you'll have a Quad (with the Billboard option enabled, so the player will see the text orthogonally no matter the camera angle), a Viewport (with the Render target enabled), and a Label as a child of said Viewport. Then just add a script with the magic code:

get_node("area/quad").get_material_override().set_texture(FixedMaterial.PARAM_DIFFUSE, get_node("viewport").get_render_target_texture())

This will make your Quad render the label of your Viewport. You'll probably want some other methods on the billboard_label scene to make it more functional, like "set_billboard_text(string)" to edit the Viewport label without having to access all the nodes 'from the outside'. Than finally, when you want your enemy to display the damage, just create your "billboard_label" via script, set it's text, and animate it accordingly (probably by using a simple timer and queue_freeing() by the end of it).

by (304 points)
+7 votes

Another option is to acquire the desired point in 3D, then unproject the point to a screen coordinate through the camera.unproject() method. Use that new 2D vector to align some desired 2D node on a CanvasLayer node. The CanvasLayer node will overlay as a GUI, and has layer Z ordering as well if you need things to appear in front or behind other 2D items.

I set up a simple example with a label. I used a position3D to fake the location of where you'd like the text to appear, then unprojected it, offset it by half the width of the label to center the label in the position where it would appear on the canvas layer.

extends Spatial

func _ready():
    var above_head = get_node("TestCube/Position3D")
    var label = get_node("CanvasLayer/Label")
    var camera = get_node("Camera")

    var offset = Vector2(label.get_size().width/2, 0)

    label.set_pos(camera.unproject_position(above_head.get_translation()) - offset)



by (5,240 points)
+1 vote

For Godot 3.2 I came up with adding a label as a child of Sprite3D

onready var sprite = $Sprite3D
onready var label = $Sprite3D/Label

func get_camera():
    var r = get_node('/root')
    return r.get_viewport().get_camera()

func position_label(label:Label, point3D:Vector3):
    var camera = get_camera()
    var cam_pos = camera.translation
    var offset = Vector2(label.get_size().x/2, 0)
    label.rect_position = camera.unproject_position(point3D) - offset

To prevent displaying it when behind the camera or out of range I only call it

func _process(_delta):
    var cam = get_camera()
    var test_point:Vector3 = sprite.global_transform.origin
    if not cam.is_position_behind(test_point):
        if test_point.distance_to(cam.global_transform.origin) < 8.0:
        position_label(label, test_point)
by (638 points)
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.