Overview

While SALSA Suite v2 has a rich Editor (design-time) setup environment, all functionality can be implemented at runtime as well. Due to the myriad available settings options and configuration details, full runtime setup is involved. Most of the information in this document can be found elsewhere within the Suite documentation; however, this article aims to consolidate the workflow basics and refer to the details available elsewhere.

General Workflow

All modules in the Suite (SALSA, EmoteR, and Eyes) follow these general rules:

  1. Setup the model character settings and Expression(s) with ExpressionComponent(s).
  2. Bake the configuration data into serialized controller data.

See the API configuration examples for SALSA (with EmoteR) and Eyes. Focus particularly on the last few lines for examples of how and when to perform the data baking.

NOTE: In the SALSA API Configuration Example, there are two configuration sections, one for SALSA and one for EmoteR. The controller data baking is performed at the end of each section.

Much of the expression and expression component configuration details are not available at runtime and will generate errors unless they are baked into the controllers. This is the unfortunate side-effect of Unity serialization with derived Types and trying to provide access to configure these Types in the Inspector. Nonetheless, we have tried to make this as easy as possible. If you are only working in design-time, this is a requirement you don't need to worry about. The Suite handles this for you when the project runs. However, at runtime, you do need to ensure this is part of your flow.

Implementing the Workflow

As mentioned above, there are two main steps in the runtime workflow -- configure the model and bake the controller data. Due to the detail required for implementing character configuration at runtime, it is highly recommended to simply examine the two API configuration examples (SALSA example and Eyes example).

That being said, you may be wondering if it is possible to utilize a OneClick script to implement runtime configuration. The answer is yes. This will configure your supported character, but the configuration data still needs to be baked into the underlying controllers.

Using a OneClick for Runtime Configuration

