Search Unity

Feedback Code review request: architectural solution for a very simple directional sprite system

Discussion in 'Scripting' started by unity_88E3DB00651776C6ACC5, Jan 3, 2023.

  1. unity_88E3DB00651776C6ACC5

    unity_88E3DB00651776C6ACC5

    Joined:
    Oct 5, 2022
    Posts:
    2
    So. I wrote scripts that process the sprite of an object based on the angle at which the camera is looking at it (doom-style directional sprites). I'd like you to review my code, point out bad decisions, and generally tell me what you think about it.

    The abstract class SpriteRotation contains the logic of assigning a sprite to a SpriteRenderer of an object:

    Code (CSharp):
    1. [RequireComponent(typeof(SpriteRenderer))]
    2. public abstract class SpriteRotation : MonoBehaviour
    3. {
    4.         [SerializeField] protected Transform character;
    5.  
    6.         protected SpriteRenderer spriteRenderer;
    7.         protected Transform cameraTransform;
    8.    
    9.         private void Start()
    10.         {
    11.             spriteRenderer = GetComponent<SpriteRenderer>();
    12.             cameraTransform = Camera.main!.transform;
    13.         }
    14.  
    15.         private void Update()
    16.         {
    17.             spriteRenderer.sprite = GetSectorSprite();
    18.         }
    19.    
    20.         protected float GetCameraToCharacterAngle() =>
    21. Vector2.SignedAngle(character.forward.XZ(), cameraTransform.forward.XZ());
    22.  
    23.         protected abstract Sprite GetSectorSprite();
    24. }
    The art style of my project requires sprite replacement in both 8 and 16 directions. Therefore, I have child classes from the abstract class that override the method containing the logic of determining the appropriate sprite:

    Code (CSharp):
    1. public class EightDirectional : SpriteRotation
    2. {
    3.         [SerializeField] private EightDirectionalSpritesContainer spritesContainer;
    4.    
    5.         protected override Sprite GetSectorSprite()
    6.         {
    7.             var angle = GetCameraToCharacterAngle();
    8.  
    9.             return Mathf.Abs(angle) switch
    10.             {
    11.                 < 22.5f => spritesContainer.up,
    12.                 < 67.5f => angle < 0 ? spritesContainer.upLeft : spritesContainer.upRight,
    13.                 < 112.5f => angle < 0 ? spritesContainer.left : spritesContainer.right,
    14.                 < 157.5f => angle < 0 ? spritesContainer.downLeft : spritesContainer.downRight,
    15.                 _ => spritesContainer.down
    16.             };
    17.         }
    18. }
    Code (CSharp):
    1. public class SixteenDirectional : SpriteRotation
    2. {
    3.         [SerializeField] private SixteenDirectionalSpritesContainer spritesContainer;
    4.    
    5.         protected override Sprite GetSectorSprite()
    6.         {
    7.             var angle = GetCameraToCharacterAngle();
    8.  
    9.             return Mathf.Abs(angle) switch
    10.             {
    11.                 < 11.25f => spritesContainer.up,
    12.                 < 33.75f => angle < 0 ? spritesContainer.upLeft0 : spritesContainer.upRight0,
    13.                 < 56.25f => angle < 0 ? spritesContainer.upLeft : spritesContainer.upRight,
    14.                 < 78.75f => angle < 0 ? spritesContainer.upLeft1 : spritesContainer.upRight1,
    15.                 < 101.25f => angle < 0 ? spritesContainer.left : spritesContainer.right,
    16.                 < 123.75f => angle < 0 ? spritesContainer.downLeft0 : spritesContainer.downRight0,
    17.                 < 146.25f => angle < 0 ? spritesContainer.downLeft : spritesContainer.downRight,
    18.                 < 168.75f => angle < 0 ? spritesContainer.downLeft1 : spritesContainer.downRight1,
    19.                 _ => spritesContainer.down
    20.             };
    21.         }
    22. }
    Sprites are stored in Serializable structures respectively:

    Code (CSharp):
    1. [System.Serializable]
    2. public struct EightDirectionalSpritesContainer
    3. {
    4.         [FormerlySerializedAs("Up")] public Sprite up;
    5.         [FormerlySerializedAs("UpRight")] public Sprite upRight;
    6.         [FormerlySerializedAs("Right")] public Sprite right;
    7.         [FormerlySerializedAs("DownRight")] public Sprite downRight;
    8.    
    9.         [FormerlySerializedAs("Down")] public Sprite down;
    10.         [FormerlySerializedAs("DownLeft")] public Sprite downLeft;
    11.         [FormerlySerializedAs("Left")] public Sprite left;
    12.         [FormerlySerializedAs("UpLeft")] public Sprite upLeft;
    13. }
    Code (CSharp):
    1. [System.Serializable]
    2. public struct SixteenDirectionalSpritesContainer
    3. {
    4.         [FormerlySerializedAs("Up")] public Sprite up;
    5.         [FormerlySerializedAs("UpRight0")] public Sprite upRight0;
    6.         [FormerlySerializedAs("UpRight")] public Sprite upRight;
    7.         [FormerlySerializedAs("UpRight1")] public Sprite upRight1;
    8.         [FormerlySerializedAs("Right")] public Sprite right;
    9.         [FormerlySerializedAs("DownRight0")] public Sprite downRight0;
    10.         [FormerlySerializedAs("DownRight")] public Sprite downRight;
    11.         [FormerlySerializedAs("DownRight1")] public Sprite downRight1;
    12.    
    13.         [FormerlySerializedAs("Down")] public Sprite down;
    14.         [FormerlySerializedAs("DownLeft0")] public Sprite downLeft0;
    15.         [FormerlySerializedAs("DownLeft")] public Sprite downLeft;
    16.         [FormerlySerializedAs("DownLeft1")] public Sprite downLeft1;
    17.         [FormerlySerializedAs("Left")] public Sprite left;
    18.         [FormerlySerializedAs("UpLeft0")] public Sprite upLeft0;
    19.         [FormerlySerializedAs("UpLeft")] public Sprite upLeft;
    20.         [FormerlySerializedAs("UpLeft1")] public Sprite upLeft1;
    21. }
    I have a feeling that child classes are superfluous and inappropriate, but at the same time I don't know how I could change this.

    Thank you very much in advance for your reply
     
    Last edited: Jan 3, 2023
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
  3. unity_88E3DB00651776C6ACC5

    unity_88E3DB00651776C6ACC5

    Joined:
    Oct 5, 2022
    Posts:
    2
    If I understood you correctly, this is exactly what I am doing: I am getting the signed angle between the directions of camera and object and the based on that angle determine in which direction the object is in relation to the player: UpLeft, UpRight, Down, Up, etc. And I also have a script for billboarding that I didn't include here because it has nothing to do with replacing sprites.
     
    Last edited: Jan 3, 2023
  4. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,997
    For the math I'd have used, well, math:
     sNum=(signedAngle+180-12.25f)/22.5f
    should give you up, up-right, right ... for 8 of them, and similar for 16. Maybe some trial and error. Then the struct holding them could be an array, allowing you to find the sprite with
    mySprites[sNum];
    . But I can see the small advantage of having the sprites in named variables, even if it does require a pile of IF's.

    Using child classes seems fine -- not better, but not really worse (the most common way subclasses are worse is that people don't understand them). I'm assuming each "thing" is locked to an 8 or 16 sprite count (ogres always have 8, dogs always have 16). If not, changing the subclass is a pain. So you could do it old school -- no subclasses, instead add a spriteCount variable, set to 8 or 16, and use
    if(spriteCount==8)
    .