A question about timers (and tilemaps?)

Does it make sense to compare a repeating timer with delay 0 with late_update() or early_update() sort of thing?

During update() you have a gameobject record an x and y value into a module

Wrapped in the timer, you have gameobjects in external collections from the first gameobject take the recorded values and call tilemap.get_tile() and write that into a table with msg.get_url() as the key

[Or vice versa]

Now we can get through the limitation of tilemap.get_tile() being unable to access external collections by retrieving the recorded values during update() while enforcing the order…?

Technically I think you’re right, a repeating timer with delay 0 would essentially be an “early_update”, but this all seems like a very complex and convoluted way to get a tile. What are you trying to do exactly?

Unfortunately this will most likely not work the way you expect. You can use URLs as table keys, but the problem is: every time you use msg.url() you will get a different URL object back.

For example, if you do this:

local t = {}
local url1 = msg.url()
local url2 = msg.url() -- Both URLs to the same object.
t[url1] = 1
t[url2] = 2
pprint(t)

The print output you get is:

{ --[[0x7f42480d42b0]]
  url: [main:/main#main] = 2,
  url: [main:/main#main] = 1
}

Even though they are URLs to the same object, they are different keys.
Afterwards, if you do this:

local url3 = msg.url() -- A third URL to the same object.
print(t[url3])

You get nil.

1 Like

I see, that’s interesting.
So far I’ve actually been doing this already within that fact without knowing it.

I am storing the url object made this way in the script’s self reference and then accessing it through a module to filter them. In other words, the filtering object actually just has a table of references to the objects that are not being filtered out.

The way that my design has worked out, any entity that has to interact with the tilemap does tilemap.get_tile() from it’s own point of view through functions in a module. So it’s required my objects are in the same collection as the tilemaps right now.

It would allow me to move some things like the object that spawns the player / enemies / other objects that need to interact with the tilemap to a higher level. Instead of needing every scene to include a copy of the prefab that spawns the objects in (only because it needs to be in the same collection) I could have it at the same level as something like my UI or the camera.

Being able to do that, I could do something like load an entirely different set of tilemaps through collection proxy while retaining information about the other objects, without having to store it somewhere only for the new scene’s factories to access it just for initialization. Maybe even to the effect of something similar to the dimension swapping in Ratchet & Clank: Rift Apart

Sorry for dropping the ball on this thread.

So the big problem is that you can’t use tilemap.get_tile() from outside a proxy collection. Is there a reason why you need to load them with a proxy rather than spawning them with a factory? It seems like the features of collection proxies are getting in your way rather than helping, possibly the collection that is loaded via proxy should be “higher up” in the hierarchy.

Besides that, if you need to access the tiles from all over the place, I would just store whatever data you need in a module, and keep it up to date if necessary. So whenever you spawn a tilemap, just copy all of the tile indices into a 2D array. Then you can just get a tile, instantly, from anywhere.

1 Like

I hadn’t thought of that solution. It makes sense, but thinking about it - it would probably make more problems. Having the ‘level’ made up of modular tilemap collections that also contain spawn locations and environment props on the same level means I would need to explicitly track each of those things somehow in order to unload and then load a new ‘level’ correctly. The proxy lets me load these ‘level’ collections which have multiple tilemap collections which each might contain up to 100 or more separate gameobjects. Manually unloading everything whenever I go through a door sounds like more trouble than it’s worth…

… Actually thinking about it even more this solution is potentially viable if I use the door to store a gamestate and use a single proxy with the collection of level factories, and just reload the single proxy using the new stored gamestate to choose the correct factory. I’ll just finalize the proxy without unloading and re-initialize if I go with that solution.

This is a solution I did consider before, but it seemed kind of jank to me when considering how to implement with tilemaps that could have offsets not a multiple of the tilesize. I’m using (world_position - tilemap offset) % tilesize to get the coordinates that get_tile() uses. It’s necessary as I’ve modeled my tilemap collision after more traditional methods from 2nd - 4th console generations. So that would be an extra unit of data I would have to store and interpret when accessing the manually stored tilemap data.

Perhaps someone from the @Defold team can elaborate on why this is the case? I can’t seem to find anything in the documentation that could explain this restriction. Is having it this way necessary to another feature that the engine depends on?

1 Like

Sounds like you have a pretty big project so I’m sure there’s plenty of aspects I don’t know about, but I didn’t think you would need to change the collection at all, just: instead of using a proxy with “load” and “unload” messages, you use a factory with create() and delete() functions. The contents would be the same.

You just have one table of IDs that you need to store if using a factory. Using the example from the collection factory API ref:

-- Spawn collection:
local self.enemy_ids = collectionfactory.create("#enemyfactory", pos, rot, props, scale)
...
-- Delete collection:
go.delete(self.enemy_ids)
2 Likes

Ah, yes. Thinking about how everything in the collections would be “in the same place” from there distracted me from this. The fact that I can even forget like that and still have so many options and ideas to engineer solutions is really a testament to how flexible Defold is.

Loaded collection proxies are intended to be treated as “isolated worlds” with their own input stack, physics, scene hierarchy, updated frequency etc. When this was designed way way back it was decided that all interaction with the objects and components in a world through Defold APIs such as tilemap.get_tile() must happen from scripts in the same world. And all “world to world” communication happens via message passing.

So it is based on an old design which I still think makes sense today.

I see. I suppose it forces any possibility or “world to world” data flow to be kept clean based on the limits of message passing. And anything that needs to be more complex would also require solutions that apply a given concept like OOP or functional programming in a way that is probably almost impossible to turn into spaghetti code at that point. So essentially it is a kind of data structure with a given method of data flow should you use it that way.

That’s pretty cool. You could do something wild like have a multiplayer game where both players are playing two entirely different ‘games’ through proxies on their own clients but still affecting each other quite easily with that design.

Thanks for getting back! Finding this out really helps me frame some architecture decisions having to do with handling the world / levels in my game a lot better.

1 Like