A Simple 2D Collision Map Template for You (C++ and SFML)

Started by RetroRain, November 27, 2022, 12:11:23 AM

Previous topic - Next topic

RetroRain

Hey guys.  Long time no see.

I just finished working on a simple 2D collision map system in C++, using the SFML graphics library.  The whole point of making this, is so that you can easily implement a collision system for your 2D games, without the need for any spatial partitioning.

I have found implementing collision detection to be the hardest part of game programming.

My system simply uses a little bit of math to get the tile coordinates and displaces the player accordingly.

If you want to make a game like Pacman or Zelda 1, use the 8x8 tile system.  If you want to make a game like Mario, Megaman, or Metroid, use the 16x16 tile system.

All you have to do is create and import your tilemap, select the tile mode based on the cell size of your tilemap, and create a simple 2D tilemap array.  The collision map is meant to be the backbone of your tilemap.

In other words, you will have two maps: One for your tiles (the tilemap), and one for the Collision Map (which is what this is), containing the tile properties, such as solids, spikes, water, etc.

This code is for anyone who knows C++ and SFML but has had a hard time with implementing 2D collision detection.

Another reason I made up this template, is so that I can convert it to 6502 assembly code when I go back to making a NES homebrew.

The code is in a single file.  All you have to do is copy and paste this into your IDE of choice!

NOTE: The minimal recommended player size is 16x16.  Too big, such as 64x64, will cause collision issues.

Here is the code.  Enjoy!

#include <SFML/Graphics.hpp>
#include <iostream>
#include <vector>

class Player
{
    public:
        float x, y, width, height, quarterWidth, quarterHeight, hsp, vsp, hMove, vMove, moveSpeed;
};

//Globals
Player player;
int keyUp, keyDown, keyLeft, keyRight = 0;
std::vector<std::vector<int>> map;
int cellSize = 0;
int numRows, numCols = 0;
bool gridLines = false;

void cellSizePrompt();
void gridLinesPrompt();
int getTile(float x, float y);

