Enhanced Asset API

Today we’re pleased to update the engine and tools with a new Asset API. We’ve finessed the API to make it more intuitive and also added some extra features to make it easier for you to preload or stream in assets at any stage of your application.

This post should introduce some of the new APIs, plus give you an upgrade guide for areas of the API which have changed.

First some terms. When we talk about a “resource” we are referring to some data, usually stored in a file, that can be loaded by a PlayCanvas application. When the resource data is loaded, the engine creates a instance of some class that came from that data. e.g. the resource “image.png”, is loaded to a creates an instance of a `pc.Texture`. The same applies for a 3D model, an animation or sound effect.

When we talk about as “asset” we are referring to a reference to a resource, with some associated data. For example a texture asset looks something like this:

{
 "id": 14761,
 "name": "Cerberus_G",
 "type": "texture",
 "preload": true,
 "file": {
  "url": "files/assets/14761/1/Cerberus_G.jpg",
  "size": 816084,
  "hash": "dc49dac4f4775191b7643b4583b3ac3f",
  "filename": "Cerberus_G.jpg"
 },
 "data": {
  "minfilter": "linear_mip_linear",
  "name": "Cerberus_G",
  "magfilter": "linear",
  "addressu": "repeat",
  "addressv": "repeat",
  "anisotropy": 1
 },
}

You can see the asset data for this texture as an ID, a Name, a reference to the file where the resource data is, plus some additional data which isn’t stored in the image file. This data is used to create the texture resource. Some assets contain lots of data (materials) some none (a text file).

Once loaded into the engine, you can use the asset registry to find assets, and you can access the loaded resource (if it is loaded) from the asset using the `asset.resources` property.

On to the changes:

Editor Changes

Asset Preloading

preload

The first new feature is asset preload. Previously we would load any assets that were referenced by your scene. We changed this behaviour so that now assets have a specific preload property which you can enable or disable. Any asset marked as preload, and we default this to true for new assets, will be loaded during the loading screen stage and will be ready to use when your application starts. Any that are not marked as preload, will remain on the server, ready to be loaded when you need them.

We’ve updated existing assets to mark all existing target assets are preload. So you may need to uncheck this option on some assets if you don’t actually need them.

Export and Publishing

When you publish an app or choose to download an export of your app, we now include all assets in the package. As you can choose whether or not to preload assets, this means that you can select only a subset of your assets to be loaded before your application starts. Then you can load other assets at any other stage later on. This lets you use many more assets in your games and applications but not worry about start up time. Just stream your assets in later on.

Script Loading Order

Script loading has had an overhaul, we now load scripts in parallel with other assets so load times should be reduced. We have also changed which scripts are loaded. Previously, we’d only load scripts which are referenced in scenes. We now load all scripts in your repository. Because of this we’ve introduced a way to set the order in which your scripts are loaded.

script-priority

Found in the menu, you can use the script loading priority dialog to choose scripts you wish to load first.

API changes

Asset Registry

The `AssetRegistry` object has changed slightly in the new API. The core methods are shown below. As before the `AssetRegistry` object is available in your scripts as `app.assets`.

app.assets.get(id) - get by id
app.assets.find(name, type) - search by name, return first result
app.assets.findAll(name, type) - search by name, return all
app.assets.load(asset) - load remote resource for asset

The main change here is that `load()` no longer accepts a list of assets and it no longer returns a promise. We’re removing promise from the engine in favour of node.js style callbacks and events.

Note, we have left in a compatibility version of assets.load which accepts a list of assets and returns a promise. You should update your code to remove this as it will not be in the engine forever.

Asset Events

The asset registry (and the asset object) fire a consistent set of events which you can use to react to changes on the registry. These can be used to monitor loading progress or respond if you wish to dynamically add assets to the registry.

assets.on("add", callback) - triggered when asset added to registry
assets.on("add:{id}", callback) - triggered when asset added to registry
assets.on("add:url:{url}", callback) - triggered when asset added to registry
assets.on("remove", callback) - triggered when asset added to registry
assets.on("remove:{id}", callback) - triggered when asset added to registry
assets.on("remove:url:{url}", callback) - triggered when asset added to registry
assets.on("load", callback) - triggered on successful load
assets.on("load:{id}", callback) - triggered on successful load
assets.on("error", callback) - triggered on asset loading error
assets.on("error:{id}", callback) - triggered on asset loading error
asset.on("change", callback) - triggered when asset data changes

