Simple shapes for game objects that work like labels

I’ve added this request. Open to thoughts, feedback, or alternative solutions I haven’t considered.

Is your feature request related to a problem? Please describe (REQUIRED):

It is currently confusing and/or difficult to add a graphic element that is both easily modifiable and mobile. Healthbars are a great example. The forum has examples of workarounds, but I think something similar to the Label component would be awesome.

Labels follow the game object and do not require much code to work. They are also easy to edit and update.

Making a GUI node follow a game object is not so simple; especially when updating for different camera views and updates.

Describe the solution you’d like (REQUIRED):

I’d like to see the option to add simple shapes, such as boxes, circles, etc., to a game object that work just like labels. These simple objects could be used when you want to have simple visual updates to a game object, such as healthbars or unit selection.

It would be even better if these simple shapes could have a % property. For example, the ability to define a rectangle and give it a value of 100% would be an awesome and simple way to implement healthbars. Other features already included in the GUI, such as textures, would be even better.

I think this would not be so difficult to implement since the things that would get this to work are already in the editor.

Describe alternatives you’ve considered (REQUIRED):

I’ve tested sprites, but those scale from the center instead of from the far x axis like a healthbar. Also, because a sprite is not a GUI element.

I’ve also tested nodes, but these are difficult to update. I’ve gotten as far as making a node spawn on a game object, but it doesn’t update with the screen. It was also difficult to implement because I had to make a new material, render, etc.

The desired result would look like this, but with simple shapes that could be modified instead of text. For now, I’ll be using text since that is currently what I know of that works best.


The sprites inherit the scale from the parent. Try offsetting the sprite, then scale the parent.

I read your comment about this from another thread. :slight_smile: Still doesn’t seem like an ideal solution for what I want to do, unfortunately.

1 Like

There’s a few alternatives I can think of:

  • The DrawPixels extension is fairly versatile and could be used for things like this. It does need a sprite component with an assigned texture that it can draw into, but is otherwise quite easy to work with
  • Using one gui component per healthbar/shape group, with the gui adjust mode set to none and a material with the same “tile” tag as sprites would render it in the same position as the game object it is attached to
  • Using a single gui component and world to screen conversion of positions for the game objects which should have shapes. The orthographic camera has support for the world to screen coordinate conversion. You would also have to pass a message per game object every frame with new coordinates for the units in your game.
  • Using the mesh component. This is more low level but it could work as the base for a new shape extension

Someone should do a library for this based on the mesh component :wink: I’ve played with the mesh component this past week and honestly it’s pretty amazing the range of stuff that you can do with it, especially the fact that you have custom vertex formats.


I’m able to get the node to spawn on the game object by doing this, but it doesn’t stay attached when I move the screen.

I’m looking at the world_to_screen code to see if I can figure this out. If I were to have, for example, 20-30 different units with healthbars, would passing that many messages per frame hurt performance?

Took a quick look. May try it out if I can’t get the solutions above working.

Don’t feel like I understand what’s going on behind the scenes enough to go this route, but I’m confident I will soon.

Hmm, you are right. I thought that would work.

I’m thinking something like this:

-- tank.script
local camera = require ""
local CAMERA_ID = hash("/camera")

function init(self)
	local id = go.get_id()"#units_ui", "register_unit", { id = id, health = })

function final(self)
	local id = go.get_id()"#units_ui", "unregister_unit", { id = id })

function update(self, dt)
	local id = go.get_id()
	local world_pos = go.get_world_position(id)
	local screen_pos = camera.world_to_screen(CAMERA_ID, world_pos)"#units_ui", "update_unit_health", { id = id, pos = screen_pos, health = })

-- units_ui.gui_script
function init(self)
	-- table to keep track of all units and their gui nodes
	self.units = {}

function on_message(self, message_id, message, sender)
	-- create a new entry into self.units and create the gui nodes needed
	if message_id == hash("register_unit") then
		local unit = {
			node = ... create gui node
		self.units[] = unit
	-- delete the gui node associated with a unit and remove it from the list
	elseif message_id == hash("register_unit") then
		local unit = self.units[]
		self.units[] = nil
	-- update the position of the node associated with a unit
	elseif message_id == hash("update_unit_health") then
		local id =
		local pos = message.pos
		local health =
		local unit = self.units[id]
		gui.set_position(unit.node, pos)

If you are concerned about the messages it’s possible to replace the messages with a shared Lua module that tracks the units and their positions. Updated by tank.script and read by units_ui.gui_script


Trying to implement this, but getting this error.

Also getting an error with the camera. Trying to reverse engineer what’s going on to fix the error.


This is pseudo code, not actual code.
The comment is meant to explain what is left as a separate task to complete (i.e. “create gui node”)

Ah, that makes sense. I was confused because “create gui node” isn’t actual code. But I thought it was a command I was unfamiliar with.

I think this was meant to say UNregister unit.

I believe this is the correct way to create a node.


But still having issues here:



local CAMERA_ID = hash("/mapcamera")

Fixed the camera issue. My camera is /mapcamera, not /camera.

1 Like

Managed to clear up all of the errors. It’s however still not 100% working as intended.

Setting this aside for the moment, because what’s happening is over my head, and working on something that I better understand — using messaging and collisions for combat.

Yeah, sorry about that. I typed up that code real quick this morning without actually running it so I expected it to contain at least one error :slight_smile:

Ah, yeah, that doesn’t look quite right. Are you sending unit position updates each frame to move the healthbars?

Yes. My code:

function update(self, dt)
	local id = go.get_id()
	local world_pos = go.get_world_position(id)
	local screen_pos = camera.world_to_screen(CAMERA_ID, world_pos)"#healthbar", "update_unit_health", { id = id, pos = screen_pos, health = })

The healthbar moves when a unit moves, but it is not aligned properly.

1 Like

Ah, it might be the adjust mode of the gui:

Check which Adjust Mode you have set on the health bars and then change the world_to_screen to match:

-- pass in the adjust mode of your node
local screen_pos = camera.world_to_screen(CAMERA_ID, world_pos, gui.ADJUST_MODE_XYZ)

The adjust mode is fit, but adding gui.ADJUST_FIT isn’t making a difference, nor is the other adjust mode options.

Is it possible to replicate the functionality of a Label? Adding a label to a game object simply… works flawlessly. If that were possible, but with a GUI node, that would be perfect.

I want to come back to this because I almost have it working. I actually had to set up the Adjust Reference to “per node” instead of “disabled” to get the healthbar to stay attached to the unit.

One issue with this setup is the healthbar doesn’t stick when panning the camera. Instead, it slowly moves around the unit. I tried to demonstrate in the video what I mean. There’s also an issue when zooming out, as seen at the end of the video.

1 Like

Are you using go.get_world_position() for this? That function will always be a frame behind because world transforms get calculated after component update(), before rendering. So you’ll have last frame’s data in there until new ones get calculated.

I recommend using go.get_position()directly on the game objects. If you have some hierarchy, you can walk it using go.get_parent() and calculate the final positions / rotations yourself

I’m using go.get_world_position() because that’s the only option that is working correctly that I know of.

local id = go.get_id()
	local world_pos = go.get_world_position(id)
	local screen_pos = camera.world_to_screen(CAMERA_ID, world_pos)"#health_gui", "update_unit_health", { id =, pos = screen_pos, health = })

Changing to go.get_position doesn’t do the trick. When I pan the screen, it moves around and and doesn’t stay attached to the unit.

I’m not sure how I can get parenting to work since it’s a GUI and not a game object. I suppose I could turn the unit into a collection, so I can attach the GUI to a game object and attach that to the unit.

You’re thinking too complicated. You’re on the right path. You need the world position of the game object so that you can then convert it to screen coords and put your GUI node at the right position. All of this is right. All I’m suggesting is you use go.get_position() recursively on the game object and all its parents to get the final world position. Something like:

local world_pos = vmath.vector3(0)
local go_id = id
while go_id do
  -- If your game objects are rotated and scaled,
  -- you need to do more stuff here to account for that
  -- but for simple translation, adding the positions will work
  world_pos = world_pos + go.get_position(go_id)

  go_id = go.get_parent(go_id)

This is what I used to get it working.

function update(self, dt)
    local world_pos = go.get_position( + go.get_position(self.parent)
    local screen_pos = camera.world_to_screen(CAMERA_ID, world_pos)"#health_gui", "update_unit_health", { id =, pos = screen_pos, health = })

The positioning is directly on the center of the unit, instead of above, and the GUI scales to too large of a size when zooming out, but these are things I can fix. The important thing is it’s attached to the unit without the delay.