Question about asset loading and scenes

Hi,

I am not sure how to handle this, so I hope someone can point me in the right direction.

We are building a large urban area with Playcanvas, and want to split up our indoor levels in different scenes.
Now I did some testing and Chrome DevTools revealed that Playcanvas is preloading all the project assets, not just the ones for the current scene. So I did some more digging, and it looks like the engine preload phase runs before the actual scene hierarchy is loaded, which is not ideal in our case because with every additional level we build the overall loading time will increase.

So I’d like to change this behaviour for our project, I disabled preloading for all of our assets, and now Playcanvas only loads assets that are needed for a given scene, of course with the drawback that models and textures pop up as they are loaded.

Now there are still some things unclear to me, and so far I couldn’t find a solution to this:

  • How can I fire an event when these scene assets are done loading? I would need this for handling the loading screen and also for triggering LOD streaming, and as far as I can see there is no build in application event, just the load event for each asset. So I think I need a way to get all assets of the current scene, but I am not sure how to do this.

  • Is there a way to set preloading to false by default for all newly created assets?

  • And most importantly: Is this the right way to handle this? Did I miss something important?

There is an event that get fired when an asset is loaded but it does require a reference to the asset. You can check out this sample project of loading assets at runtime (press Space to start the load): https://playcanvas.com/project/436584/overview/sample-load-assets-at-runtime

Afraid not. You can group select assets though and tick/untick them as needed.

The most common way to do an open world streaming is to load areas of the world before the player can really see it. So if the world was split up into a grid for example:

[1][2][3][4]
[5][6][7][8]
[9][10][11][12]

If the player is in zone 7, then the game should load the zones connected to it (e.g. 3, 6, 7, 8, 11) and unload everything else.

If it is less of an open world and you just want to get the player into the game as soon as possible, I would load assets that the player would see first when they enter the game, and then load everything else in the background starting with the areas that are closest to the player first and working out.

1 Like

Hi Steven,

thanks for your help! Yes I was thinking about 2 approaches:

  • For doing the open world we put the outside area in one scene and structure the entity hierarchy to match a grid, exactly like you described
  • For indoor levels we use a separate scene per level, which is loaded as soon as the user enters a building

For either approach I need to find a way to get a list of assets referenced by a certain scene/grid zone, so I can do some smart on-demand loading. Is there a built-in way to get these assets?
I think marking every asset with tags for all the scenes/zones it is used in will grow tedious quickly, so I am looking for a way to search them hierarchically., e.g. all assets referenced by descendants of zone 1.

The way I would be thinking of would be tags which you don’t want to do. Unfortunately, it is the most efficient way that I can think of. Again, you can multi/group select the assets and add the tags en-masse.

That said, when an entity is enabled and assets that it is referencing haven’t yet been loaded, it will trigger a load of those assets automatically.

Cool, I did not know that, thank you!

Well it’s not that I don’t want to use tags, we do use them for more static asset properties like e.g. the level of detail, but I was just thinking that as our project grows a lot of assets will get reused, and managing their scene and zone location with tags is not something I would look forward to :slight_smile:

Anyways, I was experimenting with some recursive code to do hierarchy based searches:

var addAssetToList = function (assetID, assetDictionary) {
  if (assetID && !assetDictionary.hasOwnProperty(assetID)) {
    var asset = _app.assets.get(assetID);
    if (asset && !asset.loaded) {
      assetDictionary[assetID] = asset;
    }
    return asset;
  }
};
var searchMaterialAssets = function (materialID, assetDictionary) {
  var materialAsset = addAssetToList(materialID, assetDictionary);
  if (materialAsset) {
    // check all material map files
    addAssetToList(materialAsset.data.aoMap, assetDictionary);
    addAssetToList(materialAsset.data.diffuseMap, assetDictionary);
    addAssetToList(materialAsset.data.specularMap, assetDictionary);
    addAssetToList(materialAsset.data.metalnessMap, assetDictionary);
    addAssetToList(materialAsset.data.glossMap, assetDictionary);
    addAssetToList(materialAsset.data.emissiveMap, assetDictionary);
    addAssetToList(materialAsset.data.opacityMap, assetDictionary);
    addAssetToList(materialAsset.data.normalMap, assetDictionary);
    addAssetToList(materialAsset.data.heightMap, assetDictionary);
    addAssetToList(materialAsset.data.sphereMap, assetDictionary);
    addAssetToList(materialAsset.data.cubeMap, assetDictionary);
    addAssetToList(materialAsset.data.lightMap, assetDictionary);
  }
};
var searchChildrenForAssets = function (node, assetDictionary) {
  for (var i = 0; i < node.children.length; i++) {
    // check model component
    if (node.children[i].model) {
      if (node.children[i].model.type === 'asset') {
        // the model asset is the mesh file
        var modelAsset = addAssetToList(node.children[i].model.asset, assetDictionary);
        // check asset material
        if (modelAsset) {
          for (var j = 0; j < modelAsset.data.mapping.length; j++) {
            searchMaterialAssets(modelAsset.data.mapping[j].material, assetDictionary);
          }
        }
        // also check entity materials
        if (node.children[i].model.mapping) {
          for (var meshInstanceId in node.children[i].model.mapping) {
            searchMaterialAssets(node.children[i].model.mapping[meshInstanceId], assetDictionary);
          }
        }
      }
      // TODO: check materials of other model types
    }
    // TODO: check other component types
    searchChildrenForAssets(node.children[i], assetDictionary);
  }
};
var assetDictionary = {};
var root = _app.root.findByName('Root');
searchChildrenForAssets(root, assetDictionary);

It works really well so far for meshes, materials and textures, but of course has to be extended for other components like animations or audio.

2 Likes