Managing groups of assets for bigger games

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.

Getting ready

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.

How to do it…

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.

Giving shape to the idea

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.

Empowering AssetManager

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.

Note

A map is a data structure consisting of a set of key-value pairs that can be quickly accessed through the key.

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");

How it works…

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());

There's more…

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:

  • Associate file extensions to asset types: This means that you don't need to indicate the type of the asset as a string. For instance, it will just link the .png extension to texture data, adding some meta-information in the filename to deal with exceptions.
  • Automatically scan asset folders: Not worrying about individual asset files, just look for folders and convert its contents into one group of assets.

    Note

    The File.list() method can cause issues while deploying on desktop platforms.

See also

  • We will now move on to Chapter 8, User Interfaces with Scene2D, and Chapter 10, Rigid Body Physics with Box2D, which will feed your brain with new ideas about adding functionalities to AssetManager.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.176.5