Overview for CrazyMinnow.SALSA.Emoter.cs

EmoteR also has a great custom Inspector, very similar in design to SALSA, facilitating all of the emoting functionality. However, there may be times when more complex functionality is desired. For this reason, EmoteR has an underlying API, allowing developers to programmatically configure and control EmoteR's workflow. SALSA LipSync Suite is a complete re-write of the original v1 code, and EmoteR was pulled out of RandomEyes into its own module, therefore the previous RandomEyes CustomShapes API has changed and is no longer valid.

Namespace: CrazyMinnow.SALSA

All of the main Suite modules reside in the CrazyMinnow.SALSA namespace. If your custom code cannot find Emoter ensure you have included a reference to the appropriate namespace. Please ensure you understand C# namespaces and their usage. There are a myriad of references available on the Internet discussing the subject. If you fail to reference the appropriate SALSA Suite namespaces, you will not be able to use the SALSA Suite API.

The following properties and methods are available for: CrazyMinnow.SALSA.Emoter.cs

Properties (for CrazyMinnow.SALSA.Emoter configuration)

There are many public fields available in the EmoteR library. Most of these are implemented for EmoteR's runtime configuration and control.

References

QueueProcessor queueProcessor

A QueueProcessor is a required reference link needed to initiate priority-based animations. If setting EmoteR up in a runtime environment, a QueueProcessor must be instantiated and EmoteR must know where it is.

[Range(0f, 1f)] float lipsyncEmphasisChance = 1.0f

Not truely a reference, however, EmoteR uses this value to calculate the chance a SALSA-triggered emphasizer emote will be fired. This is a percentage-based chance. Recommend this be set to 100% (1.0f) and the corresponding Salsa.emphasizerTrigger value in SALSA to 0% (0.0f).

Emote Lists

EmoteR utilizes a list of each emote type for selection and control. There are currently four types of emotes:

  • Random
  • LipSync Emphasis
  • Repeater
  • Manual

All emotes are added first to the manual pool during configuration and are subsequently added to the specialty pools by calling UpdateEmoteLists().

NOTE: During normal design-time to run-time operation, Awake() calls the appropriate pre-processing configuration methods: UpdateExpressionControllers() and UpdateEmoteLists(); therefore, it is necessary to manually call these operations if configuring at runtime. See their respective entries in this documentation.

List randomEmotes (random emotes)
List lipsyncEmphasisEmotes (lipsync emphasis)
List repeaterEmotes (repeater emotes)
List emotes (manually triggered emotes)

Within the EmoteExpression data structure are flags which indicate which specialty emote list/pool an emote should belong to. All emotes are part of the manual pool "emotes". To apply an emote to a specialty pool, enable the appropriate flag: EmoteExpression.isRandomEmote
EmoteExpression.isLipsyncEmphasisEmote
EmoteExpression.isRepeaterEmote

For example, adding an emote to both the random and repeater specialty pools:

Emoter emoter;
emoter.emotes.Clear();
emoter.emotes.Add(new EmoteExpression("lookUp", new InspectorControllerHelperData(), true, false, false, 0f));
emoter.emotes[0].isRandomEmote = true;

Additionally, if a Repeater emote pool is enabled, there is an additional field to specify how long the emote should delay before the repeater cycles again.

EmoteExpression.repeaterDelay

For example, to add the above created emote to the repeater pool as well:

emoter.emotes[0].isRepeaterEmote = true;
emoter.emotes[0].repeaterDelay = 1.5f;

It may also be beneficial, depending on your configuration to make emotes persist in the QueueProcessor. The benefit of doing so is the emote components will continue to remain in the QueueProcessor's LateUpdate() processing cycle, even when they have completed their full animation cycle and therefore will override external animation mechanisms that utilize the Update() cycle, such as mecanim.

NOTE: If this is not a concern, it is optimally performant to allow emotes to drop out of the queue when their animation cycle has finished.

EmoteExpression.isPersistent = false;


The following is an overall example of configuring an emote at runtime. Example: (adding a blendshape emote from the boxhead model, "lookUp"). See the EmoteR API-Example for more detail.

var emoter = selectedObject.GetComponent<Emoter>();
var smr = selectedObject.GetComponent<SkinnedMeshRenderer>();
emoter.emotes.Clear(); // ensure there are no residual emotes.

