How could I go about handling a lot of images for a single sprite?

I have a single game object with a sprite whose animation can be 1 of a few 250 images, each is roughly 600x500 pixels. I am targeting mobile, so I can only fit 12 images on an atlas before it becomes larger than 2048x2048.

Is it possible to change atlas files for a sprite at runtime? What are my options for solving this?

Thanks in advanced!

Wow, ok, so that’s a ton of images (and they are large as well!). So, yes, you cannot change atlas for a sprite at run-time. And you cannot have all of the atlases loaded at once due to the amount of memory they would require. Your best bet would be one of the following solutions:

Thanks @britzl!

I should probably clarify a bit: The image is selected randomly when the collection is loaded and will not change until the collection is reloaded, so I don’t need all images to be loaded, in memory while the game is running, just need to be able to select one on initialization.

With that said, are the options listed above still my best bet?

The thing is that for game objects and sprites, all data is static. You need to include all data that may or may not be displayed in your collections. @britzl gave what I believe are the only options:

  • Create one collection for each variant and use a set of 250 proxies to load, then randomize which proxy to use. You probably want to automate the building of these with a python script or similar.
  • Use GUI nodes. They can display dynamic texture data (see http://www.defold.com/ref/gui/#gui.set_texture:node-texture).

except…

  • Write a native extension that changes the texture buffer data on your sprite. (We plan to release the Defold SDK documentation very soon and also some examples.)

So what I ended up doing was adding 21 sprites to the game object with an atlas of 12 images each, then depending on which image I want to show, I play the animation from whichever sprite has the image, and make all other sprites invisible.

Here is an example (everything is hard-coded at the moment):

function on_message(self, message_id, message, sender)
	if message_id == hash("set_sprite") then
		for id = 1, 21 do
    		go.set("#sprite" .. id, "tint", vmath.vector4())
		end
		local sprite_id = math.ceil(message.id / 12)
		msg.post("#sprite" .. sprite_id, "play_animation", {id = hash("sk" .. message.id)})
		go.set("#sprite" .. sprite_id, "tint", vmath.vector4(1))
		go.set("#script", "sprite_id", message.id)
	end
end

The only issue I am having with this (besides the obvious waste of resources) is that it now crashes on my 1st gen iPad Mini when loading the collection containing all the sprites. It works fine on my Mac and iPhone 6s Plus.

I will look into the other solutions in more depth, but they will require quite a bit a restructuring.

You’re running out of memory. You cannot have 21126005004=302400000=~302Mb of images in memory at once, at least not on older devices. You need to use 21 collection proxies, each holding a game object with a sprite referencing a 12 image atlas, and load the one you wish to use.

1 Like

So I went ahead and switch over to the gui/load_resource method. It’s working well now. The collection reloads much quicker.

@britzl and @sicher thank you for you help an insight!

The only issue now is that the same 4 images are loaded every time on the iPad mini, so my random number generator isn’t working, but only on that device. I will search the forums and possibly address this in a separate topic.

1 Like

Figured out the random issue on the iPad by just changing os.time() in my randomseed to os.clock(), in case anyone else searches for this same problem.

1 Like

This is quite surprising. Are you saying that os.time() always returns the same value on your iPad? It’s worth noting that os.time() returns the time in seconds. If you want more granularity you can use socket.gettime().

It’s also important to note that you should seed just once, when the application starts, not every update.

(There are cases when you do want to reseed an application, but those are rare)

1 Like

Maybe I am just using the seed wrong? I am calling randomseed in the init of my player script, but I have multiple instances of the player game object in my collection. Should randomseed only be called once per application run, once per collection, or once per script?

Again, I apologize, I am still getting acquainted to Lua.

randomseed sets the initial value for the random generator. If you set it to a new value each run (os.clock or socket.gettime) you will have random values for the rest of the app’s lifespan. Any call to random will get a new value. So one call to randomseed at game start is enough.

Note that this behaviour of random and randomseed can be used to get the same series of random numbers each time the app runs, by feeding the same seed. This is good for generating content that should be the same each time the game is run.

All scripts share the same context (unless you explicitly select to use different contexts between GUI and world, in which case there’ll be two contexts). When you seed you’ll create a starting point for the pseudo random generator to generate random numbers from, reseeding will reset the generator to the starting position you specify. If you reseed with os.time twice per second and generate a random number after each seed you will get the same number since you’ll reset the generator to the same starting position.

It should be enough for almost every game to seed just once when the bootstrap collection is started, so just once per game session. If you want to see the same behavior every run you can seed with a fixed value instead, which is good for debugging.

Awesome, I moved the randomseed to my bootstrap script so that there is only a single call per application run, and now the RNG is working everywhere on all devices!

Thanks for clarifying @sicher and @jakob.pogulis, I am loving Defold so far about a week into using it!

@britzl, os.time() is indeed working correctly on the iPad, just user error! :relieved:

3 Likes

So back to my initial issue which I solved by dynamically loading the images to a gui node. It would be nice if we could dynamically load images to sprites (or some other game object component) to avoid having to pass so much logic through the messaging system from the game object script to the gui script. I’m sure my situation is a corner case and there could be some underlying engine architecture preventing this, but I figured I’d mention it just in case it happens to be an easy change! :wink: