It’s been almost two years since I wrote “First Person Grid Movement with Unity3D“, in which I showed how to use Unity3D to replicate the grid-based movement typical of “blobbers” like Eye of the Beholder. The most common question I’ve had about that article is how to implement collisions. So that’s what I’m going to show you in this article, using Unity Editor 2021.3.25f1 LTS.
Creating a Project
Open the Unity Hub and create a new project. Make sure you select the 3D template, and call it whatever you want.
Adding the Game Script
- In the Assets pane, right-click and select Create -> C# Script. You can call it whatever you want, but I’ll call it Game.
- Drag it onto your Main Camera object.
- Double-click the script to open it in your IDE (e.g. Visual Studio Code).
- In your
Update()
function, paste the following code which is where we left off in “First Person Grid Movement with Unity3D“:
void Update()
{
if (Input.GetKeyDown(KeyCode.W))
this.transform.position += this.transform.rotation * Vector3.forward;
else if (Input.GetKeyDown(KeyCode.S))
this.transform.position += this.transform.rotation * Vector3.back;
else if (Input.GetKeyDown(KeyCode.A))
this.transform.position += this.transform.rotation * Vector3.left;
else if (Input.GetKeyDown(KeyCode.D))
this.transform.position += this.transform.rotation * Vector3.right;
else if (Input.GetKeyDown(KeyCode.Q))
this.transform.rotation *= Quaternion.Euler(0, -90, 0);
else if (Input.GetKeyDown(KeyCode.E))
this.transform.rotation *= Quaternion.Euler(0, 90, 0);
}
Adding a Map
So now we can move the camera, but we don’t have collisions yet. In fact, we don’t even have anything to collide with yet.
Unity3D comes with its own set of colliders you can use, but since we’re assuming movement in a 2D grid, then it’s much easier to just store a simple map in a 2D array and check what’s in the destination cell before moving into it. We’ll use this same 2D array to generate walls in the scene rather than putting cubes manually into the scene as we did in the earlier article. The following should do:
private string[] map = new[] {
"XXXXXXXXXXXX",
"X X",
"XX XX XX XX",
"XX XX XXX XX",
"XX XXX X",
"XXX X X XX X",
"X X X X",
"X XXXXXXXX X",
"X X X",
"XXX XX XX",
"X X XX X",
"XXXXXXXXXXXX",
};
Since a string behaves like an array of characters, it’s good enough to use an array of strings instead of a 2D array of characters.
Generating Walls
After the map, let’s also add a field that we can use to set the prefab we’ll use as a wall:
[SerializeField]
GameObject wallPrefab;
Back in the Unity Editor:
- Create a cube (GameObject menu -> 3D Object -> Cube) in the scene.
- Drag it from the Hierarchy pane into your Assets pane to create a prefab.
- Rename the Cube prefab in your Assets pane to Wall.
- Delete the cube in the scene.
- Select the Main Camera, then drag the Wall prefab into the Wall Prefab slot of the Game script as shown above.
Now that we have the Wall prefab set up, let’s go back into the script and add some code that will create the walls on startup:
void Start()
{
for (int z = 0; z < map.Length; z++)
{
for (int x = 0; x < map[z].Length; x++)
{
if (map[z][x] == 'X')
{
Vector3 position = new Vector3(x, 0, z);
Instantiate(wallPrefab, position, Quaternion.identity);
}
}
}
}
Because the Y-axis points upwards and we’re dealing with a flat 2D grid, it’s useful to note that Y is always zero and we’re dealing with the X- and Z-axes when moving.
Positioning the Camera
Press Play and we can get a first peek at the generated walls:
It’s clear that the camera is a little off, but we can already see that we’ve successfully generated some sort of maze. Thanks to the movement script we copied earlier, you can move around (WASD for forward, backward, left and right movement, Q to turn left, and E to turn right) and find a good starting point for the camera, e.g. (X, Y, Z) = (5, 0, 1):
Implementing Collision Detection
So now we come to the original problem: we’re able to walk through the walls. Now that we have a map, all we need to do to detect collisions is to check what’s in the square we’re moving to, before we move into it. For this, we’ll add a little helper function:
private void updatePositionIfNoCollision(Vector3 newPosition)
{
var x = System.Convert.ToInt32(newPosition.x);
var z = System.Convert.ToInt32(newPosition.z);
if (map[z][x] == ' ')
this.transform.position = newPosition;
}
This function takes the position of the square we’re about to move into as an input, then if that square is clear on the map, it updates the camera’s position. Conversely, if that square contains an ‘X’, then nothing will happen.
So in the Update() function, instead of updating the position directly when we handle the movement keys, we instead call this helper function to move only on condition that the target square is clear:
void Update()
{
if (Input.GetKeyDown(KeyCode.W))
updatePositionIfNoCollision(this.transform.position + this.transform.rotation * Vector3.forward);
else if (Input.GetKeyDown(KeyCode.S))
updatePositionIfNoCollision(this.transform.position + this.transform.rotation * Vector3.back);
else if (Input.GetKeyDown(KeyCode.A))
updatePositionIfNoCollision(this.transform.position + this.transform.rotation * Vector3.left);
else if (Input.GetKeyDown(KeyCode.D))
updatePositionIfNoCollision(this.transform.position + this.transform.rotation * Vector3.right);
else if (Input.GetKeyDown(KeyCode.Q))
this.transform.rotation *= Quaternion.Euler(0, -90, 0);
else if (Input.GetKeyDown(KeyCode.E))
this.transform.rotation *= Quaternion.Euler(0, 90, 0);
}
That’s all. If you press Play now, you’ll find that you can roam freely around the map, but you can’t move into walls.
Conclusion
Collision detection on a 2D grid is pretty easy. You just need to keep track of where you are and what’s in the square you’re moving into.