// add the first emote...
// NOTE: we add the emote to the manual pool, the 'emotes' List<>.
emoter.emotes.Add(new EmoteExpression("lookUp", new InspectorControllerHelperData(), true, false, false, 0f));

// configure the emote's settings...
// since the list was cleared above, it is known this is the first emote,
// we will use index zero in the list.
emoter.emotes[0].isRandomEmote = true;  emote added.
var emote = emoter.emotes[0].expData;  // cache the emote config data for convenience.
emote.components[0].name = "lookUp emote component";
emote.components[0].durationOn = .5f;
emote.components[0].durationHold = .2f;
emote.components[0].durationOff = .3f;
emote.controllerVars[0].smr = smr;
emote.controllerVars[0].blendIndex = smr.sharedMesh.GetBlendShapeIndex("lookUp");
emote.controllerVars[0].minShape = 0f;
emote.controllerVars[0].maxShape = 1f;

// if implemented at runtime, we need to update the ExpressionController's 
// for each emote and subsequently update the emote lists/pools.
emoter.UpdateExpressionControllers();
emoter.UpdateEmoteLists();

Operational Settings

bool useRandomEmotes = true

Flag indicating EmoteR should process emotes in the random pool.

bool isChancePerEmote = true

When enabled, the specified random chance is calculated per chosen emote. The number of emotes chosen per random cycle is specified via NumRandomEmotesPerCycle.

int NumRandomEmotesPerCycle (read/write)

Specifies the number of emotes to be chosen/fired from the random pool per random clock cycle. This value must be greater than zero (if the pool contains emotes) and cannot exceed the number of emotes in the pool. If a value < 1 is specified while emotes are in this pool, the value will be set as 1. If a value higher than the number of emotes is specified, the value will be clamped at the pool count.
NOTE: If emotes are programmatically added/removed from this pool, the number of emotes chosen will be overriden during processing to avoid out-of-range errors.

float randomEmoteMinTimer = 1.0f

The minimum time that should pass before another set of random emotes is chosen to activate.

float randomEmoteMaxTimer = 2.0f

The maximum time that should pass before another set of random emotes is chosen to activate.

[Range(0f, 1f)] float randomChance = 0.5f

The percentage-based chance emotes chosen from the random pool will fire. If isChancePerEmote is true, this value will be calculated for each emote chosen to fire this round.


bool useRandomFrac

Enabling this option will compute a random fractional (percentage) value to be applied to the emote's max extent. This setting applies to all emotes configured on this EmoteR instance.

[Range(0f, 1f)] float randomFracBias = 0.5f

When useRandomFrac is enabled, the fractional (percentage) value will be a minimum of this amount. For example, if randomFracBias is 0.6f, the computed random fractional will be between 60 - 100% of the emote's configured maximum extent.


bool useRandomHoldDuration
float randomHoldDurationMin = 0.1f
float randomHoldDurationMax = 0.5f

useRandomHoldDuration applies only to round-trip emotes. Enabling this option computes a timeframe between randomHoldDurationMin and randomHoldDurationMax and substitutes this value for the configured durationHold time.

Methods

Configuration Methods

As mentioned in the emote lists section above, there are two methods that should be called during a runtime configuration of EmoteR to properly configure the controllers and emote lists. You can see an example of this in the EmoteR API-Example.

void UpdateExpressionControllers()

To implement the underlying data structure and also allow serialization to be maintained in the Inspector, we chose to implement a helper class for defining ExpressionComponent data. This allowed us to build a rich, custom inspector and also maintain a generic component implementation on the back-end where the QueueProcessor handled all objects the same way and in priority fashion. Due to this implementation, building an ExpressionComponent at runtime requires the associated controllers to be created and updated according to the associated data. Essentially, this bakes the controller helper data (used by the inspector or configured at runtime) into the controller data object(s), creating the necessary derived data and linkages to the proper ExpressionControllers. This should be called after the emotes and emote components have been programmatically constructed. See the EmoteR API-Example for a detailed configuration example. Call this method to update the controllers of all emote components.

void UpdateEmoteLists()

Call this method to update the specialty emote lists after all emotes have been configured in the manual pool and also after UpdateExpressionControllers() has been called. This will parse the emotes list and add emotes to the specialty lists as indicated by the appropriate flags.

Operational Methods

void TurnOffAll()

Turn off all configured/registered emotes. This is normally called in OnEnable(), but it may be desirable to call it in other circumstances. NOTE: to smoothly turn emotes off, ensure Emoter.emotes[expressionIndex].expData.components[componentIndex].isSmoothDisable is true prior to calling this method. Setting isSmoothDisable to false will turn all emotes off instantly.

