Creating Colliders by Magic

So we realized that our wall colliders were too many to be efficient. Each wall segment the size of one tile had its own collider which was created by the tile itself at the time of its instantiation. Every wall instance knew what kind of wall segment it was (information that was also used for determining which texture to load), and shaped the collider accordingly. (We had some struggles here because the shape of certain wall segments called for a more complex collider than a rectangle. It could have been achieved with two rectangular colliders, but this would have caused further problems with looping through all wall colliders when using them for collision checking; so we stuck with only one for each segment but adjusted the size of some colliders until there were no gaps.) In the picture below, the colliders can be seen visualized in yellow. Note that the angled perspective entails the colliders being placed at the bottom of the walls.

old_colliders_EDIT2

The point is that many colliders in a row just as well could be merged into one big collider to greatly reduce the number of collision calculations to be done. I coded a function that looks at the tile map and creates the colliders separately from the walls, storing pointers to them in a separate std::vector for later access. The result looks like below.

What it basically does is finding the end segments and creating colliders between them. It does so by looping through the walls looking at one at a time, starting at the upper left corner. The tile map can be seen below.

First of all, we copy the numbers in the tile map to an std::vector for easier access, like below. (The numbers in the tile map are actually separated by commas and it doesn’t contain any blank spaces like above. Those just make it align more prettily for a blog post.)

std::string number;
    for (int i = 0; i <= height - 1; i++)
    {
        for (int j = 0; j <= width - 1; j++)
        {
            std::getline(dataStream, number, ',');
            wallData[j + i * width] = stoi(number);
        }
    }

We do horizontal and vertical walls separately. The first round we do horizontal, and start by looking for any segment that works as a left end. (There are four different: 1, 3, 8 & 14.) After finding one, we step to the right until finding any segment that works as a right end (2, 4, 7 & 14). We then create a collider between these two tiles (the height being the constant value of a wall’s thickness). After this we continue from the rightmost tile and repeat the process of finding a left end segment and then a right end segment. When having reached the end of the row, we go to the row below, and so on. The code for this can be seen below:

int index = 0;
while (index < size)
{
    if (wallData[index] == 1 || wallData[index] == 3 || wallData[index] == 8 || wallData[index] == 14) // Search for left end segments
    {
        for (int search = index + 1; (search % width) != 0; search++) // Search until on the first position in a row, which means search while on the current row.
        {
            if (wallData[search] == 2 || wallData[search] == 4 || wallData[search] == 13 || wallData[search] == 7) // Search for right end segments
            {
                int colWidth, x, y, addLeft = 0, addRight = 0; // addLeft and addRight are to increase the width for the horizontal straight end pieces which are somewhat special
                if (wallData[index] == 14)
                    addLeft = 48;
                else
                    addLeft = 0;
                if (wallData[search] == 13)
                    addRight = 48;
                else
                    addRight = 0;
                colWidth = (search - index) * 128 + 32 + addLeft + addRight;

                x = (index % width) * 128 - 16 - addLeft;
                y = (floor(index / width) + 1) * 128 - 16; // floor() is just for clarity. Division between integers doesn't produce a remainder

                Collider* collider = new Collider(x, y); // Create the new collider
                collider->SetWidthHeight(colWidth, 32); // The height of horizontal walls is 32, the walls' thickness
                m_wall_colliders.push_back(collider); // Put the new collider in an std::vector for easy access
                index = search; // Set the position for the next search
                break;
            }
        }
    }
    index++; // Step one index to the right
}

 

Creating the vertical walls is very similar, except counting with adjacent positions vertically requires a bit more thinking than horizontally. For example, going one position down means adding the map’s width. I simplified the iteration so some unnecessary positions are checked, but this is insignificant in terms of optimization since it is still quite light and, most importantly, only performed at the start of each level.

index = 0;
<pre>while (index < size)
{
    if (wallData[index] == 1 || wallData[index] == 6 || wallData[index] == 2 || wallData[index] == 10)
    {
        for (int search = index; search < size; search += width)
        {
            if (wallData[search] == 3 || wallData[search] == 4 || wallData[search] == 11 || wallData[search] == 9)
            {
                int colHeight = (floor(search / width) - floor(index / width)) * 128 + 32;

                int x = (index % width) * 128 - 16;
                int y = floor((index / width) + 1) * 128 - 16;

                Collider* collider = new Collider(x, y);
                collider->SetWidthHeight(32, colHeight);
                m_wall_colliders.push_back(collider);
                index++;
                break;
            }
        }
    }
    index++;
}
Advertisements

2 thoughts on “Creating Colliders by Magic

  1. Greetings Johan, this was a very interesting blog post with a lot of interesting information. I have not even considered optimizing collision in such a way to make a collision box for multiple “objects”. This is a very smart solution with a lot of detailed description on why and how you coded it. This is the first blog post that i have read that will help me on progressing with my project (if i work on separate collisions). Even though this type of collision is only applicable on static objects/walls this is still very valuable for loading levels.
    I do not really have anything to complain about since everything is explained in detail with a lot of describing pictures.
    So i will fill out with my comment with a question, are the colliders line colliders or are they made like a square out of vertices?
    I would assume making line colliders dynamically would have been easier then the way you did it, and maybe even more optimized during run time, though I am not sure.
    Overall i have received a lot of insight from your blog post, and it will be easier for me to create seperate collision boxes now, or specifically optimized collision checking for multiple objects.

    Like

    1. Thank you for reading! Each collider is an instance of a Collider class, which contains position, width and height. I think this is the easiest way since I know that all the walls have a thickness (the same thickness, even) so each wall would have needed several line colliders anyway. I hope I understood your question correctly.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s