Creating a Custom OneClick

This is a relatively easy process currently applicable to SALSA and/or EmoteR settings changes. There are generally 3 files associated with a OneClick (not counting the OneClickBase, which is required for all OneClicks). The ACG OneClick has an extra file for LOD information and switching. And UMA is quite a different beast altogether.

The 3 common files consist of:

  • An Editor script which allows you to apply a OneClick from the Editor GameObject menu.
  • A runtime script for SALSA and EmoteR settings.
  • A runtime script for the Eyes module configuration.

1. Duplicate the Provided OneClick Scripts

For this example, we will use the CC3 set of scripts.

  • Duplicate (ctrl-D) the exiting CC3 OneClick Editor file.
    duplicate editor file
  • And give it a unique name (i.e. MyCustomOneClickEditor).
    rename editor file
  • Change the Class name of your custom OneClickEditor script to match the file name and give the menu item a unique name.
    change class menu name
  • Perform the same operations to duplicate the SALSA/EmoteR OneClick script, give it a unique name (MyCustomOneClick), and change the Class name to match the file name.
  • Perform the same operations to duplicate the Eyes OneClick script, give it a unique name (MyCustomOneClickEyes), and change the Class name to match the file name.
    duplicate runtime files
  • Open the custom OneClickEditor file again and link it to the new SALSA/EmoteR and Eyes scripts in the ApplyOneClick() method.
    edit editor file

2. Modify Existing OneClick Settings

Adjust any settings in the new custom OneClick runtimes for SALSA/EmoteR and Eyes.

To adjust existing settings for SALSA or EmoteR, it is pretty easy to simply look at the MyCustomOneClick.cs file (SALSA/EmoteR config) and add, remove, or change settings to your liking. Just change the values, names, etc. to your preference. For example, looking at the CC3 OneClick file we copied, in the SALSA configuration section, we have a relatively easy configuration to work with.

2.1. Modifying SALSA Viseme Definitions

First, locate the definition of a new viseme expression named "w", followed by a bone ExpressionComponent definition, followed by a shape ExpressionComponent definition. Bare in mind, these definitions are simplified into the OneClickBase configuration command "language".

NewExpression("w");
AddBoneComponent("^CC_Base_Teeth02$",
    new TformBase(new Vector3(-0.08919834f, 0.02136874f, 6.878996E-05f),
    new Quaternion(0.9989499f, -0.04581843f, 5.542967E-07f, 2.483174E-07f),
    new Vector3(1f, 1f, 1f)),
    0.08f, 0f, 0.06f,
    "CC_Base_Teeth02",
    false, true, false);
AddShapeComponent(new []{"^Mouth_Lips_Open.*$"}, 0.08f, 0f, 0.06f, "Mouth_Lips_Open", 0.378f, true);

2.1.1. THE START OF A NEW VISEME

The new viseme expression command is easy enough to understand, call the NewExpression() method, passing a string parameter containing the new viseme name.

NewExpression("w");

Any additional component definitions following the NewExpression() command are applied to this Expression (viseme or emote). Therefore, issue the NewExpression() command and then add 'bone' or 'shape' component definitions, then signify a new viseme or emote with another NewExpression() command, followed by more component definitions, etc.


2.1.2. SHAPE COMPONENT DEFINITION

Since it is a bit simpler, let's first examine a shape component definition. The command AddShapeComponent() defines the minimal requirements for a new shape component:

AddShapeComponent(new []{"^Mouth_Lips_Open.*$"}, 0.08f, 0f, 0.06f, "Mouth_Lips_Open", 0.378f, true);
  • First we pass an array of search strings (one or more) consisting of possible shape names. This example is using the CC3 OneClick as a model and has a single search string in simple regex format. In some OneClicks there are multiple shape names to look for depending on the generation of model or the options chosen at export. Therefore, you may see a more complex parameter in the OneClick you are copying/adjusting or it may simply be a single string literal or regex. For example, here is a search array from the DAZ OneClick: new[] {"head__eCTRLvW", "head__VSMW", "head__CTRLVSMW"}. When executing, OneClickBase will sequentially look for a blendshape match in the order provided. It will stop looking after the first success. NOTE: The search type used for these strings is either string literal or regex (.net). See information below (last parameter - bool value) for signifying (to OneClickBase) which is used.
  • The next 3 parameters (0.08f, 0f, 0.06f) are the animation timings (ON/Hold/OFF). NOTE: as per the SALSA documentation, the "Hold" value is not used by SALSA. This value is arbitrary and can be any float value.
  • The string parameter ("Mouth_Lips_Open") is the name that will be assigned to the new component.
  • Next is the blendshape amount for the "max" position (animate ON). NOTE this value is passed as "normalized" value of the blendshape. In this case, 0.378f would equate to a blendshape value of 37.8f when set in the SkinnedMeshRenderer. Also note, this is no longer a clamped [0f .. 1f] value and does support the over/under-driving capabilities in Unity 2018.3+. It is still necessary to represent the value as a 'fractional' value to support the SALSA Core internals and provide backwards compatibility. Therefore, simply use values that derive from the SkinnedMeshRenderer blendshape values divided by 100. i.e. (-32.5f = -0.325f) and (126.2f = 1.262f).
  • Finally, an optional boolean parameter, which defines the search type: simple string literal (false: default) or regex (true).

2.1.3. BONE COMPONENT DEFINITION