CrazyMinnow.SALSA.Emoter emoter;
emoter.TurnOffAll();

bool RemoveFromPool(string emote, PoolType poolType)

Programmatically remove an existing emote from a specific specialty pool at runtime. Simply setting the membership flag will not change the emote's pool membership. When configuring Emoter in the inspector, the pool membership flags are set and the pools are parsed at runtime startup (automatically running Emoter.UpdateEmoteLists();). It is possible to change pool membership flags and then re-run Emoter.UpdateEmoteLists() at runtime. However, it is more efficient to simply run the Emoter.RemoveFromPool() and Emoter.AddToPool() functions. Returns success/failure status. NOTE: This function uses the configured EmoteR emote name (not the blendshape/bone/etc. name).

CrazyMinnow.SALSA.Emoter emoter;
string emoteName = "someEmoteExpression";
if (emoter.RemoveFromPool(emoteName, Emoter.PoolType.Repeater))
    Debug.Log("Removed repeater: " + emoteName);
else
    Debug.Log("Failed to remove repeater: " + emoteName);

bool AddToPool(string emote, PoolType poolType)

Programmatically add an exiting emote (by EmoteExpression string name) to a specific specialty pool at runtime. See RemoveFromPool above for more details. Returns success/failure status. NOTE: This function uses the configured EmoteR emote name (not the blendshape/bone/etc. name).

CrazyMinnow.SALSA.Emoter emoter;
string emoteName = "someEmoteExpression";
if (emoter.AddToPool(emoteName, Emoter.PoolType.Repeater))
    Debug.Log("Added repeater: " + emoteName);
else
    Debug.Log("Failed to add repeater: " + emoteName);

void RandomEmote(float chance, int numEmotes)

While this method is internally used to trigger a random set of emotes (from the random pool) during the configured random clock cycle, it may be desirable to trigger random emotes (from the random pool) via another programmatic means. This method allows you to easily do so. Simply specify a normalized percentage chance and a number of emotes to trigger in the random pool.

Manually Triggering an Emote

There are two overrides for manually triggering an emote. The difference is simply in whether you are specifying the emote by name or by index.

void ManualEmote(string name, ExpressionComponent.ExpressionHandler handler, float duration = 0.0f, bool isActivating = true, float frac = 1.0f)

Manually trigger an emote using the configured emote name. The handler parameter specifies one-way or round-trip. If triggering a one-way emote, another call to this method will be required to turn it off.

One-way example:

Emoter emoter;
// turn emote "your emote name" ON
emoter.ManualEmote("your emote name", ExpressionComponent.ExpressionHandler.OneWay);
// turn emote "your emote name" OFF, the 0f time is arbitrary and does not matter.
emoter.ManualEmote("your emote name", ExpressionComponent.ExpressionHandler.OneWay, 0f, false);

Round-trip example:

Emoter emoter;
// turn emote "your emote name" ON, with a 1.5 second hold duration, it will automatically turn off.
emoter.ManualEmote("your emote name", ExpressionComponent.ExpressionHandler.RoundTrip, 1.5f);

NOTE: If you wish to specify a fractional percentage of your emote's max extents, you may pass it as the final parameter. If activating a one-way emote to 65% of its max extents, you can do the following -- the hold time does not matter.

One-way example, 65% of max extent:

Emoter emoter;
// turn emote "your emote name" ON, note the duration time is irrelevant.
emoter.ManualEmote("your emote name", ExpressionComponent.ExpressionHandler.OneWay, 0f, true, 0.65f);
// turn off "your emote name"
emoter.ManualEmote("your emote name", ExpressionComponent.ExpressionHandler.OneWay, 0f, false);

Round-trip example, 53% of max extent:

Emoter emoter;
// turn emote "your emote name" ON, with a 1.5 second hold duration, it will automatically turn off.
// NOTE: the isActivating bool is irrelevant if the handler is RoundTrip.
emoter.ManualEmote("your emote name", ExpressionComponent.ExpressionHandler.RoundTrip, 1.5f, true, 0.53%);

void ManualEmote(int index, ExpressionComponent.ExpressionHandler handler, bool isActivating = true, float duration = 0.0f, float frac = 1.0f)

This override operates exactly as the above except requires an int index value vice string name value.