A good pattern for loading and using an asset is like this:

asset.ready(function (asset) {
    // use asset.resource here 
});
assets.load(asset);

asset.ready() will call the callback when if when the asset is loaded. It’s it’s already loaded from before, the callback is called immediately. asset.load() does nothing if the asset is already loaded.

Resource Loader

The resource loader is a lower level interface than the asset registry. Most users won’t need to access this directly as they will use the asset registry to load data. But this API has changed significantly. We’ve made it much simpler:

app.loader.load(url, type, callback);

I’ll just leave it there, see the API docs if you need more information.

Application Scene Loading

We’ve added a simple API for loading new scene data via the `app` object

app.loadSceneHierarchy(url, callback) - load a scene file, get hierarchy, append hierarchy to app.root
app.loadSceneSettings(url, callback) - load a scene file, apply settings (lighting/physics) to current scene

These two functions accept the URL of the scene file which will be in the format “scene_id.json” e.g. “100.json”.

Upgrade guide

Hopefully you’ll find your application continues work as before, only now it loads faster and you have more options available to you for dynamically loading assets. However, this is a breaking API change so you may need to update your projects.

app.assets.load(asset)

If you are using this and passing in a single asset, we no longer return a promise. You should replace your code with this:

asset.ready(function (asset) {
  // use asset
});
app.assets.load(asset);

app.assets.load(assets);

If you are loading a list of assets, our compatible load should still work. However, you should update your code to use the new loading format. As below

var toload = []; // list of assets
var count = 0;
toload.forEach(function (asset) {
   asset.ready(function (asset) {
   count++;
   if (count === toload.length) {
     // done
   }
 });
 app.assets.load(asset); 
});

app.loader.request()

If you were using this to make resource requests like loading Packs or asking for texture data. This is all new. You should be able to replace these calls with some other method. Maybe `app.loadSceneHierarchy()` or by using asset preloading to delay loading texture data. However if you really need to load a resource directly. You can use the resource loader API:

app.loader.load(url, function (err, resource) {
  if (!err) {
    // use resource
  }
});

That’s it! If you notice any problems or have trouble upgrading, get in touch on the forum.

dave

6 thoughts on “Enhanced Asset API

  1. Please edit this post, it has some mistakes, for example, “a instance -> an instance”, “data. e.g. -> data, e.g.”, “is loaded to a creates -> … to and creates”, “talk about as “asset” -> … about an “asset””, “to mark all existing target assets are preload -> … as preload”, “assets.on(“remove”, callback) – triggered when asset added to registry -> … asset is removed from registry”, “will call the callback when if when the asset is loaded. It’s it’s already loaded -> … callback when the asset … If it’s already …”

    Now I have some questions.
    1 – Why did you decide to not include ammo.js in exports?
    2 – How is preloading all the scripts from every pack in a project makes one specific pack load faster?

  2. I’ve updated the post with the corrections you’ve mentioned. Thanks.

    1 – This is a bug, ammo should be included in the export. A fix will be deployed this morning
    2 – Scripts load faster because they are no longer loaded in blocking manner. All script requests are made simultaneously. This isn’t related to preloading all scripts from a repository.

  3. I mean, why do we need to load some script that is used in pack B, when we are loading pack A? And what if this script’s size (from pack B) is more than 1MB? Now when loading pack A we need to load unnecessary script that will take extra time. Is that so?

    1. In that case, yes, there would be a penalty as you would be preloading the large script file.

      Depending on circumstances, is the large contents of the script data? In which case you could separate it into an asset?

      Ideally we will be concatenating scripts together before loading which will mean all code is loaded up front.

  4. Will the changes be reflected in the tutorials? I have a hard time with seeing these in action as they are currently presented. I am very new, while I understand that these are just patch notes and not designed for newbies. However, I am eager to get started creating games. I just don’t want to do things the wrong way.

  5. Please ignore my last ignorant comment. I just realized it already is in the tutorials. I must have overlooked them before! Please continue the excellence! It is appreciated!

Comments are closed.