// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 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.Samples.Jobs { /// /// A wrapper that manages an Animation Job (the struct nested inside this class) /// which rotates a set of bones to allow the character to dynamically lean over independantly of their animations. /// /// /// /// The axis around which the bones are rotated can be set to achieve several different effects: /// /// The right axis allows bending forwards and backwards. /// The up axis allows turning to either side. /// The forward axis allows leaning to either side. /// /// /// This script is based on an implementation by ted-hou on GitHub. /// /// Sample: /// /// Hit Impacts /// /// /// https://kybernetik.com.au/animancer/api/Animancer.Samples.Jobs/SimpleLean /// public class SimpleLean : AnimancerJob, IDisposable { /************************************************************************************************************************/ #region Initialization /************************************************************************************************************************/ public SimpleLean( AnimancerGraph animancer, Vector3 axis, NativeArray leanBones) { Animator animator = animancer.Component.Animator; _Job = new() { root = animator.BindStreamTransform(animator.transform), bones = leanBones, axis = axis, angle = AnimancerUtilities.CreateNativeReference(), }; 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(); /// Cleans up the s. /// Called by . private void Dispose() { if (_Job.angle.IsCreated) _Job.angle.Dispose(); if (_Job.bones.IsCreated) _Job.bones.Dispose(); } /// Destroys the and restores the graph connection it was intercepting. public override void Destroy() { Dispose(); base.Destroy(); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Job /************************************************************************************************************************/ /// An that applies a lean effect to an . /// /// /// Sample: /// /// Hit Impacts /// /// /// https://kybernetik.com.au/animancer/api/Animancer.Samples.Jobs/Job /// public struct Job : IAnimationJob { /************************************************************************************************************************/ public TransformStreamHandle root; public NativeArray bones; public Vector3 axis; public NativeArray angle; /************************************************************************************************************************/ public readonly void ProcessRootMotion(AnimationStream stream) { } /************************************************************************************************************************/ public void ProcessAnimation(AnimationStream stream) { float angle = this.angle[0] / bones.Length; Vector3 worldAxis = root.GetRotation(stream) * axis; Quaternion offset = Quaternion.AngleAxis(angle, worldAxis); for (int i = bones.Length - 1; i >= 0; i--) { TransformStreamHandle bone = bones[i]; bone.SetRotation(stream, offset * bone.GetRotation(stream)); } } /************************************************************************************************************************/ } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ } }