Custom Avatars & Advanced CoPresence (Unity)

In Cavrnus, CoPresence operates through Property values, which are communicated Transiently and do not get logged in the Journal.

Local vs Remote User Avatars

In Cavrnus, the Local and Remote Avatars are separate. When a remote user joins, we will Instantiate() the Avatar specified in the Cavrnus Spatial Connector, and provide it with relevant property paths (this will be explained below).

Local User’s, by contrast, are left up to the Application to provide/configure. If you wish to display a local Avatar, use a first person camera, or include an XR Rig, it will need to be done by your project. Then, there are a handful of simply steps to have that object send CoPresence data to Cavrnus.

Sending Transform Data from Local User

Each user is responsible for sending their own CoPresence Data. To do this, you simply need to select the local user’s Camera/Rig/etc, and then use the menu item Set Selected Object As Local User. This will add the Cavrnus Local User Flag component, as well as a Sync Transform + Cavrnus Properties Container.

image-20240221-020514.png

And that’s it, you’re done!

At runtime, the Cavrnus Local User Flag will go through all the Sync Transform scripts and disable their ability to receive data from the server. This ensures that whatever character controller you are using is “send only” and won’t be fighting with Property Data coming in from the server. After that it will simply monitor your local Transform and send CoPresence updates via Properties!

Using a Custom (Remote) Avatar

To create a Custom Avatar for remote users, we’ll assume you already have a model you wish to use. Here we will be using a simple Robot mesh.

image-20240220-234656.png

The first step is to add a SyncTransform component to the root of the prefab. This will move the Avatar wherever the Remote User says they currently are.

Then, we can take this Prefab and drop it into the Cavrnus Spatial Connector.

Now, all users should spawn with this Avatar!

There are a couple things to note about how this works at runtime under-the-hood:

  1. When a remote user’s Avatar is spawned, it will iterate through all of its Sync Components and uncheck “Send My Changes”. This ensures that only the user in question can update their CoPresence.

  2. Notice that right now the Unique Container Name is called “Robot Remote”. Nodes below it may be called something like “Robot Remote/Head”. When it is spawned for a User, that User’s ID will replace “Robot Remote”. So the first Properties Container will become something like “/users/0wu9vekvi24”, and the Head would then become “/users/0wu9vekvi24/Head”. Any Containers that don’t start with “Robot Remote” will retain their original property path.

Adding UI/Name Tags to Remote Avatar

In addition to the CoPresence Transform, which is constantly being sent by the Remote User, there are additional user Properties which are exposed through properties, but are set elsewhere. These include things like the User’s Name, weather they are Muted, what Volume they are Speaking at, etc. This data is actually sent over-the-wire using different channels, but is copied into Properties for your convenience under-the-hood. Since the data is coming in from elsewhere, these properties are read-only, and any attempt to Post() to them will fail. While a simple BindXProperty call will retrieve them, for your convenience we have added calls to get them directly in the CavrnusShortcutsLibrary.

You can, of course, also use the Sync Components.

As a basic example, we can add a Text element to the custom Avatar we have just made. We will then add a Sync Tmp Text Component to it.

For the Property Name, we will use “name” which can be looked up in the CavrnusShortcutsLibrary as the user’s name property.

We will need to uncheck Send My Changes since, as stated, these are read-only.

We need to assign our text as the Text Component.

Finally, for the Unique Container Name, it should match the name of the root Cavrnus Properties Container. At runtime these properties will all have a unique ID like: “/users/0wu9vekvi24”. By setting our Unique Container Name as “Robot Remote” in this case, we ensure that at runtime the correct name is copied in.

With this done, the avatar will now show the user’s name above it:

The name tag we provide in the existing built-in avatar is built entirely using the above tools & principals.

Adding Hands/Rigging to Remote Avatar

While, by default, Avatars need only have a SyncTransform on their root node, if you wanted to send over additional data you need only add more components to the relevant parts.

Let’s say the Remote User had an XR Rig and we wanted to synchronize their hands. To do this, simply select the relevant part, and add another SyncTransform component to it. The UniqueContainerName will automatically fill in using the name of the path (“Robot Remote/LeftHand”). This way, as long as data is being sent to that Property at runtime, the hand will show it.

This same concept applies to Rigged Avatars & Bone Transforms. For those it may be worth writing your own script since attaching SyncComponents may be difficult. But here is an example of how you could implement the receiving end for a Remote Avatar:

public void BindAvatarBones(CavrnusSpaceConnection spaceConn) { foreach(var bone in skinnedMeshRenderer.bones) { string ContainerName = GetComponent<CavrnusPropertiesContainer>().UniqueContainerName; string PropertyName = bone.name; //We assume the bones all have unique names. If they don't, ensure this is unique some other way. spaceConn.BindTransformPropertyValue(ContainerName, PropertyName, boneTrns => { bone.localPosition = boneTrns.LocalPosition; bone.localEulerAngles = boneTrns.LocalEulerAngles; bone.localScale = boneTrns.LocalScale; }); } }

Sending Hands/Rigging from Local Player Character

The above is, of course, irrelevant, if no data is being sent to the given properties.

Assuming you have already followed the steps above and run Set Selected Object As Local User on your player character, you now have to decide how you want to send out the additional Transforms.

In general, this should mirror the Receiving End used by Remote Avatars. So, if you are sending hands from an XR Rig, simply attach SyncTransform components to the Rig’s controllers/hands/etc.

IMPORTANT: Make sure that the UniqueContainerName for this hand is identical to that used by the Remote Avatar. Otherwise the Remote Avatar won’t find them.

However, also make sure that the first element of the path matches the root UniqueContinerName of your local rig, as that is what will be replaced by the Unique ID at runtime.

So, matching the previous example, the UniqueContainerName for the XR Rig’s controller should be “XR Rig/LeftHand”

As for posting Bone Transforms, you will want to do that via a script just like the one above:

private Dictionary<string, CavrnusLivePropertyUpdate<CavrnusTransformData>> boneSenders = new Dictionary<string, CavrnusLivePropertyUpdate<CavrnusTransformData>>(); private void Update() { foreach (var bone in skinnedMeshRenderer.bones) { var currentBoneTransform = new CavrnusTransformData(bone.localPosition, bone.localEulerAngles, bone.localScale); if (!boneSenders.ContainsKey(bone.name)) { string ContainerName = GetComponent<CavrnusPropertiesContainer>().UniqueContainerName; string PropertyName = bone.name; //We assume the bones all have unique names. If they don't, ensure this is unique some other way. boneSenders[bone.name] = spaceConn.BeginTransientTransformPropertyUpdate(ContainerName, PropertyName, currentBoneTransform); } else { boneSenders[bone.name].UpdateWithNewData(currentBoneTransform); } } }

XR Headset Transform Troubleshooting

XR headsets present some unique difficulties. Specifically, they often have several levels of Hierarchy, all of which are affecting the Transforms. This means a simple SyncTransform component will not always do the trick, since it will only capture one level of that.

To resolve this, it is recommended that you use a SyncWorldTransform script on XR Headsets instead. This will ensure that they send/recv their whole transform.