How to get the sprite of a gameobject in code?

I’m using factory.create to make a game object, and storing its id like this

id = factory.create("#my_factory")

but then how do I get the go_name of this game object so that I can get the sprite of it using

msg.url(go_name .. "#sprite")

?

I tried looking at the docs and all I see is socket and fragment and a bunch of greek that doesn’t make any sense. What I tried with the advice of chat GPT is

local go_name = msg.url(id, nil, "sprite")

but this returns an error. I’m a mere mortal not an AI and I can’t parse the docs either for this.

OK it seems the correct syntax is

msg.url(nil, go_instance_id, "sprite")

I will end up wrapping this in a sane function and never worry about it again because this ordering and “socket” “fragment” nonsense is painful to remember

there is also more friction in that when specifying a game object name and getting its sprite, you have to use for example

msg.url(go_name .. "#sprite")

but when constructing it using the instance returned from factory.create you have to use

msg.url(nil, go_instance_id, "sprite")

notice the difference of using the # or not, which makes creating a wrapper function that handles both cases trickier.
it seems to depend on which parameter you’re working with, with this msg.url function that determines which syntax you have to remember - something taken from a cryptography textbook perhaps?

I’m new to Defold but it at least seems to me a better solution would be to have the game object name, and all other properties, as properties in a table of the returned game object, and return the game object instead of just a instance id, if that’s what is happening (I still have no clue due to how confusingly this is implemented)

so for example it would be used like this:

local my_game_object = factory.create("#my_factory")
my_game_object.instance_id
my_game_object.name
my_game_object.position
my_game_object.scale  -- etc, etc

A URL is made of three components – socket, path, fragment. These components can mean multiple things, but usually it’s:

  • Socket: Collection name
  • Path: Game object’s ID
  • Fragment: Component’s ID

So: collection, object, component. Collections contain objects, objects contain components. Each component is represented as a hash; a string that’s been shortened to a single number.

When written as a string instead, a full URL looks like "collection:object#component". The collection: part can be left out, which means that the name of the collection that this script is in will be used. Same deal for the object part.

In that first bit, the code is creating a string like "player#sprite" which is then converted into a URL object. This isn’t used very much, since all the functions in Defold that need a URL will automatically do that conversion for you (see that sprite.play_flipbook() will take a string as its first argument, for example).

Doing:

go_instance_id .. "#sprite"

doesn’t work, because go_instance_id is a hash (objects made by a factory are named instance0, instance1, etc., then that name is hashed and returned by factory.create()) and hashes aren’t strings that can be joined together.

The more useful form of msg.url() which fixes that issue is:

msg.url(nil, go_name, "sprite")
msg.url(nil, go_instance_id, "sprite")

It’ll take either a hash or a string (or nil) for each component, convert them all into hashes, and then make a URL out of 'em.

I don’t see how that’s too useful. This:

my_game_object.position = vmath.vector3(1,2,3)

isn’t much easier than:

go.set_position(go_instance_id, vmath.vector3(1,2,3))

Plus, Defold doesn’t like to do “magic” stuff like making a table that calls functions when you read/write to it. It would rather you just call the function yourself instead.

Though it might be convenient to have factory.create() return the URLs of all the object’s components, it’s very rare that you need the URL of more than one component, and making new tables in Lua (especially with how often the function is called in a lot of games!) can hurt performance.

3 Likes

It would be great if I could use

msg.url(nil, go_name, "sprite")
msg.url(nil, go_instance_id, "sprite")

but I can’t, it doesn’t work, because what is returned by factory.create is a hash that doesn’t give the go_instance_id that will work in that function

essentially it seems like the game engine screws you over by converting what it returns to you into an unusable hash that doesn’t work with msg.url

It’s the correct way to do it, and is in the factory manual:

Can you give more details of your case? Error messages and code samples should make it clear what’s going wrong.

2 Likes

this is the code I’m using to test it. I have a game object in main.collection named “selected_arrow” which has a sprite named sprite as a component of it, the factory creation also works normally (creates one game object with one sprite in it named sprite). The only way I can seem to get one of them to disappear is with the plain string name identifier in line 2.

I suspect if the syntax of msg.url is correct, it’s msg.post that is expecting something else? It’s absolutely insane to me that it’s this complicated. I should not be forced into having everything the built in functions return be transformed into hashes which I then have to transform back into other things…