Adding a bone component is a little more involved since it is a slightly more complex controller type:

AddBoneComponent("^CC_Base_Teeth02$",
    new TformBase(new Vector3(-0.08919834f, 0.02136874f, 6.878996E-05f),
    new Quaternion(0.9989499f, -0.04581843f, 5.542967E-07f, 2.483174E-07f),
    new Vector3(1f, 1f, 1f)),
    0.08f, 0f, 0.06f,
    "CC_Base_Teeth02",
    false, true, false);
  • The first parameter is the regex search (from the context of the root/parent) for the bone GameObject, in this case "CC_Base_Teeth02".
  • The next three values are the "max" setting for the position (TformBase), rotation (Quaternion), and scale (Vector3). This is where the bone will animate towards when the animation is turned ON.
  • The next three values (0.08f, 0f, 0.06f) are the animation timings (ON/Hold/OFF).
  • The string value "CC_Base_Teeth02" is the name assigned to the component.
  • Finally, the three booleans apply to the constraints on the bone transform (position, rotation, scale). In this case, we are applying the "max" settings configured in the TformBase, Quaternion, and Vector3 parameters above, restricting the bone to rotational changes. Position (TformBase) is false, Rotation (Quaternion) is true, and scale (Vector3) is false. SALSA will only apply rotational animation to the transform of this bone.

2.2. Modifying EmoteR Emote Definitions

Emotes are defined nearly identically to SALSA visemes with one small addition, the additional configuration call to set the EmoteR specialty pool flags as noted in the example below. This configuration creates an emote named "browsUp" and looks for 4 different blendshapes to create shape components with.

NewExpression("browsUp");
AddEmoteFlags(false, true, false, 1f);
AddShapeComponent(new []{"^Brow_Raise_L.*$"}, 0.2f, 0.1f, 0.15f, "Brow_Raise_L", 0.907f, true);
AddShapeComponent(new []{"^Brow_Raise_R.*$"}, 0.25f, 0.2f, 0.1f, "Brow_Raise_R", 0.72f, true);
AddShapeComponent(new []{"^Brow_Raise_Inner_R.*$"}, 0.25f, 0.2f, 0.15f, "Brow_Raise_Inner_R", 0.68f, true);
AddShapeComponent(new []{"^Brow_Raise_Inner_L.*$"}, 0.2f, 0.08f, 0.15f, "Brow_Raise_Inner_L", 0.687f, true);

Creating a new emote definition is nearly identical to a SALSA viseme, first use the NewExpression() command with the string name as a parameter. See the SALSA information above for more detail.


2.2.1. EMOTE FLAGS AND EXPRESSION DYNAMICS (FRACTIONAL)

Here's the real difference between an emote and viseme definition. An Emote can be a member of up to 3 specialty pools and we define the specialty pool flags with the AddEmoteFlags() command:

AddEmoteFlags(false, true, false, 1f);
  • Three bools for 3 pools: random pool, lipsync emphasis pool, and repeater pool. Pass true for inclusion and false for exclusion in the respective pool.
  • Additionally, there is an float value passed that indicates the overall emote's Expression Dynamics or fractional variation value.

2.2.2. DEFINING EMOTE COMPONENTS

Other than the specialty pool command, an emote definition is pretty much identical to a viseme definition using the OneClickBase command language. Define one or more bone and/or shape ExpressionComponents in the same way as a SALSA viseme.


3. Adding New Settings or Overriding Defaults

The command language is not all-inclusive of the available settings and is designed (currently) as a minimal configuration utility. There are many more options available to configure global SALSA and EmoteR settings, individual viseme and emote settings, and even component-level settings per viseme or emote.

To add new settings for SALSA or EmoteR, we recommend adding any special code below the DoOneClickiness() method call (in file MyCustomOneClick.cs). DoOneClickiness() is the final call in the SALSA/EmoteR script that initiates the creation of the SALSA and EmoteR configuration once it has been defined. Therefore, it is advised to place your additional settings-configuration code below this call to prevent them from potentially being overwritten by DoOneClickiness() or other default config values when DoOneClickiness() is invoked.

NOTE: See the SALSA API and/or the EmoteR API for information on available settings and options. See API usage with ExpressionComponents for more information on modifying ExpressionComponent settings.

For example:

DoOneClickiness(gameObject, clip);  // add/change configuration settings below this method call.

// EmoteR global settings
emoter.lipsyncEmphasisChance = 0.0f;
salsa.emphasizerTrigger = 0.0f;

// Change settings on a specific 'emote'...
var myemote = emoter.FindEmote("My New Emote Name");
myemote.isPersistent = true;
myemote.isRandomEmote = true;

// Change settings on the components of a specific 'emote'...
foreach (var component in myemote.expData.components)
    component.easing = LerpEasings.EasingType.Linear;

// Find a viseme in SALSA and change its components' settings...must exist or will error...
var myviseme = salsa.visemes[salsa.visemes.FindIndex(v => v.expData.name == "My New Viseme")];
foreach (var component in myviseme.expData.components)
{
    component.durationOn = 0.11f;
    component.easing = LerpEasings.EasingType.CircleIn;
}

// Change settings in all SALSA visemes...
foreach (var viseme in salsa.visemes)
{
    foreach (var component in viseme.expData.components)
    {
        component.durationOff = 0.08f;        
    }
}