Add Introduction
This commit is contained in:
@@ -1,4 +1,559 @@
|
|||||||
## Animator As Code (AacV0)
|
# Animator As Code
|
||||||
|
|
||||||
|
**Animator As Code** is a small Unity Editor facility to generate Avatars 3.0 Animator layers and animations from a [fluent builder](https://en.wikipedia.org/wiki/Fluent_interface) syntax written in C#.
|
||||||
|
|
||||||
|
Describing your animators as code provides the following advantages:
|
||||||
|
|
||||||
|
- you do not need to edit your animations by hand every time you add remove or change the location of a component in your hierarchy
|
||||||
|
- you will not need to edit a hundred transitions by hand if you need to rectify your animator
|
||||||
|
|
||||||
|
It is written with VRChat Avatars 3.0 use cases in mind; the API is opinionated to facilitate writing such animators in a concise way, hopefully requiring as little additional tweaking.
|
||||||
|
|
||||||
|
# Interested? Join my Discord Server
|
||||||
|
|
||||||
|
This is a work in progress, I am looking for feedback!
|
||||||
|
|
||||||
|
[Join the Invitation Discord Server!](https://discord.com/invite/58fWAUTYF8)
|
||||||
|
|
||||||
|
# Install
|
||||||
|
|
||||||
|
There are currently no releases.
|
||||||
|
|
||||||
|
Clone the repository within a subfolder of your Unity project, or download the source code and install in any subfolder of your project.
|
||||||
|
|
||||||
|
The project can be located within `Assets/AnimatorAsCodeFramework` but you can choose any location.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
## #0 Toggle a GameObject
|
||||||
|
|
||||||
|
This example shows:
|
||||||
|
|
||||||
|
- Creating a FX layer
|
||||||
|
- Creating states
|
||||||
|
- Creating animations
|
||||||
|
- Declaring a Bool parameter
|
||||||
|
- Creating transitions with conditions
|
||||||
|
-
|
||||||
|

|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class GenExample0_ToggleGo : MonoBehaviour
|
||||||
|
{
|
||||||
|
public VRCAvatarDescriptor avatar;
|
||||||
|
public AnimatorController assetContainer;
|
||||||
|
public string assetKey;
|
||||||
|
public GameObject item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Create()
|
||||||
|
{
|
||||||
|
var my = (GenExample0_ToggleGo) target;
|
||||||
|
// The avatar is used here:
|
||||||
|
// - to find the FX playable layer animator, where a new layer will be created.
|
||||||
|
// - to resolve the relative animation path to the item.
|
||||||
|
// The generated animation files are stored in the asset container.
|
||||||
|
var aac = AacExample.AnimatorAsCode("Example 0", my.avatar, my.assetContainer, my.assetKey, AacExample.Options().WriteDefaultsOff());
|
||||||
|
|
||||||
|
// Create a layer in the FX animator.
|
||||||
|
// Additional layers can be created in the FX animator (see later in the manual).
|
||||||
|
var fx = aac.CreateMainFxLayer();
|
||||||
|
|
||||||
|
// The first created state is the default one connected to the "Entry" node.
|
||||||
|
// States are automatically placed on the grid (see later in the manual).
|
||||||
|
var hidden = fx.NewState("Hidden")
|
||||||
|
// Animation assets are generated as sub-assets of the asset container.
|
||||||
|
// The animation path to my.skinnedMesh is relative to my.avatar
|
||||||
|
.WithAnimation(aac.NewClip().Toggling(my.item, false));
|
||||||
|
var shown = fx.NewState("Shown")
|
||||||
|
.WithAnimation(aac.NewClip().Toggling(my.item, true));
|
||||||
|
|
||||||
|
// Creates a Bool parameter in the FX layer.
|
||||||
|
// Parameters are added to the Animator if a parameter with the same name
|
||||||
|
// does not exist yet.
|
||||||
|
var itemParam = fx.BoolParameter("EnableItem");
|
||||||
|
|
||||||
|
// Transitions are created with a set of default values
|
||||||
|
// That can be changed in the Generator settings (see later in the manual).
|
||||||
|
hidden.TransitionsTo(shown).When(itemParam.IsTrue());
|
||||||
|
shown.TransitionsTo(hidden).When(itemParam.IsFalse());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## #1 Toggle a SkinnedMeshRenderer with two conditions
|
||||||
|
|
||||||
|
This example shows:
|
||||||
|
|
||||||
|
- Simple toggle animations
|
||||||
|
- Groups of Bool parameters
|
||||||
|
- Simple transitions
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class GenExample1_ToggleSmr : MonoBehaviour
|
||||||
|
{
|
||||||
|
public VRCAvatarDescriptor avatar;
|
||||||
|
public AnimatorController assetContainer;
|
||||||
|
public string assetKey;
|
||||||
|
public SkinnedMeshRenderer skinnedMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Create()
|
||||||
|
{
|
||||||
|
var my = (GenExample1_ToggleSmr) target;
|
||||||
|
var aac = AacExample.AnimatorAsCode(SystemName, my.avatar, my.assetContainer, my.assetKey);
|
||||||
|
|
||||||
|
var fx = aac.CreateMainFxLayer();
|
||||||
|
var hidden = fx.NewState("Hidden")
|
||||||
|
// The runtime type of my.skinnedMesh is used within the animation.
|
||||||
|
// In this case, the "SkinnedMeshRenderer" component is disabled.
|
||||||
|
.WithAnimation(aac.NewClip().TogglingComponent(my.skinnedMesh, false));
|
||||||
|
var shown = fx.NewState("Shown")
|
||||||
|
.WithAnimation(aac.NewClip().TogglingComponent(my.skinnedMesh, true));
|
||||||
|
|
||||||
|
// This creates two Bool parameters in the animator.
|
||||||
|
// The resulting value can be used in conditions.
|
||||||
|
var accessoriesParams = fx.BoolParameters("EnableAccessories", "AccessoryThing");
|
||||||
|
|
||||||
|
// The following line creates one transition.
|
||||||
|
// The conditions are "EnableAccessories is true" and "AccessoryThing is true"
|
||||||
|
hidden.TransitionsTo(shown).When(accessoriesParams.AreTrue());
|
||||||
|
|
||||||
|
// The following line creates two transitions:
|
||||||
|
// - The first transition is "EnableAccessories is false"
|
||||||
|
// - The second transition is "AccessoryThing is false"
|
||||||
|
shown.TransitionsTo(hidden).When(accessoriesParams.IsAnyFalse());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## #2 Animate a SkinnedMesh with Motion time
|
||||||
|
|
||||||
|
This example shows:
|
||||||
|
|
||||||
|
- Describing animations with simplified keyframes
|
||||||
|
- Animating a state with Motion Time (formerly known as Normalized Time)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class GenExample2_Animate : MonoBehaviour
|
||||||
|
{
|
||||||
|
public VRCAvatarDescriptor avatar;
|
||||||
|
public AnimatorController assetContainer;
|
||||||
|
public string assetKey;
|
||||||
|
public SkinnedMeshRenderer wedgeMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Create()
|
||||||
|
{
|
||||||
|
var my = (GenExample2_Animate) target;
|
||||||
|
var aac = AacExample.AnimatorAsCode(SystemName, my.avatar, my.assetContainer, my.assetKey);
|
||||||
|
|
||||||
|
var fx = aac.CreateMainFxLayer();
|
||||||
|
|
||||||
|
fx.NewState("Motion")
|
||||||
|
.WithAnimation(aac.NewClip().Animating(clip =>
|
||||||
|
{
|
||||||
|
clip.Animates(my.wedgeMesh, "blendShape.Wedge").WithFrameCountUnit(keyframes =>
|
||||||
|
keyframes.Easing(0, 100f).Easing(28, 0).Easing(29, 0).Easing(30, 0).Easing(31, 0).Easing(32, 0).Easing(60, 100f)
|
||||||
|
);
|
||||||
|
clip.Animates(my.wedgeMesh, "material._Metallic").WithFrameCountUnit(keyframes =>
|
||||||
|
keyframes.Constant(0, 1f).Constant(28, 0).Constant(60, 0)
|
||||||
|
);
|
||||||
|
}))
|
||||||
|
.MotionTime(fx.FloatParameter("WedgeAmount"));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## #3 Using Parameter Drivers
|
||||||
|
|
||||||
|
This example shows:
|
||||||
|
|
||||||
|
- Driving parameters using VRC Parameter Driver behavior
|
||||||
|
- VRChat Parameters
|
||||||
|
- Creating systems with multiple FX layers
|
||||||
|
- Complex transitions
|
||||||
|
- *Show code...*
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class GenExample3_Gesturing : MonoBehaviour
|
||||||
|
{
|
||||||
|
public VRCAvatarDescriptor avatar;
|
||||||
|
public AnimatorController assetContainer;
|
||||||
|
public string assetKey;
|
||||||
|
public SkinnedMeshRenderer iconMesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GenExample3_Gesturing my;
|
||||||
|
private AacFlBase aac;
|
||||||
|
|
||||||
|
private void Create()
|
||||||
|
{
|
||||||
|
my = (GenExample3_Gesturing) target;
|
||||||
|
aac = AacExample.AnimatorAsCode(SystemName, my.avatar, my.assetContainer, my.assetKey);
|
||||||
|
|
||||||
|
CreateMainLayer();
|
||||||
|
CreateSupportingLayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateMainLayer()
|
||||||
|
{
|
||||||
|
var layer = aac.CreateMainFxLayer();
|
||||||
|
|
||||||
|
var dirtyCheckParameter = layer.BoolParameter("AAC_INTERNAL_GesturingIcon_DirtyCheck");
|
||||||
|
|
||||||
|
// ### Create states
|
||||||
|
var lackOfChangeDetected = layer.NewState("Animate To NoChange")
|
||||||
|
.WithAnimation(IconAppears());
|
||||||
|
|
||||||
|
// By default, states have an animation that animates a dummy object for 1 frame.
|
||||||
|
var noChange = layer.NewState("NoChange", 1, 0).RightOf();
|
||||||
|
|
||||||
|
var changeDetected = layer.NewState("Animate To Changing").Under()
|
||||||
|
.WithAnimation(IconDisappears());
|
||||||
|
|
||||||
|
// This creates a clip that animates a dummy object for 1.5f seconds.
|
||||||
|
var changing = layer.NewState("Changing", 0, 1).LeftOf()
|
||||||
|
.WithAnimation(aac.DummyClipLasting(1.5f, AacFlUnit.Seconds));
|
||||||
|
|
||||||
|
// When this state is entered, the parameter is driven to the value of false.
|
||||||
|
var stillChanging = layer.NewState("Still Changing", 0, 2).Under()
|
||||||
|
.Drives(dirtyCheckParameter, false);
|
||||||
|
|
||||||
|
// ------
|
||||||
|
|
||||||
|
// ### Create transitions
|
||||||
|
lackOfChangeDetected.TransitionsTo(changeDetected).AfterAnimationIsAtLeastAtPercent(0.7f).When(dirtyCheckParameter.IsTrue());
|
||||||
|
|
||||||
|
// The transition duration is 30% of the animation duration.
|
||||||
|
lackOfChangeDetected.TransitionsTo(noChange).AfterAnimationFinishes().WithTransitionDurationPercent(0.3f);
|
||||||
|
|
||||||
|
noChange.TransitionsTo(changeDetected).When(dirtyCheckParameter.IsTrue());
|
||||||
|
|
||||||
|
// By using AfterAnimationFinishes, the transition will trigger after the animation
|
||||||
|
// for the icon appearing finishes.
|
||||||
|
changeDetected.TransitionsTo(changing).AfterAnimationFinishes();
|
||||||
|
|
||||||
|
changing.TransitionsTo(stillChanging).When(dirtyCheckParameter.IsTrue());
|
||||||
|
// By using AfterAnimationFinishes, the transition will trigger after 1.5 seconds,
|
||||||
|
// which is the length of the animation in Changing.
|
||||||
|
changing.TransitionsTo(lackOfChangeDetected).AfterAnimationFinishes();
|
||||||
|
|
||||||
|
// The transition will immediately happen upon entering, by using Exit time set to 0.
|
||||||
|
stillChanging.AutomaticallyMovesTo(changing);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateSupportingLayer()
|
||||||
|
{
|
||||||
|
// Create an additional FX layer.
|
||||||
|
var layer = aac.CreateSupportingFxLayer("Detection");
|
||||||
|
var reevaluating = layer.NewState("Reevaluating", -1, 0);
|
||||||
|
|
||||||
|
foreach (var left in Enumerable.Range(0, 8))
|
||||||
|
{
|
||||||
|
foreach (var right in Enumerable.Range(0, 8))
|
||||||
|
{
|
||||||
|
var state = layer.NewState($"Gesture {left} {right}", left, right)
|
||||||
|
// When this state is entered, the parameter is driven to the value of true.
|
||||||
|
.Drives(layer.BoolParameter("AAC_INTERNAL_GesturingIcon_DirtyCheck"), true);
|
||||||
|
|
||||||
|
reevaluating.TransitionsTo(state)
|
||||||
|
// Use ".Av3" to access VRChat standard parameters.
|
||||||
|
// Accessing these parameters will create the corresponding parameter in the animator.
|
||||||
|
.When(layer.Av3().GestureLeft.IsEqualTo(left))
|
||||||
|
.And(layer.Av3().GestureRight.IsEqualTo(right));
|
||||||
|
state.TransitionsTo(reevaluating)
|
||||||
|
.When(layer.Av3().GestureLeft.IsNotEqualTo(left))
|
||||||
|
.Or()
|
||||||
|
.When(layer.Av3().GestureRight.IsNotEqualTo(right));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AacFlClip IconAppears()
|
||||||
|
{
|
||||||
|
return aac.NewClip().Animating(clip =>
|
||||||
|
{
|
||||||
|
clip.Animates(my.iconMesh, "blendShape.Wedge")
|
||||||
|
.WithFrameCountUnit(keyframes => keyframes.Easing(0, 0f).Easing(10, 100f));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private AacFlClip IconDisappears()
|
||||||
|
{
|
||||||
|
return aac.NewClip().Animating(clip =>
|
||||||
|
{
|
||||||
|
clip.Animates(my.iconMesh, "blendShape.Wedge")
|
||||||
|
.WithFrameCountUnit(keyframes => keyframes.Easing(0f, 100f).Easing(10, 0f));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Reference
|
||||||
|
|
||||||
|
## Typical animator creation steps
|
||||||
|
|
||||||
|
Animator As Code is generally used in the following steps:
|
||||||
|
|
||||||
|
- Declare an Animator As Code
|
||||||
|
- Create one or multiple layers
|
||||||
|
- Create the states
|
||||||
|
- Create the animations at the same time
|
||||||
|
- Create the transitions
|
||||||
|
|
||||||
|
## Declare an Animator As Code (AAC)
|
||||||
|
|
||||||
|
In order to use Animator As Code (AAC), first, declare it with a configuration.
|
||||||
|
|
||||||
|
The AAC configuration requires the following:
|
||||||
|
|
||||||
|
- A system name.
|
||||||
|
- Animator As Code describes systems. A system can have multiple layers, not only across playable layer animators, but also within a single playable layer animator. All created layers will be prefixed with this system name.
|
||||||
|
- It is up to you to decide where the boundaries of the system lies.
|
||||||
|
- An avatar descriptor, animator root, and default value root.
|
||||||
|
- This is used to select the playable layer animators to use.
|
||||||
|
- This is also used to select the root transform that animations will use for relative paths.
|
||||||
|
- This is used to collect the default values for some animations.
|
||||||
|
- *In general, all three of them are the same object, but this is not mandatory.*
|
||||||
|
- An asset container.
|
||||||
|
- Animations are generated by Animator As Code, and the large quantity of generated assets can be quite messy. To limit littering your project, such assets will be generated as sub-assets of a container. The container is of type Animator Controller, but it doesn’t need to have any layers within it.
|
||||||
|
- An asset key.
|
||||||
|
- The asset key a prefix that all generated assets will use. When creating the layers, all generated assets that uses that prefix will be removed upon invocation of `aac.ClearPreviousAssets()`.
|
||||||
|
- A provider of defaults.
|
||||||
|
- Animator As Code is opinionated, and sometimes you want to tweak the default values. The provider of defaults is executed when a state is created, an animation is created, a transition is created. This will let you tweak the generation process.
|
||||||
|
|
||||||
|
To declare it:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
string systemName;
|
||||||
|
VRCAvatarDescriptor avatar;
|
||||||
|
AnimatorController assetContainer;
|
||||||
|
string assetKey;
|
||||||
|
|
||||||
|
var aac = AacV0.Create(new AacConfiguration
|
||||||
|
{
|
||||||
|
SystemName = systemName,
|
||||||
|
AvatarDescriptor = avatar,
|
||||||
|
AnimatorRoot = avatar.transform,
|
||||||
|
DefaultValueRoot = avatar.transform,
|
||||||
|
AssetContainer = assetContainer,
|
||||||
|
AssetKey = assetKey,
|
||||||
|
DefaultsProvider = new AacDefaultsProvider(writeDefaults: false)
|
||||||
|
});
|
||||||
|
// Remove all previously generated assets from the asset container
|
||||||
|
// that match the asset key.
|
||||||
|
aac.ClearPreviousAssets();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create one or multiple layers
|
||||||
|
|
||||||
|
Using AAC, create layers in your animators. A system can have multiple layers across animators.
|
||||||
|
|
||||||
|
There is one main layer, and multiple supporting layers, per animator.
|
||||||
|
|
||||||
|
- The main layer will be named exactly after your system name.
|
||||||
|
- The supporting layers will be prefixed by the system name, and appended with a suffix of your choice.
|
||||||
|
|
||||||
|
You are in no obligation to create a main layer. If you think several layers of the same animators are of equal importance, you can declare them as being supporting layers.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var fx = aac.CreateMainFxLayer();
|
||||||
|
var detection = aac.CreateSupportingFxLayer("Detection");
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create states
|
||||||
|
|
||||||
|
Using your layer, you may now create states. Your state will be configured with defaults based on your AAC configuration, most notably, the Write Defaults setting.
|
||||||
|
|
||||||
|
By default, states have a dummy animation that lasts one frame (1/60th of a second).
|
||||||
|
|
||||||
|
It is intended to create the animation clip directly while declaring the state, this will be explained later on.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var hidden = fx.NewState("Hidden")
|
||||||
|
.WithAnimation(aac.NewClip().Toggling(my.item, false));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Creating parameters for use within layers
|
||||||
|
|
||||||
|
Using your layer, you can create parameters.
|
||||||
|
|
||||||
|
The parameter will be added to the animator the layer belongs to.
|
||||||
|
|
||||||
|
If you need to reuse a parameter across multiple layers, you need to invoke it on all relevant layers.
|
||||||
|
|
||||||
|
*(Once done, you are not forced to reuse the resulting parameter instance on the same layer it was created from)*
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
|
||||||
|
fx.NewState("Motion")
|
||||||
|
// This creates a Float parameter on the FX layer.
|
||||||
|
.MotionTime(fx.FloatParameter("WedgeAmount"))
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
If a parameter with the same name already exists in the animator, it will not be created again.
|
||||||
|
|
||||||
|
*This is also true if the animator already has a parameter with the same name but with a different type, and no error will be raised.*
|
||||||
|
|
||||||
|
There are also dedicated functions to obtain Avatars 3.0 parameters, such as.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var gestureLeftWeight = fx.Av3().GestureLeftWeight
|
||||||
|
```
|
||||||
|
|
||||||
|
## Forcing the value of parameters
|
||||||
|
|
||||||
|
In some rare cases you may wish to override the default value of animator parameters, for example in use for special conditions, for use in blend trees as a constant value.
|
||||||
|
|
||||||
|
Using your layer:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
fx.OverrideValue(fx.FloatParameter("SmoothingAmount"), 0.7f)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Visually organize your states
|
||||||
|
|
||||||
|
By default, a newly created state will be visually placed under the previously created state.
|
||||||
|
|
||||||
|
For more control, states have `LeftOf`, `RightOf`, `Over`, `Under`, `Shift` operators, which let you move a state to be visually next to another state.
|
||||||
|
|
||||||
|
The value is in grid units.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var init = fx.NewState("Init"); // This is the first state. By default it is at (0, 0)
|
||||||
|
var a = fx.NewState("A"); // This will be placed under Init.
|
||||||
|
var b = fx.NewState("B"); // This will be placed under A.
|
||||||
|
var c = fx.NewState("C").RightOf(a); // This will be placed right of A.
|
||||||
|
var d = fx.NewState("D"); // This will be placed under C.
|
||||||
|
var e = fx.NewState("E").RightOf(); // This will be placed right of D.
|
||||||
|
var alternate = fx.NewState("Alternate").Over(c); // This will be placed over C.
|
||||||
|
|
||||||
|
// This will be placed relative to Alternate: 2 blocks over, and 1 to the right.
|
||||||
|
var reset = fx.NewState("Reset").Shift(alternate, 1, -2);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create an animation
|
||||||
|
|
||||||
|
By default, states have a dummy animation that lasts one frame (1/60th of a second). If you want the state to play an animation of your choice, there is usually an invocation to the `.WithAnimation(...)` function:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var hidden = fx.NewState("Hidden")
|
||||||
|
.WithAnimation(aac.NewClip().Toggling(my.item, false));
|
||||||
|
```
|
||||||
|
|
||||||
|
The invocation uses AAC to create a new clip inside the asset container. You don’t need to specify a name for this clip, it is irrelevant.
|
||||||
|
|
||||||
|
Animations use object references instead of paths. They are converted to paths in the animation clip asset by resolving the relative path to the animator root of the AAC configuration.
|
||||||
|
|
||||||
|
Most of the functions lets you create single-frame constant animations.
|
||||||
|
|
||||||
|
For more complex animations, use the `.Animating(...)` function.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
fx.NewState("Motion")
|
||||||
|
.WithAnimation(aac.NewClip().Animating(clip =>
|
||||||
|
{
|
||||||
|
clip.Animates(my.wedgeMesh, "blendShape.Wedge").WithFrameCountUnit(keyframes =>
|
||||||
|
keyframes.Easing(0, 100f).Easing(28, 0).Easing(29, 0).Easing(30, 0).Easing(31, 0).Easing(32, 0).Easing(60, 100f)
|
||||||
|
);
|
||||||
|
clip.Animates(my.wedgeMesh, "material._Metallic").WithFrameCountUnit(keyframes =>
|
||||||
|
keyframes.Constant(0, 1f).Constant(28, 0).Constant(60, 0)
|
||||||
|
);
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create transitions and define conditions
|
||||||
|
|
||||||
|
Using the states, you can create transitions between states. Your transition will be configured with defaults based on your AAC configuration, defined within the DefaultsProvider.
|
||||||
|
|
||||||
|
To create a transition from Any, Exit, or Entry, there are some functions in the states like `.TransitionsFromAny()` or `.Exits()`.
|
||||||
|
|
||||||
|
Start defining conditions for that transition using the `.When(...)` operator.
|
||||||
|
|
||||||
|
Parameters have functions that generate conditions once invoked.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
reevaluating.TransitionsTo(state)
|
||||||
|
// Use ".Av3" to access VRChat standard parameters.
|
||||||
|
// Accessing these parameters will create the corresponding parameter in the animator.
|
||||||
|
.When(layer.Av3().GestureLeft.IsEqualTo(left))
|
||||||
|
.And(layer.Av3().GestureRight.IsEqualTo(right));
|
||||||
|
state.TransitionsTo(reevaluating)
|
||||||
|
.When(layer.Av3().GestureLeft.IsNotEqualTo(left))
|
||||||
|
.Or()
|
||||||
|
.When(layer.Av3().GestureRight.IsNotEqualTo(right));
|
||||||
|
```
|
||||||
|
|
||||||
|
## When(), And(), and Or() operators
|
||||||
|
|
||||||
|
In animator transitions, all conditions must be verified for the transition to occur; this is effectively a “AND” of all of the conditions (A && B && C).
|
||||||
|
|
||||||
|
This is done by using the `.And(...)` operator: `.When(A).And(B).And(C)`
|
||||||
|
|
||||||
|
To represent “OR”, new transitions need to be created; ((A && B && C) || (D && E)) results in:
|
||||||
|
|
||||||
|
- Transition 1: (A && B && C)
|
||||||
|
- Transition 2: (D && E)
|
||||||
|
|
||||||
|
This is done by using the `.Or(...)` operator: `.When(A).And(B).And(C).Or().When(D).And(E)`
|
||||||
|
|
||||||
|
From this limitation, conditions with nested OR cannot be expressed easily, such as:
|
||||||
|
|
||||||
|
- 🚫 (F && (G || H) && (J || K))
|
||||||
|
|
||||||
|
## Conditions that generate multiple transitions
|
||||||
|
|
||||||
|
Some conditions generate multiple transitions, such as `boolParameters.IsAnyFalse()`
|
||||||
|
|
||||||
|
In order to still let you express these expressions easily, the following applies:
|
||||||
|
|
||||||
|
- These conditions can only be used in a `.When(...)` operator, and not within a `.And(...)`
|
||||||
|
- If such a condition is used, calling `.And(...)` will apply the condition to all transitions generated by the `.When(...)` operator up until the next `Or(...)` operator if any exists.
|
||||||
|
- For example:
|
||||||
|
`.When(fx.BoolParameters(I, J).IsAnyFalse()).And(K.IsTrue())`
|
||||||
|
- Is equivalent to:
|
||||||
|
`.When(I.IsFalse()).And(K.IsTrue()).Or().When(J.IsFalse()).And(K.IsTrue())`
|
||||||
|
|
||||||
|
## Use the WhenConditions() operator to build in a `for` loop
|
||||||
|
|
||||||
|
The presence of the `.When(...)` operator can make it difficult to build conditions iteratively in a `for` loop.
|
||||||
|
|
||||||
|
For this purpose, use the `.WhenConditions()` operator. This will let you build conditions using the `.And(...)` operator.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var conditions = state.TransitionsFromEntry().WhenConditions();
|
||||||
|
for (var i = 0; i < numberOfBits; i++)
|
||||||
|
{
|
||||||
|
conditions.And(parameter[i].IsEqualTo(bitMask[i]));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create many-to-one transitions using a `foreach` loop
|
||||||
|
|
||||||
|
There is no facility to create multiple identical transitions from multiple states to another state.
|
||||||
|
|
||||||
|
Use a regular `foreach` loop to achieve this.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
foreach (var cancelWhenNotAllowed in new[] {auto, reverse, manual, custom, done})
|
||||||
|
{
|
||||||
|
cancelWhenNotAllowed.TransitionsTo(idle).When(allowSystemParameter.IsFalse());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Reference manual
|
||||||
|
|
||||||
|
## Animator As Code (AacV0)
|
||||||
- `static AacFlBase Create(AacConfiguration configuration)` /// Create an Animator As Code (AAC) base.
|
- `static AacFlBase Create(AacConfiguration configuration)` /// Create an Animator As Code (AAC) base.
|
||||||
|
|
||||||
# Base (AacFlBase)
|
# Base (AacFlBase)
|
||||||
|
|||||||
Reference in New Issue
Block a user