Swapping Avatars at Runtime (Unity)
Note: Before reading this, you should first make sure you understand Custom Avatars & Advanced CoPresence.
Now that you can change which remote Avatar your users will have, you may be wondering if different users can use different remote avatars, or even swap them out at runtime?
In short: no, but actually yes!
We only ever allow for you to specify one Remote User Avatar in the Cavrnus Spatial Connector. This is the “thing” that will be spawned every time a user joins. However, by using the properties system in interesting ways we can make this “avatar” do a lot of powerful stuff.
Swapping Sub-Parts of an Avatar
Before we dive fully into swapping out entire Avatars, let’s see how we can swap in and out objects that are a part of the avatar. In this example, let’s assume we have a very basic avatar holding a sword and a shield.
We are making a game that lets users switch out their weapon from their inventory. In this case they can choose between “Sword” and “Hammer”. Do synchronize this, we will use a string property that we will call “Item”. When the avatar’s “Item” property equals “sword” we show the sword, otherwise we show the hammer. One easy way to do this would be to write a custom No-Code Component to handle this.
using CavrnusSdk.PropertySynchronizers;
using UnityEngine;
public class SyncItem : CavrnusValueSyncString
{
public GameObject SwordPrefab;
public GameObject HammerPrefab;
public Transform HandTransform;
public override string GetValue()
{
if (HandTransform.GetChild(0).gameObject.name.ToLowerInvariant().Contains("sword"))
return "sword";
else
return "hammer";
}
public override void SetValue(string value)
{
if(value == "sword")
{
//Destroy the old item
GameObject.Destroy(HandTransform.GetChild(0).gameObject);
//Spawn the new item
GameObject.Instantiate(SwordPrefab, HandTransform);
}
else
{
//Destroy the old item
GameObject.Destroy(HandTransform.GetChild(0).gameObject);
//Spawn the new item
GameObject.Instantiate(HammerPrefab, HandTransform);
}
}
}
Next, let’s set this property in a simple UI menu. Since this is a property we are posting to a user, we need to make it Transient. Therefore, the first thing we will do is await a Space Connection, and that Connection’s local user. Then we kick off a LivePropertyUpdate that we will be sending values to based on UI inputs. Finally, we make two simple functions to run when the user hits the buttons.
using CavrnusSdk.API;
using UnityEngine;
public class InventoryMenu : MonoBehaviour
{
private CavrnusLivePropertyUpdate<string> liveItemUpdater;
private void Start()
{
CavrnusFunctionLibrary.AwaitAnySpaceConnection(spaceConn =>
{
spaceConn.AwaitLocalUser(localUser =>
{
liveItemUpdater = spaceConn.BeginTransientStringPropertyUpdate(localUser.ContainerId, "Item", "sword");
});
});
}
public void UseSword()
{
if(liveItemUpdater != null)
liveItemUpdater.UpdateWithNewData("sword");
}
public void UseHammer()
{
if (liveItemUpdater != null)
liveItemUpdater.UpdateWithNewData("hammer");
}
}
For this example we’ll just make two buttons to switch between them.
With this users can now swap in-and-out bits of their avatars at runtime.
Giving Properties to Avatar Sub-Parts
Now that we have established how a property can be used to dynamically create/destroy objects, let’s see how those objects can, themselves, show properties!
To do this, we need simply attach CavrnusPropertiesContainer components and ValueSync behaviors to our new object immediately after it spawns.
Note: The ValueSync behaviors run on Start()
which doesn’t occur immediately, so as long as the CavrnusPropertiesContainer's ContainerName, and all other values are assigned in the same function you won't have difficulty with the ValueSync component trying to use bad values.
public override void SetValue(string value)
{
if(value == "sword")
{
//Destroy the old item
GameObject.Destroy(HandTransform.GetChild(0).gameObject);
//Spawn the new item
var item = GameObject.Instantiate(SwordPrefab, HandTransform);
SetupItemWithProperties(item);
}
else
{
//Destroy the old item
GameObject.Destroy(HandTransform.GetChild(0).gameObject);
//Spawn the new item
var item = GameObject.Instantiate(HammerPrefab, HandTransform);
SetupItemWithProperties(item);
}
}
private void SetupItemWithProperties(GameObject item)
{
//Set the item's properties container name to be the same as our own.
//It could be different if we wanted.
string myContainerName = GetComponent<CavrnusPropertiesContainer>().UniqueContainerName;
var container = item.AddComponent<CavrnusPropertiesContainer>();
container.UniqueContainerName = myContainerName;
var syncColor = item.AddComponent<SyncMaterialInstanceColor>();
syncColor.PropertyName = "ItemColor";
}
Now, we can add logic in the UI buttons to post a ColorProperty named “ItemColor”, and it shows up on the object!
Swapping the “Entire” Avatar
Now that we have established how the avatar that gets spawned can create and destroy its own objects, and that those objects can have their own properties, the final step is to combine those systems to swap out the “entire” avatar.
As we have stated before, this is technically impossible, you can only specify one Remote Avatar in the Cavrnus Spatial Connector.
However, imagine if that sphere Co-Presence you see above was invisible (aka it didn’t have any renderers/geometry/etc). As far as your customers would know, their avatar was either a sword or a hammer, and they could switch between them.
Taking this a step further, let’s imagine you wanted your customers to be able to switch between a “Walking Mode” avatar, and a “Flying Mode” avatar. To achieve this, you would make an empty object called something like “Remote Avatar Display”, with a String Synchronizer on it. This is the object you will plug into the Cavrnus Spatial Connector.
What we end up with looks essentially identical to the Item swapper above. Any additional scripts/behaviors you want these “avatars” to have can also be set up like above.
Since there is no geometry here, as far as users are concerned this manager object doesn’t exist.
Loading in Custom Content As Avatars
While the above samples involve fairly simple switches between a specified set of prefabs, you could take this concept in any direction you wish. So long as you can write the code to interpret the properties, you can do anything.
To give one example here, if you wanted to have some sort of integration with a system ReadyPlayerMe, you could allow users to set their Ready Player Me ID somewhere, and post it as a property to their container. Then, instead of switching between prefabs, you could have a SyncReadyPlayerMe script which passes the value into a loader, which would then import the avatar.