local sprite_1 = msg.url(nil, hash("selected_arrow"), "sprite")
local sprite_2 = msg.url(nil, "selected_arrow", "sprite")
local id = factory.create("#unit_factory", vmath.vector3(0, 0, 0))
local sprite_3 = msg.url(nil, id, "sprite")

msg.post(sprite_1, "disable") -- does not work
msg.post(sprite_2, "disable") -- works
msg.post(sprite_3, "disable") -- does not work

OK the problem was the sprite in the game_object spawned by the unit_factory was not named sprite. I didn’t realize msg.url was using the name of the sprite rather than the type of object (it being a sprite)

1 Like

My 2 cents as a noob looking from the outside:

In conclusion, in order to get a child sprite url named “sprite” under a game object, you can use this:

local sprite_url = msg.url(nil, name_or_id, "sprite")

do socket-path-fragment URLS actually make sense when everything is in lua and lua has an elegant table data structure to represent hierarchical information already? absolutely not.

this should be retrieved instead using something like get_url(collection.name_or_id.sprite)
or
get_url(collection.name_or_id["sprite"])

Instead of reinventing the lua-wheel and making the barrier to entry immensely higher.

If you wanted to, you could implement convenience stuff to get_url() allowing users to omit the collection, if there’s only one that exists, or based on contextual script file location, and so on - but to reinvent lua tables as some kind of abstract socket - path - fragment system is very weird.

You can use fragment:

local id = factory.create("#unit_factory", vmath.vector3(0, 0, 0))
local sprite_3 = msg.url(id)
sprite_3.fragment = "sprite"
msg.post(sprite_3, "disable")
5 Likes

local sprite_3 = msg.url(id)

doesn’t actually return sprite_3 though, it returns game_object_hash_id I think

“fragment” is a poor term for child component, in my opinion, and is again caused by this weird abstract wheel reinvention

another problem is the meaning of id, hash_id, and URL are all conflated in this system

what it should be is just a path, for example path = get_path(name_or_id)
and then get_path(name_or_id.sprite) or path.sprite is just a continuation of the path to the sprite, if the sprite is named sprite

the second problem is it’s turned into an unusable hash by the defold lua functions, rather than keeping the path as it really exists, so we can’t directly use it like this with dot notation, we have to pass around the hash_id to functions like msg.post and msg.url - a better system is to convert things to a hash under the hood, but not make users deal with them.

I know that’s how defold does things, my takeaway is that Defold’s addressing system is an abstract layer built ontop of lua’s tables that forces users to learn it (it’s not as intuitive as lua tables) and then converts it back to a lua table address under the hood.

If I want to pass a message to a file script named “cursor” and tell it to disable the cursor sprite from within main.script, a more intuitive way than using defolds addressing system would be to do something like get_path(file_name.collection.game_object.sprite) and then toggle its properties (which should also be exposed in the editor) directly like
file_name.collection_name.game_object_name_or_instance_name.sprite.enabled = true

if I was working inside the cursor.script, this would get simplified to
collection_name.game_object_name_or_instance_name.sprite.enabled = true

If there was only one collection, then defold could implement convenience methods to look up paths so that we could shorten it to

game_object_name_or_instance_name.sprite.enabled = true

and the final result would be something like

cursor.sprite.enabled = true

instead of

msg.post("#sprite", "enable") which probably does the same thing I’m talking about under the hood, but throws on an abstraction layer everyone has to learn and keep mental track of

That might be the case, but I also think you realise that it is highly unlikely to change. You could perhaps build something like what you suggested on top of Defold to have your own system for interacting with game objects and their components using a Lua module.

Not sure if it is more intuitive. Are we discussing this kind of hierarchy?

Screenshot 2024-10-01 at 23.37.41

If you wish to disable the sprite on the cursor game object from main.script you do:

msg.post("cursor#sprite", "disable")

I wouldn’t bother with sending the message from main.script to cursor.script and then have cursor.script disable the sprite if I can do it straight from main.script.

I fail to see how cursor.sprite.enabled = true is that much more convenient than msg.post("#sprite", "enable")

Both what you suggest and the message passing is an abstraction layer on top of C++ engine code isn’t it?

Your suggestion is to mirror the internal state of the engine in Lua tables which means a lot of Lua tables using a lot of memory. It will also mean that there has to be a mechanism using either Lua meta tables or some kind of polling in the engine to detect when the developer changes a value, which in turn will trigger a change in the underlying engine.

Sending a message on the other hand is asynchronous and will allow the engine or your own code to be reactive, thus using no CPU when nothing has changed and only spend CPU cycles when a message is received.

3 Likes

britzl,
Thank you for a developer’s response that is very encouraging. I’m very new to defold and I suspect my feedback won’t be higher quality for some time, but after years I should have much more direct and meaningful suggestions for improvements. For now, all of my ranting should be seen as a newbies pain points, I know there is room for the way it’s currently done to be improved but I’m not experienced enough in defold (or game programming in general) to be able to articulate it yet.

For example, everything you said about messages could be kept, and the existing msg.post() functions and msg.url() functions could be kept, but the following syntax could be used instead IMO
for example:

msg.post("cursor.sprite.enabled", "true")

or even

msg.post("cursor.sprite", "enable")

instead of relying on a (hashed?) game_object id, which is tricky to get. For example, in the editor if I name something “cursor”, I can start writing about it, or its child like cursor.sprite, instantly, without having to first use go.get_id(cursor) to get the go_id and then msg.url(nil, go_id, “sprite”) to get the sprite.

While I don’t necessarily think defold will change to this syntax, I see potential opportunities for perhaps an extension of some kind that enables something like this, or perhaps defold implements working with this syntax in addition to the current syntax.

I do like the idea of first-in-first-out queues, rather than blocking code - which messaging seems to better handle, I just don’t think we should be limited to using defold’s URLs since lua table URLs like cursor.sprite would be a lot more intuitive to any new-to-defold but not-new-to-lua user.

Some additional friction is caused by having to use
go.set(sprite, "tint", vmath.vector4(r, g, b, a))
when really I’m tinting a sprite, not a game object, but I’m using a go.set function. Why can’t I use msg.post(“cursor#sprite”, “tint”, color) for example to be more consistent in sending messages to everything?

Intuitively I would expect there to be something like sprite.tint() functions exposed by defold, rather than a go.set function which then takes sprite as a parameter, etc.
The go.set first parameter is also expecting a go_id - which is not hinted at by the name. A personal lua best practice IMO is to have the function name explain what kind of parameters you’re expecting. For example,
sprite_tint(sprite, color) is a lot more informative that you need to specify a sprite, and then a tint, for sprite_tint to work. go.set does too much for one function IMO for it to be descriptive.

Another question - why do sprites, which are named components in the editor, have a url, which I use with their functions, but I have to use something called an instance_id with game objects - why not URLs everywhere for game objects just like with components (sprites)?

So a way to make this more consistent would be to add a function called go.get_url(go_name) instead of using go.get_id(go_name), and then use URLs for both components and game objects in all functions.

However, if I have to use go.get_url() for game objects and msg.url() to get a url for sprites, it’s still not as consistent as it could be. A better function would be something like go.get_url(go_name, sprite_name)

As I said in my first post, functions in Defold will accept both a URL object and a string representing a URL. You can simply write a line like this in the script attached to the cursor object:

msg.post("#sprite", "enable")

Or if you want to do it from a script in a different object:

msg.post("cursor#sprite", "enable")

This applies to all kinds of functions: msg.post(), go.set(), sprite.play_flipbook(), etc.

In this case, tint is only a constant defined in the default sprite material, which can be changed when developing the game or at runtime. A dedicated function for it doesn’t make sense.

It’s a generic “set this property of this thing” function. Its parameters are the target thing, the target property, and the value to set it to. I don’t know how this could be clearer? If you’re confused about what properties each component type has, they’re listed in the API reference.

The “instance_id” is the ID of the game object set in the editor (or dynamically set in the case of objects spawned by a factory), which is exactly the same thing as the path part of that object’s URL.

Granted, the editor is somewhat confusing about this. The field labeled “Url” is the object’s ID (the thing that’s returned by go.get_id()) and the “Id” is what you’ve called the object’s name:
image

Functions that operate on game objects like go.set_position() can take in:

  • a URL object
  • a string representing a URL
  • the object’s ID

There are very few cases where you ever need to manually construct a URL object with msg.url(). By far the most common case where’s it’s necessary is the one this thread started with, where you need to access a component in an object spawned by a factory. 90% of the time in Defold you can just use a string like I’ve shown above.

4 Likes