Skip to content

feat: prepare ability to bundle additional assets in avatar#798

Draft
hai-vr wants to merge 1 commit intoBasisVR:developerfrom
hai-vr:additional-assets-in-bundle
Draft

feat: prepare ability to bundle additional assets in avatar#798
hai-vr wants to merge 1 commit intoBasisVR:developerfrom
hai-vr:additional-assets-in-bundle

Conversation

@hai-vr
Copy link
Copy Markdown
Contributor

@hai-vr hai-vr commented May 4, 2026

Summary

Some users often create avatars that have GameObjects or Components turned OFF by default, only to be turned ON later during a session using a toggle; some days it may not even become ON. This raises the question, should assets contained inside those GameObjects or Components be loaded later, rather than loaded at the same time as the avatar?

This PR lays some preliminary work to figure out if this question is worth pursuing. This adds the ability to append additional assets to BasisAvatar bundles, and exposes a method for consumers to load them.

A new field in BasisAvatar can now define additional assets to be added to the avatar during the build:

  • When an avatar is being processed to be built or tested, the BundleAdditionalAssets field may contain a list of (key + asset) pairs. This list is meant to be filled by external avatar processing scripts during the build process, and is otherwise empty or undefined otherwise. The asset is not a GameObject, Transform, or Component; they are probably going to be Mesh, Material, or Texture2D assets.
  • When that avatar is done processing and a bundle build is about to start, we create as many ScriptableObject containing a reference to each asset, and persist them in a temporary directory.
    • We are using a ScriptableObject to store a reference to the asset, because Mesh assets located inside .FBX files have the .FBX as the asset path, which is the GameObject of the FBX prefab, so AssetDatabase.GetAssetPath cannot be used directly.
  • The asset reference is removed from the BundleAdditionalAssets field, and the asset path to that ScriptableObject is put in its place instead.
  • The list of asset paths to those ScriptableObject is appended to the list of assets inside the bundle, and then the bundle is built.

A method is also provided to load it. No consumer of that method is provided in this PR, but it has been tested using the following code:

if (!TryFindKey(avatar.BundleAdditionalAssets, meshAssetKey, out var deferredMesh))
{
    BasisDebug.LogError($"Deferred asset for mesh {meshAssetKey} not found, late loading will not continue.");
    return;
}

var assetMesh = deferredMesh.asset != null
    ? deferredMesh.asset // For use in "Test in Editor"
    : (await BasisLoadHandler.LoadAdditionalAssetInAlreadyLoadedBundle(player.AvatarMetaData, deferredMesh.assetPath, false));
if (assetMesh is not Mesh mesh)
{
    BasisDebug.LogError("Deferred asset is not a mesh, late loading will not continue.");
    return;
}

Demonstration on an uploaded .BEE file (the HVR Late Loading component is for demonstration purposes, and is not part of this PR):

SpNTwpbpnn.mp4

Required checks

All boxes below must be ticked before this PR can merge. If a check is genuinely N/A, tick it anyway and explain under Notes.

  • Tested — I built and ran this locally. The change works in the editor and (where relevant) in a built player.
  • Transform access is combined and limited — In hot paths, transform reads/writes go through TransformAccessArray or are otherwise batched. I have not added per-frame transform.position / transform.rotation / transform.localPosition calls inside loops.
  • Addressables used for asset/memory loading — Any new asset loads go through Addressables. No new Resources.Load, no direct asset references that pull large content into memory on scene load.
  • No new GetComponent / AddComponent where avoidable — Where unavoidable, the result is cached on a field. None of these calls run inside Update, LateUpdate, FixedUpdate, jobs, or other per-frame code paths.
  • Per-frame work is scheduled through BasisEventDriver — Any new per-frame work hooks into BasisEventDriver rather than adding standalone Update / LateUpdate / FixedUpdate callbacks on a MonoBehaviour.
  • Considered jobification — I asked whether this work can be moved to a Unity Job (Burst-compiled where possible). If it can, it is. If it cannot, the reason is in Notes.

Testing details

Tick the platforms you actually tested on. Leave the rest unticked — these are informational and do not block merge.

  • Windows
  • Linux
  • Android
  • iOS
  • macOS

Input / control mode coverage:

  • Tested in VR (note headset under Notes)
  • Tested in desktop / non-VR mode
  • Tested with phone controls (mobile touch input)
  • N/A — change does not touch player/XR/input code

Where applicable, confirm these flows still work after your changes:

  • Hot-switching (desktop ↔ VR mode swap at runtime)
  • Avatar swapping
  • Server swapping (joining / leaving / changing servers)
  • N/A — change does not touch any of the above

Notes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant