Add animation store for pre-made clips

aac.AnimationStore(folder) seeds a clip from an existing .anim asset and
returns a normal ClipBuilder, so pre-made clips work anywhere generated
clips do and can still be edited (looping, keyframes, toggles, blend
shapes). The Unity side clones the asset into the container and applies
the script's changes to the clone, leaving the original untouched.

- ClipData gains optional source; looping becomes Option<bool> so an
  untouched store clip keeps the asset's own looping setting.
- Add rust/examples/animation_store.rhai, a worked store example.
This commit is contained in:
2026-09-18 14:52:02 +01:00
parent 42a1234e7f
commit 2c3b670ab9
10 changed files with 377 additions and 17 deletions
@@ -36,7 +36,7 @@ namespace AnimatorAsCrab.V1
var floatingParameters = CreateParameters(graph, layers, controller);
var clips = new Dictionary<string, AacFlClip>();
var clips = new Dictionary<string, Motion>();
foreach (var clip in graph.Clips)
{
clips[clip.Name] = CreateClip(aac, clip);
@@ -126,19 +126,38 @@ namespace AnimatorAsCrab.V1
controller.parameters = current;
}
private static AacFlClip CreateClip(AacFlBase aac, AacCrabClip graph)
// A generated clip is created empty; a store clip starts from a clone of a pre-made asset.
// Either way the script's curves and looping override are applied to the clip owned by this
// generation, so a pre-made asset is never modified.
private static Motion CreateClip(AacFlBase aac, AacCrabClip graph)
{
var clip = aac.NewClip(graph.Name);
if (graph.Looping)
AacFlClip clip;
if (!string.IsNullOrEmpty(graph.Source))
{
var original = AssetDatabase.LoadAssetAtPath<AnimationClip>(graph.Source);
if (original == null)
{
throw new InvalidOperationException(
$"Animation store clip '{graph.Source}' does not exist. Check the store folder and the clip name.");
}
clip = new AacFlClip(AacAccessorForExtensions.AccessConfiguration(aac), aac.DuplicateAsset(original));
}
else
{
clip = aac.NewClip(graph.Name);
}
if (graph.Looping == true)
{
clip.Looping();
}
else
else if (graph.Looping == false)
{
clip.NonLooping();
}
return clip.Animating(edit =>
clip.Animating(edit =>
{
foreach (var curve in graph.Curves)
{
@@ -149,6 +168,7 @@ namespace AnimatorAsCrab.V1
.WithAnimationCurve(new AnimationCurve(keys));
}
});
return clip.Clip;
}
private static Type UnityType(AacCrabTargetType target)
@@ -167,7 +187,7 @@ namespace AnimatorAsCrab.V1
private static AacFlBlendTree CreateBlendTree(
AacFlBase aac,
AacCrabBlendTree graph,
IReadOnlyDictionary<string, AacFlClip> clips,
IReadOnlyDictionary<string, Motion> clips,
IReadOnlyDictionary<string, AacFlBlendTree> trees,
IReadOnlyDictionary<string, AacFlFloatParameter> floatingParameters)
{
@@ -232,7 +252,7 @@ namespace AnimatorAsCrab.V1
private static Motion MotionOf(
AacCrabMotionRef reference,
IReadOnlyDictionary<string, AacFlClip> clips,
IReadOnlyDictionary<string, Motion> clips,
IReadOnlyDictionary<string, AacFlBlendTree> trees)
{
switch (reference.Type)
@@ -243,7 +263,7 @@ namespace AnimatorAsCrab.V1
throw new InvalidOperationException($"Clip '{reference.Name}' is not declared.");
}
return clip.Clip;
return 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))
@@ -266,7 +286,7 @@ namespace AnimatorAsCrab.V1
private static void BuildLayer(
AacFlLayer layer,
AacCrabLayer graph,
IReadOnlyDictionary<string, AacFlClip> clips,
IReadOnlyDictionary<string, Motion> clips,
IReadOnlyDictionary<string, AacFlBlendTree> trees)
{
if (graph.StateMachine == null)
@@ -59,7 +59,10 @@ namespace AnimatorAsCrab.V1
public sealed class AacCrabClip
{
public string Name { get; set; }
public bool Looping { get; set; }
/// <summary>Looping override; null for a store clip that keeps the source asset's setting.</summary>
public bool? Looping { get; set; }
/// <summary>Asset path of a pre-made clip seeded into a store clip; null for generated clips.</summary>
public string Source { get; set; }
public List<AacCrabCurve> Curves { get; set; }
}