0

I'm trying to make the game Snake, and I'm trying to get the apple functionality working. What this script is meant to do, is whenever my Snake goes over the Apple, the apple disappears and reappears at a random location on the screen. But instead it does nothing, any idea as to why?

P.S: Camera is Size 10 and Aspect Ratio is 16:9 which is why I have some weird Random.Range values. Also I used Debug.Log in Update to make sure that the variable worked, and yes it does, whenever my snake moves its coordinates are displayed.

public class Apple_RandomSpawn : MonoBehaviour
{
    private Vector2 foodPos;
    private Snake_Move snakeMove;

    void Start()
    {
        SpawnApple();
    }

    public void Update()
    {
        transform.position = new Vector2(foodPos.x, foodPos.y);
        SnakeAte();
    }

    public void SpawnApple()
    {
        foodPos = new Vector2(Random.Range(-17, 17), Random.Range(-9, 9));
    }

    public void SnakeAte()
    {
        if (Mathf.Approximately(foodPos.x, snakeMove.pos.x) && Mathf.Approximately(foodPos.y, snakeMove.pos.y))
        {
            SpawnApple();
        }
    }
}
CodingMike
  • 33
  • 7
  • Vector2's `x` and `y` are `float`. My guess is that `foodPos.x` is like `0.121` and `snakeMove.pos.x` is like `0.1213` or something. I recommend stepping through with the debugger. – ProgrammingLlama Apr 03 '20 at 01:02
  • Should I then add some sort of rounding component? Cause the snake moves by 0.1 in the x value every 1/60th of a second, which makes the snake's movement a float. Don't know how I would fix that xD – CodingMike Apr 03 '20 at 01:07
  • If that is the problem, I'd probably check if the distance between them is less than some number. Also note that floating point numbers can't accurately represent all fractional values, in the same way that we can't represent 1/3 accurately in base 10. See [Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) for more info. – ProgrammingLlama Apr 03 '20 at 01:08
  • @John I changed the Debug.Log to display foodPos - snakeMove.pos and I'm getting very small values with 0.1s so yes I think you're right. – CodingMike Apr 03 '20 at 01:11

2 Answers2

1

First of all, it does not directly have something to do with your problem, but DO NOT put GetComponent() or GameObject.Find() in the Update() function. These two, especially GameObject.Find() function is super heavy, so It's recommended that you should call these kinds of function inside Start() or Awake(), or at the initiallization of a class. It can directly, and heavily impact the performance of your game, so here's my suggestion:

[SerializeField]
private Snake_Move snakeMove;

And drag your gameobject (which Snake_Head component is attatched) via Inspector. You should always consider this way first, rather than using GameObject.Find() and GetComponent().

Second, Float is not recommend to compare equality directly through =, since there must be rounding error. There is a help function with regard to comparing two float value in Unity, like Mathf.Approximately(float a, float b). Directly comparing two float value via = would almost always not work as you might think.

Third, It doesn't seem to be that there are no Instantiate() function in your code, but you are try to use one apple object, and every time you consume it, just change it's position. Then why does Object.Destroy(gameObject) exists? What you're doing is, just destroying the apple when you get it first time. I think you have to remove the Destroy() function, and SpawnApple() changes the target coordinate of apple, and the position will be updated in Update() function.

And it's no need to indirectly set the target position, and update to it in Update() function. You can directly set the position of apple, like:

// assuming you followed my suggestio aboven about snakeMove.

public void Update()
    {
        SnakeAte();

        Debug.Log(snakeMove.pos);
    }

public void SpawnApple()
    {
        transform.position = new Vector2(Random.Range(-17, 17), Random.Range(-9, 9));
    }

public void SnakeAte()
    {
        if (foodPos == snakeMove.pos)
        {
            SpawnApple();
        }
    }
MyBug18
  • 2,135
  • 2
  • 11
  • 25
  • Oh my god what I could have just called a variable the whole time?!!?! I spent like 30 minutes trying to call a variable from a different class lmao What's the point of GameObject.Find then? – CodingMike Apr 03 '20 at 01:13
  • @CodingMike Yes, what you were doing was, Scan the whole scene, find gameobject with the name of "SnakeHead", and try to get component `Snake_Move`. It's not that significant problem, but when you try to do this *60 times per second*, it becomes problem. – MyBug18 Apr 03 '20 at 01:15
  • I changed the if statement to; Mathf.Approximately(foodPos.x, snakeMove.pos.x) && Mathf.Approximately(foodPos.y, snakeMove.pos.y and it still didn't work. Did I do it wrong? haha – CodingMike Apr 03 '20 at 01:19
  • Hmm it still doesn't work, and I'm getting a console Error which says "Object reference not set to an instance of an object". I'll edit my post so I can update the code. – CodingMike Apr 03 '20 at 01:26
  • Are you sure that you have given the reference to `snakeMove` field via inspector? https://docs.unity3d.com/Manual/script-Serialization-BuiltInUse.html – MyBug18 Apr 03 '20 at 01:32
  • Not gonna lie I'm new and slightly confused haha. After serializing the field, what value should I give it? Isn't the position of the snake constantly updating? – CodingMike Apr 03 '20 at 01:37
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/210839/discussion-between-mybug18-and-codingmike). – MyBug18 Apr 03 '20 at 01:37
0

First of all your null ref in your last comment comes from: private Snake_Move snakeMove; Its a private variable and its never assigned. You either need to make it public/[SerialistField] and assign it in inspect or have some sort of initialize function that give it a value.

For the hit detection Mathf.Approximately is good if you wan to check if 2 floats are exactly the same. If you'r checking 2 positions of moving objects the chance of them being exactly the same is very low and may rely on frame rate and such. Keeping your implementation you can check instead for a minimum distance between the 2 positions. You can tweak DISTANCE_THRESHOLD for a value that suits your better.

public class Apple_RandomSpawn : MonoBehaviour
        {
            private const float DISTANCE_THRESHOLD = 0.1f;
            private Vector2 foodPos;
            private Snake_Move snakeMove;

            void Start()
            {
                SpawnApple();
            }

            public void Update()
            {                   
                SnakeAte();
            }

            public void SpawnApple()
            {
                foodPos = new Vector2(Random.Range(-17, 17), Random.Range(-9, 9));
                transform.position = new Vector2(foodPos.x, foodPos.y);
            }

            public void SnakeAte()
            {
                if (Vector3.Distance(foodPos, snakeMove) < DISTANCE_THRESHOLD)
                {
                    SpawnApple();
                }
            }
        }

Now remember that since your teleporting the apple to a purely random location it might teleport right back on top of your snake :).

leo Quint
  • 777
  • 1
  • 5
  • 12