It is relatively easy to leverage a set of OneClick scripts to configure your character model at runtime. Please refer to the OneClick documentation for more information on OneClicks. The OneClick scripts use an Editor script to call configuration scripts on a pre-selected Hierarchy GameObject (usually the root of the character model's hierarchy structure). You can see these calls at the bottom of any of our available OneClick packages. Bear in mind, you may also need to unpack your character if it is a prefab and you are using Unity 2018.3+. You can see an example of how to do this in the OneClick Editor script.

When you are ready to configure your character, you will need to call each of the associated OneClick runtime configuration scripts. The following will use the Reallusion CC3 scripts as an example.

GameObject gameObj; // usually the character root object in the hierarchy.
OneClickCC3.Setup(gameObj, null);
OneClickCC3Eyes.Setup(gameObj);

NOTE: While the AudioClip is likely not necessary for your project, it is required for the default OneClick script. This is what makes it one-clicky. However, you can simply pass a null as the parameter and set your AudioSource.clip elsewhere...or you can set it here.

Baking Controller Data

To bake the data, all modules essentially operate the same way. There are; however, a couple of small nuances to EmoteR and to Eyes that are different from SALSA. SALSA and EmoteR are very similar, so lets examine them first.

SALSA and EmoteR Requirements

Once the character model is configured (for runtime configuration, refer to the API and the API Configuration Example), the controller data needs to be updated. At design-time, this occurs in the Awake() game loop cycle. In order to implement it at runtime, call the controller update function for SALSA. Leverage the API to perform this function.

CrazyMinnow.SALSA.Salsa salsa; // reference to Salsa

salsa.UpdateExpressionControllers(); // Bake configuration data into the controllers.

For EmoteR, the process is the same; however, it also requires one additional step to update the specialty emote lists. Leverage the API to perform these functions.

CrazyMinnow.SALSA.Emoter emoter; // reference to Emoter

emoter.UpdateExpressionControllers(); // Bake configuration data into the controllers.
emoter.UpdateEmoteLists(); // Parse emotes into their associated emote lists.

Eyes Requirements

The Eyes workflow is very similar, but has some nuances to consider. Eyes is actually a set of four smaller modules that operate as a related set. The separate mini-modules have their own associated controller data and require baking the controller data from configuration data for each module implemented.

Additionally, once the character model's configuration is complete, several computational gizmos are required for orientation and movement or rotation calculations. The Eyes.Initialize() method is used to perform this requirement.

CrazyMinnow.SALSA.Eyes eyes;

eyes.Initialize(); // configure calculation gizmos

// Bake configuration data into controllers.
eyes.UpdateRuntimeExpressionControllers(ref eyes.heads);
eyes.UpdateRuntimeExpressionControllers(ref eyes.eyes);
eyes.UpdateRuntimeExpressionControllers(ref eyes.blinklids);
eyes.UpdateRuntimeExpressionControllers(ref eyes.tracklids);

UMA Considerations

NOTE: Crazy Minnow Studio does not provide support for UMA or other 3rd party integration systems. If you have difficulties with UMA, please utilize the UMA support options for assistance.

Ensure you are familiar with the OneClick Setup and UMA OneClick information.

UMA is bit different to work with due to the way it builds the character at runtime. The DynamicCharacterAvatar (DCA) resolved some of the issues of trying to ease the burden of working with a character system that was difficult or impossible to create a prefab from, but still has its nuances.

If the desire is to simply wire up an UMA character at runtime it is not much different from the workflow described above. However, if an UMA character is wired up and the designer wishes to take advantage of the ability to change the character at runtime (while SALSA Suite is running) there are a couple of considerations to take into account.

Be aware, UMA has many different options and can be hooked up in many ways. The following is an example of how we work with the DCA object and may not work in all situations.

SALSA Suite Cleanup

If any component in the SALSA Suite is running, there are some housekeeping tasks to accomplish. SALSA and EmoteR implement the same mechanism to stop/start their respective modules (EmoteR has one additional requirement). Eyes itself consists of 4 sub-modules and uses a slightly different mechanism to accomplish the shutdown and startup tasks. This is mainly because the sub-modules can be shut off independently.

In addition to shutting the modules down, if using a system like UMA where it is possible to instantly destroy the character, but not the character root object where the SALSA Suite lives, the QueueProcessor can be left with a bunch of missing components, creating null-references. Fortunately, there is a mechanism for dealing with QueueProcessor cleanup as well.

Stop the Running Modules

Stop the SALSA and EmoteR modules (use the normal Unity component enable/disable mechanism) and pay close attention to the slight difference in stopping the Eyes module.

Cleaning up the QueueProcessor must always occur after stopping the main Suite modules. Otherwise, they (SALSA, EmoteR, Eyes) may continue to register components into the QueueProcessor.

GameObject salsaSuiteObject;
salsaSuiteObject.GetComponent<Salsa>().enabled = false;
salsaSuiteObject.GetComponent<Emoter>().enabled = false;
salsaSuiteObject.GetComponent<Eyes>().EnableAll(false);

// Clear the queues...
var qp = salsaSuiteObject.GetComponent<QueueProcessor>();
qp.priorityQueueHeads.Clear();
qp.priorityQueueEyes.Clear();
qp.priorityQueueLipsync.Clear();
qp.priorityQueueEmotes.Clear();
qp.priorityQueueBlinks.Clear();
qp.priorityQueueLids.Clear();

NOTE: In the next version of SALSA Suite v2, the QueueProcessor will gain a new option to flush the queues: QueueProcessor.Flush()

Start the Modules

Before you implement the workflow described earlier in this article to apply your character configuration to your model avatar, ensure you restart the required modules. Also, be sure to do this after the character avatar has been rebuilt. In UMA, you should leverage one of the callback methods available to the UMA DCA system. Please refer to UMA documentation and UMA support mechanisms for more details or questions. Crazy Minnow Studio does not provide support for the UMA product.

NOTE: It is not technically necessary to start the Eyes sub-modules back up when using the UMA OneClick, since it will do this automatically. However, if using a different system this may be required.

// The following should be run after UMA has notified you the avatar is ready.
GameObject salsaSuiteObject;
salsaSuiteObject.GetComponent<Salsa>().enabled = true;
salsaSuiteObject.GetComponent<Emoter>().enabled = true;
salsaSuiteObject.GetComponent<Eyes>().EnableAll(true);

Considerations for Configuring the UMA Avatar

As mentioned above, setting up an UMA character that was running and is instantly rebuilt has some nuances from the normal runtime setup workflow. The avatar was linked up and running and now has been stopped, most likely destroyed, and rebuilt. Having been rebuilt, some of the systems SALSA Suite uses to provide its functionality need to be re-linked -- namely the UMA Expression Player (UEP).

After the Suite modules have been re-enabled and the UMA OneClick setup reapplied, it is necessary to re-configure the UMA OneClick UmaUepDriver component. This is a simple one-line call that re-links the UEP and also re-links the bones used by Eyes for head and eye animation.

NOTE: This call requires the UMAData structure be passed to it for proper configuration. This means it is necessary to have a link to the already-built avatar. It is preferred to use one of the UMA DCA callbacks to run your configuration script since it provides the data in the callback and you can be sure UMA is ready for you to work with the completed/updated chacater avatar.

GameObject salsaSuiteObject;
salsaSuiteObject.GetComponent<UmaUepDriver>().CharacterCreated(umaData);

IMPORTANT:
Now you can continue to setup your avatar as you would any other runtime character except for one small difference. Since the UmaUepDriver component configuration was called previously, the Eyes.Initialize() method was automatically called. Therefore, you should not call this function again when setting up your avatar. It won't harm anything, but it will build another set of calculation gizmos in your scene, creating objects that are unnecessary. The next SALSA Suite update will fix this issue. See the next section for a wrap-up of all of the above code snippets.

Putting It All Together (UMA)

The following is provided as a mostly-complete set of the above example code for re-wiring an UMA avatar that is built and running SALSA Suite and is then changed and rebuilt by UMA:

// Called by some function or functionality in your application to 'change'
// the UMA avatar.
public void ReconfigureRunningUma()
{
    Debug.Log("SuiteRelink: Initiated.");
    DisableSalsaSuite(); // see below

    // ...call your UMA 'change' here...change race, recipe, etc.
}

// This method should be called by your UMA callback...i.e. CharacterUpdated, etc.
// NOTE: It requires the UMAData structure to complete the re-setup of your avatar.
private void RelinkSalsaSuite(UMAData umaData)
{
    Debug.Log("SuiteRelink: Re-linking.");
    GameObject salsaSuiteObject;

    // re-enable SALSA Suite
    salsaSuiteObject.GetComponent<Salsa>().enabled = true;
    salsaSuiteObject.GetComponent<Emoter>().enabled = true;

    // OneClick runtime setup...
    AudioClip clip = salsaSuiteObject.GetComponent<AudioSource>().clip;
    OneClickUmaDcs.Setup(salsaSuiteObject.gameObject, null);
    OneClickUmaDcsEyes.Setup(salsaSuiteObject.gameObject);

    // reconfigure the UmaUepDriver...
    salsaSuiteObject.GetComponent<UmaUepDriver>().CharacterCreated(umaData);

    // Bake Expression controllers for SALSA and EmoteR...
    Salsa salsa = salsaSuiteObject.GetComponent<Salsa>();
    salsa.UpdateExpressionControllers();
    Emoter emoter = salsaSuiteObject.GetComponent<Emoter>();
    emoter.UpdateExpressionControllers();
    emoter.UpdateEmoteLists();

    // Bake Expression controllers for Eyes...
    // NOTE: We don't call Eyes.Initialize() here...already applied
    //  in the UmaUepDriver call above.
    Eyes eyes = salsaSuiteObject.GetComponent<Eyes>();
    eyes.UpdateRuntimeExpressionControllers(ref eyes.heads);
    eyes.UpdateRuntimeExpressionControllers(ref eyes.eyes);
    eyes.UpdateRuntimeExpressionControllers(ref eyes.blinklids);
    eyes.UpdateRuntimeExpressionControllers(ref eyes.tracklids);
}

private void DisableSalsaSuite()
{
    Debug.Log("SuiteRelink: Disable Suite components.");
    // disable Suite components to prevent writing to queue.
    salsaSuiteObject.GetComponent<Salsa>().enabled = false;
    salsaSuiteObject.GetComponent<Emoter>().enabled = false;
    salsaSuiteObject.GetComponent<Eyes>().EnableAll(false);

    // clear QueueProcessor
    var qp = salsaSuiteObject.GetComponent<QueueProcessor>();
    qp.priorityQueueHeads.Clear();
    qp.priorityQueueEyes.Clear();
    qp.priorityQueueLipsync.Clear();
    qp.priorityQueueEmotes.Clear();
    qp.priorityQueueBlinks.Clear();
    qp.priorityQueueLids.Clear();
}