I am pretty sure that you will find yourself an authentic master of the Libgdx AssetManager
class after going over this chapter as you are now able to load and unload whatever you want, whenever you want, but wait! It means that you will have to manage all the resources that will appear in your game one by one. Definitely, masters don't like to work on repetitive tasks that can easily lead to errors.
Undoubtedly, the answer is to group assets. This is why we will tweak the default Libgdx AssetManager
class a little bit in this recipe. Group organization might vary depending on the scope and type of game, so it is up to you to decide whether you will group per level, characters, menus, and so on, and what will you stream on the spot.
You can still put the icing on top of the cake by adopting a data-driven approach. It consists of describing your asset groups in an XML or JSON file in such a way as to modify your code the least possible when adding/removing resources to groups or simply changing names.
Mixing the grouping idea with the data-driven approach definitely helps you to organize assets more efficiently, keep memory consumption under control, and save a lot of time.
As usual, make sure the sample projects are easily accessible which are under the [cookbook]/samples
folder, to fiddle with the code of this recipe. Make sure you have them imported into your Eclipse workspace. Just in case you need it, take a look at Chapter 1, Diving into Libgdx, where the process is explained step by step.
The code for this recipe is in the GroupingAssetsSample
class, but it directly relies on the com.cookbook.assetmanager
package that contains an enhanced AssetManager
and Asset
class to wrap any type of asset.
Before rolling your sleeves up, it is good to lay the groundwork so that you can have a global overview of the full recipe. I refer to the root content file from which the assets' information will be extracted at runtime. It must be organized in groups, which in turn contain their specific asset data. To carry on with the technologies used in this chapter, JSON will be the chosen notation standard:
{ "base": [ { "type": "com.badlogic.gdx.graphics.Texture", "path": "data/loading_screen/background.png" } ], "game_screen": [ { "type": "com.cookbook.animation.SpriteAnimationData", "path": "data/caveman-sheet.json" } ] }
In the preceding example, there are two groups with one asset each, for which type and path attributes must be specified. Besides them, there must be an extra option in our syntax; otherwise, we will be limiting the strength of AssetManager
:
{ "base": [ { "type": "com.badlogic.gdx.g2d.BitmapFont", "path": "data/menu_screen/font.fnt" "parameters": { "flip": true } } ] }
Now that you have an understanding of what we are working on, let's dig deeper.
A good approach to take in order to strengthen our AssetManager
class is to create a new wrapper class that is composed of the built-in Libgdx AssetManager
class and some extra functionality:
public class Assets implements Disposable, AssetErrorListener { private AssetManager manager; //composition relationship private ObjectMap<String, Array<Asset>> groups; ... }
Following Libgdx's lead and being aware of the resources we have, we cannot forget to free memory, and this is why we implement the Disposable
interface. In the same way, AssetErrorListener
gives us the power of managing errors when the load is not successful.
Last but not least, the groups
field will store an array of assets per string name. The ObjectMap
class is a Libgdx unordered map where null keys are not allowed, and works very fast on consulting, removing, and retrieving content. The reason for choosing this data type is because we will initialize our custom AssetManager
class once at the beginning of our application, and from that moment on, we will just load/unload assets from specified groups using calls to the get()
method of ObjectMap
.
Make use of the AssetErrorListener
interface to get notified about runtime errors related to AssetManager
. Add support to custom data types, and then parse your asset files:
public Assets(String assetFile) { manager = new AssetManager(); manager.setErrorListener(this); //Custom datatypes loaders manager.setLoader(MyCustomAssetType.class, new MyCustomAssetType Loader(new InternalFileHandleResolver())); manager.setLoader(SpriteAnimationData.class, new SpriteAnimationLoader(new InternalFileHandleResolver())); loadGroups(assetFile); }
Adding the capability of loading user-created data types to the manager is achievable through the setLoader()
method in which you specify your custom class jointly with your custom loader, and also select the desired FileHandleResolver
.
The loadGroups(String assetFile)
method will do the dirty work by parsing the file and filling the groups variable with the needed information. Don't hesitate to consult the Assets.java
source code file to go further into JSON details.
The extra values generated come with the capability of loading groups:
public void loadGroup(String groupName) { Array<Asset> assets = groups.get(groupName, null); if(assets != null) for(Asset asset : assets) manager.load(asset.path, asset.type, asset.parameters); }
The unloading function member code is only differenced by the call to unload:
public void unloadGroup(String groupName) { Array<Asset> assets = groups.get(groupName, null); if(assets != null) for(Asset asset : assets) if(manager.isLoaded(asset.path, asset.type) manager.unload(asset.path); }
The rest of the base functionalities of AssetManager
are implemented by calling the Libgdx built-in AssetManager
methods, for instance:
public boolean update() { return manager.update(); }
The same happens with getLoader(...)
, isLoaded(...)
, finishLoading()
, getProgress()
, and get(...)
. Remember that the default Libgdx get()
methods for the AssetManager
class include the synchronized keyword, which implies that it is not possible to execute it on one thread while it is being executed on another at the same time over the same object. The reason to use this is the fact that it is a good way to prevent thread interferences and memory-consistency errors.
Until now, our class has been listening for errors related to AssetManager
, but we are not managing them. You only have to override the error(...)
method from the AssetErrorListener
interface:
@Override public void error(AssetDescriptor asset, Throwable throwable) { //Manage errors however you want }
The Throwable
class is the Java superclass for errors and exceptions. If you want to know more about this, visit http://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html.
Finally, make use of the Disposable
interface:
@Override public void dispose() { manager.dispose(); }
Once our custom-enhanced AssetManager
version is done, just use it this way in your main module:
manager = new Assets("data/assets.json"); manager.loadGroup("base"); manager.loadGroup("loading_screen");
The group-loading feature is considerably easy to carry out, but undoubtedly, the Asset
class simplifies things a lot, resulting in very generalized code, without treating each type of asset separately.
Consequently, the Asset
data type is in charge of representing any type of asset by the previously mentioned three attributes: type
, parameters
, and path
. It implements the Json.Serializable
interface in order to create an instance of this class from a valid JSON string. The magic happens with the public Class<?> type;
attribute in the overridden read method:
type = Class.forName(jsonData.get("type").asString());
It seems like everything on AssetManager
is covered in this chapter, but there are still some cool features that you can add to the developed implementation. Some ideas are:
.png
extension to texture data, adding some meta-information in the filename to deal with exceptions.AssetManager
.18.227.134.133