I've created a simple utility class for use in my Unity2D project, which allows a Sprite to have separate sprites for when it is facing East, North, West, or South (that's the order that z-axis rotation places them in). There aren't too many Unity-isms in the code, and the part that I'm most concerned with is the internal logic. It seems like I'm duplicating a lot of code to handle each direction the sprite can face; is there a way to better handle this? [[As this is a relatively simple 2D game in early development, clarity and maintainability are preferred even if it takes a performance hit; e.g. using a dynamic lookup versus hard-coding it.]]
I've done my best to explain the Unity-isms below the code.
using UnityEngine;
using System;
namespace towerofbabble.scripts
{
/// <summary>
/// A controller for a SpriteRenderer with directional sprites
/// </summary>
[RequireComponent(typeof(SpriteRenderer))]
public class DirectionalSprite : MonoBehaviour
{
[SerializeField]
private SpriteRenderer _spriteRenderer;
#if UNITY_EDITOR
void Awake()
{
SpriteRenderer spriteRenderer = this.GetComponent<SpriteRenderer>();
if (_spriteRenderer != spriteRenderer)
{
Debug.LogError("SpriteRenderer is not assigned correctly.", this);
_spriteRenderer = spriteRenderer;
}
Sprite sprite = _spriteRenderer.sprite;
updateSprite();
if (sprite != _spriteRenderer.sprite)
{
Debug.LogError("Sprite is not assigned correctly.", this);
}
}
#endif
[SerializeField]
private Sprite _east;
[SerializeField]
private Sprite _north;
[SerializeField]
private Sprite _west;
[SerializeField]
private Sprite _south;
public enum Direction
{
RIGHT = 0,
UP = 1,
LEFT = 2,
DOWN = 3
}
/// <summary>
/// Convert a Vector2 to the equivalent Direction.
/// If Vector2 is not alligned to an axis, throws AgumentException.
/// </summary>
/// <param name="vector">The Vector2 to convert</param>
/// <returns>The equivalent Direction</returns>
public static Direction Vector2Direction(Vector2 vector)
{
vector = vector.normalized;
if (vector == Vector2.right)
{
return Direction.RIGHT;
}
else if (vector == Vector2.up)
{
return Direction.UP;
}
else if (vector == Vector2.left)
{
return Direction.LEFT;
}
else if (vector == Vector2.down)
{
return Direction.DOWN;
}
else
{
throw new ArgumentException("Argument must be a vector along an axis.", "vector");
}
}
/// <summary>
/// Convert a Direction to the equivalent Vector2.
/// The resulting Vector2 will be a unit vector along an axis.
/// </summary>
/// <param name="dir">The Direction to convert</param>
/// <returns>The equivalent Vector2</returns>
public static Vector2 DirectionVector2(Direction dir)
{
if (dir == Direction.RIGHT)
{
return Vector2.right;
}
else if (dir == Direction.UP)
{
return Vector2.up;
}
else if (dir == Direction.LEFT)
{
return Vector2.left;
}
else if (dir == Direction.DOWN)
{
return Vector2.down;
}
else
{
throw new Exception("Unreachable Code");
}
}
[SerializeField]
private Direction _looking = Direction.RIGHT;
/// <summary>
/// The Direction in which this DirectionalSprite is facing.
/// </summary>
public Direction Looking
{
get { return _looking; }
set
{
_looking = value;
updateSprite();
}
}
/// <summary>
/// Update the attatched SpriteRenderer to reflect Looking state.
/// </summary>
private void updateSprite()
{
if (_looking == Direction.RIGHT)
{
_spriteRenderer.sprite = _east;
}
else if (_looking == Direction.UP)
{
_spriteRenderer.sprite = _north;
}
else if (_looking == Direction.LEFT)
{
_spriteRenderer.sprite = _west;
}
else if (_looking == Direction.DOWN)
{
_spriteRenderer.sprite = _south;
}
else
{
throw new Exception("Unreachable Code");
}
}
}
}
Unity-isms
Feel free to skip if you understand basic Unity paradigms, and you should be able to understand the code even without looking over this section, as most of it is self explanatory.
From the top:
UnityEngine
: the namespace whereallmost Unity-specific APIs live.[RequireComponent]
: Requires a component to be attached to theGameObject
that this script is attached to.SpriteRenderer
: Component that handles the rendering of a 2D sprite.MonoBehaviour
: Base class that all Unity scripts derive from.[SerializeField]
: Forces Unity to serialize the field. This means that a value will be loaded dynamically at run-time, as set in the Editor. (Public variables are automatically serialized.)void Awake()
: Part of theMonoBehaviour
API. Invoked once, at object creation. Suggested use is as the constructor, as serialized fields' values are undefined at construction time, but all Objects in the scene are guaranteed to be fully de-serialized by the timeAwake()
is invoked on any of them. The access level ofAwake()
and the otherMonoBehaviour
methods do not matter, but are left as default protection by convention.#if UNITY_EDITOR
: Preproccessor directive that only compiles the code if on that platform. In this case, if in the editor, but not in a build. I'm using this to check that serialized references have been set correctly, and fixing and logging an error if they are not. It is more performant and more Unity-esque to assign references in the Editor, but accessing them programmatically adds another level of security.this.GetComponent<>()
: Method ofMonoBehaviour
that gets the attached component of the given type, or null if none exist.Debug
: Self explanatoryVector2
: Simple vector class. X and Y floats.
If I missed something leave a comment and I'll add it to the list