int main()
{
    //Prompt user to enter in the cell size
    cellSizePrompt();

    //Prompt user to toggle grid lines on or off
    gridLinesPrompt();

    //Resize array based on the cell size
    if (cellSize == 8)
    {
        map.resize(30, std::vector<int>(32, 0));
        numRows = 30;
        numCols = 32;
    }
    else
    {
        map.resize(15, std::vector<int>(16, 0));
        numRows = 15;
        numCols = 16;
    }

    //Place four solid tiles on the map
    map[4][4] = 1;
    map[4][11] = 1;
    map[10][4] = 1;
    map[10][11] = 1;

    //Set up the window
    sf::RenderWindow window(sf::VideoMode(256, 240), "");
    window.setSize(sf::Vector2u(1024, 960));
    window.setPosition(sf::Vector2i(440, 15));
    window.setFramerateLimit(60);

    //Initialize the player
    player.x = 100;
    player.y = 100;
    player.width = 16;
    player.quarterWidth = player.width / 4;
    player.height = 16;
    player.quarterHeight = player.height / 4;
    player.hsp = 0;
    player.vsp = 0;
    player.hMove = 0;
    player.vMove = 0;
    player.moveSpeed = 1;

    //Game loop
    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed) window.close();
            if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) window.close();
        }

        //Input keys
        keyUp = sf::Keyboard::isKeyPressed(sf::Keyboard::Up) * -1;
        keyDown = sf::Keyboard::isKeyPressed(sf::Keyboard::Down);
        keyLeft = sf::Keyboard::isKeyPressed(sf::Keyboard::Left) * -1;
        keyRight = sf::Keyboard::isKeyPressed(sf::Keyboard::Right);

        /////////Player horizontal movement
        player.hMove = keyLeft + keyRight;
        player.hsp = player.hMove * player.moveSpeed;
        player.x += player.hsp;
       
        /////////Player horizontal collision

        //Horizontal collision from the left
        if (player.hsp > 0)
        {
            if (getTile(player.x + player.width - 1, player.y) == 1 ||
                getTile(player.x + player.width - 1, player.y + player.quarterHeight) == 1 ||
                getTile(player.x + player.width - 1, player.y + (player.height / 2)) == 1 ||
                getTile(player.x + player.width - 1, player.y + (player.height - player.quarterHeight)) == 1 ||
                getTile(player.x + player.width - 1, player.y + player.height - 1) == 1)
            {
                int temp = int(player.x + player.width - 1) / cellSize;
                temp = temp - (player.width / cellSize);
                temp = temp * cellSize;
                player.x = temp;
            }
        }

        //Horizontal collision from the right
        if (player.hsp < 0)
        {
            if (getTile(player.x, player.y) == 1 ||
                getTile(player.x, player.y + player.quarterHeight) == 1 ||
                getTile(player.x, player.y + (player.height / 2)) == 1 ||
                getTile(player.x, player.y + (player.height - player.quarterHeight)) == 1 ||
                getTile(player.x, player.y + player.height - 1) == 1)
            {
                int temp = int(player.x) / cellSize;
                temp = temp + 1;
                temp = temp * cellSize;
                player.x = temp;
            }
        }

        /////////Player vertical movement
        player.vMove = keyUp + keyDown;
        player.vsp = player.vMove * player.moveSpeed;
        player.y += player.vsp;

        /////////Player vertical collision

        //Vertical collision from above
        if (player.vsp > 0)
        {
            if (getTile(player.x, player.y + player.height - 1) == 1 ||
                getTile(player.x + player.quarterWidth, player.y + player.height - 1) == 1 ||
                getTile(player.x + (player.width / 2), player.y + player.height - 1) == 1 ||
                getTile(player.x + (player.width - player.quarterWidth), player.y + player.height - 1) == 1 ||
                getTile(player.x + player.width-1, player.y + player.height - 1) == 1)
            {
                int temp = int(player.y + player.height - 1) / cellSize;
                temp = temp - (player.height / cellSize);
                temp = temp * cellSize;
                player.y = temp;
            }
        }

        //Vertical collision from below
        if (player.vsp < 0)
        {
            if (getTile(player.x, player.y) == 1 ||
                getTile(player.x + player.quarterWidth, player.y) == 1 ||
                getTile(player.x + (player.width / 2), player.y) == 1 ||
                getTile(player.x + (player.width - player.quarterWidth), player.y) == 1 ||
                getTile(player.x + player.width-1, player.y) == 1)
            {
                int temp = int(player.y) / cellSize;
                temp = temp + 1;
                temp = temp * cellSize;
                player.y = temp;
            }
        }

        window.clear();

        //Draw the map
        for (int i = 0; i < numRows; i++)
        {
            for (int j = 0; j < numCols; j++)
            {
                if (gridLines)
                {
                    if (map[i][j] == 0)
                    {
                        sf::RectangleShape rect(sf::Vector2f(cellSize, cellSize));
                        rect.setPosition(j * cellSize, i * cellSize);
                        rect.setFillColor(sf::Color::Transparent);
                        rect.setOutlineThickness(-1);
                        rect.setOutlineColor(sf::Color::Green);
                        window.draw(rect);
                    }
                }
                if (map[i][j] == 1)
                {
                    sf::RectangleShape rect(sf::Vector2f(cellSize, cellSize));
                    rect.setPosition(j * cellSize, i * cellSize);
                    rect.setFillColor(sf::Color::Transparent);
                    rect.setOutlineThickness(-1);
                    rect.setOutlineColor(sf::Color::Red);
                    window.draw(rect);
                }
            }
        }

        //Draw the player
        sf::RectangleShape rect(sf::Vector2f(player.width, player.height));
        rect.setPosition(player.x, player.y);
        rect.setFillColor(sf::Color::Transparent);
        rect.setOutlineThickness(-1);
        rect.setOutlineColor(sf::Color::Yellow);
        window.draw(rect);
       
        window.display();
    }
    return 0;
}

void cellSizePrompt()
{
    std::cout << "Please enter the cell size.  Type 8 for 8x8 or 16 for 16x16: ";
    std::cin >> cellSize;
    if (cellSize != 8 && cellSize != 16) cellSizePrompt();
}

void gridLinesPrompt()
{
    std::cout << "Grid lines on or off?  Type 0 for NO, or any other number for YES: ";
    std::cin >> gridLines;
}

int getTile(float x, float y)
{
    return (map[int(y)/cellSize][int(x)/cellSize]);
}
My YouTube Channel: RetroRainZX85 - https://www.youtube.com/channel/UCdHK6fSwUlcM-q8_EgZQfdw

RetroRain

#1
I added two more collision points to all four sides of the player object, for a more robust collision detection.  The code has been updated in the first post.

Also, I updated the note.  The minimal recommended player size is 16x16.  Too big, such as 64x64, will cause collision issues.

And last but not least, a video and some screenshots of the template in action:

https://www.youtube.com/watch?v=MmyrOkBuIx4


My YouTube Channel: RetroRainZX85 - https://www.youtube.com/channel/UCdHK6fSwUlcM-q8_EgZQfdw

0liveTr33

Chain links!!! left is less than left and right is less than left etc... i did this stuft over and over collisions can be a butt. but what about pixel perfect coliision? A bw map contrasted over another sillhouette map now whats up w dat stuff. YOOOOOO!!!!!!!! YAA TRICK YAAA :angel: