// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Animancer
{
/// A set of up/right/down/left animations.
///
/// Documentation:
///
/// Directional Animation Sets
///
/// https://kybernetik.com.au/animancer/api/Animancer/DirectionalAnimationSet
///
[CreateAssetMenu(
menuName = Strings.MenuPrefix + "Directional Animation Set/4 Directions",
order = Strings.AssetMenuOrder + 3)]
[AnimancerHelpUrl(typeof(DirectionalAnimationSet))]
public class DirectionalAnimationSet : ScriptableObject,
IAnimationClipSource
{
/************************************************************************************************************************/
[SerializeField]
private AnimationClip _Up;
/// [] The animation facing up (0, 1).
/// was not called before setting this value.
public AnimationClip Up
{
get => _Up;
set
{
AssertCanSetClips();
_Up = value;
AnimancerUtilities.SetDirty(this);
}
}
/************************************************************************************************************************/
[SerializeField]
private AnimationClip _Right;
/// [] The animation facing right (1, 0).
/// was not called before setting this value.
public AnimationClip Right
{
get => _Right;
set
{
AssertCanSetClips();
_Right = value;
AnimancerUtilities.SetDirty(this);
}
}
/************************************************************************************************************************/
[SerializeField]
private AnimationClip _Down;
/// [] The animation facing down (0, -1).
/// was not called before setting this value.
public AnimationClip Down
{
get => _Down;
set
{
AssertCanSetClips();
_Down = value;
AnimancerUtilities.SetDirty(this);
}
}
/************************************************************************************************************************/
[SerializeField]
private AnimationClip _Left;
/// [] The animation facing left (-1, 0).
/// was not called before setting this value.
public AnimationClip Left
{
get => _Left;
set
{
AssertCanSetClips();
_Left = value;
AnimancerUtilities.SetDirty(this);
}
}
/************************************************************************************************************************/
#if UNITY_ASSERTIONS
private bool _AllowSetClips;
#endif
/// [Assert-Only]
/// Determines whether the properties are allowed to be set.
///
[System.Diagnostics.Conditional(Strings.Assertions)]
public void AllowSetClips(bool allow = true)
{
#if UNITY_ASSERTIONS
_AllowSetClips = allow;
#endif
}
/// [Assert-Only]
/// Throws an if wasn't called.
///
[System.Diagnostics.Conditional(Strings.Assertions)]
public void AssertCanSetClips()
{
#if UNITY_ASSERTIONS
AnimancerUtilities.Assert(_AllowSetClips,
$"{nameof(AllowSetClips)}() must be called before attempting to set any of" +
$" the animations in a {nameof(DirectionalAnimationSet)}" +
$" to ensure that they are not changed accidentally.");
#endif
}
/************************************************************************************************************************/
/// Returns the animation closest to the specified `direction`.
public virtual AnimationClip GetClip(Vector2 direction)
{
if (direction.x >= 0)
{
if (direction.y >= 0)
return direction.x > direction.y ? _Right : _Up;
else
return direction.x > -direction.y ? _Right : _Down;
}
else
{
if (direction.y >= 0)
return direction.x < -direction.y ? _Left : _Up;
else
return direction.x < direction.y ? _Left : _Down;
}
}
/************************************************************************************************************************/
#region Directions
/************************************************************************************************************************/
/// The number of animations in this set.
public virtual int ClipCount
=> 4;
/************************************************************************************************************************/
/// Up, Right, Down, or Left.
///
/// Documentation:
///
/// Directional Animation Sets
///
/// https://kybernetik.com.au/animancer/api/Animancer/Direction
///
public enum Direction
{
/// .
Up,
/// .
Right,
/// .
Down,
/// .
Left,
}
/************************************************************************************************************************/
/// Returns the name of the specified `direction`.
protected virtual string GetDirectionName(int direction)
=> ((Direction)direction).ToString();
/************************************************************************************************************************/
/// Returns the animation associated with the specified `direction`.
public AnimationClip GetClip(Direction direction)
=> direction switch
{
Direction.Up => _Up,
Direction.Right => _Right,
Direction.Down => _Down,
Direction.Left => _Left,
_ => throw AnimancerUtilities.CreateUnsupportedArgumentException(direction),
};
/// Returns the animation associated with the specified `direction`.
public virtual AnimationClip GetClip(int direction)
=> GetClip((Direction)direction);
/************************************************************************************************************************/
/// Sets the animation associated with the specified `direction`.
public void SetClip(Direction direction, AnimationClip clip)
{
switch (direction)
{
case Direction.Up: Up = clip; break;
case Direction.Right: Right = clip; break;
case Direction.Down: Down = clip; break;
case Direction.Left: Left = clip; break;
default: throw AnimancerUtilities.CreateUnsupportedArgumentException(direction);
}
}
/// Sets the animation associated with the specified `direction`.
public virtual void SetClip(int direction, AnimationClip clip) => SetClip((Direction)direction, clip);
/************************************************************************************************************************/
/// [Editor-Only]
/// Attempts to assign the `clip` to one of this set's fields based on its name and
/// returns the direction index of that field (or -1 if it was unable to determine the direction).
///
public virtual int SetClipByName(AnimationClip clip)
{
var name = clip.name;
int bestDirection = -1;
int bestDirectionIndex = -1;
var directionCount = ClipCount;
for (int i = 0; i < directionCount; i++)
{
var index = name.LastIndexOf(GetDirectionName(i));
if (bestDirectionIndex < index)
{
bestDirectionIndex = index;
bestDirection = i;
}
}
if (bestDirection >= 0)
SetClip(bestDirection, clip);
return bestDirection;
}
/************************************************************************************************************************/
#region Conversion
/************************************************************************************************************************/
/// Returns a vector representing the specified `direction`.
public static Vector2 DirectionToVector(Direction direction)
=> direction switch
{
Direction.Up => Vector2.up,
Direction.Right => Vector2.right,
Direction.Down => Vector2.down,
Direction.Left => Vector2.left,
_ => throw AnimancerUtilities.CreateUnsupportedArgumentException(direction),
};
/// Returns a vector representing the specified `direction`.
public virtual Vector2 GetDirection(int direction)
=> DirectionToVector((Direction)direction);
/************************************************************************************************************************/
/// Returns the direction closest to the specified `vector`.
public static Direction VectorToDirection(Vector2 vector)
{
if (vector.x >= 0)
{
if (vector.y >= 0)
return vector.x > vector.y ? Direction.Right : Direction.Up;
else
return vector.x > -vector.y ? Direction.Right : Direction.Down;
}
else
{
if (vector.y >= 0)
return vector.x < -vector.y ? Direction.Left : Direction.Up;
else
return vector.x < vector.y ? Direction.Left : Direction.Down;
}
}
/************************************************************************************************************************/
/// Returns a copy of the `vector` pointing in the closest direction this set type has an animation for.
public static Vector2 SnapVectorToDirection(Vector2 vector)
{
var magnitude = vector.magnitude;
var direction = VectorToDirection(vector);
vector = DirectionToVector(direction) * magnitude;
return vector;
}
/// Returns a copy of the `vector` pointing in the closest direction this set has an animation for.
public virtual Vector2 Snap(Vector2 vector)
=> SnapVectorToDirection(vector);
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#region Collections
/************************************************************************************************************************/
/// Adds all animations from this set to the `clips`, starting from the specified `index`.
public void AddClips(AnimationClip[] clips, int index)
{
var count = ClipCount;
for (int i = 0; i < count; i++)
clips[index + i] = GetClip(i);
}
/// [] Adds all animations from this set to the `clips`.
public void GetAnimationClips(List clips)
{
var count = ClipCount;
for (int i = 0; i < count; i++)
clips.Add(GetClip(i));
}
/************************************************************************************************************************/
///
/// Adds unit vectors corresponding to each of the animations in this set to the `directions`, starting from
/// the specified `index`.
///
public void AddDirections(Vector2[] directions, int index)
{
var count = ClipCount;
for (int i = 0; i < count; i++)
directions[index + i] = GetDirection(i);
}
/************************************************************************************************************************/
/// Calls and .
public void AddClipsAndDirections(AnimationClip[] clips, Vector2[] directions, int index)
{
AddClips(clips, index);
AddDirections(directions, index);
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
}
}