In this article we are going to see a strategy to place an object in a random labyrinth position using the Random.Range method and the sword pedestal prefab created in the second article of the project, click here to download the files and see how to configure the prefabs.
Antes de comenzar te invito a ver el siguiente video.
Descripción del problema
We need the pedestal with the incrusted sword observed in figure 1 to appear anywhere in the labyrinth.
In video 4 we use empty GameObjects to designate specific positions in which to place the character’s prefab.
In this case we are looking for a more complex solution, first we are going to choose one of the pieces that compose the labyrinth. The piece we choose will be a duplicate of one of the pieces shown in figure 3.
It is not enough just to choose the piece, we need to obtain a position within it and this position must be within a region in which you can walk. We see these regions in figure 4.
When analyzing the problem we see that we will have to make several random decisions and it would be desirable that the solution we propose is independent of the number of pieces that the labyrinth has. In other words, if we add more pieces from figure 4, they are automatically added to the selection system.
Strategy: Define Segments
Given one of the pieces in figure 4, we are able to draw one or two internal lines where the player can circulate within the piece.
Let’s consider the ends of these two lines. Figure 6 identifies four points that would be the ends of these two segments.
These points could be represented with Empty GameObjects as children of the piece.
In figure 7 we see these two segments drawn.
Then we could place the pedestal with the sword at any point of one of the segments.
First of all, let’s choose one of the two segments, let’s suppose segment B (figure 8) formed by empty GameObjects B1 and B2.
Then we’ll take a random point between the two empty GameObjects, figure 9.
Finally at that chosen point we will place the pedestal.
In the case of the pieces of the corridor and dead end that only have one direction, we will make coincide the points A and B, that way we will have two coincident segments, then we will be able to use the same solution that for the rest of the pieces.
Implementation of the strategy
We have worked out a plan to place the pedestal somewhere inside one of the pieces of the labyrinth. Now based on this we are going to solve the problem.
First in the hierarchy I’m going to separate the obstruction pieces from the others, because these pieces are not going to be considered in our solution.
In the figure 1 we see selected the pieces that we are going to use.
We need to find the references of these pieces in our code to be able to choose one of them. The simplest way to do this is to use a Tag.
I’m going to create a Tag called “SpawnPiece” and assign it to all the pieces selected in figure 12.
Next we create the Script “LabyrinthPiece” (labyrinth piece) that will be assigned to all the pieces selected in figure 12.
In the Script first we will define four GameObjects that will be points A1, A2, B1 and B2. We declare them as serializable fields so that they appear in the inspector and we can assign them manually.
We select any piece type Crossroads and assign the Script LabyrinthPiece. In figure 19 we see that in the inspector appear the fields for GameObjects.
Next we’re going to create the four empty GameObjects that we’ll call A1, A2, B1 and B2. In figure 20 we see created the first point. Note that it is defined as the son of an Empty GameObject called Spawn, which in turn is the son of the crossroads piece.
We are going to position these four objects according to figure 6, at the ends of the imaginary segments that represent the walkable area inside the piece.
We assign these objects to their respective fields in the inspector, within the LabyrinthPiece component.
Finally we apply the changes. This is very important because we are applying the changes on the Prefab of the crossroads, that is to say that all the crossroads of the labyrinth will now have their own objects A1, A2, B1 and B2 and will have assigned the component LabyrinthPiece, with their own points loaded in the fields.
We can check that by checking every crossroads in the hierarchy and checking that it has these points and the Script assigned to it.
What follows is to repeat the process for the other pieces. In figure 26 we see the T-shaped piece, this case is similar to the bifurcation only that one of the imaginary segments will have its end in the center of the piece.
In the corridor piece we create only points A1 and A2. In figure 28 we see that these points are also assigned in fields B1 and B2 respectively.
In the corner piece, figure 29, the imaginary segments will have two coincident points, we could create only three Empty GameObjects and one of them assign it for example to A2 and B1, but we chose to create the four points.
The case of the dead-end piece is the same as that of the aisle only with less distance.
In figure 31 we see that in points B1 and B2 we repeat points A.
Method for choosing a random position of a piece – Random.Range
In the Script LabyrinthPiece we are going to create a public method that will return a Vector3 that will indicate a random position of the piece.
The first instruction will be to declare a Vector3 called position which will be the one we return to at the end of the execution.
Let us remember that they are two imaginary segments formed one by points A1 and A2, another by points B1 and B2. So then let’s do an if to choose one segment or the other.
In the if argument we use Random.Value to generate a random number between 0-1 and check if this value is less than 0.5f. This means that we will have a 50% chance of choosing segment A and another 50% of choosing segment B.
To choose a random point of the imaginary segment formed by the points we use the Vector3.Lerp method, which will make a linear interpolation between two Vectors3 that we indicate.
The method receives three arguments, the first two are the Vector3 between which it will be interpolated and the third parameter is the interpolation point we are interested in.
To exemplify the interpolation function consider the following: if the third value of the Lerp method is 0 we will have a Vector3 equal to the first parameter indicated. If it is worth 1 we will have a Vector3 equal to the second parameter indicated. And if it is worth 0.5f we will have a Vector3 that will be located exactly in the central point between the two Vectors3 that we indicate as parameters.
In this way we use Random.Range to generate a Vector3 that will be in some position between the points indicated in the first two parameters of the Lerp method and we assign that vector to the Vector3 position that we had defined at the beginning.
In an if region we use the position of points A1 and A2. In the other if region we do exactly the same but with points B1 and B2.
Finally we return the Vector3 position.
All this explained is summarized in the 7 lines of the GetRandomPosition method in figure 32.
Now, this we did was for the Script LabyrinthPiece that is assigned to each piece of the labyrinth.
In the GameControl Script we are going to create a method that will place the pedestal in a random position in the labyrinth and make use of the public method of LabyrinthPiece.
We begin by defining the fields shown in figure 33 below the comment “//Video 9”. These are the fields and variables that we will use to solve the problem.
In the GameControl component in the inspector (assigned to GameObject Control), fill in the fields. In SpawnPieceTag type “SpawnPiece”.
In ObjectToFind we will assign the Prefab of the pedestal with the sword, which is the object to find.
In the minimum distance for the moment we write the value 75. Then we will see what this variable is used for.
It is not necessary that the LabyrinthPieces array appears in the inspector so I am going to remove the [SerializeField] selected in figure 37.
In the StartGame method we are going to find all the GameObjects of the hierarchy that have the Tag that we indicate in the inspector. Last instruction of the startGame method in figure 40.
Then we declare the PlaceObjectToFind method (figure 41) and call this method from the StartGame method (figure 42).
What we will do with this method is to choose a random piece from the labyrinth, making sure that piece is far enough away from the player using the “minDistance” variable that we assigned 70 in the inspector. If the selected piece does not meet this requirement we will choose another piece again. This is taken care of by the While loop shown in figure 43.
Once we find a part that meets the requirements, we will place the pedestal at a random point inside. For this we use a version of the Instantiate method, which receives three parameters: The first is the object to be found stored in the “objectToFind” field, the second is the position that we will receive automatically from the labyrinth piece executing the GetRandomPosition method of the LabyrinthPiece component assigned to it. The third parameter is the rotation, here we will indicate: Quaternion.identity (a rotation identity).
It is important that we save the reference of this new object that we have created, we will do it in “objectToFindInstance” (last instruction in figure 43). This way when the game is over we can destroy this object manually.
In the EndGame method we destroy the instance of the object to be found, figure 44.
When you enter the game mode and press the Start button, everything seems to be working correctly. The pedestal with the sword appears in a random labyrinth position inside one of the pieces.
Place object in other regions
There are regions of the labyrinth that are outside the pieces, for example the one highlighted in figure 47. We might be interested in placing the object in a position belonging to this area.
How could we reuse what we have done?
To begin with we created an Empty GameObject and called it SpawnArea and placed it between the pieces of the labyrinth.
Then we created four empty GameObjects to represent points A1, A2, B1 and B2. We place these objects at the ends of the two imaginary segments of the area highlighted in green in Figure 49.
Then we create a Prefab along with the other pieces of the labyrinth (figure 51), because we may reuse this object by changing the internal points.
Then we assign the component LabyrinthPiece and place the internal points in the respective fields.
Do not forget to assign the Tag SpawnPiece in the inspector, otherwise these areas will not be considered when choosing a position for the pedestal. In my case, as can be seen in figure 53, I had not assigned it and I spent several minutes testing for the pedestal to appear in these areas.
When I tried the game, I noticed that the door was quite large in relation to the character, so I made it a little smaller and applied the changes.
Another problem I detected was that there were pieces with the Tag SpawnPiece whose inner region was inaccessible to the player. In figure 55 you see one of these pieces, if the pedestal appears here, the player won’t be able to find it.
The solution to this is to select this piece and remove the Tag SpawnPiece, this way the piece will not be considered.
In this article we managed to place the pedestal in a random position within the labyrinth.
To do this we had to analyze the type of pieces that make up the labyrinth, establish certain rules and propose a strategy to solve our problem.
We used object-oriented programming thinking to create a flexible solution that fits all types of parts.
As usually happens, there is no single way to solve a problem. Another way to address this situation is to make a Navmesh Bake and take a point within these regions.