Unity3D: Dungeon Crawler Movement with Collisions

Strolling through the 11th level of Eye of the Beholder (1991).

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

Creating a new Unity3D 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

Attach the new Game script to the Main Camera object.
  1. In the Assets pane, right-click and select Create -> C# Script. You can call it whatever you want, but I’ll call it Game.
  2. Drag it onto your Main Camera object.
  3. Double-click the script to open it in your IDE (e.g. Visual Studio Code).
  4. 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;
Use the Wall prefab as the input GameObject to the Game script.

Back in the Unity Editor:

  1. Create a cube (GameObject menu -> 3D Object -> Cube) in the scene.
  2. Drag it from the Hierarchy pane into your Assets pane to create a prefab.
  3. Rename the Cube prefab in your Assets pane to Wall.
  4. Delete the cube in the scene.
  5. 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:

An initial view of 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):

The view from (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.

10 Years of Programmer’s Ranch

Today marks the 10th anniversary since the launch of my earlier tech blog, Programmer’s Ranch, with its first article. To celebrate this occasion, I’d like to reflect on the impact of this blog.

History

Programmer’s Ranch today.

Having launched my first website back in 2002, I was writing language tutorials as early as 2003. These helped me consolidate the web technology (and, later, programming languages) I was learning, while also sharing that knowledge with others. I enjoyed this, but it’s also true that lengthy technical writing is tedious, time-consuming, and gets obsolete relatively quickly. I soon grew weary of formal language tutorials and decided to shift towards more casual articles in which I could write about any topics I found interesting at the time, without having to adhere to any particular structure.

Programmer’s Ranch was launched on 2nd May 2013, after a series of websites and blogs that preceded it, and during some very unfortunate life circumstances. Still early in my career at the time, I observed that many programming topics aren’t really as hard as they seem; it would be much easier to learn them if they were explained in a simple and clear manner. I sought to develop this idea with Programmer’s Ranch by regularly writing fun and concise articles that explained various programming and other tech concepts.

Between 2nd May 2013 and 4th October 2014 – less than a year and a half – I published 91 articles at Programmer’s Ranch. By then, it was clear that the Blogger platform wasn’t great for writing technical articles, and so I launched Gigi Labs on 24th October 2014 to continue writing on a better platform. With over 300 articles published here since then, my writing style, focus and frequency have changed over the years, but they continue to build upon the foundations and values that Programmer’s Ranch established ten years ago.

Writing Style

As I mentioned earlier, by the time I launched Programmer’s Ranch, I felt that programming didn’t need to be so hard for beginners. I was frustrated by unnecessary complexity and poor communication that led to so many obstacles to learning, even in a time when internet adoption was widespread. Today, an additional decade of IT and life experience has only served to reinforce this idea. Over the years, I’ve observed that poor communication, incompetence, bureaucracy and even corruption have not only brought many IT companies down to their knees, but also adversely affect various aspects of everyday life.

After some time trying to find my voice with the blog, I wrote the Programmer’s Ranch Writing Style Guide, hoping to keep my own writing of consistent quality and also inspire others. It’s nothing more than a few tips defining the general writing style that I felt worked for Programmer’s Ranch. The writing style has simplicity and clarity at its core, and is also reflected in the blog’s tagline: “More beef. Less bull.” It’s a radical departure from formal scientific papers, lengthy books, and various kinds of documentation which actually make learning harder.

Documentation is, in my opinion, one of the biggest failures of the software industry. Many companies and individuals seem to think of documentation as reference material, like an encyclopaedia. For instance, they publish a list of classes or endpoints exposed by their API and expect users of their software to make sense of them. In reality, what users usually need when working with a new library or API for the first time is basic usage examples. Given that (in my experience) most developers don’t like to write, the proliferation of open source projects hasn’t quite improved the situation.

Poor writing is, in reality, a specific case of poor communication. I can think of many examples outside of technical writing where overcomplication and lack of clarity cause problems. For instance, mystery meat navigation shifting to household furniture and appliances with modern/minimal designs, agile development approaches exacerbating the problems they were designed to solve, and the automation of customer service channels leaving customers struggling to ask a basic question about a service they’re paying for.

As a result, I feel it’s a breath of fresh air to read a technical article that is clear and concise once in a while. Even though there are countless tutorials about basic topics like HTML and CSS, it’s still nice (and helpful for newcomers) whenever someone writes about them in an accessible manner. Tania Rascia‘s website is the closest example of the Programmer’s Ranch writing style that I’ve found in the wild, and her focus on quality content and distancing from “ads, trackers, social media, affiliates, and sponsored posts” is quite likely behind its success.

Why I Write

There are many reasons to write on the web. The more altruistic of these is to share knowledge. Writing is a medium that endures, and although technical topics you write about may not have as long-lasting an impact as the works of Shakespeare, it is still very common for an article to help people for many years to come. Also, writing is easy to search and skim through, unlike other media such as audio or video.

There are also more personal and individual reasons to write, including:

  • Teaching others helps consolidate one’s own knowledge.
  • It’s therapeutic, sometimes requiring a level of focus that enables flow.
  • It can help demonstrate expertise and build one’s own reputation.
  • It helps remember topics and solutions from several years earlier.
  • It’s useful to save time arguing about the same topics over and over again.

Writing on the web does also have some disadvantages that the aspiring tech blogger would do well to be aware of:

  • It takes a lot of time to write good quality articles.
  • You won’t necessarily get any tangible benefit from it.
  • More specialised and unique articles will likely get less attention.
  • There are lots of rude and ungrateful people on the internet.

Conclusion

When I launched Programmer’s Ranch ten years ago, it was the beginning of my own journey towards maturity in technical writing. Although I haven’t always written good quality articles, I believe that many of them have been useful to a large audience and continue to be so. Their success lies not only in their content but also in the way it is communicated.

The web, the IT industry, and society in general are filled with content that is mediocre at best, making it hard for us to find the information we need even in an age where information is abundant and easy to obtain. There’s a lot we can improve in society by raising the bar, communicating better, and focusing on quality in the things that are important to us.