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 Super Mario Bros., Megaman, or Metroid, use the 16x16 tile system.

All you have to do is import your tilemap, select the tile mode based on the cell size of your tile map, 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!

One minor thing.  If you decide to use 8x8 tile mode, you are better off using a player object with the dimensions 16x16.  Any bigger, such as 32x32, can cause the player to walk through solid tiles.  The same can happen if your player is too big, such as 64x64.  The system checks 3 collision points on the sides of the player to handle collision detection without the need for any spatial partitioning system.

Here is the code.  Enjoy!

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

class Player
{
public:
float x, y, width, height, 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.height = 16;
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.height / 2)) == 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.height / 2)) == 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.width / 2), 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.width / 2), 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