Classic Game Sprite Rotation Problems (Doom, Daggerfall, etc.)

As the title suggests, I’m trying to write a script that will change the sprite based on the angle of the sprite.
It seems to change the sprite on startup, but will not change with the angles. I’m probably overlooking something, but at this point I feel that I need assistance.

How might I improve this?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Billboard : MonoBehaviour {
    public bool multiAngle;

    GameObject player;
    public Sprite[] sprites = new Sprite[8];

    // Use this for initialization
    void Start () {
        player = GameObject.FindGameObjectWithTag ("Player");
    }
   
    // Update is called once per frame
    void Update () {
        transform.LookAt(transform.position + Camera.main.transform.rotation * Vector3.forward, Camera.main.transform.rotation * Vector3.up);

        if (multiAngle) {
            MultiAngle ();
        }
    }

    void MultiAngle(){
        float rotation = this.transform.rotation.y;
        float[] angles = new float[8];

        angles [0] = 0;
        angles [1] = 45;
        angles [2] = 90;
        angles [3] = 135;
        angles [4] = 180;
        angles [5] = -45;
        angles [6] = -90;
        angles [7] = -135;


        // {0, 45, 90, 135, 180}

        if (rotation >= angles [0]) {
            this.GetComponent<SpriteRenderer>().sprite = sprites [0];
        } else if (rotation >= angles [1]) {
            this.GetComponent<SpriteRenderer>().sprite = sprites [1];
        } else if (rotation >= angles [2]) {
            this.GetComponent<SpriteRenderer>().sprite = sprites [2];
        } else if (rotation >= angles [3]) {
            this.GetComponent<SpriteRenderer>().sprite = sprites [3];
        } else if (rotation >= angles [4]) {
            this.GetComponent<SpriteRenderer>().sprite = sprites [4];
        } else if (rotation <= -angles [1]) {
            this.GetComponent<SpriteRenderer>().sprite = sprites [5];
        } else if (rotation <= -angles [2]) {
            this.GetComponent<SpriteRenderer>().sprite = sprites [6];
        } else if (rotation <= -angles [3]) {
            this.GetComponent<SpriteRenderer>().sprite = sprites [7];
        }
   
    }
}

Your first issue is in line 26. You’re checking the y component of the rotation, which is not what you see in the Y field of the Transform inspector. It’s the Y of a quaternion, which is going to be between -1 and 1, and will bear no obvious relation to the value you’re looking for.

What you think you’re using would be transform.eulerAngles.y. BUT! Before you go and do that, you should know that Euler angles are hideously unreliable. So you should: A) learn what Euler angles are, B) learn that you should basically never use them, and C) use the Atan2 method described in the linked article.

Finally, you shouldn’t be checking tons of array values individually, you should be using the power of math to take the angle value you got and transforming that into a frame number. You can use sprites.Length for this, and you don’t need an array of numbers at all. All you need is to manipulate your Atan2 result into a “zero to one” value (you can divide by (Mathf.PI * 2) and then I believe add 0.5 to get into that range), or “normalized” value would be the term used. Then, multiply that number by sprites.Length, and then take Mathf.FloorToInt to change that to a frame number. Voila: any number of frame sprites, with only a few lines of code, and much less prone to typos!

float angle = Mathf.Atan2(transform.forward.y, transform.forward.x); //swap x, y, z, etc here depending on how your transform is configured
float normalizedValue = (angle / (Mathf.PI * 2f) ) + 0.5f;
int myIndex = Mathf.FloorToInt(normalizedValue * sprites.Length);
mySpriteRenderer.sprite = sprites[myIndex];

But your final thing is: You’re gonna need a second obejct to display your sprite, because your main object must retain its original rotation. Have mySpriteRenderer (above) refer to this second object, and THAT’s the object that should point towards the camera.

3 Likes

In the code above, the first if condition tested (if (rotation >= angles [0]) {) probably succeeds, and since all the others are else-if, nothing else runs.

Perhaps a better approach would be to use the angle to determine which sprite you want.

 float angle = yourOwnNotionOfAngularDirection;
 if (angle < 0) angle += 360.0f;
 angle += 22.5f; // one sixteenth (half of an eighth pie slice)
 int dir8 = (int)(angle / 45.0f);
 dir8 &= 7;  // force it to never be other than 0 to 7
 this.GetComponent<SpriteRenderer>().sprite = sprites [dir8];

I didn’t run the above, just typed it in, but it makes the point. Apologies for typos.

Best of all, you can put those intermediate results (angle and dir8) up on the screen in UnityEngine.UI.Text objects and debug any further issues you might see.

Ninja edit: changed to have you track your own notion of “which way,” as pointed out by @StarManta above.

1 Like

Woah… didn’t expect to get responses this fast. Anyways, thank you to @StarManta , the article and advice is helpful so far, and thank you to @Kurt-Dekker for your advice.

1 Like