shity basic one shot
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9ee87b13ed70e4b4088b2d660abfe504
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,919 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace AnimatorAsCode.V1
|
||||
{
|
||||
/// Starter class for Animator As Code V1. Call this to obtain an instance of AacFlBase.<br/><br/>
|
||||
/// The intent of the namespace AnimatorAsCode.V1, and the class AacV1,
|
||||
/// is to allow Animator As Code V1 to be simultaneously installed inside projects where:<br/>
|
||||
/// - Instances of Animator As Code V0 may exist,<br/>
|
||||
/// - Instances of Animator As Code V1 derivatives may exist,<br/>
|
||||
/// - Future instances of Animator As Code V2 might exist.<br/><br/>
|
||||
/// For this reason, in case of a breaking change, there should be different versions of Animator As Code released
|
||||
/// with a different package name, namespace, and initializer class name, so that the user may install dependencies
|
||||
/// that rely on different versions of Animator As Code in the same project.
|
||||
public static class AacV1
|
||||
{
|
||||
/// Create an Animator As Code (AAC) base.
|
||||
public static AacFlBase Create(AacConfiguration configuration)
|
||||
{
|
||||
return new AacFlBase(configuration);
|
||||
}
|
||||
}
|
||||
|
||||
public struct AacConfiguration
|
||||
{
|
||||
/// A name that will be used as the prefix of all animators layers created.
|
||||
public string SystemName;
|
||||
// Please consult "https://docs.hai-vr.dev/docs/products/animator-as-code/migrating-v0-to-v1" on how to migrate this property
|
||||
// public VRCAvatarDescriptor AvatarDescriptor;
|
||||
/// A reference to the animator root. All relative paths will be made relative to this animator root.
|
||||
public Transform AnimatorRoot;
|
||||
/// Unused. A reference to a root, where default values will be sampled from.
|
||||
public Transform DefaultValueRoot;
|
||||
/// A persistent asset where all created assets will be added into, based on the value of ContainerMode.
|
||||
public Object AssetContainer;
|
||||
/// Defines whether created assets should be added to the AssetContainer.
|
||||
public Container ContainerMode;
|
||||
/// A prefix which will be used in name of all assets, so that the created assets can be removed during subsequent executions of AnimatorAsCode, assuming that your process is destructive.
|
||||
public string AssetKey;
|
||||
/// An object that will provide default values. When in doubt, use `new AacDefaultsProvider(...)`
|
||||
public IAacDefaultsProvider DefaultsProvider;
|
||||
/// An object that will provide abstraction for asset container object.
|
||||
/// AssetContainer field value will be used if left null.
|
||||
public IAacAssetContainerProvider AssetContainerProvider;
|
||||
|
||||
private Dictionary<Type, object> _additionalData; // Nullable
|
||||
|
||||
/// For use by users of extension functions: Store additional data in this configuration.<br/>
|
||||
/// This additional data can be used during the operation of those extension functions.<br/>
|
||||
/// Example: Use this to store a reference to a platform-specific avatar component.
|
||||
public AacConfiguration WithAdditionalData<T>(T value)
|
||||
{
|
||||
var conf = this;
|
||||
if (_additionalData == null) conf._additionalData = new Dictionary<Type, object>();
|
||||
conf._additionalData[typeof(T)] = value;
|
||||
return conf;
|
||||
}
|
||||
|
||||
/// Attempts to retrieve additional data stored by `WithAdditionalData`.<br/>
|
||||
/// Returns true when such data is available.
|
||||
public bool TryGetAdditionalData<T>(out T value) where T : class
|
||||
{
|
||||
if (_additionalData != null && _additionalData.TryGetValue(typeof(T), out var result))
|
||||
{
|
||||
value = (T)result;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal IAacAssetContainerProvider ContainerProvider
|
||||
{
|
||||
get
|
||||
{
|
||||
AssetContainerProvider ??= new AacSimpleAssetContainerProvider(this);
|
||||
return AssetContainerProvider;
|
||||
}
|
||||
}
|
||||
|
||||
/// Values that define how created assets should be added to the AssetContainer.
|
||||
public enum Container
|
||||
{
|
||||
/// Store all created assets in the AssetContainer.
|
||||
Everything,
|
||||
/// Only store created assets in the AssetContainer if that asset requires persistence.<br/>
|
||||
/// Right now, only AnimatorController assets require persistence.
|
||||
OnlyWhenPersistenceRequired,
|
||||
/// Do not store any assets in the AssetContainer.<br/>
|
||||
/// In this case, the value provided in the AssetContainer of the configuration does not matter.
|
||||
Never
|
||||
}
|
||||
}
|
||||
|
||||
public class AacFlLayer
|
||||
{
|
||||
/// Exposes the underlying AnimatorAsCode StateMachine object of this layer.
|
||||
public AacFlStateMachine StateMachine => _stateMachine;
|
||||
|
||||
private readonly AnimatorController _animatorController;
|
||||
private readonly AacConfiguration _configuration;
|
||||
private readonly string _fullLayerName;
|
||||
private readonly AacFlStateMachine _stateMachine;
|
||||
|
||||
private readonly List<string> _floatParameters = new List<string>();
|
||||
private readonly List<string> _intParameters = new List<string>();
|
||||
private readonly List<string> _boolParameters = new List<string>();
|
||||
private readonly Dictionary<string, float> _floatOverrides = new Dictionary<string, float>();
|
||||
private readonly Dictionary<string, int> _intOverrides = new Dictionary<string, int>();
|
||||
private readonly Dictionary<string, bool> _boolOverrides = new Dictionary<string, bool>();
|
||||
|
||||
internal AacFlLayer(AnimatorController animatorController, AacConfiguration configuration, AacFlStateMachine stateMachine, string fullLayerName)
|
||||
{
|
||||
_animatorController = animatorController;
|
||||
_configuration = configuration;
|
||||
_fullLayerName = fullLayerName;
|
||||
_stateMachine = stateMachine;
|
||||
}
|
||||
|
||||
/// Create a new state, initially positioned below the last generated state of this layer.<br/>
|
||||
/// 🔺 If the name is already used, a number will be appended at the end.
|
||||
public AacFlState NewState(string name)
|
||||
{
|
||||
var lastState = _stateMachine.LastNodePosition();
|
||||
var state = _stateMachine.NewState(name, 0, 0).Shift(lastState, 0, 1);
|
||||
return state;
|
||||
}
|
||||
|
||||
/// Create a new state at a specific position x and y, in grid units. The grid size is defined in the DefaultsProvider of the AacConfiguration of AAC. x positive goes right, y positive goes down.<br/>
|
||||
/// 🔺 If the name is already used, a number will be appended at the end.
|
||||
public AacFlState NewState(string name, int x, int y)
|
||||
{
|
||||
return _stateMachine.NewState(name, x, y);
|
||||
}
|
||||
|
||||
/// Create a new state machine, initially positioned below the last generated state of this layer.<br/>
|
||||
/// 🔺 If the name is already used, a number will be appended at the end.
|
||||
public AacFlStateMachine NewSubStateMachine(string name)
|
||||
{
|
||||
return _stateMachine.NewSubStateMachine(name);
|
||||
}
|
||||
|
||||
/// Create a new state machine at a specific position `x` and `y`, in grid units. The grid size is defined in the DefaultsProvider of the AacConfiguration of AAC. `x` positive goes right, `y` positive goes down.<br/>
|
||||
/// 🔺 If the name is already used, a number will be appended at the end.
|
||||
public AacFlStateMachine NewSubStateMachine(string name, int x, int y)
|
||||
{
|
||||
return _stateMachine.NewSubStateMachine(name, x, y);
|
||||
}
|
||||
|
||||
/// Create a transition from Any to the `destination` state.
|
||||
public AacFlTransition AnyTransitionsTo(AacFlState destination)
|
||||
{
|
||||
return _stateMachine.AnyTransitionsTo(destination);
|
||||
}
|
||||
|
||||
/// Create a transition from Any to the `destination` state machine.
|
||||
public AacFlTransition AnyTransitionsTo(AacFlStateMachine destination)
|
||||
{
|
||||
return _stateMachine.AnyTransitionsTo(destination);
|
||||
}
|
||||
|
||||
// Create a transition from the Entry to the `destination` state.
|
||||
public AacFlEntryTransition EntryTransitionsTo(AacFlState destination)
|
||||
{
|
||||
return _stateMachine.EntryTransitionsTo(destination);
|
||||
}
|
||||
|
||||
// Create a transition from the Entry to the `destination` state machine.
|
||||
public AacFlEntryTransition EntryTransitionsTo(AacFlStateMachine destination)
|
||||
{
|
||||
return _stateMachine.EntryTransitionsTo(destination);
|
||||
}
|
||||
|
||||
/// Create a Bool parameter in the animator.
|
||||
public AacFlBoolParameter BoolParameter(string parameterName)
|
||||
{
|
||||
_boolParameters.Add(parameterName);
|
||||
return _stateMachine.InternalBackingAnimator().BoolParameter(parameterName);
|
||||
}
|
||||
|
||||
/// Create a Trigger parameter in the animator, but returns a Bool parameter for use in AAC.
|
||||
public AacFlBoolParameter TriggerParameterAsBool(string parameterName)
|
||||
{
|
||||
_boolParameters.Add(parameterName);
|
||||
return _stateMachine.InternalBackingAnimator().TriggerParameter(parameterName);
|
||||
}
|
||||
|
||||
/// Create a Float parameter in the animator.
|
||||
public AacFlFloatParameter FloatParameter(string parameterName)
|
||||
{
|
||||
_floatParameters.Add(parameterName);
|
||||
return _stateMachine.InternalBackingAnimator().FloatParameter(parameterName);
|
||||
}
|
||||
|
||||
/// Create an Int parameter in the animator.
|
||||
public AacFlIntParameter IntParameter(string parameterName)
|
||||
{
|
||||
_intParameters.Add(parameterName);
|
||||
return _stateMachine.InternalBackingAnimator().IntParameter(parameterName);
|
||||
}
|
||||
|
||||
/// Create multiple Bool parameters in the animator, and returns a group of multiple Bools.
|
||||
public AacFlBoolParameterGroup BoolParameters(params string[] parameterNames)
|
||||
{
|
||||
_boolParameters.AddRange(parameterNames);
|
||||
return _stateMachine.InternalBackingAnimator().BoolParameters(parameterNames);
|
||||
}
|
||||
|
||||
/// Create multiple Trigger parameters in the animator, but returns a group of multiple Bools for use in AAC.
|
||||
public AacFlBoolParameterGroup TriggerParametersAsBools(params string[] parameterNames)
|
||||
{
|
||||
_boolParameters.AddRange(parameterNames);
|
||||
return _stateMachine.InternalBackingAnimator().TriggerParameters(parameterNames);
|
||||
}
|
||||
|
||||
/// Create multiple Float parameters in the animator, and returns a group of multiple Floats.
|
||||
public AacFlFloatParameterGroup FloatParameters(params string[] parameterNames)
|
||||
{
|
||||
_floatParameters.AddRange(parameterNames);
|
||||
return _stateMachine.InternalBackingAnimator().FloatParameters(parameterNames);
|
||||
}
|
||||
|
||||
/// Create multiple Int parameters in the animator, and returns a group of multiple Ints.
|
||||
public AacFlIntParameterGroup IntParameters(params string[] parameterNames)
|
||||
{
|
||||
_intParameters.AddRange(parameterNames);
|
||||
return _stateMachine.InternalBackingAnimator().IntParameters(parameterNames);
|
||||
}
|
||||
|
||||
/// Combine multiple Bool parameters into a group.
|
||||
public AacFlBoolParameterGroup BoolParameters(params AacFlBoolParameter[] parameters)
|
||||
{
|
||||
_boolParameters.AddRange(parameters.Select(parameter => parameter.Name));
|
||||
return _stateMachine.InternalBackingAnimator().BoolParameters(parameters);
|
||||
}
|
||||
|
||||
/// Combine multiple Bool parameters into a group. There is no difference with BoolParameters(...) and is provided only for semantic purposes.
|
||||
public AacFlBoolParameterGroup TriggerParametersAsBools(params AacFlBoolParameter[] parameters)
|
||||
{
|
||||
_boolParameters.AddRange(parameters.Select(parameter => parameter.Name));
|
||||
return _stateMachine.InternalBackingAnimator().TriggerParameters(parameters);
|
||||
}
|
||||
|
||||
/// Combine multiple Float parameters into a group.
|
||||
public AacFlFloatParameterGroup FloatParameters(params AacFlFloatParameter[] parameters)
|
||||
{
|
||||
_floatParameters.AddRange(parameters.Select(parameter => parameter.Name));
|
||||
return _stateMachine.InternalBackingAnimator().FloatParameters(parameters);
|
||||
}
|
||||
|
||||
/// Combine multiple Int parameters into a group.
|
||||
public AacFlIntParameterGroup IntParameters(params AacFlIntParameter[] parameters)
|
||||
{
|
||||
_intParameters.AddRange(parameters.Select(parameter => parameter.Name));
|
||||
return _stateMachine.InternalBackingAnimator().IntParameters(parameters);
|
||||
}
|
||||
|
||||
/// Set the Bool value of `toBeForced` parameter to `value` in the animator.
|
||||
public AacFlLayer OverrideValue(AacFlBoolParameter toBeForced, bool value)
|
||||
{
|
||||
_boolOverrides[toBeForced.Name] = value;
|
||||
var parameters = _animatorController.parameters;
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
if (param.name == toBeForced.Name)
|
||||
{
|
||||
param.defaultBool = value;
|
||||
}
|
||||
}
|
||||
|
||||
_animatorController.parameters = parameters;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Set the Float value of `toBeForced` parameter to `value` in the animator.
|
||||
public AacFlLayer OverrideValue(AacFlFloatParameter toBeForced, float value)
|
||||
{
|
||||
_floatOverrides[toBeForced.Name] = value;
|
||||
var parameters = _animatorController.parameters;
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
if (param.name == toBeForced.Name)
|
||||
{
|
||||
param.defaultFloat = value;
|
||||
}
|
||||
}
|
||||
|
||||
_animatorController.parameters = parameters;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Set the Int value of `toBeForced` parameter to `value` in the animator.
|
||||
public AacFlLayer OverrideValue(AacFlIntParameter toBeForced, int value)
|
||||
{
|
||||
_intOverrides[toBeForced.Name] = value;
|
||||
var parameters = _animatorController.parameters;
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
if (param.name == toBeForced.Name)
|
||||
{
|
||||
param.defaultInt = value;
|
||||
}
|
||||
}
|
||||
|
||||
_animatorController.parameters = parameters;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Set the Avatar Mask of the layer.
|
||||
public AacFlLayer WithAvatarMask(AvatarMask avatarMask)
|
||||
{
|
||||
var finalFullLayerName = _fullLayerName;
|
||||
_animatorController.layers = _animatorController.layers
|
||||
.Select(layer =>
|
||||
{
|
||||
if (layer.name == finalFullLayerName)
|
||||
{
|
||||
layer.avatarMask = avatarMask;
|
||||
}
|
||||
|
||||
return layer;
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Set the Avatar Mask of the layer to be an Avatar Mask which denies all transforms. The asset is generated into the container.
|
||||
public AacFlLayer WithAvatarMaskNoTransforms()
|
||||
{
|
||||
ResolveAvatarMask(new Transform[0]);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Set the Avatar Mask of the layer to be an Avatar Mask that allows the specified transforms. If `paths` is an empty array, all transforms are denied, which is effectively the same as calling `.WithAvatarMaskNoTransforms()`. The asset is generated into the container.
|
||||
public AacFlLayer ResolveAvatarMask(Transform[] paths)
|
||||
{
|
||||
var avatarMask = AacInternals.NewAvatarMask(_configuration, _fullLayerName);
|
||||
|
||||
if (paths.Length == 0)
|
||||
{
|
||||
avatarMask.transformCount = 1;
|
||||
avatarMask.SetTransformActive(0, false);
|
||||
avatarMask.SetTransformPath(0, "_ignored");
|
||||
}
|
||||
else
|
||||
{
|
||||
avatarMask.transformCount = paths.Length;
|
||||
for (var index = 0; index < paths.Length; index++)
|
||||
{
|
||||
var transform = paths[index];
|
||||
avatarMask.SetTransformActive(index, true);
|
||||
avatarMask.SetTransformPath(index, AacInternals.ResolveRelativePath(_configuration.AnimatorRoot, transform));
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int) AvatarMaskBodyPart.LastBodyPart; i++)
|
||||
{
|
||||
avatarMask.SetHumanoidBodyPartActive((AvatarMaskBodyPart) i, false);
|
||||
}
|
||||
|
||||
WithAvatarMask(avatarMask);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Set the Default State of the layer.
|
||||
public AacFlLayer WithDefaultState(AacFlState newDefaultState)
|
||||
{
|
||||
_stateMachine.WithDefaultState(newDefaultState);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <b>FOR USE ONLY BY EXTENSION FUNCTIONS:</b><br/>
|
||||
/// Exposes the internal state machine.
|
||||
public AacFlStateMachine InternalStateMachine()
|
||||
{
|
||||
return _stateMachine;
|
||||
}
|
||||
|
||||
/// Copy all created parameters to the other layer, and overrides it with a value if a value was stored.
|
||||
public AacFlLayer CopyParametersAndOverridesTo(AacFlLayer otherLayer)
|
||||
{
|
||||
foreach (var key in _floatParameters)
|
||||
{
|
||||
var param = otherLayer.FloatParameter(key);
|
||||
if (_floatOverrides.TryGetValue(key, out var value)) otherLayer.OverrideValue(param, value);
|
||||
}
|
||||
foreach (var key in _intParameters)
|
||||
{
|
||||
var param = otherLayer.IntParameter(key);
|
||||
if (_intOverrides.TryGetValue(key, out var value)) otherLayer.OverrideValue(param, value);
|
||||
}
|
||||
foreach (var key in _boolParameters)
|
||||
{
|
||||
var param = otherLayer.BoolParameter(key);
|
||||
if (_boolOverrides.TryGetValue(key, out var value)) otherLayer.OverrideValue(param, value);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// Change the layer weight of this layer.
|
||||
public AacFlLayer WithWeight(float weight)
|
||||
{
|
||||
var finalFullLayerName = _fullLayerName;
|
||||
_animatorController.layers = _animatorController.layers
|
||||
.Select(layer =>
|
||||
{
|
||||
if (layer.name == finalFullLayerName)
|
||||
{
|
||||
layer.defaultWeight = weight;
|
||||
}
|
||||
|
||||
return layer;
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// Change the blending mode of this layer.
|
||||
public AacFlLayer WithBlendingMode(AnimatorLayerBlendingMode blendingMode)
|
||||
{
|
||||
var finalFullLayerName = _fullLayerName;
|
||||
_animatorController.layers = _animatorController.layers
|
||||
.Select(layer =>
|
||||
{
|
||||
if (layer.name == finalFullLayerName)
|
||||
{
|
||||
layer.blendingMode = blendingMode;
|
||||
}
|
||||
|
||||
return layer;
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides methods for use by extension functions, exposing methods departing from normal fluent interface usage.
|
||||
/// These methods are entering a staging phase as of V1.1.0. It is not recommended to use them.
|
||||
public static class AacAccessorForExtensions
|
||||
{
|
||||
/// NOT FOR PUBLIC USE: This method is entering a staging phase as of V1.1.0. It is not recommended to use it.
|
||||
public static AacConfiguration AccessConfiguration(AacFlBase aacBase)
|
||||
{
|
||||
return aacBase.AccessConfiguration();
|
||||
}
|
||||
|
||||
/// NOT FOR PUBLIC USE: This method is entering a staging phase as of V1.1.0. It is not recommended to use it.
|
||||
public static AacFlLayer AccessCreateLayer(AacFlBase aacBase, AnimatorController animator, string layerName)
|
||||
{
|
||||
return aacBase.AccessCreateLayer(animator, layerName);
|
||||
}
|
||||
}
|
||||
|
||||
public class AacFlBase
|
||||
{
|
||||
private readonly AacConfiguration _configuration;
|
||||
|
||||
/// NOT FOR PUBLIC USE: Internal use only so that destructive workflow can access this. Has been replaced with AacAccessorForExtensions.AccessConfiguration
|
||||
[Obsolete("This has been made private/internal in V1.2.0. Use AacAccessorForExtensions.AccessConfiguration(...) instead")]
|
||||
internal AacConfiguration InternalConfiguration()
|
||||
{
|
||||
return _configuration;
|
||||
}
|
||||
|
||||
internal AacConfiguration AccessConfiguration()
|
||||
{
|
||||
return _configuration;
|
||||
}
|
||||
|
||||
internal AacFlBase(AacConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
/// Create a new clip. The asset is generated into the container.
|
||||
public AacFlClip NewClip()
|
||||
{
|
||||
var clip = AacInternals.NewClip(_configuration, Guid.NewGuid().ToString());
|
||||
return new AacFlClip(_configuration, clip);
|
||||
}
|
||||
|
||||
/// Create a new clip that is a copy of `originalClip`. The asset is generated into the container.
|
||||
public AacFlClip CopyClip(AnimationClip originalClip)
|
||||
{
|
||||
var newClip = UnityEngine.Object.Instantiate(originalClip);
|
||||
var clip = AacInternals.RegisterClip(_configuration, Guid.NewGuid().ToString(), newClip);
|
||||
return new AacFlClip(_configuration, clip);
|
||||
}
|
||||
|
||||
/// Create a new BlendTree asset. The asset is generated into the container.
|
||||
public AacFlNonInitializedBlendTree NewBlendTree()
|
||||
{
|
||||
return new AacFlNonInitializedBlendTree(AacInternals.NewBlendTreeAsRaw(_configuration, Guid.NewGuid().ToString()));
|
||||
}
|
||||
|
||||
/// Create a new BlendTree asset and returns a native BlendTree object. The asset is generated into the container. You may use NewBlendTree() instead to obtain a fluent interface.
|
||||
public BlendTree NewBlendTreeAsRaw()
|
||||
{
|
||||
return AacInternals.NewBlendTreeAsRaw(_configuration, Guid.NewGuid().ToString());
|
||||
}
|
||||
|
||||
/// Create a new clip with a name. However, the name is only used as a suffix for the asset. The asset is generated into the container.
|
||||
public AacFlClip NewClip(string name)
|
||||
{
|
||||
var clip = AacInternals.NewClip(_configuration, name);
|
||||
return new AacFlClip(_configuration, clip);
|
||||
}
|
||||
|
||||
/// Create a new BlendTree asset with a name. However, the name is only used as a suffix for the asset. The asset is generated into the container.<br/>
|
||||
/// Added in 1.3.0.
|
||||
public AacFlNonInitializedBlendTree NewBlendTree(string name)
|
||||
{
|
||||
return new AacFlNonInitializedBlendTree(AacInternals.NewBlendTreeAsRaw(_configuration, name));
|
||||
}
|
||||
|
||||
/// Create a new BlendTree asset with a name and returns a native BlendTree object. However, the name is only used as a suffix for the asset. The asset is generated into the container. You may use NewBlendTree() instead to obtain a fluent interface.<br/>
|
||||
/// Added in 1.3.0.
|
||||
public BlendTree NewBlendTreeAsRaw(string name)
|
||||
{
|
||||
return AacInternals.NewBlendTreeAsRaw(_configuration, name);
|
||||
}
|
||||
|
||||
/// Create a new clip which animates a dummy transform for a specific duration specified in an unit (Frames or Seconds).
|
||||
public AacFlClip DummyClipLasting(float numberOf, AacFlUnit unit)
|
||||
{
|
||||
var dummyClip = AacInternals.NewClip(_configuration, $"D({numberOf} {Enum.GetName(typeof(AacFlUnit), unit)})");
|
||||
|
||||
return new AacFlClip(_configuration, dummyClip)
|
||||
.Animating(clip => clip.Animates("_ignored", typeof(GameObject), "m_IsActive")
|
||||
.WithUnit(unit, keyframes => keyframes.Constant(0, 0f).Constant(numberOf, 0f)));
|
||||
}
|
||||
|
||||
/// Duplicate a new asset into the container and return it. For example, use this to create modified material variants. This asset will be removed the same way as other generated assets.
|
||||
public T DuplicateAsset<T>(T assetToDuplicate) where T : Object
|
||||
{
|
||||
return AacInternals.DuplicateAssetIntoContainer(_configuration, assetToDuplicate);
|
||||
}
|
||||
|
||||
// For backwards compatibility, generates an animation with 2 keyframes that are at a distance of 1 / 60 of 1 frame,
|
||||
// that is 1 / 3600 seconds.
|
||||
// This was due to DummyClipLasting returning a clip with unit conversion applied twice,
|
||||
// resulting in an "undesired" animation which was functional anyways.
|
||||
// Since this dummy clip is used internally on all uninitalized states,
|
||||
// preserve this behaviour so that existing animators don't break due to this change.
|
||||
private AacFlClip AnomalousSingleKeyframeClip()
|
||||
{
|
||||
var numberOf = 1f;
|
||||
var unit = AacFlUnit.Frames;
|
||||
|
||||
var dummyClip = AacInternals.NewClip(_configuration, $"D({numberOf} {Enum.GetName(typeof(AacFlUnit), unit)})");
|
||||
|
||||
var duration = unit == AacFlUnit.Frames ? numberOf / 60f : numberOf;
|
||||
return new AacFlClip(_configuration, dummyClip)
|
||||
.Animating(clip => clip.Animates("_ignored", typeof(GameObject), "m_IsActive")
|
||||
.WithUnit(unit, keyframes => keyframes.Constant(0, 0f).Constant(duration, 0f)));
|
||||
}
|
||||
|
||||
/// Create a new animator controller. The asset is generated into the container.
|
||||
public AacFlController NewAnimatorController()
|
||||
{
|
||||
var animatorController = AacInternals.NewAnimatorController(_configuration, Guid.NewGuid().ToString());
|
||||
return new AacFlController(_configuration, animatorController, this);
|
||||
}
|
||||
|
||||
/// Create a new animator controller with a name. However, the name is only used as a suffix for the asset. The asset is generated into the container.
|
||||
public AacFlController NewAnimatorController(string name)
|
||||
{
|
||||
var animatorController = AacInternals.NewAnimatorController(_configuration, name);
|
||||
return new AacFlController(_configuration, animatorController, this);
|
||||
}
|
||||
|
||||
//---- ## Destructive workflow
|
||||
|
||||
/// Destructive workflow: Create a main layer for an arbitrary AnimatorController, clearing the previous one of the same system. You are not obligated to have a main layer.
|
||||
public AacFlLayer CreateMainArbitraryControllerLayer(AnimatorController controller) => DoCreateLayer(controller, _configuration.DefaultsProvider.ConvertLayerName(_configuration.SystemName));
|
||||
|
||||
/// Destructive workflow: Create a supporting layer for an arbitrary AnimatorController, clearing the previous one of the same system and suffix. You can create multiple supporting layers with different suffixes, and you are not obligated to have a main layer to create a supporting layer.
|
||||
public AacFlLayer CreateSupportingArbitraryControllerLayer(AnimatorController controller, string suffix) => DoCreateLayer(controller, _configuration.DefaultsProvider.ConvertLayerNameWithSuffix(_configuration.SystemName, suffix));
|
||||
|
||||
/// Destructive workflow: Clears the topmost layer of an arbitrary AnimatorController, and returns it.
|
||||
public AacFlLayer CreateFirstArbitraryControllerLayer(AnimatorController controller) => DoCreateLayer(controller, controller.layers[0].name);
|
||||
|
||||
/// NOT FOR PUBLIC USE: Internal use only so that destructive workflow can access this. Has been replaced with AacAccessorForExtensions.CreateLayer
|
||||
[Obsolete("This has been made private/internal in V1.2.0. Use AacAccessorForExtensions.CreateLayer(...) instead")]
|
||||
internal AacFlLayer InternalDoCreateLayer(AnimatorController animator, string layerName)
|
||||
{
|
||||
return DoCreateLayer(animator, layerName);
|
||||
}
|
||||
|
||||
internal AacFlLayer AccessCreateLayer(AnimatorController animator, string layerName)
|
||||
{
|
||||
return DoCreateLayer(animator, layerName);
|
||||
}
|
||||
|
||||
private AacFlLayer DoCreateLayer(AnimatorController animator, string layerName)
|
||||
{
|
||||
var ag = new AacAnimatorGenerator(animator, CreateEmptyClip().Clip, _configuration.DefaultsProvider, _configuration.AnimatorRoot);
|
||||
var machine = ag.CreateOrClearLayerAtSameIndex(layerName, 1f);
|
||||
|
||||
return new AacFlLayer(animator, _configuration, machine, layerName);
|
||||
}
|
||||
|
||||
internal AacFlLayer DoCreateLayerWithoutDeleting(AnimatorController animator, string layerName)
|
||||
{
|
||||
var ag = new AacAnimatorGenerator(animator, CreateEmptyClip().Clip, _configuration.DefaultsProvider, _configuration.AnimatorRoot);
|
||||
var machine = ag.CreateOrClearLayerAtSameIndex(layerName, 1f, null, false);
|
||||
|
||||
return new AacFlLayer(animator, _configuration, machine, layerName);
|
||||
}
|
||||
|
||||
private AacFlClip CreateEmptyClip()
|
||||
{
|
||||
var emptyClip = AnomalousSingleKeyframeClip();
|
||||
return emptyClip;
|
||||
}
|
||||
|
||||
/// Removes all assets from the asset container matching the specified asset key.
|
||||
public void ClearPreviousAssets()
|
||||
{
|
||||
_configuration.ContainerProvider.ClearPreviousAssets();
|
||||
}
|
||||
|
||||
/// If you are not creating an animator, this returns an object from which you can obtain animator parameter objects. You should use this class if you are creating BlendTree assets without any animator controllers to back it. Otherwise, it is strongly recommended to obtain animator parameter objects directly from the layer objects instead of using NoAnimator(), as the use of NoAnimator() will not result in the registration of any parameters inside the animator controller.
|
||||
public AacFlNoAnimator NoAnimator()
|
||||
{
|
||||
return new AacFlNoAnimator();
|
||||
}
|
||||
|
||||
/// Returns a new AacFlModification instance, granting you access to this destructive modification API. You will need to reuse this object throughout.<br/>
|
||||
/// Added in 1.3.0.
|
||||
public AacFlModification Modification()
|
||||
{
|
||||
return new AacFlModification(_configuration, this);
|
||||
}
|
||||
}
|
||||
|
||||
public class AacFlNoAnimator
|
||||
{
|
||||
private readonly List<string> _floatParameters = new List<string>();
|
||||
private readonly List<string> _intParameters = new List<string>();
|
||||
private readonly List<string> _boolParameters = new List<string>();
|
||||
private readonly Dictionary<string, float> _floatOverrides = new Dictionary<string, float>();
|
||||
private readonly Dictionary<string, int> _intOverrides = new Dictionary<string, int>();
|
||||
private readonly Dictionary<string, bool> _boolOverrides = new Dictionary<string, bool>();
|
||||
|
||||
internal AacFlNoAnimator()
|
||||
{
|
||||
}
|
||||
|
||||
/// Create a Float parameter, for use without a backing animator.
|
||||
public AacFlFloatParameter FloatParameter(string parameterName)
|
||||
{
|
||||
_floatParameters.Add(parameterName);
|
||||
return AacFlFloatParameter.Internally(parameterName);
|
||||
}
|
||||
|
||||
/// Create an Int parameter, for use without a backing animator.
|
||||
public AacFlIntParameter IntParameter(string parameterName)
|
||||
{
|
||||
_intParameters.Add(parameterName);
|
||||
return AacFlIntParameter.Internally(parameterName);
|
||||
}
|
||||
|
||||
/// Create a Bool parameter, for use without a backing animator.
|
||||
public AacFlBoolParameter BoolParameter(string parameterName)
|
||||
{
|
||||
_boolParameters.Add(parameterName);
|
||||
return AacFlBoolParameter.Internally(parameterName);
|
||||
}
|
||||
|
||||
/// Stores the Float value of `toBeForced` parameter to `value`, which will be used in the CopyParametersAndOverridesTo() function.
|
||||
public AacFlNoAnimator OverrideValue(AacFlFloatParameter toBeForced, float value)
|
||||
{
|
||||
_floatOverrides[toBeForced.Name] = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Stores the Int value of `toBeForced` parameter to `value`, which will be used in the CopyParametersAndOverridesTo() function.
|
||||
public AacFlNoAnimator OverrideValue(AacFlIntParameter toBeForced, int value)
|
||||
{
|
||||
_intOverrides[toBeForced.Name] = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Stores the Bool value of `toBeForced` parameter to `value`, which will be used in the CopyParametersAndOverridesTo() function.
|
||||
public AacFlNoAnimator OverrideValue(AacFlBoolParameter toBeForced, bool value)
|
||||
{
|
||||
_boolOverrides[toBeForced.Name] = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Copy all created parameters to the other layer, and overrides it with a value if a value was stored.
|
||||
public AacFlNoAnimator CopyParametersAndOverridesTo(AacFlLayer otherLayer)
|
||||
{
|
||||
foreach (var key in _floatParameters)
|
||||
{
|
||||
var param = otherLayer.FloatParameter(key);
|
||||
if (_floatOverrides.TryGetValue(key, out var value)) otherLayer.OverrideValue(param, value);
|
||||
}
|
||||
foreach (var key in _intParameters)
|
||||
{
|
||||
var param = otherLayer.IntParameter(key);
|
||||
if (_intOverrides.TryGetValue(key, out var value)) otherLayer.OverrideValue(param, value);
|
||||
}
|
||||
foreach (var key in _boolParameters)
|
||||
{
|
||||
var param = otherLayer.BoolParameter(key);
|
||||
if (_boolOverrides.TryGetValue(key, out var value)) otherLayer.OverrideValue(param, value);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public class AacFlController
|
||||
{
|
||||
/// Exposes the underlying Unity AnimatorController.
|
||||
[PublicAPI] public AnimatorController AnimatorController { get; }
|
||||
|
||||
private readonly AacConfiguration _configuration;
|
||||
private readonly AacFlBase _base;
|
||||
|
||||
internal AacFlController(AacConfiguration configuration, AnimatorController animatorAnimatorController, AacFlBase originalBase)
|
||||
{
|
||||
AnimatorController = animatorAnimatorController;
|
||||
_configuration = configuration;
|
||||
_base = originalBase;
|
||||
}
|
||||
|
||||
/// Create a new layer with a specific suffix. You cannot create multiple layers with the same suffix on the same controller.
|
||||
public AacFlLayer NewLayer(string suffix) => _base.DoCreateLayerWithoutDeleting(AnimatorController, _configuration.DefaultsProvider.ConvertLayerNameWithSuffix(_configuration.SystemName, suffix));
|
||||
|
||||
/// Create a new layer. You cannot invoke this method multiple times on the same controller.
|
||||
public AacFlLayer NewLayer() => _base.DoCreateLayerWithoutDeleting(AnimatorController, _configuration.SystemName);
|
||||
}
|
||||
|
||||
/// Removes animators from an animator controller.<br/>
|
||||
/// This class is exposed for use by extension functions that need to remove layers.
|
||||
public class AacAnimatorRemoval
|
||||
{
|
||||
private readonly AnimatorController _animatorController;
|
||||
|
||||
[PublicAPI] public AacAnimatorRemoval(AnimatorController animatorController)
|
||||
{
|
||||
_animatorController = animatorController;
|
||||
}
|
||||
|
||||
/// Remove a single layer having that exact name.
|
||||
public void RemoveLayer(string layerName)
|
||||
{
|
||||
var index = FindIndexOf(layerName);
|
||||
if (index == -1) return;
|
||||
|
||||
AacInternals.NoUndo(_animatorController, () => _animatorController.RemoveLayer(index));
|
||||
}
|
||||
|
||||
private int FindIndexOf(string layerName)
|
||||
{
|
||||
return _animatorController.layers.ToList().FindIndex(layer => layer.name == layerName);
|
||||
}
|
||||
}
|
||||
|
||||
public class AacAnimatorGenerator
|
||||
{
|
||||
private readonly AnimatorController _animatorController;
|
||||
private readonly AnimationClip _emptyClip;
|
||||
private readonly IAacDefaultsProvider _defaultsProvider;
|
||||
private readonly Transform _animatorRoot;
|
||||
|
||||
internal AacAnimatorGenerator(AnimatorController animatorController, AnimationClip emptyClip, IAacDefaultsProvider defaultsProvider, Transform animatorRoot)
|
||||
{
|
||||
_animatorController = animatorController;
|
||||
_emptyClip = emptyClip;
|
||||
_defaultsProvider = defaultsProvider;
|
||||
_animatorRoot = animatorRoot;
|
||||
}
|
||||
|
||||
internal void CreateParamsAsNeeded(params AacFlParameter[] parameters)
|
||||
{
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
switch (parameter)
|
||||
{
|
||||
case AacFlIntParameter _:
|
||||
CreateParamIfNotExists(parameter.Name, AnimatorControllerParameterType.Int);
|
||||
break;
|
||||
case AacFlFloatParameter _:
|
||||
CreateParamIfNotExists(parameter.Name, AnimatorControllerParameterType.Float);
|
||||
break;
|
||||
case AacFlBoolParameter _:
|
||||
CreateParamIfNotExists(parameter.Name, AnimatorControllerParameterType.Bool);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
internal void CreateTriggerParamsAsNeeded(params AacFlBoolParameter[] parameters)
|
||||
{
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
CreateParamIfNotExists(parameter.Name, AnimatorControllerParameterType.Trigger);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateParamIfNotExists(string paramName, AnimatorControllerParameterType type)
|
||||
{
|
||||
if (_animatorController.parameters.FirstOrDefault(param => param.name == paramName) == null)
|
||||
{
|
||||
AacInternals.NoUndo(_animatorController, () => _animatorController.AddParameter(paramName, type));
|
||||
}
|
||||
}
|
||||
|
||||
// DEPRECATED: This causes the editor window to glitch by deselecting, which is jarring for experimentation
|
||||
internal AacFlStateMachine CreateOrRemakeLayerAtSameIndex(string layerName, float weightWhenCreating, AvatarMask maskWhenCreating = null)
|
||||
{
|
||||
var originalIndexToPreserveOrdering = FindIndexOf(layerName);
|
||||
if (originalIndexToPreserveOrdering != -1)
|
||||
{
|
||||
AacInternals.NoUndo(_animatorController, () => _animatorController.RemoveLayer(originalIndexToPreserveOrdering));
|
||||
}
|
||||
|
||||
AddLayerWithWeight(layerName, weightWhenCreating, maskWhenCreating);
|
||||
if (originalIndexToPreserveOrdering != -1)
|
||||
{
|
||||
var items = _animatorController.layers.ToList();
|
||||
var last = items[items.Count - 1];
|
||||
items.RemoveAt(items.Count - 1);
|
||||
items.Insert(originalIndexToPreserveOrdering, last);
|
||||
_animatorController.layers = items.ToArray();
|
||||
}
|
||||
|
||||
var layer = TryGetLayer(layerName);
|
||||
var machinist = new AacFlStateMachine(layer.stateMachine, _emptyClip, new AacBackingAnimator(this), _defaultsProvider, _animatorRoot);
|
||||
return machinist
|
||||
.WithAnyStatePosition(0, 7)
|
||||
.WithEntryPosition(0, -1)
|
||||
.WithExitPosition(7, -1);
|
||||
}
|
||||
|
||||
internal AacFlStateMachine CreateOrClearLayerAtSameIndex(string layerName, float weightWhenCreating, AvatarMask maskWhenCreating = null, bool allowDeletion = true)
|
||||
{
|
||||
var originalIndexToPreserveOrdering = FindIndexOf(layerName);
|
||||
if (originalIndexToPreserveOrdering != -1)
|
||||
{
|
||||
if (!allowDeletion)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot create layer with name {layerName} as it already exists. When creating layers using NewAnimatorController, you may only use unique names.");
|
||||
}
|
||||
RecursivelyClearChildrenMachines(_animatorController.layers[originalIndexToPreserveOrdering].stateMachine);
|
||||
|
||||
_animatorController.layers[originalIndexToPreserveOrdering].stateMachine.stateMachines = new ChildAnimatorStateMachine[0];
|
||||
_animatorController.layers[originalIndexToPreserveOrdering].stateMachine.states = new ChildAnimatorState[0];
|
||||
_animatorController.layers[originalIndexToPreserveOrdering].stateMachine.entryTransitions = new AnimatorTransition[0];
|
||||
_animatorController.layers[originalIndexToPreserveOrdering].stateMachine.anyStateTransitions = new AnimatorStateTransition[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
AacInternals.NoUndo(_animatorController, () => _animatorController.AddLayer(_animatorController.MakeUniqueLayerName(layerName)));
|
||||
originalIndexToPreserveOrdering = _animatorController.layers.Length - 1;
|
||||
}
|
||||
|
||||
var layers = _animatorController.layers;
|
||||
layers[originalIndexToPreserveOrdering].avatarMask = maskWhenCreating;
|
||||
layers[originalIndexToPreserveOrdering].defaultWeight = weightWhenCreating;
|
||||
_animatorController.layers = layers;
|
||||
|
||||
var layer = TryGetLayer(layerName);
|
||||
var machinist = new AacFlStateMachine(layer.stateMachine, _emptyClip, new AacBackingAnimator(this), _defaultsProvider, _animatorRoot);
|
||||
_defaultsProvider.ConfigureStateMachine(layer.stateMachine);
|
||||
return machinist;
|
||||
}
|
||||
|
||||
private void RecursivelyClearChildrenMachines(AnimatorStateMachine parentMachine)
|
||||
{
|
||||
// TODO: RemoveStateMachine might already be recursive
|
||||
foreach (var childStateMachineHolder in parentMachine.stateMachines)
|
||||
{
|
||||
RecursivelyClearChildrenMachines(childStateMachineHolder.stateMachine);
|
||||
AacInternals.NoUndo(parentMachine, () => parentMachine.RemoveStateMachine(childStateMachineHolder.stateMachine));
|
||||
}
|
||||
}
|
||||
|
||||
private int FindIndexOf(string layerName)
|
||||
{
|
||||
return _animatorController.layers.ToList().FindIndex(layer1 => layer1.name == layerName);
|
||||
}
|
||||
|
||||
private AnimatorControllerLayer TryGetLayer(string layerName)
|
||||
{
|
||||
return _animatorController.layers.FirstOrDefault(it => it.name == layerName);
|
||||
}
|
||||
|
||||
private void AddLayerWithWeight(string layerName, float weightWhenCreating, AvatarMask maskWhenCreating)
|
||||
{
|
||||
AacInternals.NoUndo(_animatorController, () => _animatorController.AddLayer(_animatorController.MakeUniqueLayerName(layerName)));
|
||||
|
||||
var mutatedLayers = _animatorController.layers;
|
||||
mutatedLayers[mutatedLayers.Length - 1].defaultWeight = weightWhenCreating;
|
||||
mutatedLayers[mutatedLayers.Length - 1].avatarMask = maskWhenCreating;
|
||||
_animatorController.layers = mutatedLayers;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58cdce083e9546d4a823075644127bb9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace AnimatorAsCode.V1
|
||||
{
|
||||
public abstract class AacAnimatorNode
|
||||
{
|
||||
protected internal abstract Vector3 GetPosition();
|
||||
protected internal abstract void SetPosition(Vector3 position);
|
||||
}
|
||||
|
||||
public abstract class AacAnimatorNode<TNode> : AacAnimatorNode where TNode : AacAnimatorNode<TNode>
|
||||
{
|
||||
protected readonly AacFlStateMachine ParentMachine;
|
||||
protected readonly IAacDefaultsProvider DefaultsProvider;
|
||||
protected readonly Transform AnimatorRoot;
|
||||
|
||||
protected AacAnimatorNode(AacFlStateMachine parentMachine, IAacDefaultsProvider defaultsProvider, Transform animatorRoot)
|
||||
{
|
||||
ParentMachine = parentMachine;
|
||||
DefaultsProvider = defaultsProvider;
|
||||
AnimatorRoot = animatorRoot;
|
||||
}
|
||||
|
||||
internal AacFlStateMachine RootMachine()
|
||||
{
|
||||
if (ParentMachine != null) return ParentMachine.RootMachine();
|
||||
if (this is AacFlStateMachine root)
|
||||
return root;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Move the node the left of the other node in the graph.
|
||||
public TNode LeftOf(AacAnimatorNode otherNode) => MoveNextTo(otherNode, -1, 0);
|
||||
|
||||
/// Move the node the right of the other node in the graph.
|
||||
public TNode RightOf(AacAnimatorNode otherNode) => MoveNextTo(otherNode, 1, 0);
|
||||
|
||||
/// Move the node to be over the other node in the graph.
|
||||
public TNode Over(AacAnimatorNode otherNode) => MoveNextTo(otherNode, 0, -1);
|
||||
|
||||
/// Move the node to be under the other node in the graph.
|
||||
public TNode Under(AacAnimatorNode otherNode) => MoveNextTo(otherNode, 0, 1);
|
||||
|
||||
/// Move the node to the left of the last created node of the state machine this belongs to in the graph.
|
||||
public TNode LeftOf() => MoveNextTo(null, -1, 0);
|
||||
|
||||
/// Move the node to the right of the last created node of the state machine this belongs to in the graph.
|
||||
public TNode RightOf() => MoveNextTo(null, 1, 0);
|
||||
|
||||
/// Move the node to be over the last created node of the state machine this belongs to in the graph.
|
||||
public TNode Over() => MoveNextTo(null, 0, -1);
|
||||
|
||||
/// Move the node to be under the last created node of the state machine this belongs to in the graph.
|
||||
public TNode Under() => MoveNextTo(null, 0, 1);
|
||||
|
||||
/// Move the node to be at a specific position in grid units, where x positive goes right, and y positive goes down.
|
||||
public TNode At(int x, int y)
|
||||
{
|
||||
SetPosition(new Vector3(x * DefaultsProvider.Grid().x, y * DefaultsProvider.Grid().y, 0));
|
||||
return (TNode) this;
|
||||
}
|
||||
|
||||
/// Move the state to be shifted next to the other state in the graph, in grid units. shiftX positive goes right, shiftY positive goes down.
|
||||
public TNode Shift(AacAnimatorNode otherState, int shiftX, int shiftY) => MoveNextTo(otherState, shiftX, shiftY);
|
||||
|
||||
private TNode MoveNextTo(AacAnimatorNode otherStateOrSecondToLastWhenNull, int x, int y)
|
||||
{
|
||||
if (otherStateOrSecondToLastWhenNull == null)
|
||||
{
|
||||
var siblings = ParentMachine.GetChildNodes();
|
||||
var other = siblings[siblings.Count - 2];
|
||||
Shift(other.GetPosition(), x, y);
|
||||
|
||||
return (TNode) this;
|
||||
}
|
||||
|
||||
Shift(otherStateOrSecondToLastWhenNull.GetPosition(), x, y);
|
||||
|
||||
return (TNode) this;
|
||||
}
|
||||
|
||||
// FIXME API: Vector3 is really odd as a type.
|
||||
/// Given another position in non-grid units, move the state to be shifted next to that position, in grid units. shiftX positive goes right, shiftY positive goes down.
|
||||
public TNode Shift(Vector3 otherPosition, int shiftX, int shiftY)
|
||||
{
|
||||
SetPosition(otherPosition + new Vector3(shiftX * DefaultsProvider.Grid().x, shiftY * DefaultsProvider.Grid().y, 0));
|
||||
return (TNode) this;
|
||||
}
|
||||
|
||||
/// Resolve the path of an item relative to the AnimatorRoot.
|
||||
public string ResolveRelativePath(Transform item)
|
||||
{
|
||||
return AacInternals.ResolveRelativePath(AnimatorRoot, item);
|
||||
}
|
||||
|
||||
/// Create a behaviour of the given type if it doesn't exist, or returns the first behaviour of that type.
|
||||
public abstract TBehaviour EnsureBehaviour<TBehaviour>() where TBehaviour : StateMachineBehaviour;
|
||||
|
||||
/// Create a behaviour of the given type.
|
||||
public abstract TBehaviour CreateNewBehaviour<TBehaviour>() where TBehaviour : StateMachineBehaviour;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aed20a9c2e3c00b4e8a45d96d3d2e2e7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,55 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace AnimatorAsCode.V1
|
||||
{
|
||||
public interface IAacAssetContainerProvider
|
||||
{
|
||||
void SaveAsPersistenceRequired(Object objectToAdd);
|
||||
void SaveAsRegular(Object objectToAdd);
|
||||
void ClearPreviousAssets();
|
||||
}
|
||||
|
||||
internal sealed class AacSimpleAssetContainerProvider : IAacAssetContainerProvider
|
||||
{
|
||||
private readonly AacConfiguration _configuration;
|
||||
|
||||
internal AacSimpleAssetContainerProvider(AacConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public void SaveAsPersistenceRequired(Object objectToAdd)
|
||||
{
|
||||
if (_configuration.AssetContainer == null) return;
|
||||
if (_configuration.ContainerMode != AacConfiguration.Container.Never) AssetDatabase.AddObjectToAsset(objectToAdd, _configuration.AssetContainer);
|
||||
}
|
||||
|
||||
public void SaveAsRegular(Object objectToAdd)
|
||||
{
|
||||
if (_configuration.AssetContainer == null) return;
|
||||
if (_configuration.ContainerMode == AacConfiguration.Container.Everything) AssetDatabase.AddObjectToAsset(objectToAdd, _configuration.AssetContainer);
|
||||
}
|
||||
|
||||
public void ClearPreviousAssets()
|
||||
{
|
||||
if (_configuration.AssetContainer == null) return;
|
||||
var allSubAssets = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(_configuration.AssetContainer));
|
||||
foreach (var subAsset in allSubAssets)
|
||||
{
|
||||
if (
|
||||
(
|
||||
subAsset.name.StartsWith($"{AacInternals.AutoGeneratedPrefix}{_configuration.AssetKey}__")
|
||||
|| subAsset.name.StartsWith($"{AacInternals.AutoGeneratedLegacyPrefix}{_configuration.AssetKey}__")
|
||||
)
|
||||
|
||||
&& (subAsset is AnimationClip || subAsset is BlendTree || subAsset is AvatarMask))
|
||||
{
|
||||
AssetDatabase.RemoveObjectFromAsset(subAsset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 548220720b88e034288c669042c5d30a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,69 @@
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace AnimatorAsCode.V1
|
||||
{
|
||||
public interface IAacDefaultsProvider
|
||||
{
|
||||
void ConfigureState(AnimatorState state, AnimationClip emptyClip);
|
||||
void ConfigureTransition(AnimatorStateTransition transition);
|
||||
string ConvertLayerName(string systemName);
|
||||
string ConvertLayerNameWithSuffix(string systemName, string suffix);
|
||||
Vector2 Grid();
|
||||
void ConfigureStateMachine(AnimatorStateMachine stateMachine);
|
||||
}
|
||||
|
||||
public class AacDefaultsProvider : IAacDefaultsProvider
|
||||
{
|
||||
private readonly bool _writeDefaults;
|
||||
|
||||
[PublicAPI] public AacDefaultsProvider(bool writeDefaults = false)
|
||||
{
|
||||
_writeDefaults = writeDefaults;
|
||||
}
|
||||
|
||||
public virtual void ConfigureState(AnimatorState state, AnimationClip emptyClip)
|
||||
{
|
||||
state.motion = emptyClip;
|
||||
state.writeDefaultValues = _writeDefaults;
|
||||
}
|
||||
|
||||
public virtual void ConfigureTransition(AnimatorStateTransition transition)
|
||||
{
|
||||
transition.duration = 0;
|
||||
transition.hasExitTime = false;
|
||||
transition.exitTime = 0;
|
||||
transition.hasFixedDuration = true;
|
||||
transition.offset = 0;
|
||||
transition.interruptionSource = TransitionInterruptionSource.None;
|
||||
transition.orderedInterruption = true;
|
||||
transition.canTransitionToSelf = false;
|
||||
}
|
||||
|
||||
public virtual string ConvertLayerName(string systemName)
|
||||
{
|
||||
return systemName;
|
||||
}
|
||||
|
||||
public virtual string ConvertLayerNameWithSuffix(string systemName, string suffix)
|
||||
{
|
||||
return $"{systemName}__{suffix}";
|
||||
}
|
||||
|
||||
public Vector2 Grid()
|
||||
{
|
||||
return new Vector2(250, 70);
|
||||
}
|
||||
|
||||
public void ConfigureStateMachine(AnimatorStateMachine stateMachine)
|
||||
{
|
||||
var grid = Grid();
|
||||
stateMachine.anyStatePosition = grid * new Vector2(0, 7);
|
||||
stateMachine.entryPosition = grid * new Vector2(0, -1);
|
||||
stateMachine.exitPosition = grid * new Vector2(7, -1);
|
||||
stateMachine.parentStateMachinePosition = grid * new Vector2(3, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fdf8c18af0705344fb844f6674cd11bb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,763 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace AnimatorAsCode.V1
|
||||
{
|
||||
public class AacFlClip
|
||||
{
|
||||
private readonly AacConfiguration _component;
|
||||
|
||||
/// Exposes the underlying Unity Clip asset.
|
||||
[PublicAPI] public AnimationClip Clip { get; }
|
||||
|
||||
internal AacFlClip(AacConfiguration component, AnimationClip clip)
|
||||
{
|
||||
_component = component;
|
||||
Clip = clip;
|
||||
}
|
||||
|
||||
/// Set the clip to be looping.
|
||||
public AacFlClip Looping()
|
||||
{
|
||||
var settings = AnimationUtility.GetAnimationClipSettings(Clip);
|
||||
settings.loopTime = true;
|
||||
AnimationUtility.SetAnimationClipSettings(Clip, settings);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Set the clip to be non-looping.
|
||||
public AacFlClip NonLooping()
|
||||
{
|
||||
var settings = AnimationUtility.GetAnimationClipSettings(Clip);
|
||||
settings.loopTime = false;
|
||||
AnimationUtility.SetAnimationClipSettings(Clip, settings);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Start editing the clip with a lambda expression.
|
||||
public AacFlClip Animating(Action<AacFlEditClip> action)
|
||||
{
|
||||
action.Invoke(new AacFlEditClip(_component, Clip));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Enable or disable GameObjects. This lasts one frame. The array can safely contain null values.
|
||||
public AacFlClip Toggling(GameObject[] gameObjectsWithNulls, bool value)
|
||||
{
|
||||
var defensiveObjects = gameObjectsWithNulls.Where(o => o != null); // Allow users to remove an item in the middle of the array
|
||||
foreach (var component in defensiveObjects)
|
||||
{
|
||||
var binding = AacInternals.Binding(_component, typeof(GameObject), component.transform, "m_IsActive");
|
||||
|
||||
AacInternals.SetCurve(Clip, binding, AacInternals.OneFrame(value ? 1f : 0f));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Enable or disable a GameObject. This lasts one frame.
|
||||
public AacFlClip Toggling(GameObject gameObject, bool value)
|
||||
{
|
||||
var binding = AacInternals.Binding(_component, typeof(GameObject), gameObject.transform, "m_IsActive");
|
||||
|
||||
AacInternals.SetCurve(Clip, binding, AacInternals.OneFrame(value ? 1f : 0f));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Change a blendShape of a skinned mesh. This lasts one frame.
|
||||
public AacFlClip BlendShape(SkinnedMeshRenderer renderer, string blendShapeName, float value)
|
||||
{
|
||||
var binding = AacInternals.Binding(_component, typeof(SkinnedMeshRenderer), renderer.transform, $"blendShape.{blendShapeName}");
|
||||
|
||||
AacInternals.SetCurve(Clip, binding, AacInternals.OneFrame(value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Change a blendShape of multiple skinned meshes. This lasts one frame. The array can safely contain null values.
|
||||
public AacFlClip BlendShape(SkinnedMeshRenderer[] rendererWithNulls, string blendShapeName, float value)
|
||||
{
|
||||
var defensiveObjects = rendererWithNulls.Where(o => o != null); // Allow users to remove an item in the middle of the array
|
||||
foreach (var component in defensiveObjects)
|
||||
{
|
||||
var binding = AacInternals.Binding(_component, typeof(SkinnedMeshRenderer), component.transform, $"blendShape.{blendShapeName}");
|
||||
|
||||
AacInternals.SetCurve(Clip, binding, AacInternals.OneFrame(value));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Change a blendShape of a skinned mesh, with an animation curve.
|
||||
public AacFlClip BlendShape(SkinnedMeshRenderer renderer, string blendShapeName, AnimationCurve animationCurve)
|
||||
{
|
||||
var binding = AacInternals.Binding(_component, typeof(SkinnedMeshRenderer), renderer.transform, $"blendShape.{blendShapeName}");
|
||||
|
||||
AacInternals.SetCurve(Clip, binding, animationCurve);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Change a blendShape of multiple skinned meshes, with an animation curve. The array can safely contain null values.
|
||||
public AacFlClip BlendShape(SkinnedMeshRenderer[] rendererWithNulls, string blendShapeName, AnimationCurve animationCurve)
|
||||
{
|
||||
var defensiveObjects = rendererWithNulls.Where(o => o != null); // Allow users to remove an item in the middle of the array
|
||||
foreach (var component in defensiveObjects)
|
||||
{
|
||||
var binding = AacInternals.Binding(_component, typeof(SkinnedMeshRenderer), component.transform, $"blendShape.{blendShapeName}");
|
||||
|
||||
AacInternals.SetCurve(Clip, binding, animationCurve);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public AacFlClip Positioning(Transform transform, Vector3 localPosition)
|
||||
{
|
||||
// Single-valued overloads must not tolerate null values
|
||||
if (transform == null) throw new NullReferenceException("Transform must not be null");
|
||||
return Positioning(new GameObject[]{ transform.gameObject }, localPosition);
|
||||
}
|
||||
|
||||
public AacFlClip RotatingUsingEulerInterpolation(Transform transform, Vector3 localEulerAngles)
|
||||
{
|
||||
// Single-valued overloads must not tolerate null values
|
||||
if (transform == null) throw new NullReferenceException("Transform must not be null");
|
||||
return RotatingUsingEulerInterpolation(new GameObject[]{ transform.gameObject }, localEulerAngles);
|
||||
}
|
||||
|
||||
public AacFlClip RotatingUsingQuaternionInterpolation(Transform transform, Quaternion localQuaternionAngles)
|
||||
{
|
||||
// Single-valued overloads must not tolerate null values
|
||||
if (transform == null) throw new NullReferenceException("Transform must not be null");
|
||||
return RotatingUsingQuaternionInterpolation(new GameObject[]{ transform.gameObject }, localQuaternionAngles);
|
||||
}
|
||||
|
||||
public AacFlClip Scaling(Transform transform, Vector3 scale)
|
||||
{
|
||||
// Single-valued overloads must not tolerate null values
|
||||
if (transform == null) throw new NullReferenceException("Transform must not be null");
|
||||
return Scaling(new GameObject[]{ transform.gameObject }, scale);
|
||||
}
|
||||
|
||||
public AacFlClip Positioning(GameObject gameObject, Vector3 localPosition)
|
||||
{
|
||||
// Single-valued overloads must not tolerate null values
|
||||
if (gameObject == null) throw new NullReferenceException("GameObject must not be null");
|
||||
return Positioning(new GameObject[]{ gameObject }, localPosition);
|
||||
}
|
||||
|
||||
public AacFlClip RotatingUsingEulerInterpolation(GameObject gameObject, Vector3 localEulerAngles)
|
||||
{
|
||||
// Single-valued overloads must not tolerate null values
|
||||
if (gameObject == null) throw new NullReferenceException("GameObject must not be null");
|
||||
return RotatingUsingEulerInterpolation(new GameObject[]{ gameObject }, localEulerAngles);
|
||||
}
|
||||
|
||||
public AacFlClip RotatingUsingQuaternionInterpolation(GameObject gameObject, Quaternion localQuaternionAngles)
|
||||
{
|
||||
// Single-valued overloads must not tolerate null values
|
||||
if (gameObject == null) throw new NullReferenceException("GameObject must not be null");
|
||||
return RotatingUsingQuaternionInterpolation(new GameObject[]{ gameObject }, localQuaternionAngles);
|
||||
}
|
||||
|
||||
public AacFlClip Scaling(GameObject gameObject, Vector3 scale)
|
||||
{
|
||||
// Single-valued overloads must not tolerate null values
|
||||
if (gameObject == null) throw new NullReferenceException("GameObject must not be null");
|
||||
return Scaling(new GameObject[]{ gameObject }, scale);
|
||||
}
|
||||
|
||||
public AacFlClip Positioning(Transform[] transformsWithNulls, Vector3 localPosition)
|
||||
{
|
||||
return Positioning(AsGameObjectsWithNulls(transformsWithNulls), localPosition);
|
||||
}
|
||||
|
||||
public AacFlClip RotatingUsingEulerInterpolation(Transform[] transformsWithNulls, Vector3 localEulerAngles)
|
||||
{
|
||||
return RotatingUsingEulerInterpolation(AsGameObjectsWithNulls(transformsWithNulls), localEulerAngles);
|
||||
}
|
||||
|
||||
public AacFlClip RotatingUsingQuaternionInterpolation(Transform[] transformsWithNulls, Quaternion localQuaternionAngles)
|
||||
{
|
||||
return RotatingUsingQuaternionInterpolation(AsGameObjectsWithNulls(transformsWithNulls), localQuaternionAngles);
|
||||
}
|
||||
|
||||
public AacFlClip Scaling(Transform[] transformsWithNulls, Vector3 scale)
|
||||
{
|
||||
return Scaling(AsGameObjectsWithNulls(transformsWithNulls), scale);
|
||||
}
|
||||
|
||||
private static GameObject[] AsGameObjectsWithNulls(Transform[] transformsWithNulls)
|
||||
{
|
||||
return transformsWithNulls.Select(o => o != null ? o.gameObject : null).ToArray();
|
||||
}
|
||||
|
||||
/// Change the position of a GameObject in local space. This lasts one frame. This lasts one frame. The array can safely contain null values.
|
||||
public AacFlClip Positioning(GameObject[] gameObjectsWithNulls, Vector3 localPosition)
|
||||
{
|
||||
var defensiveObjects = gameObjectsWithNulls.Where(o => o != null); // Allow users to remove an item in the middle of the array
|
||||
foreach (var component in defensiveObjects)
|
||||
{
|
||||
AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalPosition.x"), AacInternals.OneFrame(localPosition.x));
|
||||
AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalPosition.y"), AacInternals.OneFrame(localPosition.y));
|
||||
AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalPosition.z"), AacInternals.OneFrame(localPosition.z));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public AacFlClip RotatingUsingEulerInterpolation(GameObject[] gameObjectsWithNulls, Vector3 localEulerAngles)
|
||||
{
|
||||
var defensiveObjects = gameObjectsWithNulls.Where(o => o != null); // Allow users to remove an item in the middle of the array
|
||||
foreach (var component in defensiveObjects)
|
||||
{
|
||||
// See https://forum.unity.com/threads/new-animationclip-property-names.367288/#post-2384172
|
||||
// AacInternals.SetCurve internally uses AnimationClip.SetCurve instead of AnimationUtility.SetEditorCurve, starting from V1
|
||||
AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalEuler.x"), AacInternals.OneFrame(localEulerAngles.x));
|
||||
AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalEuler.y"), AacInternals.OneFrame(localEulerAngles.y));
|
||||
AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalEuler.z"), AacInternals.OneFrame(localEulerAngles.z));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public AacFlClip RotatingUsingQuaternionInterpolation(GameObject[] gameObjectsWithNulls, Quaternion localQuaternionAngles)
|
||||
{
|
||||
var defensiveObjects = gameObjectsWithNulls.Where(o => o != null); // Allow users to remove an item in the middle of the array
|
||||
foreach (var component in defensiveObjects)
|
||||
{
|
||||
// See https://forum.unity.com/threads/new-animationclip-property-names.367288/#post-2384172
|
||||
// AacInternals.SetCurve internally uses AnimationClip.SetCurve instead of AnimationUtility.SetEditorCurve, starting from V1
|
||||
AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalRotation.x"), AacInternals.OneFrame(localQuaternionAngles.x));
|
||||
AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalRotation.y"), AacInternals.OneFrame(localQuaternionAngles.y));
|
||||
AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalRotation.z"), AacInternals.OneFrame(localQuaternionAngles.z));
|
||||
AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalRotation.w"), AacInternals.OneFrame(localQuaternionAngles.w));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public AacFlClip Scaling(GameObject[] gameObjectsWithNulls, Vector3 scale)
|
||||
{
|
||||
var defensiveObjects = gameObjectsWithNulls.Where(o => o != null); // Allow users to remove an item in the middle of the array
|
||||
foreach (var component in defensiveObjects)
|
||||
{
|
||||
AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalScale.x"), AacInternals.OneFrame(scale.x));
|
||||
AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalScale.y"), AacInternals.OneFrame(scale.y));
|
||||
AacInternals.SetCurve(Clip, AacInternals.Binding(_component, typeof(Transform), component.transform, "m_LocalScale.z"), AacInternals.OneFrame(scale.z));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public AacFlClip TogglingComponent(Component[] componentsWithNulls, bool value)
|
||||
{
|
||||
var defensiveComponents = componentsWithNulls.Where(o => o != null); // Allow users to remove an item in the middle of the array
|
||||
foreach (var component in defensiveComponents)
|
||||
{
|
||||
var binding = AacInternals.Binding(_component, component.GetType(), component.transform, "m_Enabled");
|
||||
|
||||
AacInternals.SetCurve(Clip, binding, AacInternals.OneFrame(value ? 1f : 0f));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public AacFlClip TogglingComponent(Component component, bool value)
|
||||
{
|
||||
var binding = AacInternals.Binding(_component, component.GetType(), component.transform, "m_Enabled");
|
||||
|
||||
AacInternals.SetCurve(Clip, binding, AacInternals.OneFrame(value ? 1f : 0f));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Swap a material of a Renderer on the specified slot (indexed at 0). This lasts one frame.
|
||||
public AacFlClip SwappingMaterial(Renderer renderer, int slot, Material material)
|
||||
{
|
||||
var binding = AacInternals.Binding(_component, renderer.GetType(), renderer.transform, $"m_Materials.Array.data[{slot}]");
|
||||
|
||||
AnimationUtility.SetObjectReferenceCurve(Clip, binding, new[] {
|
||||
new ObjectReferenceKeyframe { time = 0f, value = material },
|
||||
new ObjectReferenceKeyframe { time = 1/60f, value = material }
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Swap a material of a Particle System on the specified slot (indexed at 0). This lasts one frame.<br/>
|
||||
/// In practice, this will animate the ParticleSystemRenderer of that particle system.
|
||||
public AacFlClip SwappingMaterial(ParticleSystem particleSystem, int slot, Material material)
|
||||
{
|
||||
var binding = AacInternals.Binding(_component, typeof(ParticleSystemRenderer), particleSystem.transform, $"m_Materials.Array.data[{slot}]");
|
||||
|
||||
AnimationUtility.SetObjectReferenceCurve(Clip, binding, new[] {
|
||||
new ObjectReferenceKeyframe { time = 0f, value = material },
|
||||
new ObjectReferenceKeyframe { time = 1/60f, value = material }
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public class AacFlEditClip
|
||||
{
|
||||
private readonly AacConfiguration _component;
|
||||
[PublicAPI] public AnimationClip Clip { get; }
|
||||
|
||||
internal AacFlEditClip(AacConfiguration component, AnimationClip clip)
|
||||
{
|
||||
_component = component;
|
||||
Clip = clip;
|
||||
}
|
||||
|
||||
/// Animates a path the traditional way.
|
||||
public AacFlSettingCurve Animates(string path, Type type, string propertyName)
|
||||
{
|
||||
var binding = new EditorCurveBinding
|
||||
{
|
||||
path = path,
|
||||
type = type,
|
||||
propertyName = propertyName
|
||||
};
|
||||
return new AacFlSettingCurve(Clip, new[] {binding});
|
||||
}
|
||||
|
||||
/// Animates an object in the hierarchy relative to the animator root, the traditional way.
|
||||
public AacFlSettingCurve Animates(Transform transform, Type type, string propertyName)
|
||||
{
|
||||
var binding = AacInternals.Binding(_component, type, transform, propertyName);
|
||||
|
||||
return new AacFlSettingCurve(Clip, new[] {binding});
|
||||
}
|
||||
|
||||
/// Animates the active property of a GameObject, toggling it.
|
||||
public AacFlSettingCurve Animates(GameObject gameObject)
|
||||
{
|
||||
var binding = AacInternals.Binding(_component, typeof(GameObject), gameObject.transform, "m_IsActive");
|
||||
|
||||
return new AacFlSettingCurve(Clip, new[] {binding});
|
||||
}
|
||||
|
||||
/// Animates the property of a component. The runtime type of the component will be used.
|
||||
public AacFlSettingCurve Animates(Component anyComponent, string property)
|
||||
{
|
||||
var binding = Internal_BindingFromComponent(anyComponent, property);
|
||||
|
||||
return new AacFlSettingCurve(Clip, new[] {binding});
|
||||
}
|
||||
|
||||
/// Animates the property of several components. The runtime type of the component will be used. The array can safely contain null values.
|
||||
public AacFlSettingCurve Animates(Component[] anyComponentsWithNulls, string property)
|
||||
{
|
||||
var bindings = Internal_BindingsFromComponentsWithNulls(anyComponentsWithNulls, property);
|
||||
|
||||
return new AacFlSettingCurve(Clip, bindings);
|
||||
}
|
||||
|
||||
/// Animates a Float parameter of the animator (may sometimes be referred to as an Animated Animator Parameter, or AAP).
|
||||
public AacFlSettingCurve AnimatesAnimator(AacFlParameter floatParameter)
|
||||
{
|
||||
var binding = new EditorCurveBinding
|
||||
{
|
||||
path = "",
|
||||
type = typeof(Animator),
|
||||
propertyName = floatParameter.Name
|
||||
};
|
||||
return new AacFlSettingCurve(Clip, new[] {binding});
|
||||
}
|
||||
|
||||
/// Animates a color property of a component. The runtime type of the component will be used.
|
||||
public AacFlSettingCurveColor AnimatesColor(Component anyComponent, string property)
|
||||
{
|
||||
var binding = Internal_BindingFromComponent(anyComponent, property);
|
||||
return new AacFlSettingCurveColor(Clip, new[] {binding});
|
||||
}
|
||||
|
||||
/// Animates a color property of several components. The runtime type of the component will be used.
|
||||
public AacFlSettingCurveColor AnimatesColor(Component[] anyComponentsWithNulls, string property)
|
||||
{
|
||||
var bindings = Internal_BindingsFromComponentsWithNulls(anyComponentsWithNulls, property);
|
||||
|
||||
return new AacFlSettingCurveColor(Clip, bindings);
|
||||
}
|
||||
|
||||
/// Animates an HDR color property of a component (uses XYZW instead of RGBA). The runtime type of the component will be used.
|
||||
public AacFlSettingCurveColor AnimatesHDRColor(Component anyComponent, string property)
|
||||
{
|
||||
var binding = Internal_BindingFromComponent(anyComponent, property);
|
||||
return new AacFlSettingCurveColor(Clip, new[] {binding}, true);
|
||||
}
|
||||
|
||||
/// Animates an HDR color property of several components (uses XYZW instead of RGBA). The runtime type of the component will be used.
|
||||
public AacFlSettingCurveColor AnimatesHDRColor(Component[] anyComponentsWithNulls, string property)
|
||||
{
|
||||
var bindings = Internal_BindingsFromComponentsWithNulls(anyComponentsWithNulls, property);
|
||||
|
||||
return new AacFlSettingCurveColor(Clip, bindings, true);
|
||||
}
|
||||
|
||||
/// Animates an object reference of a component. The runtime type of the component will be used.
|
||||
public AacFlSettingCurveObjectReference AnimatesObjectReference(Component anyComponent, string property)
|
||||
{
|
||||
var binding = Internal_BindingFromComponent(anyComponent, property);
|
||||
return new AacFlSettingCurveObjectReference(Clip, new[] {binding});
|
||||
}
|
||||
|
||||
/// Animates an object reference of several components. The runtime type of the component will be used.
|
||||
public AacFlSettingCurveObjectReference AnimatesObjectReference(Component[] anyComponentsWithNulls, string property)
|
||||
{
|
||||
var bindings = Internal_BindingsFromComponentsWithNulls(anyComponentsWithNulls, property);
|
||||
|
||||
return new AacFlSettingCurveObjectReference(Clip, bindings);
|
||||
}
|
||||
|
||||
/// Returns an EditorCurveBinding of a component, relative to the animator root. The runtime type of the component will be used.<br/>
|
||||
/// This is meant to be used in conjunction with traditional animation APIs.
|
||||
public EditorCurveBinding BindingFromComponent(Component anyComponent, string propertyName)
|
||||
{
|
||||
return Internal_BindingFromComponent(anyComponent, propertyName);
|
||||
}
|
||||
|
||||
private EditorCurveBinding[] Internal_BindingsFromComponentsWithNulls(Component[] anyComponentsWithNulls, string property)
|
||||
{
|
||||
return anyComponentsWithNulls
|
||||
.Where(o => o != null)
|
||||
.Select(anyComponent => Internal_BindingFromComponent(anyComponent, property))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private EditorCurveBinding Internal_BindingFromComponent(Component anyComponent, string propertyName)
|
||||
{
|
||||
return AacInternals.Binding(_component, anyComponent.GetType(), anyComponent.transform, propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
public class AacFlSettingCurve
|
||||
{
|
||||
private readonly AnimationClip _clip;
|
||||
private readonly EditorCurveBinding[] _bindings;
|
||||
|
||||
internal AacFlSettingCurve(AnimationClip clip, EditorCurveBinding[] bindings)
|
||||
{
|
||||
_clip = clip;
|
||||
_bindings = bindings;
|
||||
}
|
||||
|
||||
/// Define the curve to be exactly one frame by defining two constant keyframes, usually lasting 1/60th of a second, with the desired value.
|
||||
public void WithOneFrame(float desiredValue)
|
||||
{
|
||||
foreach (var binding in _bindings)
|
||||
{
|
||||
AacInternals.SetCurve(_clip, binding, AacInternals.OneFrame(desiredValue));
|
||||
}
|
||||
}
|
||||
|
||||
/// Define the curve to last a specific amount of seconds by defining two constant keyframes, with the desired value.
|
||||
public void WithFixedSeconds(float seconds, float desiredValue)
|
||||
{
|
||||
foreach (var binding in _bindings)
|
||||
{
|
||||
AacInternals.SetCurve(_clip, binding, AacInternals.ConstantSeconds(seconds, desiredValue));
|
||||
}
|
||||
}
|
||||
|
||||
/// Start defining the keyframes with a lambda expression, expressing the unit to be in seconds.
|
||||
public void WithSecondsUnit(Action<AacFlSettingKeyframes> action)
|
||||
{
|
||||
InternalWithUnit(AacFlUnit.Seconds, action);
|
||||
}
|
||||
|
||||
/// Start defining the keyframes with a lambda expression, expressing the unit in frames.
|
||||
public void WithFrameCountUnit(Action<AacFlSettingKeyframes> action)
|
||||
{
|
||||
InternalWithUnit(AacFlUnit.Frames, action);
|
||||
}
|
||||
|
||||
/// Start defining the keyframes with a lambda expression, expressing the unit.
|
||||
public void WithUnit(AacFlUnit unit, Action<AacFlSettingKeyframes> action)
|
||||
{
|
||||
InternalWithUnit(unit, action);
|
||||
}
|
||||
|
||||
private void InternalWithUnit(AacFlUnit unit, Action<AacFlSettingKeyframes> action)
|
||||
{
|
||||
var mutatedKeyframes = new List<Keyframe>();
|
||||
var builder = new AacFlSettingKeyframes(unit, mutatedKeyframes);
|
||||
action.Invoke(builder);
|
||||
|
||||
foreach (var binding in _bindings)
|
||||
{
|
||||
AacInternals.SetCurve(_clip, binding, new AnimationCurve(mutatedKeyframes.ToArray()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Define the curve as the parameter. The duration is encoded inside the curve itself.
|
||||
public void WithAnimationCurve(AnimationCurve animationCurve)
|
||||
{
|
||||
foreach (var binding in _bindings)
|
||||
{
|
||||
AacInternals.SetCurve(_clip, binding, animationCurve);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AacFlSettingCurveObjectReference
|
||||
{
|
||||
private readonly AnimationClip _clip;
|
||||
private readonly EditorCurveBinding[] _bindings;
|
||||
|
||||
internal AacFlSettingCurveObjectReference(AnimationClip clip, EditorCurveBinding[] bindings)
|
||||
{
|
||||
_clip = clip;
|
||||
_bindings = bindings;
|
||||
}
|
||||
|
||||
/// Define the curve to be exactly one frame by defining two constant keyframes, usually lasting 1/60th of a second, with the desired object reference value.
|
||||
public void WithOneFrame(Object desiredValue)
|
||||
{
|
||||
foreach (var binding in _bindings)
|
||||
{
|
||||
AnimationUtility.SetObjectReferenceCurve(_clip, binding, new[]
|
||||
{
|
||||
new ObjectReferenceKeyframe { time = 0f, value = desiredValue },
|
||||
new ObjectReferenceKeyframe { time = 1/60f, value = desiredValue }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Obsolete. Use `WithUnit()` instead.<br/>
|
||||
/// Start defining the keyframes with a lambda expression, expressing the unit.
|
||||
[Obsolete("This function was renamed to WithUnit(...)")]
|
||||
public void WithKeyframes(AacFlUnit unit, Action<AacFlSettingKeyframesObjectReference> action)
|
||||
{
|
||||
WithUnit(unit, action);
|
||||
}
|
||||
|
||||
/// Start defining the keyframes with a lambda expression, expressing the unit.
|
||||
public void WithUnit(AacFlUnit unit, Action<AacFlSettingKeyframesObjectReference> action)
|
||||
{
|
||||
var mutatedObjectReferenceKeyframes = new List<ObjectReferenceKeyframe>();
|
||||
var builder = new AacFlSettingKeyframesObjectReference(unit, mutatedObjectReferenceKeyframes);
|
||||
action.Invoke(builder);
|
||||
|
||||
foreach (var binding in _bindings)
|
||||
{
|
||||
AnimationUtility.SetObjectReferenceCurve(_clip, binding, mutatedObjectReferenceKeyframes.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AacFlSettingKeyframesObjectReference
|
||||
{
|
||||
private readonly AacFlUnit _unit;
|
||||
private readonly List<ObjectReferenceKeyframe> _mutatedKeyframes;
|
||||
|
||||
internal AacFlSettingKeyframesObjectReference(AacFlUnit unit, List<ObjectReferenceKeyframe> mutatedKeyframes)
|
||||
{
|
||||
_unit = unit;
|
||||
_mutatedKeyframes = mutatedKeyframes;
|
||||
}
|
||||
|
||||
/// Create a keyframe for an object reference. The unit is defined by the function that invokes this lambda expression.
|
||||
public AacFlSettingKeyframesObjectReference Setting(int timeInUnit, Object value)
|
||||
{
|
||||
_mutatedKeyframes.Add(new ObjectReferenceKeyframe { time = AsSeconds(timeInUnit), value = value });
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private float AsSeconds(float timeInUnit)
|
||||
{
|
||||
switch (_unit)
|
||||
{
|
||||
case AacFlUnit.Frames:
|
||||
return timeInUnit / 60f;
|
||||
case AacFlUnit.Seconds:
|
||||
return timeInUnit;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AacFlSettingCurveColor
|
||||
{
|
||||
private readonly AnimationClip _clip;
|
||||
private readonly EditorCurveBinding[] _bindings;
|
||||
private readonly bool _hdr;
|
||||
|
||||
internal AacFlSettingCurveColor(AnimationClip clip, EditorCurveBinding[] bindings, bool hdr = false)
|
||||
{
|
||||
_clip = clip;
|
||||
_bindings = bindings;
|
||||
_hdr = hdr;
|
||||
}
|
||||
|
||||
public void WithOneFrame(Color desiredValue)
|
||||
{
|
||||
foreach (var binding in _bindings)
|
||||
{
|
||||
AacInternals.SetCurve(_clip, AacInternals.ToSubBinding(binding, _hdr ? "x" : "r"), AacInternals.OneFrame(desiredValue.r));
|
||||
AacInternals.SetCurve(_clip, AacInternals.ToSubBinding(binding, _hdr ? "y" : "g"), AacInternals.OneFrame(desiredValue.g));
|
||||
AacInternals.SetCurve(_clip, AacInternals.ToSubBinding(binding, _hdr ? "z" : "b"), AacInternals.OneFrame(desiredValue.b));
|
||||
AacInternals.SetCurve(_clip, AacInternals.ToSubBinding(binding, _hdr ? "w" : "a"), AacInternals.OneFrame(desiredValue.a));
|
||||
}
|
||||
}
|
||||
|
||||
/// Obsolete. Use `WithUnit()` instead.<br/>
|
||||
/// Start defining the keyframes with a lambda expression, expressing the unit.
|
||||
[Obsolete("This function was renamed to WithUnit(...)")]
|
||||
public void WithKeyframes(AacFlUnit unit, Action<AacFlSettingKeyframesColor> action)
|
||||
{
|
||||
WithUnit(unit, action);
|
||||
}
|
||||
|
||||
/// Start defining the keyframes with a lambda expression, expressing the unit.
|
||||
public void WithUnit(AacFlUnit unit, Action<AacFlSettingKeyframesColor> action)
|
||||
{
|
||||
var mutatedKeyframesR = new List<Keyframe>();
|
||||
var mutatedKeyframesG = new List<Keyframe>();
|
||||
var mutatedKeyframesB = new List<Keyframe>();
|
||||
var mutatedKeyframesA = new List<Keyframe>();
|
||||
var builder = new AacFlSettingKeyframesColor(unit, mutatedKeyframesR, mutatedKeyframesG, mutatedKeyframesB, mutatedKeyframesA);
|
||||
action.Invoke(builder);
|
||||
|
||||
foreach (var binding in _bindings)
|
||||
{
|
||||
AacInternals.SetCurve(_clip, AacInternals.ToSubBinding(binding, _hdr ? "x" : "r"), new AnimationCurve(mutatedKeyframesR.ToArray()));
|
||||
AacInternals.SetCurve(_clip, AacInternals.ToSubBinding(binding, _hdr ? "y" : "g"), new AnimationCurve(mutatedKeyframesG.ToArray()));
|
||||
AacInternals.SetCurve(_clip, AacInternals.ToSubBinding(binding, _hdr ? "z" : "b"), new AnimationCurve(mutatedKeyframesB.ToArray()));
|
||||
AacInternals.SetCurve(_clip, AacInternals.ToSubBinding(binding, _hdr ? "w" : "a"), new AnimationCurve(mutatedKeyframesA.ToArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AacFlSettingKeyframes
|
||||
{
|
||||
private readonly AacFlUnit _unit;
|
||||
private readonly List<Keyframe> _mutatedKeyframes;
|
||||
|
||||
// Will be made private/internal in V1.2.0.
|
||||
[Obsolete("This has been made private/internal in V1.2.0")]
|
||||
internal AacFlSettingKeyframes(AacFlUnit unit, List<Keyframe> mutatedKeyframes)
|
||||
{
|
||||
_unit = unit;
|
||||
_mutatedKeyframes = mutatedKeyframes;
|
||||
}
|
||||
|
||||
public AacFlSettingKeyframes Easing(float timeInUnit, float value)
|
||||
{
|
||||
_mutatedKeyframes.Add(new Keyframe(AsSeconds(timeInUnit), value, 0, 0));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public AacFlSettingKeyframes Constant(float timeInUnit, float value)
|
||||
{
|
||||
_mutatedKeyframes.Add(new Keyframe(AsSeconds(timeInUnit), value, 0, float.PositiveInfinity));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public AacFlSettingKeyframes Linear(float timeInUnit, float value)
|
||||
{
|
||||
float valueEnd = value;
|
||||
float valueStart = _mutatedKeyframes.Count == 0 ? value : _mutatedKeyframes.Last().value;
|
||||
float timeEnd = AsSeconds(timeInUnit);
|
||||
float timeStart = _mutatedKeyframes.Count == 0 ? value : _mutatedKeyframes.Last().time;
|
||||
float num = (float) (((double) valueEnd - (double) valueStart) / ((double) timeEnd - (double) timeStart));
|
||||
// FIXME: This can cause NaN tangents which messes everything
|
||||
|
||||
// return new AnimationCurve(new Keyframe[2]
|
||||
// {
|
||||
// new Keyframe(timeStart, valueStart, 0.0f, num),
|
||||
// new Keyframe(timeEnd, valueEnd, num, 0.0f)
|
||||
// });
|
||||
|
||||
if (_mutatedKeyframes.Count > 0)
|
||||
{
|
||||
var lastKeyframe = _mutatedKeyframes.Last();
|
||||
lastKeyframe.outTangent = num;
|
||||
_mutatedKeyframes[_mutatedKeyframes.Count - 1] = lastKeyframe;
|
||||
}
|
||||
_mutatedKeyframes.Add(new Keyframe(AsSeconds(timeInUnit), value, num, 0.0f));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private float AsSeconds(float timeInUnit)
|
||||
{
|
||||
switch (_unit)
|
||||
{
|
||||
case AacFlUnit.Frames:
|
||||
return timeInUnit / 60f;
|
||||
case AacFlUnit.Seconds:
|
||||
return timeInUnit;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AacFlSettingKeyframesColor
|
||||
{
|
||||
private AacFlSettingKeyframes _r;
|
||||
private AacFlSettingKeyframes _g;
|
||||
private AacFlSettingKeyframes _b;
|
||||
private AacFlSettingKeyframes _a;
|
||||
|
||||
internal AacFlSettingKeyframesColor(AacFlUnit unit, List<Keyframe> mutatedKeyframesR, List<Keyframe> mutatedKeyframesG, List<Keyframe> mutatedKeyframesB, List<Keyframe> mutatedKeyframesA)
|
||||
{
|
||||
_r = new AacFlSettingKeyframes(unit, mutatedKeyframesR);
|
||||
_g = new AacFlSettingKeyframes(unit, mutatedKeyframesG);
|
||||
_b = new AacFlSettingKeyframes(unit, mutatedKeyframesB);
|
||||
_a = new AacFlSettingKeyframes(unit, mutatedKeyframesA);
|
||||
}
|
||||
|
||||
public AacFlSettingKeyframesColor Easing(int frame, Color value)
|
||||
{
|
||||
_r.Easing(frame, value.r);
|
||||
_g.Easing(frame, value.g);
|
||||
_b.Easing(frame, value.b);
|
||||
_a.Easing(frame, value.a);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public AacFlSettingKeyframesColor Linear(float frame, Color value)
|
||||
{
|
||||
_r.Linear(frame, value.r);
|
||||
_g.Linear(frame, value.g);
|
||||
_b.Linear(frame, value.b);
|
||||
_a.Linear(frame, value.a);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public AacFlSettingKeyframesColor Constant(int frame, Color value)
|
||||
{
|
||||
_r.Constant(frame, value.r);
|
||||
_g.Constant(frame, value.g);
|
||||
_b.Constant(frame, value.b);
|
||||
_a.Constant(frame, value.a);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public enum AacFlUnit
|
||||
{
|
||||
Frames, Seconds
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 484e77c2a7e05754faed374970aa5ef8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,385 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace AnimatorAsCode.V1
|
||||
{
|
||||
public class AacFlBlendTree
|
||||
{
|
||||
protected AacFlBlendTree(BlendTree blendTree)
|
||||
{
|
||||
BlendTree = blendTree;
|
||||
}
|
||||
|
||||
/// Exposes the underlying Unity BlendTree asset.
|
||||
public BlendTree BlendTree { get; }
|
||||
}
|
||||
|
||||
public class AacFlNonInitializedBlendTree : AacFlBlendTree
|
||||
{
|
||||
internal AacFlNonInitializedBlendTree(BlendTree blendTree) : base(blendTree)
|
||||
{
|
||||
}
|
||||
|
||||
/// Define this BlendTree as being FreeformCartesian2D.
|
||||
public AacFlBlendTree2D FreeformCartesian2D(AacFlFloatParameter parameterX, AacFlFloatParameter parameterY)
|
||||
{
|
||||
return New2DBlendTree(parameterX, parameterY, BlendTreeType.FreeformCartesian2D);
|
||||
}
|
||||
|
||||
/// Define this BlendTree as being FreeformDirectional2D.
|
||||
public AacFlBlendTree2D FreeformDirectional2D(AacFlFloatParameter parameterX, AacFlFloatParameter parameterY)
|
||||
{
|
||||
return New2DBlendTree(parameterX, parameterY, BlendTreeType.FreeformDirectional2D);
|
||||
}
|
||||
|
||||
/// Define this BlendTree as being SimpleDirectional2D.
|
||||
public AacFlBlendTree2D SimpleDirectional2D(AacFlFloatParameter parameterX, AacFlFloatParameter parameterY)
|
||||
{
|
||||
return New2DBlendTree(parameterX, parameterY, BlendTreeType.SimpleDirectional2D);
|
||||
}
|
||||
|
||||
/// Define this BlendTree as being Simple1D.
|
||||
public AacFlBlendTree1D Simple1D(AacFlFloatParameter parameter)
|
||||
{
|
||||
BlendTree.blendType = BlendTreeType.Simple1D;
|
||||
BlendTree.blendParameter = parameter.Name;
|
||||
BlendTree.useAutomaticThresholds = false;
|
||||
|
||||
return new AacFlBlendTree1D(BlendTree);
|
||||
}
|
||||
|
||||
/// Define this BlendTree as being Direct.
|
||||
public AacFlBlendTreeDirect Direct()
|
||||
{
|
||||
BlendTree.blendType = BlendTreeType.Direct;
|
||||
|
||||
return new AacFlBlendTreeDirect(BlendTree);
|
||||
}
|
||||
|
||||
private AacFlBlendTree2D New2DBlendTree(AacFlFloatParameter parameterX, AacFlFloatParameter parameterY, BlendTreeType blendTreeType)
|
||||
{
|
||||
BlendTree.blendType = blendTreeType;
|
||||
BlendTree.blendParameter = parameterX.Name;
|
||||
BlendTree.blendParameterY = parameterY.Name;
|
||||
|
||||
return new AacFlBlendTree2D(BlendTree);
|
||||
}
|
||||
}
|
||||
|
||||
public class AacFlBlendTree2D : AacFlBlendTree
|
||||
{
|
||||
internal AacFlBlendTree2D(BlendTree blendTree) : base(blendTree)
|
||||
{
|
||||
}
|
||||
|
||||
/// Add a BlendTree in the specified coordinates. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTree2D WithAnimation(AacFlBlendTree blendTree, Vector2 pos)
|
||||
{
|
||||
return WithAnimationInternal(blendTree.BlendTree, pos.x, pos.y, null);
|
||||
}
|
||||
|
||||
/// Add a BlendTree in the specified `x` and `y` coordinates. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTree2D WithAnimation(AacFlBlendTree blendTree, float x, float y)
|
||||
{
|
||||
return WithAnimationInternal(blendTree.BlendTree, x, y, null);
|
||||
}
|
||||
|
||||
/// Add a Clip in the specified coordinates. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTree2D WithAnimation(AacFlClip clip, Vector2 pos)
|
||||
{
|
||||
return WithAnimationInternal(clip.Clip, pos.x, pos.y, null);
|
||||
}
|
||||
|
||||
/// Add a Clip in the specified `x` and `y` coordinates. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTree2D WithAnimation(AacFlClip clip, float x, float y)
|
||||
{
|
||||
return WithAnimationInternal(clip.Clip, x, y, null);
|
||||
}
|
||||
|
||||
/// Add a raw motion in the specified coordinates. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTree2D WithAnimation(Motion motion, Vector2 pos)
|
||||
{
|
||||
return WithAnimationInternal(motion, pos.x, pos.y, null);
|
||||
}
|
||||
|
||||
/// Add a raw motion in the specified `x` and `y` coordinates. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTree2D WithAnimation(Motion motion, float x, float y)
|
||||
{
|
||||
return WithAnimationInternal(motion, x, y, null);
|
||||
}
|
||||
|
||||
/// Add a BlendTree in the specified coordinates. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTree2D WithAnimation(AacFlBlendTree blendTree, Vector2 pos, Action<AacFlBlendTreeChildMotion> furtherDefiningChild)
|
||||
{
|
||||
// Disallow null here: as nulls are allowed in the internal function,
|
||||
// we want to stop early if this specific non-null overload is invoked with a null param.
|
||||
if (furtherDefiningChild == null) throw new NullReferenceException();
|
||||
|
||||
return WithAnimationInternal(blendTree.BlendTree, pos.x, pos.y, furtherDefiningChild);
|
||||
}
|
||||
|
||||
/// Add a BlendTree in the specified `x` and `y` coordinates. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTree2D WithAnimation(AacFlBlendTree blendTree, float x, float y, Action<AacFlBlendTreeChildMotion> furtherDefiningChild)
|
||||
{
|
||||
// Disallow null here: as nulls are allowed in the internal function,
|
||||
// we want to stop early if this specific non-null overload is invoked with a null param.
|
||||
if (furtherDefiningChild == null) throw new NullReferenceException();
|
||||
|
||||
return WithAnimationInternal(blendTree.BlendTree, x, y, furtherDefiningChild);
|
||||
}
|
||||
|
||||
/// Add a Clip in the specified coordinates. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTree2D WithAnimation(AacFlClip clip, Vector2 pos, Action<AacFlBlendTreeChildMotion> furtherDefiningChild)
|
||||
{
|
||||
// Disallow null here: as nulls are allowed in the internal function,
|
||||
// we want to stop early if this specific non-null overload is invoked with a null param.
|
||||
if (furtherDefiningChild == null) throw new NullReferenceException();
|
||||
|
||||
return WithAnimationInternal(clip.Clip, pos.x, pos.y, furtherDefiningChild);
|
||||
}
|
||||
|
||||
/// Add a Clip in the specified `x` and `y` coordinates. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTree2D WithAnimation(AacFlClip clip, float x, float y, Action<AacFlBlendTreeChildMotion> furtherDefiningChild)
|
||||
{
|
||||
// Disallow null here: as nulls are allowed in the internal function,
|
||||
// we want to stop early if this specific non-null overload is invoked with a null param.
|
||||
if (furtherDefiningChild == null) throw new NullReferenceException();
|
||||
|
||||
return WithAnimationInternal(clip.Clip, x, y, furtherDefiningChild);
|
||||
}
|
||||
|
||||
/// Add a raw motion in the specified coordinates. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTree2D WithAnimation(Motion motion, Vector2 pos, Action<AacFlBlendTreeChildMotion> furtherDefiningChild)
|
||||
{
|
||||
// Disallow null here: as nulls are allowed in the internal function,
|
||||
// we want to stop early if this specific non-null overload is invoked with a null param.
|
||||
if (furtherDefiningChild == null) throw new NullReferenceException();
|
||||
|
||||
return WithAnimationInternal(motion, pos.x, pos.y, furtherDefiningChild);
|
||||
}
|
||||
|
||||
/// Add a raw motion in the specified `x` and `y` coordinates. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTree2D WithAnimation(Motion motion, float x, float y, Action<AacFlBlendTreeChildMotion> furtherDefiningChild)
|
||||
{
|
||||
// Disallow null here: as nulls are allowed in the internal function,
|
||||
// we want to stop early if this specific non-null overload is invoked with a null param.
|
||||
if (furtherDefiningChild == null) throw new NullReferenceException();
|
||||
|
||||
return WithAnimationInternal(motion, x, y, furtherDefiningChild);
|
||||
}
|
||||
|
||||
private AacFlBlendTree2D WithAnimationInternal(Motion motion, float x, float y, Action<AacFlBlendTreeChildMotion> furtherDefiningChildNullable)
|
||||
{
|
||||
var children = BlendTree.children ?? new ChildMotion[0];
|
||||
var childrenList = children.ToList();
|
||||
|
||||
|
||||
var childMotionModifier = new AacFlBlendTreeChildMotion();
|
||||
furtherDefiningChildNullable?.Invoke(childMotionModifier);
|
||||
var newChildMotion = new ChildMotion
|
||||
{
|
||||
motion = motion,
|
||||
position = new Vector2(x, y),
|
||||
timeScale = childMotionModifier.TimeScale,
|
||||
mirror = childMotionModifier.Mirror,
|
||||
cycleOffset = childMotionModifier.CycleOffset
|
||||
};
|
||||
|
||||
childrenList.Add(newChildMotion);
|
||||
BlendTree.children = childrenList.ToArray();
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public class AacFlBlendTree1D : AacFlBlendTree
|
||||
{
|
||||
internal AacFlBlendTree1D(BlendTree blendTree) : base(blendTree)
|
||||
{
|
||||
}
|
||||
|
||||
/// Add a BlendTree in the specified threshold. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTree1D WithAnimation(AacFlBlendTree blendTree, float threshold)
|
||||
{
|
||||
return WithAnimationInternal(blendTree.BlendTree, threshold, null);
|
||||
}
|
||||
|
||||
/// Add a Clip in the specified threshold. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTree1D WithAnimation(AacFlClip clip, float threshold)
|
||||
{
|
||||
return WithAnimationInternal(clip.Clip, threshold, null);
|
||||
}
|
||||
|
||||
/// Add a raw motion in the specified threshold. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTree1D WithAnimation(Motion motion, float threshold)
|
||||
{
|
||||
return WithAnimationInternal(motion, threshold, null);
|
||||
}
|
||||
|
||||
/// Add a BlendTree in the specified threshold, and further define that motion. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTree1D WithAnimation(AacFlBlendTree blendTree, float threshold, Action<AacFlBlendTreeChildMotion> furtherDefiningChild)
|
||||
{
|
||||
// Disallow null here: as nulls are allowed in the internal function,
|
||||
// we want to stop early if this specific non-null overload is invoked with a null param.
|
||||
if (furtherDefiningChild == null) throw new NullReferenceException();
|
||||
|
||||
return WithAnimationInternal(blendTree.BlendTree, threshold, furtherDefiningChild);
|
||||
}
|
||||
|
||||
/// Add a Clip in the specified threshold, and further define that motion. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTree1D WithAnimation(AacFlClip clip, float threshold, Action<AacFlBlendTreeChildMotion> furtherDefiningChild)
|
||||
{
|
||||
// Disallow null here: as nulls are allowed in the internal function,
|
||||
// we want to stop early if this specific non-null overload is invoked with a null param.
|
||||
if (furtherDefiningChild == null) throw new NullReferenceException();
|
||||
|
||||
return WithAnimationInternal(clip.Clip, threshold, furtherDefiningChild);
|
||||
}
|
||||
|
||||
/// Add a raw motion in the specified threshold, and further define that motion. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTree1D WithAnimation(Motion motion, float threshold, Action<AacFlBlendTreeChildMotion> furtherDefiningChild)
|
||||
{
|
||||
// Disallow null here: as nulls are allowed in the internal function,
|
||||
// we want to stop early if this specific non-null overload is invoked with a null param.
|
||||
if (furtherDefiningChild == null) throw new NullReferenceException();
|
||||
|
||||
return WithAnimationInternal(motion, threshold, furtherDefiningChild);
|
||||
}
|
||||
|
||||
private AacFlBlendTree1D WithAnimationInternal(Motion motion, float threshold, Action<AacFlBlendTreeChildMotion> furtherDefiningChildNullable)
|
||||
{
|
||||
var children = BlendTree.children ?? new ChildMotion[0];
|
||||
var childrenList = children.ToList();
|
||||
|
||||
var childMotionModifier = new AacFlBlendTreeChildMotion();
|
||||
furtherDefiningChildNullable?.Invoke(childMotionModifier);
|
||||
childrenList.Add(new ChildMotion
|
||||
{
|
||||
motion = motion,
|
||||
threshold = threshold,
|
||||
timeScale = childMotionModifier.TimeScale,
|
||||
mirror = childMotionModifier.Mirror,
|
||||
cycleOffset = childMotionModifier.CycleOffset
|
||||
});
|
||||
BlendTree.children = childrenList
|
||||
// 1D blend trees are capricious; if the thresholds are not in the correct order, the blend tree will:
|
||||
// - misbehave at runtime as it might only blend in the 0th item no matter what
|
||||
// - it will display in the animator UI in a confusing manner
|
||||
// Sort the 1D blend tree to avoid this.
|
||||
.OrderBy(childMotion => childMotion.threshold)
|
||||
.ToArray();
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public class AacFlBlendTreeDirect : AacFlBlendTree
|
||||
{
|
||||
internal AacFlBlendTreeDirect(BlendTree blendTree) : base(blendTree)
|
||||
{
|
||||
}
|
||||
|
||||
/// Add a BlendTree driven by the specified parameter. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTreeDirect WithAnimation(AacFlBlendTree blendTree, AacFlFloatParameter parameter)
|
||||
{
|
||||
return WithAnimationInternal(blendTree.BlendTree, parameter, null);
|
||||
}
|
||||
|
||||
/// Add a Clip driven by the specified parameter. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTreeDirect WithAnimation(AacFlClip clip, AacFlFloatParameter parameter)
|
||||
{
|
||||
return WithAnimationInternal(clip.Clip, parameter, null);
|
||||
}
|
||||
|
||||
/// Add a raw motion driven by the specified parameter. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTreeDirect WithAnimation(Motion motion, AacFlFloatParameter parameter)
|
||||
{
|
||||
return WithAnimationInternal(motion, parameter, null);
|
||||
}
|
||||
|
||||
/// Add a BlendTree driven by the specified parameter, and further define that motion. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTreeDirect WithAnimation(AacFlBlendTree blendTree, AacFlFloatParameter parameter, Action<AacFlBlendTreeChildMotion> furtherDefiningChild)
|
||||
{
|
||||
// Disallow null here: as nulls are allowed in the internal function,
|
||||
// we want to stop early if this specific non-null overload is invoked with a null param.
|
||||
if (furtherDefiningChild == null) throw new NullReferenceException();
|
||||
|
||||
return WithAnimationInternal(blendTree.BlendTree, parameter, furtherDefiningChild);
|
||||
}
|
||||
|
||||
/// Add a Clip driven by the specified parameter, and further define that motion. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTreeDirect WithAnimation(AacFlClip clip, AacFlFloatParameter parameter, Action<AacFlBlendTreeChildMotion> furtherDefiningChild)
|
||||
{
|
||||
// Disallow null here: as nulls are allowed in the internal function,
|
||||
// we want to stop early if this specific non-null overload is invoked with a null param.
|
||||
if (furtherDefiningChild == null) throw new NullReferenceException();
|
||||
|
||||
return WithAnimationInternal(clip.Clip, parameter, furtherDefiningChild);
|
||||
}
|
||||
|
||||
/// Add a raw motion driven by the specified parameter, and further define that motion. By default, the timeScale is 1, cycle offset is 0, mirror is false.
|
||||
public AacFlBlendTreeDirect WithAnimation(Motion motion, AacFlFloatParameter parameter, Action<AacFlBlendTreeChildMotion> furtherDefiningChild)
|
||||
{
|
||||
// Disallow null here: as nulls are allowed in the internal function,
|
||||
// we want to stop early if this specific non-null overload is invoked with a null param.
|
||||
if (furtherDefiningChild == null) throw new NullReferenceException();
|
||||
|
||||
return WithAnimationInternal(motion, parameter, furtherDefiningChild);
|
||||
}
|
||||
|
||||
private AacFlBlendTreeDirect WithAnimationInternal(Motion motion, AacFlFloatParameter parameter, Action<AacFlBlendTreeChildMotion> furtherDefiningChildNullable)
|
||||
{
|
||||
var children = BlendTree.children ?? new ChildMotion[0];
|
||||
var childrenList = children.ToList();
|
||||
|
||||
var childMotionModifier = new AacFlBlendTreeChildMotion();
|
||||
furtherDefiningChildNullable?.Invoke(childMotionModifier);
|
||||
childrenList.Add(new ChildMotion
|
||||
{
|
||||
motion = motion,
|
||||
directBlendParameter = parameter.Name,
|
||||
timeScale = childMotionModifier.TimeScale,
|
||||
mirror = childMotionModifier.Mirror,
|
||||
cycleOffset = childMotionModifier.CycleOffset
|
||||
});
|
||||
BlendTree.children = childrenList.ToArray();
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public class AacFlBlendTreeChildMotion
|
||||
{
|
||||
internal float TimeScale { get; set; } = 1f;
|
||||
internal bool Mirror { get; set; }
|
||||
internal float CycleOffset { get; set; }
|
||||
|
||||
internal AacFlBlendTreeChildMotion()
|
||||
{
|
||||
}
|
||||
|
||||
/// Set the time scale. The time scale value is 1 by default.
|
||||
public AacFlBlendTreeChildMotion WithTimeScaleSetTo(float timeScale)
|
||||
{
|
||||
TimeScale = timeScale;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Set the mirror option. The mirror option value is false by default.
|
||||
public AacFlBlendTreeChildMotion WithMirrorSetTo(bool mirror)
|
||||
{
|
||||
Mirror = mirror;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Set the cycle offset. The cycle offset value is 0 by default.
|
||||
public AacFlBlendTreeChildMotion WithCycleOffsetSetTo(float cycleOffset)
|
||||
{
|
||||
CycleOffset = cycleOffset;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c583b42a1aa04546bd206b26387f5bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,307 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.Animations;
|
||||
using static AnimatorAsCode.V1.AacFlConditionSimple;
|
||||
using static UnityEditor.Animations.AnimatorConditionMode;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace AnimatorAsCode.V1
|
||||
{
|
||||
class AacFlConditionSimple : IAacFlCondition
|
||||
{
|
||||
private readonly Action<AacFlCondition> _action;
|
||||
|
||||
private AacFlConditionSimple(Action<AacFlCondition> action)
|
||||
{
|
||||
_action = action;
|
||||
}
|
||||
|
||||
public static AacFlConditionSimple Just(Action<AacFlCondition> action)
|
||||
{
|
||||
return new AacFlConditionSimple(action);
|
||||
}
|
||||
|
||||
public static AacFlConditionSimple ForEach(string[] subjects, Action<string, AacFlCondition> action)
|
||||
{
|
||||
return new AacFlConditionSimple(condition =>
|
||||
{
|
||||
foreach (var subject in subjects)
|
||||
{
|
||||
action.Invoke(subject, condition);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void ApplyTo(AacFlCondition appender)
|
||||
{
|
||||
_action.Invoke(appender);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class AacFlParameter
|
||||
{
|
||||
/// Expose the name of this parameter.
|
||||
public string Name { get; }
|
||||
|
||||
protected AacFlParameter(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class AacFlParameter<TParam> : AacFlParameter
|
||||
{
|
||||
protected AacFlParameter(string name) : base(name)
|
||||
{
|
||||
}
|
||||
|
||||
/// This function is used for internal purposes:<br/>
|
||||
/// Provide a float representation of the parameter.<br/>
|
||||
/// This is used mainly to derive parameter driver values on the VRChat platform.
|
||||
public abstract float ValueToFloat(TParam value);
|
||||
}
|
||||
|
||||
public abstract class AacFlNumericParameter<TParam> : AacFlParameter<TParam>
|
||||
{
|
||||
protected AacFlNumericParameter(string name) : base(name)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class AacFlFloatParameter : AacFlNumericParameter<float>
|
||||
{
|
||||
internal static AacFlFloatParameter Internally(string name) => new AacFlFloatParameter(name);
|
||||
protected AacFlFloatParameter(string name) : base(name) { }
|
||||
|
||||
/// Float is greater than other.<br/>
|
||||
/// When used on some platforms, you need to be careful as the remote value may not be the same as the local value.
|
||||
public IAacFlCondition IsGreaterThan(float other) => Just(condition => condition.Add(Name, Greater, other));
|
||||
|
||||
/// Float is less than other.<br/>
|
||||
/// When used on some platforms, you need to be careful as the remote value may not be the same as the local value.
|
||||
public IAacFlCondition IsLessThan(float other) => Just(condition => condition.Add(Name, Less, other));
|
||||
|
||||
/// This function is used for internal purposes:<br/>
|
||||
/// Returns the same value as the parameter.
|
||||
public override float ValueToFloat(float value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public class AacFlIntParameter : AacFlNumericParameter<int>
|
||||
{
|
||||
internal static AacFlIntParameter Internally(string name) => new AacFlIntParameter(name);
|
||||
protected AacFlIntParameter(string name) : base(name) { }
|
||||
|
||||
/// Int is strictly greater than `other`
|
||||
public IAacFlCondition IsGreaterThan(int other) => Just(condition => condition.Add(Name, Greater, other));
|
||||
|
||||
/// Int is strictly less than `other`
|
||||
public IAacFlCondition IsLessThan(int other) => Just(condition => condition.Add(Name, Less, other));
|
||||
|
||||
/// Int is equal to `other`
|
||||
public IAacFlCondition IsEqualTo(int other) => Just(condition => condition.Add(Name, AnimatorConditionMode.Equals, other));
|
||||
|
||||
/// Int is not equal to `other`
|
||||
public IAacFlCondition IsNotEqualTo(int other) => Just(condition => condition.Add(Name, NotEqual, other));
|
||||
|
||||
/// This function is used for internal purposes:<br/>
|
||||
/// Returns the int value as a float.
|
||||
public override float ValueToFloat(int value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public class AacFlEnumIntParameter<TEnum> : AacFlIntParameter where TEnum : Enum
|
||||
{
|
||||
internal static AacFlEnumIntParameter<TInEnum> Internally<TInEnum>(string name) where TInEnum : Enum => new AacFlEnumIntParameter<TInEnum>(name);
|
||||
protected AacFlEnumIntParameter(string name) : base(name)
|
||||
{
|
||||
}
|
||||
|
||||
/// Int is equal to `(int)other`
|
||||
public IAacFlCondition IsEqualTo(TEnum other) => IsEqualTo((int)(object)other);
|
||||
|
||||
/// Int is not equal to `(int)other`
|
||||
public IAacFlCondition IsNotEqualTo(TEnum other) => IsNotEqualTo((int)(object)other);
|
||||
}
|
||||
|
||||
public class AacFlBoolParameter : AacFlParameter<bool>
|
||||
{
|
||||
internal static AacFlBoolParameter Internally(string name) => new AacFlBoolParameter(name);
|
||||
protected AacFlBoolParameter(string name) : base(name) { }
|
||||
|
||||
/// Bool is true
|
||||
public IAacFlCondition IsTrue() => Just(condition => condition.Add(Name, If, 0));
|
||||
|
||||
/// Bool is false
|
||||
public IAacFlCondition IsFalse() => Just(condition => condition.Add(Name, IfNot, 0));
|
||||
|
||||
/// Bool is equal to `other`
|
||||
public IAacFlCondition IsEqualTo(bool other) => Just(condition => condition.Add(Name, other ? If : IfNot, 0));
|
||||
|
||||
/// Bool is not equal to `other`
|
||||
public IAacFlCondition IsNotEqualTo(bool other) => Just(condition => condition.Add(Name, other ? IfNot : If, 0));
|
||||
|
||||
/// This function is used for internal purposes:<br/>
|
||||
/// Returns 1 when the value is true, 0 otherwise.
|
||||
public override float ValueToFloat(bool value)
|
||||
{
|
||||
return value ? 1f : 0f;
|
||||
}
|
||||
}
|
||||
|
||||
public class AacFlFloatParameterGroup
|
||||
{
|
||||
internal static AacFlFloatParameterGroup Internally(params string[] names) => new AacFlFloatParameterGroup(names);
|
||||
private readonly string[] _names;
|
||||
private AacFlFloatParameterGroup(params string[] names) { _names = names; }
|
||||
public List<AacFlFloatParameter> ToList() => _names.Select(AacFlFloatParameter.Internally).ToList();
|
||||
|
||||
/// All of the Floats are greater than `other`.<br/>
|
||||
/// When used on some platforms, you need to be careful as the remote value may not be the same as the local value.
|
||||
public IAacFlCondition AreGreaterThan(float other) => ForEach(_names, (name, condition) => condition.Add(name, Greater, other));
|
||||
/// All of the Floats are less than `other`.<br/>
|
||||
/// When used on some platforms, you need to be careful as the remote value may not be the same as the local value.
|
||||
public IAacFlCondition AreLessThan(float other) => ForEach(_names, (name, condition) => condition.Add(name, Less, other));
|
||||
}
|
||||
|
||||
public class AacFlIntParameterGroup
|
||||
{
|
||||
internal static AacFlIntParameterGroup Internally(params string[] names) => new AacFlIntParameterGroup(names);
|
||||
private readonly string[] _names;
|
||||
private AacFlIntParameterGroup(params string[] names) { _names = names; }
|
||||
public List<AacFlIntParameter> ToList() => _names.Select(AacFlIntParameter.Internally).ToList();
|
||||
|
||||
/// All of the Ints are strictly greater than `other`
|
||||
public IAacFlCondition AreGreaterThan(float other) => ForEach(_names, (name, condition) => condition.Add(name, Greater, other));
|
||||
/// All of the Ints are strictly less than `other`
|
||||
public IAacFlCondition AreLessThan(float other) => ForEach(_names, (name, condition) => condition.Add(name, Less, other));
|
||||
/// All of the Ints are equal to `other`
|
||||
public IAacFlCondition AreEqualTo(float other) => ForEach(_names, (name, condition) => condition.Add(name, AnimatorConditionMode.Equals, other));
|
||||
/// All of the Ints are not equal to `other`
|
||||
public IAacFlCondition AreNotEqualTo(float other) => ForEach(_names, (name, condition) => condition.Add(name, NotEqual, other));
|
||||
}
|
||||
|
||||
public class AacFlBoolParameterGroup
|
||||
{
|
||||
internal static AacFlBoolParameterGroup Internally(params string[] names) => new AacFlBoolParameterGroup(names);
|
||||
private readonly string[] _names;
|
||||
private AacFlBoolParameterGroup(params string[] names) { _names = names; }
|
||||
public List<AacFlBoolParameter> ToList() => _names.Select(AacFlBoolParameter.Internally).ToList();
|
||||
|
||||
/// All of the Bools are true
|
||||
public IAacFlCondition AreTrue() => ForEach(_names, (name, condition) => condition.Add(name, If, 0));
|
||||
|
||||
/// All of the Bools are false
|
||||
public IAacFlCondition AreFalse() => ForEach(_names, (name, condition) => condition.Add(name, IfNot, 0));
|
||||
|
||||
/// All of the Bools are equal to `other`
|
||||
public IAacFlCondition AreEqualTo(bool other) => ForEach(_names, (name, condition) => condition.Add(name, other ? If : IfNot, 0));
|
||||
|
||||
/// All the Bools except `exceptThisMustBeTrue` are false, and the Bool of `exceptThisMustBeTrue` must be true.
|
||||
public IAacFlCondition AreFalseExcept(AacFlBoolParameter exceptThisMustBeTrue)
|
||||
{
|
||||
var group = new AacFlBoolParameterGroup(exceptThisMustBeTrue.Name);
|
||||
return AreFalseExcept(group);
|
||||
}
|
||||
|
||||
/// All the Bools except those in `exceptTheseMustBeTrue` are false, and all of the Bools in `exceptTheseMustBeTrue` must be true.
|
||||
public IAacFlCondition AreFalseExcept(params AacFlBoolParameter[] exceptTheseMustBeTrue)
|
||||
{
|
||||
var group = new AacFlBoolParameterGroup(exceptTheseMustBeTrue.Select(parameter => parameter.Name).ToArray());
|
||||
return AreFalseExcept(group);
|
||||
}
|
||||
|
||||
/// All the Bools except those in `exceptTheseMustBeTrue` are false, and all of the Bools in `exceptTheseMustBeTrue` must be true.
|
||||
public IAacFlCondition AreFalseExcept(AacFlBoolParameterGroup exceptTheseMustBeTrue) => Just(condition =>
|
||||
{
|
||||
foreach (var name in _names.Where(name => !exceptTheseMustBeTrue._names.Contains(name)))
|
||||
{
|
||||
condition.Add(name, IfNot, 0);
|
||||
}
|
||||
foreach (var name in exceptTheseMustBeTrue._names)
|
||||
{
|
||||
condition.Add(name, If, 0);
|
||||
}
|
||||
});
|
||||
|
||||
/// All the Bools except `exceptThisMustBeTrue` are true, and the Bool of `exceptThisMustBeTrue` must be false.
|
||||
public IAacFlCondition AreTrueExcept(AacFlBoolParameter exceptThisMustBeFalse)
|
||||
{
|
||||
var group = new AacFlBoolParameterGroup(exceptThisMustBeFalse.Name);
|
||||
return AreTrueExcept(group);
|
||||
}
|
||||
|
||||
/// All the Bools except those in `exceptTheseMustBeTrue` are true, and all of the Bools in `exceptTheseMustBeTrue` must be false.
|
||||
public IAacFlCondition AreTrueExcept(params AacFlBoolParameter[] exceptTheseMustBeFalse)
|
||||
{
|
||||
var group = new AacFlBoolParameterGroup(exceptTheseMustBeFalse.Select(parameter => parameter.Name).ToArray());
|
||||
return AreTrueExcept(group);
|
||||
}
|
||||
|
||||
/// All the Bools except those in `exceptTheseMustBeTrue` are true, and all of the Bools in `exceptTheseMustBeTrue` must be false.
|
||||
public IAacFlCondition AreTrueExcept(AacFlBoolParameterGroup exceptTheseMustBeFalse) => Just(condition =>
|
||||
{
|
||||
foreach (var name in _names.Where(name => !exceptTheseMustBeFalse._names.Contains(name)))
|
||||
{
|
||||
condition.Add(name, If, 0);
|
||||
}
|
||||
foreach (var name in exceptTheseMustBeFalse._names)
|
||||
{
|
||||
condition.Add(name, IfNot, 0);
|
||||
}
|
||||
});
|
||||
|
||||
/// Generates multiple transitions, verifying whether any Bool is true. This can only be used inside `.When(...)`
|
||||
public IAacFlOrCondition IsAnyTrue()
|
||||
{
|
||||
return IsAnyEqualTo(true);
|
||||
}
|
||||
|
||||
/// Generates multiple transitions, verifying whether any Bool is false. This can only be used inside `.When(...)`
|
||||
public IAacFlOrCondition IsAnyFalse()
|
||||
{
|
||||
return IsAnyEqualTo(false);
|
||||
}
|
||||
|
||||
private IAacFlOrCondition IsAnyEqualTo(bool value)
|
||||
{
|
||||
return new AacFlBoolParameterIsAnyOrCondition(_names, value);
|
||||
}
|
||||
}
|
||||
|
||||
internal class AacFlBoolParameterIsAnyOrCondition : IAacFlOrCondition
|
||||
{
|
||||
private readonly string[] _names;
|
||||
private readonly bool _value;
|
||||
|
||||
internal AacFlBoolParameterIsAnyOrCondition(string[] names, bool value)
|
||||
{
|
||||
_names = names;
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public List<AacFlTransitionContinuation> ApplyTo(AacFlNewTransitionContinuation firstContinuation)
|
||||
{
|
||||
var pendingContinuations = new List<AacFlTransitionContinuation>();
|
||||
|
||||
var newContinuation = firstContinuation;
|
||||
for (var index = 0; index < _names.Length; index++)
|
||||
{
|
||||
var name = _names[index];
|
||||
var pendingContinuation = newContinuation.When(AacFlBoolParameter.Internally(name).IsEqualTo(_value));
|
||||
pendingContinuations.Add(pendingContinuation);
|
||||
if (index < _names.Length - 1)
|
||||
{
|
||||
newContinuation = pendingContinuation.Or();
|
||||
}
|
||||
}
|
||||
|
||||
return pendingContinuations;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f088cbe74372a2547af50e6ed521f409
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
using Object = UnityEngine.Object;
|
||||
using UnityEngine;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace AnimatorAsCode.V1
|
||||
{
|
||||
public class AacFlModification
|
||||
{
|
||||
private readonly AacConfiguration _configuration;
|
||||
private readonly AacFlBase _base;
|
||||
|
||||
private readonly HashSet<Object> _objects = new();
|
||||
|
||||
internal AacFlModification(AacConfiguration configuration, AacFlBase originalBase)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_base = originalBase;
|
||||
}
|
||||
|
||||
/// Immediately removes all layers and all parameters from the given AnimatorController, and returns a AacFlController that will edit the given AnimatorController.<br/>
|
||||
/// This AnimatorController instance is memorized in the current AacFlModification instance memory.<br/>
|
||||
/// Note: The AnimatorController class is editor-only, so they can't be referenced inside scene components or asset objects. If you have a RuntimeAnimatorController instance, you should cast it to AnimatorController.
|
||||
public AacFlController ResetAnimatorController(AnimatorController controllerToReset)
|
||||
{
|
||||
_objects.Add(controllerToReset);
|
||||
|
||||
Internal_ClearAnimatorController(controllerToReset);
|
||||
|
||||
return new AacFlController(_configuration, controllerToReset, _base);
|
||||
}
|
||||
|
||||
/// Immediately removes all curves on the clip, and returns a AacFlClip that will edit the given AnimationClip.<br/>
|
||||
/// This does not reset any other attribute of the clip (e.g., is looping, etc.).<br/>
|
||||
/// This AnimationClip instance is memorized in the current AacFlModification instance memory.
|
||||
public AacFlClip ResetClip(AnimationClip clipToReset)
|
||||
{
|
||||
_objects.Add(clipToReset);
|
||||
|
||||
clipToReset.ClearCurves();
|
||||
|
||||
return new AacFlClip(_configuration, clipToReset);
|
||||
}
|
||||
|
||||
/// Immediately clears the list of children in the given BlendTree, sets the parameters to empty strings, and returns a AacFlNonInitializedBlendTree that will edit the given BlendTree.<br/>
|
||||
/// This does not reset any other attribute of the blend tree (e.g., automatic thresholds, etc.).<br/>
|
||||
/// This BlendTree instance is memorized in the current AacFlModification instance memory.<br/>
|
||||
/// Note: The BlendTree class is editor-only, so they can't be referenced inside scene components or asset objects. If you have a Motion instance that is a BlendTree instance, you should cast it to BlendTree.
|
||||
public AacFlNonInitializedBlendTree ResetBlendTree(BlendTree blendTreeToReset)
|
||||
{
|
||||
_objects.Add(blendTreeToReset);
|
||||
|
||||
blendTreeToReset.children = Array.Empty<ChildMotion>();
|
||||
blendTreeToReset.blendParameter = "";
|
||||
blendTreeToReset.blendParameterY = "";
|
||||
|
||||
return new AacFlNonInitializedBlendTree(blendTreeToReset);
|
||||
}
|
||||
|
||||
/// Immediately removes all layers and all parameters from the given AnimatorController.<br/>
|
||||
/// This AnimatorController instance is memorized in the current AacFlModification instance memory.<br/>
|
||||
/// Note: The AnimatorController class is editor-only, so they can't be referenced inside scene components or asset objects. If you have a RuntimeAnimatorController instance, you should cast it to AnimatorController.
|
||||
public AacFlModification ClearAnimatorController(AnimatorController controllerToReset)
|
||||
{
|
||||
_objects.Add(controllerToReset);
|
||||
|
||||
Internal_ClearAnimatorController(controllerToReset);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Returns a AacFlController that will edit the given AnimatorController. This does not reset the AnimatorController.<br/>
|
||||
/// This AnimatorController instance is memorized in the current AacFlModification instance memory.<br/>
|
||||
/// Note: The AnimatorController class is editor-only, so they can't be referenced inside scene components or asset objects. If you have a RuntimeAnimatorController instance, you should cast it to AnimatorController.
|
||||
public AacFlController EditAnimatorController(AnimatorController controllerToReset)
|
||||
{
|
||||
_objects.Add(controllerToReset);
|
||||
|
||||
return new AacFlController(_configuration, controllerToReset, _base);
|
||||
}
|
||||
|
||||
/// Calls `EditorUtility.SetDirty(...)` on every single AnimatorController, AnimationClip, and BlendTree asset instances previously memorized by this AacFlModification instance.
|
||||
public void SetDirtyAll()
|
||||
{
|
||||
foreach (var obj in _objects)
|
||||
{
|
||||
EditorUtility.SetDirty(obj);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Internal_ClearAnimatorController(AnimatorController controllerToReset)
|
||||
{
|
||||
while (controllerToReset.layers.Length > 0)
|
||||
{
|
||||
controllerToReset.RemoveLayer(0);
|
||||
}
|
||||
|
||||
var parameters = controllerToReset.parameters;
|
||||
for (var i = parameters.Length - 1; i >= 0; i--)
|
||||
{
|
||||
controllerToReset.RemoveParameter(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27af4c404c051cf48bc07b175b4afda7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 169d19985c9243245ac27de10a43ca6f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,162 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace AnimatorAsCode.V1
|
||||
{
|
||||
internal static class AacInternals
|
||||
{
|
||||
internal const string AutoGeneratedPrefix = "zAutogenerated/";
|
||||
// The legacy prefix is preserved, because we need to still be able to clean up
|
||||
// assets generated using that legacy prefix from previous versions.
|
||||
internal const string AutoGeneratedLegacyPrefix = "zAutogenerated__";
|
||||
|
||||
internal static AnimatorController NewAnimatorController(AacConfiguration component, string suffix)
|
||||
{
|
||||
var animatorController = new AnimatorController();
|
||||
animatorController.name = Internal_GenerateAnimationName(component, suffix); // FIXME animation name conflict
|
||||
animatorController.hideFlags = HideFlags.None;
|
||||
component.ContainerProvider.SaveAsPersistenceRequired(animatorController);
|
||||
return animatorController;
|
||||
}
|
||||
|
||||
internal static AnimationClip NewClip(AacConfiguration component, string suffix)
|
||||
{
|
||||
return RegisterClip(component, suffix, new AnimationClip());
|
||||
}
|
||||
|
||||
internal static AnimationClip RegisterClip(AacConfiguration component, string suffix, AnimationClip clip)
|
||||
{
|
||||
clip.name = Internal_GenerateAnimationName(component, suffix);
|
||||
clip.hideFlags = HideFlags.None;
|
||||
component.ContainerProvider.SaveAsRegular(clip);
|
||||
return clip;
|
||||
}
|
||||
|
||||
internal static BlendTree NewBlendTreeAsRaw(AacConfiguration component, string suffix)
|
||||
{
|
||||
var clip = new BlendTree();
|
||||
clip.name = Internal_GenerateAnimationName(component, suffix);
|
||||
clip.hideFlags = HideFlags.None;
|
||||
component.ContainerProvider.SaveAsRegular(clip);
|
||||
return clip;
|
||||
}
|
||||
|
||||
internal static T DuplicateAssetIntoContainer<T>(AacConfiguration component, T assetToDuplicate) where T : Object
|
||||
{
|
||||
var duplicated = (T)Object.Instantiate(assetToDuplicate);
|
||||
duplicated.name = Internal_GenerateAnimationName(component, assetToDuplicate.name);
|
||||
duplicated.hideFlags = HideFlags.None;
|
||||
component.ContainerProvider.SaveAsRegular(duplicated);
|
||||
return duplicated;
|
||||
}
|
||||
|
||||
private static string Internal_GenerateAnimationName(AacConfiguration component, string middle)
|
||||
{
|
||||
return AutoGeneratedPrefix + component.AssetKey + "__" + middle + "_" + Random.Range(0, Int32.MaxValue); // FIXME animation name conflict
|
||||
}
|
||||
|
||||
internal static AvatarMask NewAvatarMask(AacConfiguration component, string fullLayerName)
|
||||
{
|
||||
var avatarMask = new AvatarMask();
|
||||
avatarMask.name = AutoGeneratedPrefix + component.AssetKey + "_" + fullLayerName + "__AvatarMask";
|
||||
avatarMask.hideFlags = HideFlags.None;
|
||||
component.ContainerProvider.SaveAsRegular(avatarMask);
|
||||
return avatarMask;
|
||||
}
|
||||
|
||||
internal static EditorCurveBinding Binding(AacConfiguration component, Type type, Transform transform, string propertyName)
|
||||
{
|
||||
return new EditorCurveBinding
|
||||
{
|
||||
path = ResolveRelativePath(component.AnimatorRoot, transform),
|
||||
type = type,
|
||||
propertyName = propertyName
|
||||
};
|
||||
}
|
||||
|
||||
internal static void SetCurve(AnimationClip clip, EditorCurveBinding binding, AnimationCurve curve)
|
||||
{
|
||||
// https://forum.unity.com/threads/new-animationclip-property-names.367288/#post-2384172
|
||||
clip.SetCurve(binding.path, binding.type, binding.propertyName, curve);
|
||||
}
|
||||
|
||||
internal static AnimationCurve OneFrame(float desiredValue)
|
||||
{
|
||||
return AnimationCurve.Constant(0f, 1 / 60f, desiredValue);
|
||||
}
|
||||
|
||||
internal static AnimationCurve ConstantSeconds(float seconds, float desiredValue)
|
||||
{
|
||||
return AnimationCurve.Constant(0f, seconds, desiredValue);
|
||||
}
|
||||
|
||||
internal static string ResolveRelativePath(Transform avatar, Transform item)
|
||||
{
|
||||
if (avatar == item)
|
||||
{
|
||||
// TODO: Is this correct??
|
||||
return "";
|
||||
}
|
||||
|
||||
if (item.parent != avatar && item.parent != null)
|
||||
{
|
||||
return ResolveRelativePath(avatar, item.parent) + "/" + item.name;
|
||||
}
|
||||
|
||||
return item.name;
|
||||
}
|
||||
|
||||
internal static EditorCurveBinding ToSubBinding(EditorCurveBinding binding, string suffix)
|
||||
{
|
||||
return new EditorCurveBinding {path = binding.path, type = binding.type, propertyName = binding.propertyName + "." + suffix};
|
||||
}
|
||||
|
||||
internal static void NoUndo<T>(T obj, Action action)
|
||||
{
|
||||
try
|
||||
{
|
||||
UndoDisable(obj);
|
||||
action.Invoke();
|
||||
}
|
||||
finally
|
||||
{
|
||||
UndoEnable(obj);
|
||||
}
|
||||
}
|
||||
|
||||
internal static TResult NoUndo<T, TResult>(T obj, Func<TResult> action)
|
||||
{
|
||||
try
|
||||
{
|
||||
UndoDisable(obj);
|
||||
return action.Invoke();
|
||||
}
|
||||
finally
|
||||
{
|
||||
UndoEnable(obj);
|
||||
}
|
||||
}
|
||||
|
||||
private static void UndoDisable<T>(T state)
|
||||
{
|
||||
GetPushUndoProperty<T>().SetValue(state, false);
|
||||
}
|
||||
|
||||
private static void UndoEnable<T>(T state)
|
||||
{
|
||||
GetPushUndoProperty<T>().SetValue(state, true);
|
||||
}
|
||||
|
||||
private static PropertyInfo GetPushUndoProperty<T>()
|
||||
{
|
||||
return typeof(T)
|
||||
.GetProperty("pushUndo", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ced363cd360598343aae688003f5a57e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "AnimatorAsCode.V1",
|
||||
"references": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [
|
||||
"Newtonsoft.Json.dll"
|
||||
],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d689052aa981bf8459346a530f6e6678
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,407 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AnimatorAsCode.V1;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AnimatorAsCrab.V1
|
||||
{
|
||||
/// <summary>
|
||||
/// Turns an <see cref="AacCrabGraph"/> into an AnimatorController by driving Animator As Code V1.
|
||||
/// This uses the modification workflow: the controller is cleared and rebuilt, and no layer of the
|
||||
/// controller is touched beforehand.
|
||||
/// </summary>
|
||||
public static class AacCrabGenerator
|
||||
{
|
||||
/// <summary>Clears the AnimatorController and the assets of the same asset key, then rebuilds them.</summary>
|
||||
public static void Generate(AacCrabGraph graph, AacConfiguration configuration, AnimatorController controller)
|
||||
{
|
||||
if (graph == null) throw new ArgumentNullException(nameof(graph));
|
||||
if (controller == null) throw new ArgumentNullException(nameof(controller));
|
||||
if (graph.Controller == null) throw new InvalidOperationException("The graph has no controller.");
|
||||
|
||||
var aac = AacV1.Create(configuration);
|
||||
var modification = aac.Modification();
|
||||
|
||||
aac.ClearPreviousAssets();
|
||||
var aacController = modification.ResetAnimatorController(controller);
|
||||
|
||||
var layers = new Dictionary<string, AacFlLayer>();
|
||||
foreach (var layer in graph.Controller.Layers)
|
||||
{
|
||||
layers[layer.Name] = aacController.NewLayer(layer.Name);
|
||||
}
|
||||
|
||||
var floatingParameters = CreateParameters(graph, layers, controller);
|
||||
|
||||
var clips = new Dictionary<string, AacFlClip>();
|
||||
foreach (var clip in graph.Clips)
|
||||
{
|
||||
clips[clip.Name] = CreateClip(aac, clip);
|
||||
}
|
||||
|
||||
// Blend trees are created in declaration order, so a child motion can only refer to a tree
|
||||
// that was declared before its parent. This also makes cycles impossible.
|
||||
var blendTrees = new Dictionary<string, AacFlBlendTree>();
|
||||
foreach (var blendTree in graph.BlendTrees)
|
||||
{
|
||||
blendTrees[blendTree.Name] = CreateBlendTree(aac, blendTree, clips, blendTrees, floatingParameters);
|
||||
}
|
||||
|
||||
foreach (var layer in graph.Controller.Layers)
|
||||
{
|
||||
BuildLayer(layers[layer.Name], layer, clips, blendTrees);
|
||||
}
|
||||
|
||||
modification.SetDirtyAll();
|
||||
EditorUtility.SetDirty(controller);
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, AacFlFloatParameter> CreateParameters(AacCrabGraph graph, IReadOnlyDictionary<string, AacFlLayer> layers, AnimatorController controller)
|
||||
{
|
||||
var floatingParameters = new Dictionary<string, AacFlFloatParameter>();
|
||||
if (graph.Parameters.Count == 0)
|
||||
{
|
||||
return floatingParameters;
|
||||
}
|
||||
|
||||
if (layers.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("The controller declares parameters but no layer to hold them.");
|
||||
}
|
||||
|
||||
// Parameters are controller-wide, so any layer can create them.
|
||||
var layer = layers.First().Value;
|
||||
foreach (var parameter in graph.Parameters)
|
||||
{
|
||||
switch (parameter.Type)
|
||||
{
|
||||
case AacCrabParameterType.Float:
|
||||
floatingParameters[parameter.Name] = layer.FloatParameter(parameter.Name);
|
||||
break;
|
||||
case AacCrabParameterType.Int:
|
||||
layer.IntParameter(parameter.Name);
|
||||
break;
|
||||
case AacCrabParameterType.Bool:
|
||||
layer.BoolParameter(parameter.Name);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown parameter type {parameter.Type}.");
|
||||
}
|
||||
}
|
||||
|
||||
ApplyParameterDefaults(controller, graph.Parameters);
|
||||
return floatingParameters;
|
||||
}
|
||||
|
||||
// Animator As Code creates parameters with Unity's defaults; the graph's defaults are applied
|
||||
// afterwards by mutating the controller's own parameter list.
|
||||
private static void ApplyParameterDefaults(AnimatorController controller, IEnumerable<AacCrabParameter> parameters)
|
||||
{
|
||||
var wanted = parameters.ToDictionary(parameter => parameter.Name);
|
||||
var current = controller.parameters;
|
||||
foreach (var parameter in current)
|
||||
{
|
||||
if (!wanted.TryGetValue(parameter.name, out var declared))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (declared.Type)
|
||||
{
|
||||
case AacCrabParameterType.Float:
|
||||
parameter.defaultFloat = declared.DefaultFloat;
|
||||
break;
|
||||
case AacCrabParameterType.Int:
|
||||
parameter.defaultInt = declared.DefaultInt;
|
||||
break;
|
||||
case AacCrabParameterType.Bool:
|
||||
parameter.defaultBool = declared.DefaultBool;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
controller.parameters = current;
|
||||
}
|
||||
|
||||
private static AacFlClip CreateClip(AacFlBase aac, AacCrabClip graph)
|
||||
{
|
||||
var clip = aac.NewClip(graph.Name);
|
||||
if (graph.Looping)
|
||||
{
|
||||
clip.Looping();
|
||||
}
|
||||
else
|
||||
{
|
||||
clip.NonLooping();
|
||||
}
|
||||
|
||||
return clip.Animating(edit =>
|
||||
{
|
||||
foreach (var curve in graph.Curves)
|
||||
{
|
||||
var keys = curve.Keys
|
||||
.Select(key => new Keyframe(key.Time, key.Value, key.InTangent, key.OutTangent))
|
||||
.ToArray();
|
||||
edit.Animates(curve.Path, UnityType(curve.Target), curve.Property)
|
||||
.WithAnimationCurve(new AnimationCurve(keys));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Type UnityType(AacCrabTargetType target)
|
||||
{
|
||||
switch (target)
|
||||
{
|
||||
case AacCrabTargetType.GameObject:
|
||||
return typeof(GameObject);
|
||||
case AacCrabTargetType.SkinnedMeshRenderer:
|
||||
return typeof(SkinnedMeshRenderer);
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown curve target {target}.");
|
||||
}
|
||||
}
|
||||
|
||||
private static AacFlBlendTree CreateBlendTree(
|
||||
AacFlBase aac,
|
||||
AacCrabBlendTree graph,
|
||||
IReadOnlyDictionary<string, AacFlClip> clips,
|
||||
IReadOnlyDictionary<string, AacFlBlendTree> trees,
|
||||
IReadOnlyDictionary<string, AacFlFloatParameter> floatingParameters)
|
||||
{
|
||||
Motion Resolve(AacCrabMotionRef reference) => MotionOf(reference, clips, trees);
|
||||
|
||||
AacFlFloatParameter FloatParameter(string name)
|
||||
{
|
||||
if (name == null || !floatingParameters.TryGetValue(name, out var parameter))
|
||||
{
|
||||
throw new InvalidOperationException($"Blend tree '{graph.Name}' uses '{name}', which is not a Float parameter.");
|
||||
}
|
||||
|
||||
return parameter;
|
||||
}
|
||||
|
||||
var uninitialized = aac.NewBlendTree(graph.Name);
|
||||
switch (graph.BlendType)
|
||||
{
|
||||
case AacCrabBlendType.Simple1D:
|
||||
{
|
||||
var tree = uninitialized.Simple1D(FloatParameter(graph.ParamX));
|
||||
tree.BlendTree.useAutomaticThresholds = graph.UseAutomaticThresholds;
|
||||
foreach (var child in graph.Children)
|
||||
{
|
||||
tree.WithAnimation(Resolve(child.Motion), child.Threshold ?? 0f);
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
case AacCrabBlendType.SimpleDirectional2D:
|
||||
case AacCrabBlendType.FreeformDirectional2D:
|
||||
case AacCrabBlendType.FreeformCartesian2D:
|
||||
{
|
||||
var x = FloatParameter(graph.ParamX);
|
||||
var y = FloatParameter(graph.ParamY);
|
||||
var tree = graph.BlendType == AacCrabBlendType.SimpleDirectional2D
|
||||
? uninitialized.SimpleDirectional2D(x, y)
|
||||
: graph.BlendType == AacCrabBlendType.FreeformDirectional2D
|
||||
? uninitialized.FreeformDirectional2D(x, y)
|
||||
: uninitialized.FreeformCartesian2D(x, y);
|
||||
foreach (var child in graph.Children)
|
||||
{
|
||||
tree.WithAnimation(Resolve(child.Motion), child.Threshold ?? 0f, child.ThresholdY ?? 0f);
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
case AacCrabBlendType.Direct:
|
||||
{
|
||||
var tree = uninitialized.Direct();
|
||||
foreach (var child in graph.Children)
|
||||
{
|
||||
tree.WithAnimation(Resolve(child.Motion), FloatParameter(child.DirectParam));
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown blend type {graph.BlendType}.");
|
||||
}
|
||||
}
|
||||
|
||||
private static Motion MotionOf(
|
||||
AacCrabMotionRef reference,
|
||||
IReadOnlyDictionary<string, AacFlClip> clips,
|
||||
IReadOnlyDictionary<string, AacFlBlendTree> trees)
|
||||
{
|
||||
switch (reference.Type)
|
||||
{
|
||||
case AacCrabMotionType.Clip:
|
||||
if (!clips.TryGetValue(reference.Name, out var clip))
|
||||
{
|
||||
throw new InvalidOperationException($"Clip '{reference.Name}' is not declared.");
|
||||
}
|
||||
|
||||
return clip.Clip;
|
||||
case AacCrabMotionType.BlendTree:
|
||||
// Blend trees are built in declaration order, so a tree can only refer to an earlier one.
|
||||
if (!trees.TryGetValue(reference.Name, out var tree))
|
||||
{
|
||||
throw new InvalidOperationException($"Blend tree '{reference.Name}' is not declared yet; declare it before the motion that uses it.");
|
||||
}
|
||||
|
||||
return tree.BlendTree;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown motion type {reference.Type}.");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class MachineScope
|
||||
{
|
||||
public AacFlStateMachine Machine;
|
||||
public AacCrabStateMachine Graph;
|
||||
}
|
||||
|
||||
private static void BuildLayer(
|
||||
AacFlLayer layer,
|
||||
AacCrabLayer graph,
|
||||
IReadOnlyDictionary<string, AacFlClip> clips,
|
||||
IReadOnlyDictionary<string, AacFlBlendTree> trees)
|
||||
{
|
||||
if (graph.StateMachine == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Layer '{graph.Name}' has no state machine.");
|
||||
}
|
||||
|
||||
Motion Resolve(AacCrabMotionRef reference) => MotionOf(reference, clips, trees);
|
||||
|
||||
// State names are unique within a layer, so a single dictionary resolves every transition,
|
||||
// including the ones that cross state machines.
|
||||
var states = new Dictionary<string, AacFlState>();
|
||||
var scopes = new List<MachineScope>();
|
||||
|
||||
AacFlStateMachine CreateMachine(AacFlStateMachine machine, AacCrabStateMachine machineGraph)
|
||||
{
|
||||
scopes.Add(new MachineScope { Machine = machine, Graph = machineGraph });
|
||||
foreach (var state in machineGraph.States)
|
||||
{
|
||||
var aacState = machine.NewState(state.Name, state.Position.X, state.Position.Y);
|
||||
if (state.Motion != null)
|
||||
{
|
||||
aacState.WithAnimation(Resolve(state.Motion));
|
||||
}
|
||||
|
||||
states[state.Name] = aacState;
|
||||
}
|
||||
|
||||
foreach (var subMachine in machineGraph.SubMachines)
|
||||
{
|
||||
CreateMachine(machine.NewSubStateMachine(subMachine.Name, subMachine.Position.X, subMachine.Position.Y), subMachine);
|
||||
}
|
||||
|
||||
return machine;
|
||||
}
|
||||
|
||||
// Create every state first: transitions may point forward.
|
||||
CreateMachine(layer.StateMachine, graph.StateMachine);
|
||||
|
||||
foreach (var scope in scopes)
|
||||
{
|
||||
foreach (var state in scope.Graph.States)
|
||||
{
|
||||
foreach (var transition in state.Transitions)
|
||||
{
|
||||
ApplyTransition(states[state.Name].TransitionsTo(Destination(states, transition)), transition);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var transition in scope.Graph.AnyStateTransitions)
|
||||
{
|
||||
ApplyTransition(scope.Machine.AnyTransitionsTo(Destination(states, transition)), transition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static AacFlState Destination(IReadOnlyDictionary<string, AacFlState> states, AacCrabTransition transition)
|
||||
{
|
||||
if (!states.TryGetValue(transition.To, out var destination))
|
||||
{
|
||||
throw new InvalidOperationException($"Transition target '{transition.To}' is not a state in this layer.");
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
private static void ApplyTransition(AacFlTransition transition, AacCrabTransition graph)
|
||||
{
|
||||
transition.WithTransitionDurationSeconds(graph.Duration);
|
||||
if (graph.OrderedInterruption)
|
||||
{
|
||||
transition.WithOrderedInterruption();
|
||||
}
|
||||
else
|
||||
{
|
||||
transition.WithNoOrderedInterruption();
|
||||
}
|
||||
|
||||
if (graph.SourceInterruption)
|
||||
{
|
||||
transition.WithSourceInterruption();
|
||||
}
|
||||
|
||||
if (graph.CanTransitionToSelf)
|
||||
{
|
||||
transition.WithTransitionToSelf();
|
||||
}
|
||||
|
||||
if (graph.HasExitTime)
|
||||
{
|
||||
transition.AfterAnimationIsAtLeastAtNormalized(graph.ExitTime);
|
||||
}
|
||||
|
||||
// Conditions are applied last: Animator As Code forbids configuring a transition afterwards.
|
||||
if (graph.Conditions.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var continuation = transition.When(Condition(graph.Conditions[0]));
|
||||
for (var index = 1; index < graph.Conditions.Count; index++)
|
||||
{
|
||||
continuation = continuation.And(Condition(graph.Conditions[index]));
|
||||
}
|
||||
}
|
||||
|
||||
// Animator As Code only exposes typed comparisons for some parameter types, so conditions are
|
||||
// built the same way the library builds them internally.
|
||||
private static IAacFlCondition Condition(AacCrabCondition condition)
|
||||
{
|
||||
var parameter = condition.Parameter;
|
||||
var mode = UnityConditionMode(condition.Mode);
|
||||
var threshold = condition.Threshold;
|
||||
return AacFlConditionSimple.Just(appender => appender.Add(parameter, mode, threshold));
|
||||
}
|
||||
|
||||
private static AnimatorConditionMode UnityConditionMode(AacCrabCondMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case AacCrabCondMode.Greater:
|
||||
return AnimatorConditionMode.Greater;
|
||||
case AacCrabCondMode.Less:
|
||||
return AnimatorConditionMode.Less;
|
||||
case AacCrabCondMode.Equals:
|
||||
return AnimatorConditionMode.Equals;
|
||||
case AacCrabCondMode.NotEqual:
|
||||
return AnimatorConditionMode.NotEqual;
|
||||
case AacCrabCondMode.If:
|
||||
return AnimatorConditionMode.If;
|
||||
case AacCrabCondMode.IfNot:
|
||||
return AnimatorConditionMode.IfNot;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unknown condition mode {mode}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace AnimatorAsCrab.V1
|
||||
{
|
||||
/// <summary>
|
||||
/// The wire format produced by the Rust core. Keep in sync with rust/src/graph.rs.
|
||||
/// Property names are snake_cased by the serializer settings, so C# names must only differ
|
||||
/// from the wire format by casing; enum values are explicit.
|
||||
/// </summary>
|
||||
public static class AacCrabJson
|
||||
{
|
||||
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
|
||||
{
|
||||
// A graph the generator does not understand is a bug, not something to ignore.
|
||||
MissingMemberHandling = MissingMemberHandling.Error,
|
||||
ContractResolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy() },
|
||||
Converters = { new StringEnumConverter() },
|
||||
};
|
||||
|
||||
public static AacCrabGraph Parse(string json)
|
||||
{
|
||||
return JsonConvert.DeserializeObject<AacCrabGraph>(json, Settings);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AacCrabGraph
|
||||
{
|
||||
public string SystemName { get; set; }
|
||||
public string AssetKey { get; set; }
|
||||
public List<AacCrabParameter> Parameters { get; set; }
|
||||
public List<AacCrabClip> Clips { get; set; }
|
||||
public List<AacCrabBlendTree> BlendTrees { get; set; }
|
||||
public AacCrabController Controller { get; set; }
|
||||
}
|
||||
|
||||
public sealed class AacCrabParameter
|
||||
{
|
||||
public AacCrabParameterType Type { get; set; }
|
||||
public string Name { get; set; }
|
||||
public JToken Default { get; set; }
|
||||
|
||||
public float DefaultFloat => Default.Value<float>();
|
||||
public int DefaultInt => Default.Value<int>();
|
||||
public bool DefaultBool => Default.Value<bool>();
|
||||
}
|
||||
|
||||
public enum AacCrabParameterType
|
||||
{
|
||||
[EnumMember(Value = "float")] Float,
|
||||
[EnumMember(Value = "int")] Int,
|
||||
[EnumMember(Value = "bool")] Bool,
|
||||
}
|
||||
|
||||
public sealed class AacCrabClip
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public bool Looping { get; set; }
|
||||
public List<AacCrabCurve> Curves { get; set; }
|
||||
}
|
||||
|
||||
public sealed class AacCrabCurve
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public AacCrabTargetType Target { get; set; }
|
||||
public string Property { get; set; }
|
||||
public List<AacCrabKeyframe> Keys { get; set; }
|
||||
}
|
||||
|
||||
public enum AacCrabTargetType
|
||||
{
|
||||
[EnumMember(Value = "game_object")] GameObject,
|
||||
[EnumMember(Value = "skinned_mesh_renderer")] SkinnedMeshRenderer,
|
||||
}
|
||||
|
||||
public sealed class AacCrabKeyframe
|
||||
{
|
||||
public float Time { get; set; }
|
||||
public float Value { get; set; }
|
||||
public float InTangent { get; set; }
|
||||
public float OutTangent { get; set; }
|
||||
}
|
||||
|
||||
public sealed class AacCrabBlendTree
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public AacCrabBlendType BlendType { get; set; }
|
||||
public string ParamX { get; set; }
|
||||
public string ParamY { get; set; }
|
||||
public List<AacCrabBlendChild> Children { get; set; }
|
||||
public bool UseAutomaticThresholds { get; set; }
|
||||
}
|
||||
|
||||
public enum AacCrabBlendType
|
||||
{
|
||||
[EnumMember(Value = "simple_1d")] Simple1D,
|
||||
[EnumMember(Value = "simple_directional_2d")] SimpleDirectional2D,
|
||||
[EnumMember(Value = "freeform_directional_2d")] FreeformDirectional2D,
|
||||
[EnumMember(Value = "freeform_cartesian_2d")] FreeformCartesian2D,
|
||||
[EnumMember(Value = "direct")] Direct,
|
||||
}
|
||||
|
||||
public sealed class AacCrabBlendChild
|
||||
{
|
||||
public AacCrabMotionRef Motion { get; set; }
|
||||
public float? Threshold { get; set; }
|
||||
public float? ThresholdY { get; set; }
|
||||
public string DirectParam { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>A motion is always a reference: clips and blend trees are declared at the top level.</summary>
|
||||
public sealed class AacCrabMotionRef
|
||||
{
|
||||
public AacCrabMotionType Type { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public enum AacCrabMotionType
|
||||
{
|
||||
[EnumMember(Value = "clip")] Clip,
|
||||
[EnumMember(Value = "blend_tree")] BlendTree,
|
||||
}
|
||||
|
||||
public sealed class AacCrabController
|
||||
{
|
||||
public List<AacCrabLayer> Layers { get; set; }
|
||||
}
|
||||
|
||||
public sealed class AacCrabLayer
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public AacCrabStateMachine StateMachine { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>Name is null for the root state machine of a layer.</summary>
|
||||
public sealed class AacCrabStateMachine
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public AacCrabGridPos Position { get; set; }
|
||||
public List<AacCrabState> States { get; set; }
|
||||
public List<AacCrabStateMachine> SubMachines { get; set; }
|
||||
public List<AacCrabTransition> AnyStateTransitions { get; set; }
|
||||
}
|
||||
|
||||
public sealed class AacCrabState
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public AacCrabGridPos Position { get; set; }
|
||||
public AacCrabMotionRef Motion { get; set; }
|
||||
public List<AacCrabTransition> Transitions { get; set; }
|
||||
}
|
||||
|
||||
public sealed class AacCrabTransition
|
||||
{
|
||||
/// <summary>The name of the destination state, within the same layer.</summary>
|
||||
public string To { get; set; }
|
||||
public List<AacCrabCondition> Conditions { get; set; }
|
||||
public bool HasExitTime { get; set; }
|
||||
public float ExitTime { get; set; }
|
||||
public float Duration { get; set; }
|
||||
public bool OrderedInterruption { get; set; }
|
||||
public bool SourceInterruption { get; set; }
|
||||
public bool CanTransitionToSelf { get; set; }
|
||||
}
|
||||
|
||||
public sealed class AacCrabCondition
|
||||
{
|
||||
public string Parameter { get; set; }
|
||||
public AacCrabCondMode Mode { get; set; }
|
||||
public float Threshold { get; set; }
|
||||
}
|
||||
|
||||
public enum AacCrabCondMode
|
||||
{
|
||||
[EnumMember(Value = "greater")] Greater,
|
||||
[EnumMember(Value = "less")] Less,
|
||||
[EnumMember(Value = "equals")] Equals,
|
||||
[EnumMember(Value = "not_equal")] NotEqual,
|
||||
[EnumMember(Value = "if")] If,
|
||||
[EnumMember(Value = "if_not")] IfNot,
|
||||
}
|
||||
|
||||
public struct AacCrabGridPos
|
||||
{
|
||||
public int X { get; set; }
|
||||
public int Y { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace AnimatorAsCrab.V1
|
||||
{
|
||||
/// <summary>
|
||||
/// P/Invoke bindings for `libaac` (see rust/src/lib.rs). The native library is expected to live
|
||||
/// in the project's Assets/Plugins folder; place libaac.so, libaac.dll or libaac.dylib there.
|
||||
/// </summary>
|
||||
public static class AacCrabNative
|
||||
{
|
||||
private const string Library = "libaac";
|
||||
|
||||
[DllImport(Library, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern IntPtr aac_create();
|
||||
|
||||
[DllImport(Library, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern int aac_eval_rhai(IntPtr context, byte[] script);
|
||||
|
||||
[DllImport(Library, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern IntPtr aac_to_json(IntPtr context);
|
||||
|
||||
[DllImport(Library, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern IntPtr aac_last_error(IntPtr context);
|
||||
|
||||
[DllImport(Library, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void aac_free_string(IntPtr value);
|
||||
|
||||
[DllImport(Library, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void aac_destroy(IntPtr context);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluate a Rhai script and return the graph as JSON, or null with a message in
|
||||
/// <paramref name="error"/>. The context lives only for the duration of this call.
|
||||
/// </summary>
|
||||
public static string Evaluate(string script, out string error)
|
||||
{
|
||||
IntPtr context;
|
||||
try
|
||||
{
|
||||
context = aac_create();
|
||||
}
|
||||
catch (DllNotFoundException exception)
|
||||
{
|
||||
error = $"Could not load {Library}: {exception.Message}. Place the native library in Assets/Plugins.";
|
||||
return null;
|
||||
}
|
||||
|
||||
if (context == IntPtr.Zero)
|
||||
{
|
||||
error = "aac_create returned null.";
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var status = aac_eval_rhai(context, Utf8Z(script));
|
||||
if (status != 0)
|
||||
{
|
||||
error = ReadUtf8(aac_last_error(context)) ?? $"The script failed with status {status}.";
|
||||
return null;
|
||||
}
|
||||
|
||||
var json = aac_to_json(context);
|
||||
if (json == IntPtr.Zero)
|
||||
{
|
||||
error = ReadUtf8(aac_last_error(context)) ?? "The graph could not be serialized.";
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
error = null;
|
||||
return ReadUtf8(json);
|
||||
}
|
||||
finally
|
||||
{
|
||||
aac_free_string(json);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
aac_destroy(context);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] Utf8Z(string value)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(value);
|
||||
var terminated = new byte[bytes.Length + 1];
|
||||
Array.Copy(bytes, terminated, bytes.Length);
|
||||
return terminated;
|
||||
}
|
||||
|
||||
private static string ReadUtf8(IntPtr pointer)
|
||||
{
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Marshal.PtrToStringUTF8 is not available on all Unity runtimes, so the bytes are copied by hand.
|
||||
var length = 0;
|
||||
while (Marshal.ReadByte(pointer, length) != 0)
|
||||
{
|
||||
length++;
|
||||
}
|
||||
|
||||
var bytes = new byte[length];
|
||||
Marshal.Copy(pointer, bytes, 0, length);
|
||||
return Encoding.UTF8.GetString(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AnimatorAsCrab.V1
|
||||
{
|
||||
/// <summary>
|
||||
/// Evaluates a Rhai script with libaac and generates the Animator Controller through Animator As Code.
|
||||
/// The Unity-side configuration is never guessed: every field below is supplied by the user, and the
|
||||
/// system name and asset key are declared by the script itself.
|
||||
/// </summary>
|
||||
public class AacCrabWindow : EditorWindow
|
||||
{
|
||||
[SerializeField] private string _scriptPath = "";
|
||||
[SerializeField] private bool _generateOnScriptChange = true;
|
||||
[SerializeField] private AnimatorController _controller;
|
||||
[SerializeField] private Transform _animatorRoot;
|
||||
[SerializeField] private UnityEngine.Object _assetContainer;
|
||||
[SerializeField] private AacConfiguration.Container _containerMode = AacConfiguration.Container.Everything;
|
||||
[SerializeField] private bool _writeDefaults;
|
||||
[SerializeField] private long _lastWriteUtcTicks;
|
||||
|
||||
private string _error;
|
||||
private string _status;
|
||||
private bool _scriptChanged;
|
||||
|
||||
[MenuItem("Tools/Animator As Crab")]
|
||||
public static void Open()
|
||||
{
|
||||
var window = GetWindow<AacCrabWindow>();
|
||||
window.titleContent = new GUIContent("Animator As Crab");
|
||||
window.Show();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
EditorApplication.update += OnUpdate;
|
||||
_scriptChanged = _lastWriteUtcTicks != 0 && _lastWriteUtcTicks != LastWriteUtcTicks();
|
||||
_lastWriteUtcTicks = LastWriteUtcTicks();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
EditorApplication.update -= OnUpdate;
|
||||
}
|
||||
|
||||
private void OnUpdate()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_scriptPath) || !File.Exists(_scriptPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var ticks = LastWriteUtcTicks();
|
||||
if (ticks == _lastWriteUtcTicks)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lastWriteUtcTicks = ticks;
|
||||
_scriptChanged = true;
|
||||
if (_generateOnScriptChange)
|
||||
{
|
||||
Generate();
|
||||
}
|
||||
else
|
||||
{
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
EditorGUILayout.LabelField("Rhai script", EditorStyles.boldLabel);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
_scriptPath = EditorGUILayout.TextField(_scriptPath);
|
||||
if (GUILayout.Button("...", GUILayout.Width(28)))
|
||||
{
|
||||
var directory = string.IsNullOrEmpty(_scriptPath) ? null : Path.GetDirectoryName(_scriptPath);
|
||||
var picked = EditorUtility.OpenFilePanel("Rhai script", directory ?? string.Empty, "rhai");
|
||||
if (!string.IsNullOrEmpty(picked))
|
||||
{
|
||||
_scriptPath = picked;
|
||||
_lastWriteUtcTicks = LastWriteUtcTicks();
|
||||
_scriptChanged = false;
|
||||
Generate();
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
_generateOnScriptChange = EditorGUILayout.Toggle("Generate when the script changes", _generateOnScriptChange);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Unity side", EditorStyles.boldLabel);
|
||||
_controller = (AnimatorController)EditorGUILayout.ObjectField("Animator Controller", _controller, typeof(AnimatorController), false);
|
||||
_animatorRoot = (Transform)EditorGUILayout.ObjectField("Animator Root", _animatorRoot, typeof(Transform), true);
|
||||
_assetContainer = EditorGUILayout.ObjectField("Asset Container", _assetContainer, typeof(UnityEngine.Object), false);
|
||||
_containerMode = (AacConfiguration.Container)EditorGUILayout.EnumPopup("Container Mode", _containerMode);
|
||||
_writeDefaults = EditorGUILayout.Toggle("Write Defaults", _writeDefaults);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
if (GUILayout.Button("Generate", GUILayout.Height(24)))
|
||||
{
|
||||
Generate();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
if (_error != null)
|
||||
{
|
||||
EditorGUILayout.HelpBox(_error, MessageType.Error);
|
||||
}
|
||||
else if (_scriptChanged)
|
||||
{
|
||||
EditorGUILayout.HelpBox("The script changed since the last generation.", MessageType.Warning);
|
||||
}
|
||||
|
||||
if (_status != null)
|
||||
{
|
||||
EditorGUILayout.HelpBox(_status, MessageType.Info);
|
||||
}
|
||||
}
|
||||
|
||||
private void Generate()
|
||||
{
|
||||
_error = null;
|
||||
_status = null;
|
||||
_scriptChanged = false;
|
||||
try
|
||||
{
|
||||
_status = GenerateOrThrow();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_error = exception.Message;
|
||||
Debug.LogException(exception);
|
||||
}
|
||||
|
||||
Repaint();
|
||||
}
|
||||
|
||||
private string GenerateOrThrow()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_scriptPath))
|
||||
{
|
||||
throw new InvalidOperationException("Select a Rhai script.");
|
||||
}
|
||||
|
||||
if (!File.Exists(_scriptPath))
|
||||
{
|
||||
throw new InvalidOperationException($"'{_scriptPath}' does not exist.");
|
||||
}
|
||||
|
||||
if (_controller == null)
|
||||
{
|
||||
throw new InvalidOperationException("Select the Animator Controller to generate into.");
|
||||
}
|
||||
|
||||
if (_animatorRoot == null)
|
||||
{
|
||||
throw new InvalidOperationException("Select the animator root Transform.");
|
||||
}
|
||||
|
||||
if (_assetContainer == null)
|
||||
{
|
||||
throw new InvalidOperationException("Select the asset container that will hold the generated clips and blend trees.");
|
||||
}
|
||||
|
||||
var json = AacCrabNative.Evaluate(File.ReadAllText(_scriptPath), out var nativeError);
|
||||
if (json == null)
|
||||
{
|
||||
throw new InvalidOperationException(nativeError);
|
||||
}
|
||||
|
||||
var graph = AacCrabJson.Parse(json);
|
||||
var configuration = new AacConfiguration
|
||||
{
|
||||
SystemName = graph.SystemName,
|
||||
AssetKey = graph.AssetKey,
|
||||
AnimatorRoot = _animatorRoot,
|
||||
AssetContainer = _assetContainer,
|
||||
ContainerMode = _containerMode,
|
||||
DefaultsProvider = new AacDefaultsProvider(_writeDefaults),
|
||||
};
|
||||
|
||||
AacCrabGenerator.Generate(graph, configuration, _controller);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
return $"Generated '{graph.SystemName}' with asset key '{graph.AssetKey}': " +
|
||||
$"{graph.Controller.Layers.Count} layer(s), {graph.Clips.Count} clip(s), {graph.BlendTrees.Count} blend tree(s).";
|
||||
}
|
||||
|
||||
private long LastWriteUtcTicks()
|
||||
{
|
||||
return string.IsNullOrEmpty(_scriptPath) || !File.Exists(_scriptPath)
|
||||
? 0
|
||||
: File.GetLastWriteTimeUtc(_scriptPath).Ticks;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Haï~ (@vr_hai github.com/hai-vr)
|
||||
Copyright (c) 2024 galister (github.com/galister)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c04d39cdc2e2392a5b51bfa466760178
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user