157 lines
7.1 KiB
C#
157 lines
7.1 KiB
C#
|
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2023 Kybernetik //
|
||
|
|
||
|
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
|
||
|
|
||
|
using System;
|
||
|
using Unity.Collections;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.Animations;
|
||
|
|
||
|
namespace Animancer.Examples.Jobs
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// A wrapper that manages an Animation Job (the <see cref="Job"/> struct nested inside this class) which rotates a
|
||
|
/// set of bones to allow the character to dynamically lean over independantly of their animations.
|
||
|
/// </summary>
|
||
|
///
|
||
|
/// <remarks>
|
||
|
/// The axis around which the bones are rotated can be set to achieve several different effects:
|
||
|
/// <list type="number">
|
||
|
/// <item>The right axis allows bending forwards and backwards.</item>
|
||
|
/// <item>The up axis allows turning to either side.</item>
|
||
|
/// <item>The forward axis allows leaning to either side.</item>
|
||
|
/// </list>
|
||
|
/// <see cref="https://github.com/KybernetikGames/animancer/issues/48#issuecomment-632336377">
|
||
|
/// This script is based on an implementation by ted-hou on GitHub.</see>
|
||
|
/// </remarks>
|
||
|
///
|
||
|
/// <example><see href="https://kybernetik.com.au/animancer/docs/examples/jobs/lean">Lean</see></example>
|
||
|
///
|
||
|
/// https://kybernetik.com.au/animancer/api/Animancer.Examples.Jobs/SimpleLean
|
||
|
///
|
||
|
public sealed class SimpleLean : AnimancerJob<SimpleLean.Job>, IDisposable
|
||
|
{
|
||
|
/************************************************************************************************************************/
|
||
|
#region Initialization
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
public SimpleLean(AnimancerPlayable animancer, Vector3 axis, NativeArray<TransformStreamHandle> leanBones)
|
||
|
{
|
||
|
var animator = animancer.Component.Animator;
|
||
|
|
||
|
_Job = new Job
|
||
|
{
|
||
|
root = animator.BindStreamTransform(animator.transform),
|
||
|
bones = leanBones,
|
||
|
axis = axis,
|
||
|
angle = AnimancerUtilities.CreateNativeReference<float>(),
|
||
|
};
|
||
|
|
||
|
CreatePlayable(animancer);
|
||
|
|
||
|
animancer.Disposables.Add(this);
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
#region Control
|
||
|
/************************************************************************************************************************/
|
||
|
// The Axis probably won't change often so the setter can just get the job data and change it.
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
public Vector3 Axis
|
||
|
{
|
||
|
get => _Job.axis;
|
||
|
set
|
||
|
{
|
||
|
if (_Job.axis == value)
|
||
|
return;
|
||
|
|
||
|
_Job.axis = value;
|
||
|
_Playable.SetJobData(_Job);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
// But since the Angle could change all the time, we can exploit the fact that arrays are actualy references to avoid
|
||
|
// copying the entire struct out of the job playable then back in every time.
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
public float Angle
|
||
|
{
|
||
|
get => _Job.angle[0];
|
||
|
set => _Job.angle[0] = value;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
#region Clean Up
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
void IDisposable.Dispose() => Dispose();
|
||
|
|
||
|
/// <summary>Cleans up the <see cref="NativeArray{T}"/>s.</summary>
|
||
|
/// <remarks>Called by <see cref="AnimancerPlayable.OnPlayableDestroy"/>.</remarks>
|
||
|
private void Dispose()
|
||
|
{
|
||
|
if (_Job.angle.IsCreated)
|
||
|
_Job.angle.Dispose();
|
||
|
|
||
|
if (_Job.bones.IsCreated)
|
||
|
_Job.bones.Dispose();
|
||
|
}
|
||
|
|
||
|
/// <summary>Destroys the <see cref="_Playable"/> and restores the graph connection it was intercepting.</summary>
|
||
|
public override void Destroy()
|
||
|
{
|
||
|
Dispose();
|
||
|
base.Destroy();
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
#region Job
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
/// <summary>An <see cref="IAnimationJob"/> that applies a lean effect to an <see cref="AnimationStream"/>.</summary>
|
||
|
/// <example><see href="https://kybernetik.com.au/animancer/docs/examples/jobs/lean">Lean</see></example>
|
||
|
public struct Job : IAnimationJob
|
||
|
{
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
public TransformStreamHandle root;
|
||
|
public NativeArray<TransformStreamHandle> bones;
|
||
|
public Vector3 axis;
|
||
|
public NativeArray<float> angle;
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
public void ProcessRootMotion(AnimationStream stream) { }
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
|
||
|
public void ProcessAnimation(AnimationStream stream)
|
||
|
{
|
||
|
var angle = this.angle[0] / bones.Length;
|
||
|
var worldAxis = root.GetRotation(stream) * axis;
|
||
|
var offset = Quaternion.AngleAxis(angle, worldAxis);
|
||
|
|
||
|
for (int i = bones.Length - 1; i >= 0; i--)
|
||
|
{
|
||
|
var bone = bones[i];
|
||
|
bone.SetRotation(stream, offset * bone.GetRotation(stream));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
#endregion
|
||
|
/************************************************************************************************************************/
|
||
|
}
|
||
|
}
|