Random position spawning
- October 13, 2023
- Tutorial , Resources
Table of Contents
Introduction
One of the key elements that contributes to the interactivity of a game is the ability to seamlessly spawn objects into the game world. Whether it’s an enemy unit stepping out of the shadows, power-ups magically appearing, or obstacles falling from the sky, the art of spawning objects is a fundamental skill for game developers. In this blog post I will explain how to spawn an object at a random position on or off the screen in Unity2D. You can download the code on github or download a Unity package in the Code section to follow along. There is a class called Spawner that is associated with a game object in the scene. You can use it to play around with the different methods.
Random position offscreen
When developing several 2D games, I always wondered about a good way to spawn objects out of the player’s view. Just using a fixed value from the player character did not seem right. What happens if someone plays the game on a different screen resolution? The objects will simply not spawn at the intended location.
A better approach is to calculate the screen boundaries and use them as a reference. This can be done using the game camera. If the edges of the screen are known objects can be moved outwards with a distance determined by us.
In most cases Unity’s Main Camera is needed for this purpose. Unfortunately, the edges of the camera do not correspond to the coordinates in the scene. But they can easily be transformed into a scene position using the Camera.ScreenToWorldPoint method. The following code snippet demonstrates how to get the edges of our screen and the screen size.
float cameraDistance = mainCamera.nearClipPlane;
// Calculate the coordinates of the screen borders
BottomLeft = mainCamera.ScreenToWorldPoint(new Vector3(0, 0, cameraDistance));
TopLeft = mainCamera.ScreenToWorldPoint(new Vector3(0, Screen.height, cameraDistance));
TopRight = mainCamera.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, cameraDistance));
BottomRight = mainCamera.ScreenToWorldPoint(new Vector3(Screen.width, 0, cameraDistance));
Width = BottomRight.x - BottomLeft.x;
Height = TopLeft.y - BottomLeft.y;
I have created public fields for the four corners, the height and width, which are set in this code block. You can find the code itself in the ScreenBorders class.
With this information, we can start calculating a random position on or off the screen. The basic idea is to use Unitys Random class to get a position on one of the screen borders and then move that point an additional distance.
For example, if we want to spawn an object at the top of the screen, we can simply calculate a random x-position between the top edges. Then we take the y-variable of a top edge and add the distance we want the object to be outside of the screen:
float x = Random.Range(screenBorder.topLeft.x, screenBorder.topRight.x);
float y = screenBorder.topLeft.y + AdditionalDistance;
AdditionalDistance is again a public field that I set above in my class. Using this approach, we can decide on which side of the screen to spawn the object. A method that takes the direction as a parameter and returns a random position might look like this:
private Vector2 OutOfScreenDirection(ScreenDirection direction)
{
screenBorder.CalculateScreenBorders();
float x = Random.Range(screenBorder.BottomLeft.x - AdditionalDistance, screenBorder.BottomRight.x + AdditionalDistance);
float y = Random.Range(screenBorder.BottomLeft.y - AdditionalDistance, screenBorder.TopLeft.y + AdditionalDistance);
switch (direction)
{
case ScreenDirection.TOP:
y = screenBorder.topLeft.y + AdditionalDistance;
break;
case ScreenDirection.BOTTOM:
y = screenBorder.bottomLeft.y - AdditionalDistance;
break;
case ScreenDirection.LEFT:
x = screenBorder.bottomLeft.x - AdditionalDistance;
break;
case ScreenDirection.RIGHT:
x = screenBorder.bottomRight.x + AdditionalDistance;
break;
}
return new Vector2(x, y);
}
You will find this method in the class called RandomPosition. The ScreenDirection parameter is a value from an Enum, that defines the direction. This method returns us the position, from a certain direction, outside the player’s view. In case we don’t care for which direction the random position is calculated, we can write an additional method:
public Vector2 OutOfScreen()
{
ScreenDirection[] directions = (ScreenDirection[])Enum.GetValues(typeof(ScreenDirection));
ScreenDirection randomDirection = directions[Random.Range(0, directions.Length)];
return OutOfScreenDirection(randomDirection);
}
To demonstrate the result I will use animations for the rest of this post. In the animations the presented methods are called very often in small intervals. At the random positions I get from this, I instantiate colored circles. In the following animation it can be observed which positions are covered by the method when it is called several times.
The objects we spawn will all end up at the same distance out of the player’s view. A use case for this would be to spawn some enemies that move towards the player.
It may happen that you do not want to leave the spawn position entirely to chance. For example, it could be the case that spawn positions are only desired from the left and right. In this case we can write another method that leaves this decision to us.
public Vector2 OutOfScreenDirection(params ScreenDirection[] screenDirections)
{
ScreenDirection randomSpawnDirection = screenDirections[Random.Range(0, screenDirections.Length)];
return OutOfScreenDirection(randomSpawnDirection);
}
Instead of passing an array, I decided to use the params keyword. The params keyword allows us to add a variable number of arguments of type ScreenDirection. You can read more about this in the C# documentation. With that we can add multiple direction values as parameters and get a random position from one of them. For example if we are only interested in getting random positions from left and right, we simply pass ScreenDirection.LEFT and ScreenDirection.RIGHT as arguments to the method. You can see this example in the first image below. You can also use the method to influence the probability from which side the random position is more likely to be calculated by passing the same direction multiple times as an argument.
You may be wondering what is happening in the second image. By setting the AdditionalDistance variable to a negative value, I used the OutOfScreenDirection method to get a random position within the player’s view at the top position.
But before we go into more detail about onscreen spawning, I want to highlight the current approach we have chosen. The positions we get around the screen border will always have the same distance to the border. As a result, all possible random positions represent a rectangle. If we were to spawn multiple objects with a fixed movement speed at the same time, they would take the same amount of time to enter the player view. But what if we want to spawn objects outside the screen, but all at the same distance from the player?
For this approach, we assume that the player is in the center of the screen, as is most often the case in survivor rogue-like games. Using the distance to an edge of the screen as a radius, we can get random positions in a circular shape from the screen. For directions we use the Random class to get an angle corresponding to the direction. For example, the position to the right must be between an angle of -45 to 45. Following the same scheme as we did for the rectangle screen border, I created a new method in the RandomPosition class called private Vector2 OutOfScreenDirectionCircular(ScreenDirection direction). This method gives us a random position on a circular shape outside the screen. As before, we can specify the directions. The result will look like this:
To make it as convenient as possible to switch between rectangle and circle positions, I created a public bool field Circular inside the RandomPosition class. Use this field to toggle between the two random position types.
To cover another use case, I created another public field called SinglePoint. If this field is set to True, then the methods already presented will return only one point from one direction at a time. To understand this better the two following graphics serve.
As you can see, the points are always located at half the height/width. If this function is still unclear, I suggest looking directly in the code. All in all, this was quite a lot of information at once. Use the Spawner class to play around with the different methods. If you have any more questions, feel free to ask them in the comments. The next chapter on onscreen spawning will be easier.
Random position on screen
In the last chapter we already saw how to use a negative AdditonalDistance value to get a position inside the screen with the public Vector2 OutOfScreen() method. Getting a random position on the screen is quite simple compared to the previous methods. We just need to calculate a position between each screen boundary.
public Vector2 OnScreen()
{
screenBorder.CalculateScreenBorders();
return new Vector2(Random.Range(screenBorder.BottomLeft.x, screenBorder.BottomRight.x), Random.Range(screenBorder.BottomLeft.y, screenBorder.TopLeft.y));
}
The simulation of this approach looks like this:
Another common way to spawn objects is to spawn them around a target, for example the player. This can be especially helpful when, for example, enemies, spells, or special effects are supposed to appear near the player. Unity’s Random class has a method called insideUnitCircle, which returns a random point inside or on a circle with the radius of 1. We can use this method and multiply it with a desired radius. Also we have to add the position of the target.
public Vector2 AroundTargetInCircle(GameObject target, float radius)
{
return (Random.insideUnitCircle * radius) + (Vector2)target.transform.position;
}
If we want the random position to always have the same distance to the target, we just have to normalize the result of the Random.insideUnitCircle method. This way we always get a Vector2 with a magnitude of 1.
public Vector2 AroundTargetOnCircle(GameObject target, float radius)
{
return (Random.insideUnitCircle.normalized * radius) + (Vector2)target.transform.position;
}
Both options are shown in the following figures. The white box in the center of the screen symbolizes the target.
In our method AroundTargetInCircle it is possible that objects also appear on the target or directly next to it. However, this is not always intentional. For example, it makes no sense if an enemy spawns right next to the player and the player has no time to react. With a small adjustment in the code we can add a safety distance between the target and the spawn area. So besides the outer radius we need an inner radius that defines the safety distance. Because the spawnarea now looks like a donut, we also call the method after it:
public Vector2 AroundTargetInDonut(GameObject target, float innerRadius, float outerRadius)
{
Vector2 position = AroundTargetInCircle(target, outerRadius - innerRadius);
Vector2 direction = (position - (Vector2)target.transform.position).normalized;
return position + direction * innerRadius;
}
As you may have noticed, we use a little trick for this method. We first get a random point around the target, subtracting the safety distance from the radius. We add this safety distance back at the end. This creates an empty area around the target. An example can be seen in the following figure:
Having the random position is a good start, but what now? There are several ways to spawn objects in Unity. The most basic way is to use the instantiate method. Depending on the game you are making, you may want to consider a different approach, such as an object pool. For further reading I recommend an article by gamedevbeginner.
I hope you enjoyed the article and learned a few new things. Since this is one of my first technical posts, I’m especially happy about constructive feedback. Thanks for reading.
And now you!
- Get the code template
- Ask your questions in the comments section.
- Missing some additional spawn options?
- Interested in a post about an enemy spawner?