Gravity and collision detection in SFML - c++

This is my first attempt with SFML and game development, and I'm having issues with the collision and gravity.
I'm making a 2D platformer game using a tilemap system.
Collision seems to be working(slightly) but is very choppy and I just can't seem to get gravity to work properly. I've tried a few different tutorials but I cant get anything working and I'm at a bit of a loss right now.
If someone could point out what I'm missing here it'd be greatly appreciated!!
Heres the code I'm using for these elements:
In PlayerSprite class(attempt at gravity)
PlayerSprite::PlayerSprite(const sf::Vector2f& size) : AnimatedSprite(size)
{
playerPos = (sf::Vector2f(300.0f, 400.0f));
dead = false;
jumpHeight = 5.f;
scale = 50.f;
accelGravity = 0.5f;
maxGravity = 5.f;
velocity.x = 2.0f;
velocity.y = 2.0f;
playerTexture.loadFromFile("gfx/spritemansheet.png");
setSize(sf::Vector2f(48, 48));
setPosition(playerPos);
setTexture(&playerTexture);
}
PlayerSprite::~PlayerSprite()
{
}
void PlayerSprite::update(float dt)
{
onGround = false;
if (input->isKeyDown(sf::Keyboard::A)) {
input->setKeyUp(sf::Keyboard::A);
playerPos.x -= (dt * step) * 5;
setPosition(playerPos);
//currentAnimation = &walkBack;
}
if (input->isKeyDown(sf::Keyboard::D)) {
input->setKeyUp(sf::Keyboard::D);
playerPos.x += (dt * step) * 5;
setPosition(playerPos);
//currentAnimation = &walk;
}
if (input->isKeyDown(sf::Keyboard::W) ) {
input->setKeyUp(sf::Keyboard::Space);
playerPos.x += (dt * step) * 5;
setPosition(playerPos);
//currentAnimation = &jump;
velocity.y = -5.f * -1;
}
if (onGround == false) {
velocity.y += accelGravity;
if (velocity.y > maxGravity) {
velocity.y = maxGravity;
}
}
if (sf::Mouse::isButtonPressed(sf::Mouse::Left))
{
input->setMouseLeftUp(sf::Mouse::Left);
currentAnimation = &attack;
}
}
void PlayerSprite::setInput(Input* newInp)
{
input = newInp;
}
void PlayerSprite::collisionResponse(Sprite* sp)
{
if (velocity.x > 0) {
velocity.x = 0.f;
}
if (velocity.x < 0) {
velocity.x = 0.f;
}
if (velocity.y > 0) {
velocity.y = 0.f;
onGround = true;
}
if (velocity.y < 0) {
velocity.y = 0.f;
}
setPosition(getPosition().x, sp->getPosition().y - getSize().y);
}
Collision Detection in Game.cpp
void Game::update(float dt)
{
player.update(dt);
manager.update(dt);
std::vector<Tile>* world = worldMap.getScene();
for (int i = 0; i < (int)world->size(); i++) {
if ((*world)[i].isAlive()) {
if (checkGroundBounding(&player, &(*world)[i])) {
player.collisionResponse(&(*world)[i]);
}
}
}
void Game::render() {
beginDraw();
window->draw(bg);
window->draw(player);
manager.render(window);
worldMap.render(window);
endDraw();
}
bool Game::checkGroundBounding(PlayerSprite* b1, Tile* b2)
{
//get radius of sprites
float r1 = b1->getSize().x / 2;
float r2 = b2->getSize().x / 2;
float xposb1 = b1->getPosition().x + r1;
float xposb2 = b2->getPosition().x + r2;
float yposb1 = b1->getPosition().y + r1;
float yposb2 = b2->getPosition().y + r2;
if (pow(xposb2 - xposb1, 2) + pow(yposb2 - yposb1, 2) < pow(r1 + r2, 2)) {
return true;
}
return false;
}

you're collision is super wonky, simply set a oldPos variable to your player position, then do your movement code but only for the X axis, if collision is detected, set the position to oldPos, then set oldPos to the current position again, and repeat for the Y axis:
Pseudocode example:
//X axis
oldPos = position
if(leftKey):
position.x -= speed
if(rightKey):
position.x += speed
if(collision):
position = oldPos
//Y axis
oldPos = position
if(upKey):
position.y -= speed
if(downKey):
position.y += speed
if(collision):
position = oldPos
also you can replace your tile iteration with:
for(Tile t : *world){
t->doStuff();
}
I also don't recommend declaring your tile vector in your update function unless you hate performance

Related

C++/SFML Super Mario game collision problems with blocks

I was making a super-mario-like game in C++/SFML and while trying to write some code for Mario collisions, I had some problems: The character, when collides with a vertical stack of blocks while moving along the horizontal axis, get stuck on one side of the blocks, like if it is walking on an invisible block. I tried to modify the collision function like making mario to collide with blocks horizontally only if his position related to blocks is contained into the block coordinates.
I include some code for the movement(keyPressed is a function that returns the key pressed):
void Player::movement(Time& gameTime) {
player.move(v * gameTime.asSeconds());
view.setCenter(player.getPosition().x + 16, 590);
if (keyPressed(up) && !jumping) {
jumping = true;
v.y = jumpSpeed;
}
if (v.y > 200) {
jumping = true;
}
else {
crouch = false;
}
if (keyPressed(left)) {
if (v.x > -moveSpeed) {
v.x -= 100;
}
else {
v.x = -moveSpeed;
}
}
else if (keyPressed(right)) {
noKeyPressed = false;
if (v.x < moveSpeed) {
v.x += 100;
}
else {
v.x = moveSpeed;
}
}
else {
if (v.x < -100) {
v.x += 100;
}
else if (v.x > 100) {
v.x -= 100;
}
else {
v.x = 0;
}
}
gravity();
if (big) { //Big is a variable that tells me if mario is big or small
heightValue = 33; //heightValue is a variable that stores the sprite height in pixels
}
else {
heightValue = 16;
}
}
void Player::gravity() {
if (!big) {
if (player.getPosition().y + 32 < 1100) {
if (v.y < maxSpeed) {
v.y += 100;
}
}
if (alive) { //This is useful to check if big mario has fallen
if (player.getPosition().y + 32 >= 1200) {
player.setPosition(player.getPosition().x, 1200 - 32);
jumping = false;
alive = false;
}
}
}
else {
if (player.getPosition().y + 64 < 1100) {
if (v.y < maxSpeed) {
v.y += 100;
}
}
if (alive) { //This is useful to check if small mario has fallen
if (player.getPosition().y + 64 >= 1200) {
player.setPosition(player.getPosition().x, 1200 - 64);
jumping = false;
alive = false;
}
}
}
}
And the collision function, where the block class has 4 small blocks around the block sprite that simplify collisions:
void Player::collisions(Block* block) {
if (this->player.getGlobalBounds().intersects(block->up.getGlobalBounds())) {
if (!big) {
if (this->player.getPosition().y + heightValue <= block->block.getPosition().y) {
this->player.setPosition(this->player.getPosition().x, block->up.getPosition().y - 32);
v.y = 0;
jumping = false;
score = 100;
}
}
else {
if (this->player.getPosition().y + heightValue <= block->block.getPosition().y) {
this->player.setPosition(this->player.getPosition().x, block->up.getPosition().y - 64);
v.y = 0;
jumping = false;
score = 100;
}
}
}
if (this->player.getGlobalBounds().intersects(block->down.getGlobalBounds())) {
this->player.setPosition(this->player.getPosition().x, block->down.getPosition().y + 1);
v.y = 0;
}
if (this->player.getGlobalBounds().intersects(block->left.getGlobalBounds()) && v.x > 0) {
this->player.setPosition(block->left.getPosition().x - 32, this->player.getPosition().y);
}
else if (this->player.getGlobalBounds().intersects(block->right.getGlobalBounds()) && v.x < 0) {
this->player.setPosition(block->right.getPosition().x + 1, this->player.getPosition().y);
}
}
I hope I explained accurately the problem.
simply do your collision detection one axis at a time, this allows nice smooth movement along tiles
pseudocode example:
//X axis
oldPos = position
if(leftKey):
position.x -= speed
if(rightKey):
position.x += speed
if(collision):
position = oldPos
//Y axis
oldPos = position
if(upKey):
position.y -= speed
if(downKey):
position.y += speed
if(collision):
position = oldPos

2D collisions check between 3 and more objects

i've got a problem on my collision Check Algorithm.
The problem is when i try to resolve collisions between 3 objects, 1 of them is still not colliding and not resolving collisions, here's the code:
void check_collisions(engine_t* engine)
{
for (int i = 0; i < engine->actor_count; i++)
{
actor_t* first = (actor_t*)engine->collision_pairs->data[i];
collider_t* a = (collider_t*)get_component_by_name(first, "collider");
for(int j = 0; j < engine->actor_count; j++)
{
actor_t* second = (actor_t*)engine->collision_pairs->data[j];
if(second == first)
continue;
collider_t* b = (collider_t*)get_component_by_name(second, "collider");
hit_state_t hit = aabb(a, b);
resolve_collisions(a, b, hit.normal);
}
}
}
The problem is that when for example: i have A, B, C
A could collide with B and C at the same frame time, it seems that when more object are colliding the first one (first) will not be calculated anymore.. any idea?
void resolve_collisions(collider_t* a, collider_t* b, vec2_t normal)
{
//Stop rigidbody
vec2_t position = a->owner->transform.position;
vec2_t position2 = b->owner->transform.position;
rigid_body_t* rb = (rigid_body_t*)get_component_by_name(a->owner, "rigid_body");
// if(!rb) { SDL_Log("rigid_body not while resolving collisions"); return; }
//hit from dx
if (normal.x > 0.0f && position.x < b->owner->transform.position.x + b->size.x)
{
rb->velocity.x = 0.0f;
// SDL_Log("collided dx");
position.x = (b->owner->transform.position.x + b->size.x) + 0.7f;
}
//hit from sx
if (normal.x < 0.0f && position.x + a->size.x > b->owner->transform.position.x)
{
rb->velocity.x = 0.0f;
float offset = b->size.x - a->size.x;
float offset2 = a->size.x - b->size.x;
// SDL_Log("collided sx");
position.x = (b->owner->transform.position.x - b->size.x) + offset;
position2.x = (a->owner->transform.position.x - a->size.x) + offset2;
}
//hit from top
if (normal.y < 0.0f && position.y + a->size.y > b->owner->transform.position.y)
{
rb->velocity.y = 0.0f;
float offset = b->size.y - a->size.y;
position.y = (b->owner->transform.position.y - b->size.y) + offset;
}
//hit from bottom
if (normal.y > 0.0f && position.y < b->owner->transform.position.y + b->size.y)
{
rb->velocity.y = 0.0f;
// SDL_Log("collided bottom");
position.y = (b->owner->transform.position.y + b->size.y) + 0.7f;
}
//change pos
a->owner->transform.position = position;
}
any help would be much appreciated!
-Thanks

SFML Axis independent collision

I've implemented tilemap collision into my game, it works but the problem comes when I'm colliding on one axis and trying to move on the other. I can't slide along the wall.
in Player.cpp
void Player::update(float delta, std::vector<Tile>& tiles) {
if (sf::Keyboard::isKeyPressed(sf::Keyboard::W) || sf::Keyboard::isKeyPressed(sf::Keyboard::Up) || sf::Joystick::getAxisPosition(0, sf::Joystick::Y) < -20) {
newPos.y -= speed * delta;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A) || sf::Keyboard::isKeyPressed(sf::Keyboard::Left) || sf::Joystick::getAxisPosition(0, sf::Joystick::X) < -20) {
newPos.x -= speed * delta;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::S) || sf::Keyboard::isKeyPressed(sf::Keyboard::Down) || sf::Joystick::getAxisPosition(0, sf::Joystick::Y) > 20) {
newPos.y += speed * delta;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::D) || sf::Keyboard::isKeyPressed(sf::Keyboard::Right) || sf::Joystick::getAxisPosition(0, sf::Joystick::X) > 20) {
newPos.x += speed * delta;
}
sf::Vector2f oldPos = sprite.getPosition();
move(delta, newPos);
for (int i = 0; i < tiles.size(); i++) {
if (Collision::PixelPerfectTest(sprite, tiles[i].sprite) && tiles[i].collision) {
sprite.setPosition(oldPos);
newPos = oldPos;
}
}
}
void Player::move(float delta, sf::Vector2f position) {
sprite.setPosition(position);
}
In Collision.cpp
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;
}
That's because your collision test is all or nothing. I would do extra collision tests to see if the x or y new position is valid or not, something like:
if (tiles[i].collision && Collision::PixelPerfectTest(sprite, tiles[i].sprite))
{
sf::Vector2f checkPosX = newPos;
sf::Vector2f checkPosY = newPos;
checkPosX.y = oldPos.y;
checkPosY.x = oldPos.x;
sprite.setPosition(checkPosX);
if (!Collision::PixelPerfectTest(sprite, tiles[i].sprite))
{
newPos = checkPosX;
}
else
{
sprite.setPosition(checkPosY);
if (!Collision::PixelPerfectTest(sprite, tiles[i].sprite))
{
newPos = checkPosY;
}
else
{
sprite.setPosition(oldPos);
newPos = oldPos;
}
}
}
As an aside, if you do test tiles[i].collision first you will skip the more expensive PixelPerfectTest() test for non-collision tiles due to the expression short-circuiting.

