Properly Unloading Unused Assets

I am currently working on adding multiple levels to my game. The enemies, backgrounds, etc. will change quite a bit between levels, so I want to be able to unload the assets used for the previous level before loading the assets for the next level so that my memory usage doesn’t increase with each level the player visits. However, I have not been able to find good documentation on what the proper way is to do this even though it seems like something that should be as easy as loading assets.

This question has been asked quite a few times, but so far I have yet to find a satisfactory answer:
-Unload Assets? seems to be deleting ALL of the assets, but some of my assets should remain loaded such as pause screen graphics or data that is used in both levels.

-I’ve found the textures.remove(key) method, which seems to work for removing images. However, I also want to unload other types of assets such as audio and json files, and it’s not clear how I should remove these. Are spritesheets removed via the textures.remove method, or do we need to remove additional data elsewhere as well?

It seems like there should be something similar to scene.load for managing assets in a uniform fashion rather than having to dig through the different places things are stored, i.e. scene.unload.image(“key”). Perhaps there is and I just haven’t found it. Any help is greatly appreciated.

1 Like

I wasn’t able to find a unified method for unloading assets, so I built my own. Here is my current solution, which isn’t perfect but seems to get the job done for all of the assets types I’m using. I load assets by passing an array with URLs, keys (ids), and types into a LoadingScreen scene, which loads everything and shows a progress bar. I then pass all of that metadata along to the game scene through its init call.

Once the game scene is finished with everything, I pass the metadata to the next screen, which then calls the following static method:

static Unload(scene, data)
{
	
	/*Animations are loaded from JSON files that are linked to spritesheets, however
	  We don't want to be too picky about the order in which things are unloaded
	  so we can't guarantee those JSON files will still exist. Thus we need to get
	  the animation data from the scene so we can figure out which ones need to be
	  removed.*/
	var animationData = scene.anims.toJSON().anims;
	
	for(var i = 0; i < data.length; i++)
	{
		
		switch (data[i].type)
		{
			
			case "image":
				//Unload Images by removing from the textures cache
				scene.textures.remove(data[i].id); 
				break;
			
			case "spritesheet":
				//Spritesheets are composed of a texture and animation data, so we need to remove the texture
				scene.textures.remove(data[i].id); 

				//Pick out which animations correspond to this spritesheet so we can remove them
				var animations = animationData.filter(function(val)
				{
					
					/*Our naming convention is that Spritesheet "Key" is given animations
					  with keys matching the format "Key-AnimationName". Therefore if we
					  remove that spritesheet, we remove all of the animations corresponding
					  to it. If your naming convention is different, then modify as necessary.*/
					return (val.key.indexOf(data[i].id + "-") == 0);
					
				});
				
				for(var j = 0; j < animations.length; j++)
				{
					
					scene.anims.remove(animations[j].key);
					
				}
				
				break;
			
			case "json":
				//The cache member of a scene has a bunch of subcaches for most other assets, so remove from there as necessary
				scene.cache.json.remove(data[i].id); 
				break;
			
			case "sound":
				scene.cache.audio.remove(data[i].id);
				break;
			
			case "music":
				scene.cache.audio.remove(data[i].id);
				break;
			
		}
		
	}
	
}

This doesn’t feel ideal, but it seems to work for my purposes.

As a side note, you might be able to get away with not removing the animations as it is only a small bit of text data that shouldn’t take up too much memory. However, if in a later level for example you want to re-use the old spritesheet, you can run into issues trying to reload animations that already exist where Phaser will throw errors. In my testing it’s best just to remove them both for the sake of thoroughness and practicality later. Since we’re doing this in an interim screen such as a loading screen, the potential length of time it takes to filter through a large array isn’t really a problem.

Have you done any profiling tests to see if it works as expected?
After reading your post I searched to check some solution, but I was unsuccessful.

I’m also watching the topic, I kinda have a similar problem i think (or i will have later)

I’m using a Vue-router to handle routing for a website and that website also has lots of small / medium size games in it. I’m keep creatings and destroying games but I’m not sure if this actually removes the assets properly like @Thyebe mantioned.

refreshing the page ? :expressionless: , not very elegant but it should take care of literally everything , can always combine with php or if not available ajax/jquery … brutal and effective …

For sure probably the best but think of that you are using a vue-router or react-router for routing your website and it has lot’s of different phaser pages. That’s what we are looking for a solution =)

1 Like

I did a couple of different things to check, although I’m not running automated tests:

1.) I watched the various caches in the Firefox Debugger and ensured that they were deleted from the Phaser objects, which they were.

2.) I reloaded the same level after unloading it, which does not work if the assets are not unloaded. (In my case, the loader scene works off of Phaser’s load.on progress and load.on complete events, which do not fire if the asset is already loaded, so the level won’t load properly if the assets haven’t been cleared.)

3.) I watched the memory snapshots in the debugger. Now this is a little bit of bad news, as Javascript’s garbage collection means that when something is unloaded from Phaser’s cache it isn’t necessarily removed from memory immediately. I can see the memory go down at points though, which suggests the GC eventually catches up and clears out the old data.

Right now I’m still working on a prototype/demo, so I haven’t done too much work in optimizing memory and/or performance. I will continue to update this if my approach to unloading assets changes.

2 Likes

As you noted, the textures are in textures and all other assets are in cache. Removing a texture also removes any frame data (atlas, spritesheet). Animations don’t hold any links to JSON files, so you can always remove those files once the animations are created.

I think you have the best method, which is to keep a scene asset manifest and then load and unload from that. Couldn’t you keep both texture keys and animations keys in the manifest? Then there’s no need to cross-reference the one with the other. If you do want to cross-reference them, I think it would be easier to filter animations by matching a texture key; no need for any naming convention.

2 Likes

i understood that much lol, imo these things can be pretty much browser-dependent and not always consistent, i can sometimes have a pc slow when left unattended for an hour with a phaser (heavy) scene on the front desktop and other times the same doesn’t lag it out. I mainly use firefox as i feel thats the better benchmark since chrome claims to be faster. I have zero xp with phone stuff myself actually and im just a hobbyist. Afaik javascript is supposed to perfectly handle garbage but since phaser loads assets globally across one “game” instance i have no clue other than .destroy() . I usually dont bother (having pc-first in mind most rigs have ample memory and its not the assets or amount of assets that seem to lag it out over time. It’s usually particle systems)

anyway, im way too much of a layman. If anyone gets a working script that says this.destroy.all.unused(true) please post it to phaserlabs :stuck_out_tongue: :100:

2 Likes

Yes, keeping the animations as separate assets would work, I am only doing as I am now because if I want to load a spritesheet, my loader automatically loads an appropriately named JSON file with the animation data depending on which image is loaded so that I don’t need to link animations to spritesheets in my data. By prepending the texture key, I don’t need to worry about the collisions between animation names, i.e. every character animation has an Idle and a Death animation, but I can just call those Idle and Death in the data file and the loader will automatically give them unique names.

Perhaps a middle ground solution would be to have the loader add the generated animation keys to the metadata. That way you could remove the array filter if you wanted, while still keeping the above loading solution in place.