2025-05-09 15:40:34 +08:00

164 lines
6.9 KiB
C#

// 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 Animancer.Units;
using System.Collections.Generic;
using UnityEngine;
namespace Animancer.Samples.Sprites
{
/// <summary>
/// A more complex version of the <see cref="DirectionalBasics"/> which adds
/// running and pushing animations as well as the ability to actually move around.
/// </summary>
///
/// <remarks>
/// <strong>Sample:</strong>
/// <see href="https://kybernetik.com.au/animancer/docs/samples/sprites/character">
/// Directional Character</see>
/// </remarks>
///
/// https://kybernetik.com.au/animancer/api/Animancer.Samples.Sprites/DirectionalCharacter
///
[AddComponentMenu(Strings.SamplesMenuPrefix + "Sprites - Directional Character")]
[AnimancerHelpUrl(typeof(DirectionalCharacter))]
public class DirectionalCharacter : MonoBehaviour
{
/************************************************************************************************************************/
#if UNITY_PHYSICS_2D
/************************************************************************************************************************/
[Header("Physics")]
[SerializeField] private CapsuleCollider2D _Collider;
[SerializeField] private Rigidbody2D _Rigidbody;
[SerializeField, MetersPerSecond] private float _WalkSpeed = 1;
[SerializeField, MetersPerSecond] private float _RunSpeed = 2;
[Header("Animations")]
[SerializeField] private AnimancerComponent _Animancer;
[SerializeField] private DirectionalAnimationSet _Idle;
[SerializeField] private DirectionalAnimationSet _Walk;
[SerializeField] private DirectionalAnimationSet _Run;
[SerializeField] private DirectionalAnimationSet _Push;
[SerializeField] private Vector2 _Facing = Vector2.down;
private Vector2 _Movement;
private DirectionalAnimationSet _CurrentAnimationSet;
private static readonly TimeSynchronizer<AnimationGroup>
TimeSynchronizer = new();
public enum AnimationGroup
{
Other,
Movement,
}
/************************************************************************************************************************/
protected virtual void Awake()
{
_CurrentAnimationSet = _Idle;
}
/************************************************************************************************************************/
protected virtual void Update()
{
_Movement = SampleInput.WASD;
if (_Movement != Vector2.zero)
{
// Snap the movement to the exact directions we have animations for.
// When using DirectionalAnimationSets this means the character will only move up/right/down/left.
// But DirectionalAnimationSet8s will allow diagonal movement as well.
_Movement = _CurrentAnimationSet.Snap(_Movement);
_Movement = Vector2.ClampMagnitude(_Movement, 1);
_Facing = _Movement;
UpdateMovementState();
}
else
{
Play(_Idle, AnimationGroup.Other);
}
}
/************************************************************************************************************************/
private void Play(DirectionalAnimationSet animations, AnimationGroup group)
{
// Store the current time.
TimeSynchronizer.StoreTime(_Animancer);
_CurrentAnimationSet = animations;
_Animancer.Play(animations.GetClip(_Facing));
// If the new animation is in the synchronization group, give it the same time the previous animation had.
TimeSynchronizer.SyncTime(_Animancer, group);
}
/************************************************************************************************************************/
// Pre-allocate a list of contacts so Unity doesn't need to allocate a new one every time.
// It's static because every character can use the same list one at a time.
private static readonly List<ContactPoint2D> Contacts = new();
private void UpdateMovementState()
{
int contactCount = _Collider.GetContacts(Contacts);
for (int i = 0; i < contactCount; i++)
{
// If we're moving directly towards an object (or within 30 degrees of it), we are pushing it.
if (Vector2.Angle(Contacts[i].normal, _Movement) > 180 - 30)
{
Play(_Push, AnimationGroup.Movement);
return;
}
}
DirectionalAnimationSet animations = SampleInput.LeftShiftHold ? _Run : _Walk;
Play(animations, AnimationGroup.Movement);
}
/************************************************************************************************************************/
protected virtual void FixedUpdate()
{
// Determine the desired speed based on the current animation.
float speed = _CurrentAnimationSet == _Run ? _RunSpeed : _WalkSpeed;
_Rigidbody.velocity = _Movement * speed;
}
/************************************************************************************************************************/
#if UNITY_EDITOR
/************************************************************************************************************************/
/// <summary>[Editor-Only]
/// Sets the character's starting sprite in Edit Mode so you can see it while working in the scene.
/// </summary>
/// <remarks>Called in Edit Mode whenever this script is loaded or a value is changed in the Inspector.</remarks>
protected virtual void OnValidate()
{
if (_Idle != null)
_Idle.GetClip(_Facing).EditModePlay(_Animancer);
}
/************************************************************************************************************************/
#endif
/************************************************************************************************************************/
#else
/************************************************************************************************************************/
protected virtual void Awake()
{
SampleReadMe.LogMissingPhysics3DModuleError(this);
}
/************************************************************************************************************************/
#endif
/************************************************************************************************************************/
}
}