Gravity using CircleShape SFML

I am currently trying to set gravity for a split-screen game. The problem with my game is that the circles will rise once W or Up is pressed but the gravity does not seem to apply to them. Also when I move them left or right whilst in the air they disappear until I press W or Up again.
sf::Vector2f position(screenDimensions.x /2, screenDimensions.y / 2);
sf::Vector2f position2(position);
sf::Clock clock;
float moveSpeed = 0.5f , jumpSpeed = 0.3f;
while(Game.isOpen())
{
clock.restart();
sf::Event Event;
while(Game.pollEvent(Event))
{
switch(Event.type)
{
case sf::Event::Closed:
Game.close();
break;
case sf::Event::KeyPressed:
if(Event.key.code == sf::Keyboard::Escape)
Game.close();
break;
}
}
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
{
velocity.x = moveSpeed;
circ1.move(velocity.x, velocity.y);
}
else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
{
velocity.x = -moveSpeed;
circ1.move(velocity.x, velocity.y);
}
else
velocity.x = 0;
if(sf::Keyboard::isKeyPressed(sf::Keyboard::D))
{
velocity.x = moveSpeed;
circ2.move(velocity.x, velocity.y);
}
else if(sf::Keyboard::isKeyPressed(sf::Keyboard::A))
{
velocity.x = -moveSpeed;
circ2.move(velocity.x, velocity.y);
}
else
velocity.x = 0;
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
{
velocity.y = -jumpSpeed;
circ1.move(velocity.x, velocity.y);
}
else
if(sf::Keyboard::isKeyPressed(sf::Keyboard::W))
{
velocity.y = -jumpSpeed;
circ2.move(velocity.x, velocity.y);
}
if (circ1.getPosition().x + 10 >= view1.getSize().x / 2)
position.x = circ1.getPosition().x + 10;
else
position.x = view1.getSize().x / 2;
if (circ2.getPosition().x + 10 >= view2.getSize().x / 2)
position2.x = circ2.getPosition().x + 10;
else
position2.x = view2.getSize().x / 2;
if (circ1.getPosition().y + circ1.getRadius() < groundHeight || velocity.y > 0)
{
velocity.y += Gravity;
}
else
{
circ1.setPosition(circ1.getPosition().x, groundHeight - circ1.getRadius());
velocity.y = 0;
}
view1.setCenter(position);
view2.setCenter(position2);
Game.setView(view1);
Game.draw(bImage);
Game.draw(circ1);
Game.draw(circ2);
Game.setView(view2);
Game.draw(bImage);
Game.draw(circ2);
Game.draw(circ1);
Game.display();
Game.clear();
}
}
Help is appreciated Thank you.
If you want "jumping" per se, then you need to keep the user from jumping while already in the air. This could be accomplished by creating a bool jumping=false; at the beginning and then setting jumpingto true when a jump is initialized. Then, each time the program loops, if (jumping && floor.getGlobalBounds().intersects(circ1) || circ.getPosition().y<0){jumping=false;velocity.y=0;}.
Otherwise, you might try changing
if (circ1.getPosition().y + circ1.getRadius() < groundHeight || velocity.y > 0)
{
velocity.y += Gravity;
}
to
if (circ1.getPosition().y + circ1.getRadius() > groundHeight || velocity.y > 0)
{
velocity.y += Gravity;
}

When moving something to a point with vector math the enemy gets there and then disappears

I have been trying to use vectors to move objects at angles and I did get it working, however, when I try to move an object to a specific point it gets there and then disappears. In my code I test if within the next step if it will reach it's destination and if it will, I snap it to the destination.
void Dot::moveToVector(Vector& vec)
{
float dx;
float dy;
dx = vec.X - position.X;
dy = vec.Y - position.Y;
Vector distanceVec(dx, dy);
float distance = distanceVec.Length();
float scale;
scale = speed / distance;
velocity.X = dx * scale;
velocity.Y = dy * scale;
if(velocity.X < scale || velocity.Y < scale)
{
velocity.X = 0;
velocity.Y = 0;
position.X = vec.X;
position.Y = vec.Y;
}
move();
}
When I debugged it, one frame after it snaps into position, the x and y values of the position = -nan(0x400000).
scale = speed / distance;
If distance == 0 what do you think will happen?
When your object reaches the target position, distance becomes zero. Then you are dividing by distance. I suspect that is why your object disappears!
Here is a more straightforward way to set it up:
void Dot::moveToVector(Vector& vec)
{
Vector distanceVec = vec - position;
float distance = distanceVec.Length();
if(distance <= speed)
{
velocity.X = 0;
velocity.Y = 0;
position.X = vec.X;
position.Y = vec.Y;
}
else
{
Vector direction = (distanceVec / distance);
velocity = direction * speed;
}
move();
}