I am making a script that checks if a gameobject is in the box created by Physics2D.OverlapBox, and if it is move the enemy to a new random location. However, I’ve noticed that sometimes an enemy will spawn right next to the player.
I think that making this code recursive will fix this problem, but when I tried doing it on my own I got stack overflow errors. How can I make this code check for a gameobject multiple times without giving an error?
To understand recursion, you must understand recursion.
I wouldn’t use recursion here… it’s not a recursive problem.
I dunno what all is going on above, but just choose locations until you find one that does not overlap.
Keep in mind that if you don’t have timeout / countout functionality (give up after 1000 tries for example), your program will lock if there is no available spot to spawn, or even if the random function is simply degenerate enough to never find a spot that exists.
When you speak of a recursion it is worth noting that it doesn’t solve anything in particular by itself. It is just a technique to resolve a repeating algorithm in a way that reuses the existing method over and over.
A function call is resolved on the operating stack by saving the state the program is currently in, and then creating a new context for the new function to start fresh. This is called a stack frame. When you attempt to produce a recursive algorithm and fail, this means you’ve hit the limit of how many stack frames you can have in your program (likely by hitting the upper bound of the memory reserved for this), and this registers as Stack Overflow.
There are two flavors of recursion calls: head recursion and tail recursion.
A head recursion would begin the operation by calling itself immediately, then proceed doing its actual job. A tail recursion would do the job, then proceed with calling itself in the end. Many languages feature an optimization of tail recursion, where it can simply be translated into a loop that lies “flat” on the stack, meaning it would simply update the stack frame, without having to create new ones.
C# is not one of those languages, unfortunately, and because creating stack frames and switching context for small atomic jobs is expensive, we all simply avoid recursion in C#, unless it’s really clever and/or seldom-used, which is an extremely rare occurrence given that you really have to think it through. Well done recursion is not something you can just hack together, at least most of the time.
This also reveals that tail recursions are pretty much the same thing as a plain loop, written in a more convoluted way, and once you learn that recursions are also discouraged in C#, this should be enough to make you think in terms of loops, not recursions.
That said, I doubt you need recursion or a loop for this problem, you simply did something wrong.
Recursion is just a technique for breaking complex problems down into simpler ones. It’s not a way of making code work correctly on its own unless the problem in question is that the code should be executing multiple times. You don’t need recursion to solve that though. You can just use a loop.
I’m including the recursion implementation too just to show you how little of a difference it makes on the actual code while still having the limitation that you encountered and @orionsyndrome explained.
Loop
using UnityEngine;
public class EnemySpawn : MonoBehaviour
{
private Transform trans;
[SerializeField] private int playerDetectSize;
private void Start()
{
trans = GetComponent<Transform>();
SpawnEnemy();
}
private void SpawnEnemy()
{
Vector2 pos = GetRandomPosition();
while (Physics2D.OverlapBox(pos, trans.localScale) ||
Physics2D.OverlapBox(pos, trans.localScale * playerDetectSize, 0, 3))
{
pos = GetRandomPosition();
}
trans.position = pos;
}
private Vector2 GetRandomPosition()
{
float x = Random.Range(-8, 8);
float y = Random.Range(-4, 4);
return new Vector2(x, y);
}
}
Recursion
using UnityEngine;
public class EnemySpawn : MonoBehaviour
{
private Transform trans;
[SerializeField] private int playerDetectSize;
private void Start()
{
trans = GetComponent<Transform>();
SpawnEnemy();
}
private void SpawnEnemy()
{
Vector2 pos = GetRandomPosition();
if (Physics2D.OverlapBox(pos, trans.localScale) ||
Physics2D.OverlapBox(pos, trans.localScale * playerDetectSize, 0, 3))
{
SpawnEnemy(); // Recursive call
}
else
{
trans.position = pos;
}
}
private Vector2 GetRandomPosition()
{
float x = Random.Range(-8, 8);
float y = Random.Range(-4, 4);
return new Vector2(x, y);
}
}
The first script was modified with the help of GPT-3.5, but the second script was modified with the help of GPT-4 which likewise pointed out the limitation as can be seen in the following quote.