I'm learning SFML and making a basic 2D medieval game. As you can see in the image, the enemy collides with me when I am facing the right direction and hence it attacks me, but when I face the left direction it does not collide and hence walks straight. How do I fix this? I need it to collide even if I am facing the left direction. I have even outputted on the console if there is a collision, but it only outputs a collision if the player faces right as a collision is only happening in that case for some reason.
main.cpp
#include<iostream>
#include<SFML\Graphics.hpp>
#include "Collision.hpp"
#include "Enemy.h"
#include "Player.h"
using namespace std;
int main() {
sf::RenderWindow window(sf::VideoMode(1280, 720), "My title", sf::Style::Titlebar | sf::Style::Close);
sf::Texture backgroundTexture;
sf::RectangleShape Background;
backgroundTexture.loadFromFile("2_game_background.png");
backgroundTexture.setSmooth(true);
Background.setSize(sf::Vector2f(1280.0f, 720.0f));
Background.setOrigin(sf::Vector2f(0.0f, 0.0f));
Background.setPosition(0, 0);
Background.setTexture(&backgroundTexture);
sf::Texture playerTexture;
Collision::CreateTextureAndBitmask(playerTexture, "knight.png");
playerTexture.setSmooth(true);
Player player(&playerTexture, sf::Vector2u(10, 3),0.06f,170.0f);
sf::Texture reaper2Texture;
Collision::CreateTextureAndBitmask(reaper2Texture, "reaper2.png");
reaper2Texture.setSmooth(true);
Enemy enemy(&reaper2Texture, sf::Vector2u(11, 3), 0.06f, 100.0f);
float deltaTime = 0.0f;
sf::Clock clock;
while (window.isOpen()) {
deltaTime = clock.restart().asSeconds();
sf::Event evnt;
while (window.pollEvent(evnt)) {
switch (evnt.type) {
case sf::Event::Closed:
window.close();
break;
}
}
if (Collision::PixelPerfectTest(player.body, enemy.body)) {
enemy.UpdateAttack(deltaTime);
player.UpdateMove(deltaTime, true, enemy.faceRight);
cout << "collision" << endl;
}
else {
enemy.UpdateMove(deltaTime);
player.UpdateMove(deltaTime, false, enemy.faceRight);
cout << "no collision" << endl;
}
window.clear();
window.draw(Background);
player.Draw(window);
enemy.Draw(window);
window.display();
}
}
Animation.h
#pragma once
#include<SFML/Graphics.hpp>
class Animation
{
public:
Animation(sf::Texture* texture,sf::Vector2u imageCount,float switchTime);
sf::IntRect uvRect;
void Update(int row, float deltaTime, bool faceRight);
float switchTime;
private:
sf::Vector2u imageCount;
sf::Vector2u currentImage;
float totalTime;
};
Collision.hpp(from official SFML website)
#ifndef COLLISION_H
#define COLLISION_H
namespace Collision {
//////
/// Test for a collision between two sprites by comparing the alpha values of overlapping pixels
/// Supports scaling and rotation
/// AlphaLimit: The threshold at which a pixel becomes "solid". If AlphaLimit is 127, a pixel with
/// alpha value 128 will cause a collision and a pixel with alpha value 126 will not.
///
/// This functions creates bitmasks of the textures of the two sprites by
/// downloading the textures from the graphics card to memory -> SLOW!
/// You can avoid this by using the "CreateTextureAndBitmask" function
//////
bool PixelPerfectTest(const sf::Sprite& Object1, const sf::Sprite& Object2, sf::Uint8 AlphaLimit = 0);
//////
/// Replaces Texture::loadFromFile
/// Load an imagefile into the given texture and create a bitmask for it
/// This is much faster than creating the bitmask for a texture on the first run of "PixelPerfectTest"
///
/// The function returns false if the file could not be opened for some reason
//////
bool CreateTextureAndBitmask(sf::Texture& LoadInto, const std::string& Filename);
//////
/// Test for collision using circle collision dection
/// Radius is averaged from the dimensions of the sprite so
/// roughly circular objects will be much more accurate
//////
bool CircleTest(const sf::Sprite& Object1, const sf::Sprite& Object2);
//////
/// Test for bounding box collision using the Separating Axis Theorem
/// Supports scaling and rotation
//////
bool BoundingBoxTest(const sf::Sprite& Object1, const sf::Sprite& Object2);
}
#endif /* COLLISION_H */
Enemy.h
#pragma once
#include<SFML/Graphics.hpp>
#include "Animation.h"
class Enemy
{
public:
Enemy(sf::Texture* texture, sf::Vector2u imageCount, float switchTime, float speed);
void UpdateMove(float deltaTime);
void UpdateAttack(float deltaTime);
void Draw(sf::RenderWindow& window);
bool faceRight;
sf::Sprite body;
private:
Animation animation;
unsigned int row;
float speed;
};
Player.h
#pragma once
#include<SFML/Graphics.hpp>
#include "Animation.h"
class Player
{
public:
Player(sf::Texture* texture, sf::Vector2u imageCount, float switchTime,float speed);
void UpdateMove(float deltaTime,bool collide,bool enemyFaceRight);
void Draw(sf::RenderWindow& window);
sf::Sprite body;
private:
Animation animation;
unsigned int row;
float speed;
bool faceRight;
};
Animation.cpp
#include "Animation.h"
Animation::Animation(sf::Texture* texture, sf::Vector2u imageCount, float switchTime) {
this->imageCount = imageCount;
this->switchTime = switchTime;
totalTime = 0.0f;
currentImage.x = 0;
uvRect.width = texture->getSize().x / (float)imageCount.x;
uvRect.height = texture->getSize().y / (float)imageCount.y;
}
void Animation::Update(int row, float deltaTime,bool faceRight) {
currentImage.y = row;
totalTime += deltaTime;
if (totalTime >= switchTime) {
totalTime -= switchTime;
currentImage.x++;
if (currentImage.x >= imageCount.x) {
currentImage.x = 0;
}
}
uvRect.top = currentImage.y * uvRect.height;
if (faceRight) {
uvRect.left = currentImage.x * abs(uvRect.width);
uvRect.width = abs(uvRect.width);
}
else {
uvRect.left = (currentImage.x + 1) * abs(uvRect.width);
uvRect.width = -abs(uvRect.width);
}
}
Collision.cpp(also from official SFML website)
#include <SFML/Graphics.hpp>
#include <map>
#include "Collision.hpp"
namespace Collision
{
class BitmaskManager
{
public:
~BitmaskManager() {
std::map<const sf::Texture*, sf::Uint8*>::const_iterator end = Bitmasks.end();
for (std::map<const sf::Texture*, sf::Uint8*>::const_iterator iter = Bitmasks.begin(); iter != end; iter++)
delete[] iter->second;
}
sf::Uint8 GetPixel(const sf::Uint8* mask, const sf::Texture* tex, unsigned int x, unsigned int y) {
if (x > tex->getSize().x || y > tex->getSize().y)
return 0;
return mask[x + y * tex->getSize().x];
}
sf::Uint8* GetMask(const sf::Texture* tex) {
sf::Uint8* mask;
std::map<const sf::Texture*, sf::Uint8*>::iterator pair = Bitmasks.find(tex);
if (pair == Bitmasks.end())
{
sf::Image img = tex->copyToImage();
mask = CreateMask(tex, img);
}
else
mask = pair->second;
return mask;
}
sf::Uint8* CreateMask(const sf::Texture* tex, const sf::Image& img) {
sf::Uint8* mask = new sf::Uint8[tex->getSize().y * tex->getSize().x];
for (unsigned int y = 0; y < tex->getSize().y; y++)
{
for (unsigned int x = 0; x < tex->getSize().x; x++)
mask[x + y * tex->getSize().x] = img.getPixel(x, y).a;
}
Bitmasks.insert(std::pair<const sf::Texture*, sf::Uint8*>(tex, mask));
return mask;
}
private:
std::map<const sf::Texture*, sf::Uint8*> Bitmasks;
};
BitmaskManager Bitmasks;
bool PixelPerfectTest(const sf::Sprite& Object1, const sf::Sprite& Object2, sf::Uint8 AlphaLimit) {
sf::FloatRect Intersection;
if (Object1.getGlobalBounds().intersects(Object2.getGlobalBounds(), Intersection)) {
sf::IntRect O1SubRect = Object1.getTextureRect();
sf::IntRect O2SubRect = Object2.getTextureRect();
sf::Uint8* mask1 = Bitmasks.GetMask(Object1.getTexture());
sf::Uint8* mask2 = Bitmasks.GetMask(Object2.getTexture());
// Loop through our pixels
for (int i = Intersection.left; i < Intersection.left + Intersection.width; i++) {
for (int j = Intersection.top; j < Intersection.top + Intersection.height; j++) {
sf::Vector2f o1v = Object1.getInverseTransform().transformPoint(i, j);
sf::Vector2f o2v = Object2.getInverseTransform().transformPoint(i, j);
// Make sure pixels fall within the sprite's subrect
if (o1v.x > 0 && o1v.y > 0 && o2v.x > 0 && o2v.y > 0 &&
o1v.x < O1SubRect.width && o1v.y < O1SubRect.height &&
o2v.x < O2SubRect.width && o2v.y < O2SubRect.height) {
if (Bitmasks.GetPixel(mask1, Object1.getTexture(), (int)(o1v.x) + O1SubRect.left, (int)(o1v.y) + O1SubRect.top) > AlphaLimit&&
Bitmasks.GetPixel(mask2, Object2.getTexture(), (int)(o2v.x) + O2SubRect.left, (int)(o2v.y) + O2SubRect.top) > AlphaLimit)
return true;
}
}
}
}
return false;
}
bool CreateTextureAndBitmask(sf::Texture& LoadInto, const std::string& Filename)
{
sf::Image img;
if (!img.loadFromFile(Filename))
return false;
if (!LoadInto.loadFromImage(img))
return false;
Bitmasks.CreateMask(&LoadInto, img);
return true;
}
sf::Vector2f GetSpriteCenter(const sf::Sprite& Object)
{
sf::FloatRect AABB = Object.getGlobalBounds();
return sf::Vector2f(AABB.left + AABB.width / 2.f, AABB.top + AABB.height / 2.f);
}
sf::Vector2f GetSpriteSize(const sf::Sprite& Object)
{
sf::IntRect OriginalSize = Object.getTextureRect();
sf::Vector2f Scale = Object.getScale();
return sf::Vector2f(OriginalSize.width * Scale.x, OriginalSize.height * Scale.y);
}
bool CircleTest(const sf::Sprite& Object1, const sf::Sprite& Object2) {
sf::Vector2f Obj1Size = GetSpriteSize(Object1);
sf::Vector2f Obj2Size = GetSpriteSize(Object2);
float Radius1 = (Obj1Size.x + Obj1Size.y) / 4;
float Radius2 = (Obj2Size.x + Obj2Size.y) / 4;
sf::Vector2f Distance = GetSpriteCenter(Object1) - GetSpriteCenter(Object2);
return (Distance.x * Distance.x + Distance.y * Distance.y <= (Radius1 + Radius2) * (Radius1 + Radius2));
}
class OrientedBoundingBox // Used in the BoundingBoxTest
{
public:
OrientedBoundingBox(const sf::Sprite& Object) // Calculate the four points of the OBB from a transformed (scaled, rotated...) sprite
{
sf::Transform trans = Object.getTransform();
sf::IntRect local = Object.getTextureRect();
Points[0] = trans.transformPoint(0.f, 0.f);
Points[1] = trans.transformPoint(local.width, 0.f);
Points[2] = trans.transformPoint(local.width, local.height);
Points[3] = trans.transformPoint(0.f, local.height);
}
sf::Vector2f Points[4];
void ProjectOntoAxis(const sf::Vector2f& Axis, float& Min, float& Max) // Project all four points of the OBB onto the given axis and return the dotproducts of the two outermost points
{
Min = (Points[0].x * Axis.x + Points[0].y * Axis.y);
Max = Min;
for (int j = 1; j < 4; j++)
{
float Projection = (Points[j].x * Axis.x + Points[j].y * Axis.y);
if (Projection < Min)
Min = Projection;
if (Projection > Max)
Max = Projection;
}
}
};
bool BoundingBoxTest(const sf::Sprite& Object1, const sf::Sprite& Object2) {
OrientedBoundingBox OBB1(Object1);
OrientedBoundingBox OBB2(Object2);
// Create the four distinct axes that are perpendicular to the edges of the two rectangles
sf::Vector2f Axes[4] = {
sf::Vector2f(OBB1.Points[1].x - OBB1.Points[0].x,
OBB1.Points[1].y - OBB1.Points[0].y),
sf::Vector2f(OBB1.Points[1].x - OBB1.Points[2].x,
OBB1.Points[1].y - OBB1.Points[2].y),
sf::Vector2f(OBB2.Points[0].x - OBB2.Points[3].x,
OBB2.Points[0].y - OBB2.Points[3].y),
sf::Vector2f(OBB2.Points[0].x - OBB2.Points[1].x,
OBB2.Points[0].y - OBB2.Points[1].y)
};
for (int i = 0; i < 4; i++) // For each axis...
{
float MinOBB1, MaxOBB1, MinOBB2, MaxOBB2;
// ... project the points of both OBBs onto the axis ...
OBB1.ProjectOntoAxis(Axes[i], MinOBB1, MaxOBB1);
OBB2.ProjectOntoAxis(Axes[i], MinOBB2, MaxOBB2);
// ... and check whether the outermost projected points of both OBBs overlap.
// If this is not the case, the Separating Axis Theorem states that there can be no collision between the rectangles
if (!((MinOBB2 <= MaxOBB1) && (MaxOBB2 >= MinOBB1)))
return false;
}
return true;
}
}
Enemy.cpp
#include "Enemy.h"
#include<SFML/Graphics.hpp>
#include<iostream>
using namespace std;
Enemy::Enemy(sf::Texture* texture, sf::Vector2u imageCount, float switchTime, float speed) :
animation(texture, imageCount, switchTime) {
this->speed = speed;
row = 0;
faceRight = true;
body.setScale(sf::Vector2f(0.18f, 0.235f));
body.setPosition(-20.0f, 479.0f);
body.setTexture(*texture);
}
void Enemy::UpdateMove(float deltaTime) {
sf::Vector2f movement(0.0f, 0.0f);
//if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) {
// movement.x -= speed * deltaTime;
//}
//if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) {
movement.x += speed * deltaTime;
row = 2;
if (movement.x > 0.0f) {
faceRight = true;
}
else {
faceRight = false;
}
animation.Update(row, deltaTime, faceRight);
body.setTextureRect(animation.uvRect);
body.move(movement);
}
void Enemy::UpdateAttack(float deltaTime)
{
sf::Vector2f movement(0.0f, 0.0f);
//if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) {
// movement.x -= speed * deltaTime;
//}
//if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) {
//movement.x += speed * deltaTime;
row = 1;
//if (movement.x > 0.0f) {
// faceRight = true;
//}
//else {
// faceRight = false;
//}
animation.Update(row, deltaTime, faceRight);
body.setTextureRect(animation.uvRect);
//body.move(movement);
}
void Enemy::Draw(sf::RenderWindow& window) {
window.draw(body);
}
Player.cpp
#include "Player.h"
#include<SFML/Graphics.hpp>
#include<iostream>
using namespace std;
Player::Player(sf::Texture* texture, sf::Vector2u imageCount, float switchTime, float speed) :
animation(texture,imageCount,switchTime){
this->speed = speed;
row = 0;
faceRight = true;
body.setScale(sf::Vector2f(0.25f, 0.25f));
body.setPosition(550.0f, 490.0f);
body.setTexture(*texture);
}
void Player::UpdateMove(float deltaTime,bool collide, bool enemyFaceRight) {
sf::Vector2f movement(0.0f, 0.0f);
if (collide) {
if (enemyFaceRight) {
if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) {
movement.x += speed * deltaTime;
}
}
else {
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) {
movement.x -= speed * deltaTime;
}
}
}
else {
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) {
movement.x -= speed * deltaTime;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) {
movement.x += speed * deltaTime;
}
}
if (movement.x == 0) {
row = 1;
}
else {
row = 2;
if (movement.x > 0.0f) {
faceRight = true;
}
else {
faceRight = false;
}
}
if (sf::Mouse::isButtonPressed(sf::Mouse::Button::Left)) {
row = 0;
}
animation.Update(row, deltaTime, faceRight);
body.setTextureRect(animation.uvRect);
if (!(sf::Mouse::isButtonPressed(sf::Mouse::Button::Left))) {
body.move(movement);
}
}
void Player::Draw(sf::RenderWindow& window) {
window.draw(body);
}