3
\$\begingroup\$

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 where all most Unity-specific APIs live.
  • [RequireComponent]: Requires a component to be attached to the GameObject 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 the MonoBehaviour 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 time Awake() is invoked on any of them. The access level of Awake() and the other MonoBehaviour 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 of MonoBehaviour that gets the attached component of the given type, or null if none exist.
  • Debug: Self explanatory
  • Vector2: Simple vector class. X and Y floats.

If I missed something leave a comment and I'll add it to the list

\$\endgroup\$
4
\$\begingroup\$

Vector2Direction and DirectionVector2

You should consider to rename this methods to ToDirection() and ToVector2(). In this way it is more clear what the methods are doing, ideally this methods would live in a Convert class. Initializing for each method a dictionary would be the way to go IMO.

Assume you have done this and named this dictionary vectorsDictionary then your former Vector2Direction() method would look like so

public static Direction ToDirection(Vector2 vector)
{
    Direction direction;
    if (vectorsDictionary.TryGetValue(vector.normalized, out direction))
    {
        return direction;
    }

    throw new ArgumentException("Argument must be a vector along an axis.", "vector");
}  

In the same way you can do it for DirectionVector2 and this would be also possible for the updateSprite() method.

\$\endgroup\$
  • \$\begingroup\$ That's a good point. In Java I'd give the conversion responsibility to the Enum, but as a C# enum is just a wrapper for a primitive I guess the Convert class is a good middle ground. \$\endgroup\$ – CAD97 May 2 '16 at 15:58
  • \$\begingroup\$ I'll wait a little longer to see if anyone else has an answer before accepting this, if you don't mind. \$\endgroup\$ – CAD97 May 2 '16 at 15:59
  • \$\begingroup\$ You can wait as long as you want.;-) \$\endgroup\$ – Heslacher May 2 '16 at 16:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.