Cloud Variables In Action
Cloud variables provide a powerful way for creators to make persistent data for users within their servers and worlds. With this short example, we will demonstrate the best practices for implementing a cloud-based system to make a customizable avatar system.
Quick Refresher On Cloud Variables
- Persist across sessions and worlds within the same server as each other
- Synchronized across all clients
- User variables are stored on a per-user basis, where world variables are stored on a per-world basis, and server variables on a per-server basis.
Parameters:
Name | Type | Description |
---|---|---|
name | string | The name of the event |
value | any | The value that may associated with the event. |
worldLocation | string | The world location that may associated with the event. |
detail | string | The extra detail about the event. |
publicViewable | boolean | Is this event publicly viewable? |
Cloud Variable Manager
The CloudOutfitManager is responsible for loading and applying persistent avatar customizations stored in cloud variables. It initializes after a short delay (to avoid race conditions) and retrieves the local player’s avatar. Using a predefined list of outfit parameters (e.g., M_Shoes
, F_Shirt_Color
), it checks for saved values in the cloud, converts them to integers, and applies them to the avatar’s animation parameters. Each cloud variable is identified by a customizable prefix (e.g., my_custom_outfit
), ensuring organized storage. The script logs each step—successes, failures, and conversion errors—to a TextMeshPro
UI element for debugging.
This system ensures that players’ outfit choices persist between sessions. If cloud variables are disabled or no saved data exists, it logs warnings but continues gracefully. The conversion logic handles various numeric formats (e.g., doubles, strings), making it resilient to data type mismatches. Overall, it bridges cloud-stored preferences with real-time avatar customization.
public class CloudOutfitManager : MonoBehaviour
{
// Customizable prefix, change this to a value if your own liking!
[SerializeField] private string cloudPrefix = "my_custom_outfit";
[SerializeField] public TextMeshPro TextObject;
[SerializeField] private float initializationDelay = 2.5f;
private readonly string[] outfitParameters = new string[]
{
"M_Shoes", "M_Shirt_Logo", "M_Shirt_Color", "M_Pants", "M_Outfits",
"F_Shoes", "F_Shirt_Logo", "F_Shirt_Color", "F_Pants", "F_Outfits"
};
private void Start()
{
UpdateText("Initializing Cloud Outfit Manager...");
StartCoroutine(InitializeWithDelay());
}
private IEnumerator InitializeWithDelay()
{
UpdateText("\nWaiting for initialization delay...");
yield return new WaitForSeconds(initializationDelay);
UpdateText("\nGetting local player...");
MLPlayer localPlayer = MassiveLoopRoom.GetLocalPlayer();
if (localPlayer != null)
{
UpdateText("\nPlayer found. Loading avatar...");
OnLocalPlayerAvatarLoad();
}
else
{
UpdateText("\nERROR: Local player not found");
}
}
private void OnLocalPlayerAvatarLoad()
{
if (!CloudVariables.IsEnabled)
{
UpdateText("\nERROR: Cloud Variables disabled");
return;
}
MLPlayer player = MassiveLoopRoom.GetLocalPlayer();
if (player == null)
{
UpdateText("\nERROR: Player reference lost");
return;
}
UpdateText("\nStarting cloud variable check:");
bool foundAny = false;
int foundCount = 0;
for (int i = 0; i < outfitParameters.Length; i++)
{
string fullKey = cloudPrefix + outfitParameters[i];
UpdateText($"\nChecking {i + 1}. {fullKey}");
if (CloudVariables.UserVariables.KeyExists(fullKey))
{
try
{
object rawValue = CloudVariables.UserVariables.GetVariable(fullKey);
UpdateText($" - Found: {rawValue}");
if (TryConvertToInt(rawValue, out int intValue))
{
UpdateText($" (as int: {intValue})");
// Extract parameter name by removing prefix
string paramName = fullKey.Substring(cloudPrefix.Length);
UpdateText($"\nAttempting to set {paramName} = {intValue}");
player.SetAnimationIntValue(paramName, intValue);
UpdateText(" - Success!");
foundCount++;
foundAny = true;
}
else
{
UpdateText(" - ERROR: Could not convert to int");
}
}
catch (System.Exception e)
{
UpdateText($" - ERROR: {e.Message}");
}
}
else
{
UpdateText(" - Not found");
}
}
UpdateText(foundAny
? $"\nSuccess! Applied {foundCount} variables"
: "\nWARNING: No valid variables found");
}
private bool TryConvertToInt(object value, out int result)
{
result = 0;
try
{
if (value is double d)
{
result = (int)d;
return true;
}
if (double.TryParse(value.ToString(), out double parsed))
{
result = (int)parsed;
return true;
}
return false;
}
catch
{
return false;
}
}
private void UpdateText(string message)
{
if (TextObject != null)
{
TextObject.text += message;
}
}
}
Customization Selector
The OutFitSelector_Cloud script enables interactive avatar customization by letting players click UI elements (e.g., buttons) to change outfit parameters. When clicked, it updates the player’s avatar in real time and saves the selected value to cloud variables for persistence across sessions. Each selector is configured with:
parameterName
: The animation parameter to modify (e.g.,"M_Shoes"
).parameterValue
: The integer value to apply (e.g.,2
for a specific shoe style).isDefault
: If enabled, resets the outfit category to a baseline value (0
).
The script uses a customizable cloud prefix (my_custom_outfit
) to namespace saved variables, ensuring they don’t conflict with other systems. It automatically hooks into an MLClickable
component (e.g., a button) and validates that only the local player’s selections are processed.
Key Features:
-
Real-Time Updates: Applies changes immediately to the player’s avatar.
-
Cloud Persistence: Saves selections to cloud variables (if enabled).
-
Default Resets: Supports reverting specific outfit categories to default values.
public class OutFitSelector_Cloud : MonoBehaviour
{
// Constants
// Customizable prefix, change this to a value if your own liking!
private const string C_PREFIX = "my_custom_outfit";
// Serialized Fields
[SerializeField] private string parameterName;
[SerializeField] private int parameterValue;
[SerializeField] private MLClickable click;
[SerializeField] private bool isDefault;
[SerializeField] private string outfitName;
private void Start()
{
if (click != null)
{
click.OnPlayerClick.AddListener(SelectOption);
}
else
{
click = this.gameObject.GetComponent(typeof(MLClickable)) as MLClickable;
click.OnPlayerClick.AddListener(SelectOption);
}
}
private void SelectOption(MLPlayer player)
{
if (player == null || !player.IsLocal)
{
return;
}
if (isDefault)
{
player.SetAnimationIntValue(outfitName, 0);
if (CloudVariables.IsEnabled)
{
CloudVariables.UserVariables.SetVariable(C_PREFIX + outfitName, 0);
}
}
player.SetAnimationIntValue(parameterName, parameterValue);
if (CloudVariables.IsEnabled)
{
CloudVariables.UserVariables.SetVariable(C_PREFIX + parameterName, parameterValue);
}
}
}
Bringing It All Together
Together, OutFitSelector_Cloud and CloudOutfitManager create a robust system for avatar customization with cloud persistence. The selector handles player interactions, applying changes in real time and saving preferences to the cloud, while the manager ensures those preferences are reloaded seamlessly whenever the player returns. This approach not only enhances user experience by maintaining continuity across sessions but also demonstrates the power of cloud variables in creating persistent, personalized gameplay.
Extending the System
This foundation can be expanded to support more complex customization systems such as dynamic skin unlocks, seasonal outfits, or cross-device synchronization; by simply extending the parameter lists or cloud key structures. You can even serialize game data by storing player stats in their own cloud variable, E.G: Strength, Dexterity, Wisdom, ect! By decoupling the prefix and using clear naming conventions, the system remains scalable and adaptable. Whether for fashion-based games, RPGs, or social hubs, this pattern ensures player choices are always remembered, fostering deeper engagement and ownership over their virtual identity and space!