How to prevent objects from intersecting in C++? - c++

I have been working on creating a pong game in C++ and am facing issues with how my ball and paddle collide. Virtually, when the ball hits the paddle from the top, the ball bounces off. But if the ball hits the paddle from the side, the ball slides along the paddle like its caught and only bounces back off once it reaches the other end. I've tried messing around with the dimensions of the collision box for the paddle and using the clock method to try and count how long the ball has been colliding with the paddle, and neither have worked. Can anyone help out with this?
My main code:
#include "RealBat.h"
#include <sstream>
#include <string>
#include <cstdlib>
#include <SFML/Graphics.hpp>
#include <iostream>
using namespace sf;
int windowWidth = 1024;
int windowHeight = 868;
Bat bat(windowWidth / 2, windowHeight - 50);
sf::Vector2f ballUpdate(sf::Vector2f ballPosition, int windowWidth, int windowHeight, float *velocityX, float *velocityY){
sf::Vector2f ballPosition2 = sf::Vector2f(ballPosition.x, ballPosition.y);
ballPosition2.x += *velocityX;
ballPosition2.y += *velocityY;
if (ballPosition2.x >= windowWidth || ballPosition2.x <= 0) {
*velocityX *= -1.0f;
}
if (ballPosition2.y >= windowHeight || ballPosition2.y <= 0) {
*velocityY *= -1.0f;
}
int ballBottomLeft = ballPosition2.y + 9;
int ballRight = ballPosition2.x + 9;
int batRight = bat.getVector().x + 100;
int score = 0;
if (ballBottomLeft >= bat.getVector().y) {
if (ballPosition2.x >= bat.getVector().x && ballRight <= batRight) {
std::cout << "collisionTracker" << std::endl;
*velocityY *= -1.0f;
score = score + 1;
}
else {
score = 0;
}
return ballPosition2;
}
int main()
{
float velocityX = 0.05;
float velocityY = 0.05;
sf::Vector2f position = sf::Vector2f(10, 10);
RectangleShape ball = RectangleShape();
ball.setSize(sf::Vector2f (10, 10));
RenderWindow window(VideoMode(windowWidth, windowHeight), "ID PONG");
while (window.isOpen()) {
Event event;
while (window.pollEvent(event)) {
if (event.type == Event::Closed) {
window.close();
}
}
if (Keyboard::isKeyPressed(Keyboard::Left)) {
bat.moveLeft();
}
else if (Keyboard::isKeyPressed(Keyboard::Right)) {
bat.moveRight();
}
bat.update();
position = ballUpdate(position, windowWidth, windowHeight, &velocityX, &velocityY);
ball.setPosition(position);
window.clear(Color(148, 213, 0, 255));
window.draw(bat.getShape());
window.draw(ball);
window.display();
}
}
My Bat Header File:
#pragma once
#include <SFML/Graphics.hpp>
using namespace sf;
class Bat {
private:
Vector2f position;
RectangleShape batShape;
float batSpeed = .3f;
public:
Bat(float startX, float startY);
FloatRect getPosition();
RectangleShape getShape();
Vector2f getVector();
void moveLeft();
void moveRight();
void update();
};
My Bat cpp:
#include "RealBat.h"
Bat::Bat(float startX, float startY)
{
position.x = startX;
position.y = startY;
batShape.setSize(sf::Vector2f(100, 10));
batShape.setPosition(position);
}
FloatRect Bat::getPosition()
{
return batShape.getGlobalBounds();
}
RectangleShape Bat::getShape()
{
return batShape;
}
void Bat::moveLeft()
{
position.x -= batSpeed;
}
void Bat::moveRight()
{
position.x += batSpeed;
}
void Bat::update()
{
batShape.setPosition(position);
}
sf::Vector2f Bat::getVector () {
return position;
}

Most likely what is happening is that the ball is still within the boundary of the bat on the next time round the loop.
Picture the ball and bat like this terrible ascii representation (+ for bat, 0 for ball), ball has just hit bats right side, heading down and to the left
+--------------+
+ +
+ O
+ +
+--------------+
balls y velocity will be reversed and next iteration it will be
+--------------+
+ O+
+ +
+ +
+--------------+
This is still inside the ball's boundary, so its y velocity will be reversed again giving
+--------------+
+ +
+ O +
+ +
+--------------+
So the ball will bounce up and down inside the bat until it hits the left hand edge.
+--------------+
+ O O O O O O O+
+O O O O O O O O
+ +
+--------------+
One way to fix this would be to only reverse the Y velocity if the ball is heading down, that way the ball would just continue upwards even though it is still inside the bat. Possibly by adding:
if (*velocityY > 0.0) {
just above the collisionTracker cout call, so you don't score or bounce when heading upwards.

I had something of a similar issue when I made my pong game years ago. This is because we only check intersection and change the direction of velocity in x-axis. So when it hit from the sides the velocity is constantly inverting every frame. The way I solved it at the time was by checking the position of the center of my ball with respect to the bounds (sides) of the paddle. So, when the ball is hitting any of the sides I simply invert the direction of velocity in the y-axis. This way the ball hits the paddle and just moves away.
I'm pretty sure there's better ways to do it but I was only a beginner at the time and this was the best I could come up with.

Related

Raylib my Screen Collisions are not accurate

So I'm new to raylib and, basically, I'm trying to make a sandbox game and I am trying to make it so that when the player places a square or material when that material hits the edge of the screen it stops. Currently, my square when it falls it goes to the edge of the screen and it stops. but there's noticeable space between the screen, and the squares flicker and the squares don't stop on the same X level.
This Code is called when the user clicks on the screen. This DrawMat function is called when the user clicks and from there the square falls to the bottom of the screen.
Heres My Code
struct Mat
{
float X, Y;
float SpeedX, SpeedY;
float Force;
float Gravity;
Vector2 MousePos;
Vector2 size;
Color color;
void DrawMat() {
DrawRectangle(MousePos.x, MousePos.y, size.x, size.y, color);
MousePos.y += 9.81f;
if (MousePos.x < 0 || MousePos.x > GetScreenWidth()) {
MousePos.x *= -1;
}
if (MousePos.y < 0 || MousePos.y > GetScreenHeight()) {
MousePos.y *= -1;
}
}
};
int main() {
InitWindow(800, 600, "Speed Z Presends to you... Satisfiying, Amazing, Niffty, Dreamy. SIMULATOR");
SetWindowState(FLAG_VSYNC_HINT);
Mat Sand;
Sand.MousePos = { -100, -100 };
Sand.size = { 5, 5 };
Sand.color = YELLOW;
Sand.X = Sand.MousePos.x;
Sand.Y = Sand.MousePos.y;
Sand.SpeedX = 300;
Sand.SpeedY = 500;
Vector2 Mousepos = {-100, -100};
bool Mouseclicked = false;
Vector2 RectSize = { 2, 2 };
int Numof = 0;
std::vector<Mat> positions = {};
while (!WindowShouldClose())
{
BeginDrawing();
ClearBackground(BLACK);
for (size_t i = 0; i < positions.size(); i++)
{
positions[i].DrawMat();
}
if (IsKeyPressed(KEY_ONE)) {
Numof = 1;
}
if (Numof == 1)
{
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT))
{
Mouseclicked = true;
Mousepos = GetMousePosition();
Sand.MousePos = GetMousePosition();
positions.push_back(Sand);
}
}
Here is an image of What I mean:
I don't really understand how this code would work at all.
But if what you mean is that the Y coordinate should be as close as possible to the edge, your if statement should be like so:
if (MousePos.y < 0) {
MousePos.y *= -1;
}
else if (MousePos.y > GetScreenHeight()) {
MousePos.y = GetScreenHeight();
}
Adjust the last expression as required. Maybe you need to subtract the height of the object so it doesn't disappear:
else if (MousePos.y > GetScreenHeight() - size.y) {
MousePos.y = GetScreenHeight() - size.y;
}
Why is that necessary?
I would imagine that the rendering you make uses MousePos.x as the left position and MousePos.y as the top position like so:
/---- this corner is (MousePos.x, MousePos.y)
|
| v |
| +----------------+---- |
| | | ^ |<- screen bottom edge
| | | | size.y |
+-------------|----------------|--|--------------+
| | v
+----------------+----
So to keep the sand grain on screen, you must make sure that the corner is at a location that makes it visible. As we see on the ASCII picture, for Y it means the maximum is GetScreenHeight() - size.y.
Note that you certainly have the same issue with the X coordinate (i.e. you may want the maximum to be GetScreenWidth() - size.x).

(SFML)Player constructor not updating with correct animation when key is pressed

My sprite moves to the left, right, up and down when the correct corresponding key is pressed but only the row 0 animation is used, but for some odd reason when I press two keys at the same time like (W,A) or (S, D) is transfer to opposite animation to which side it is moving. I tried moving the if statements directing the animation update to a nested if statement inside the key press if statements and that did nothing good, I then tried just having it update based off the key press with no nested if statement, that also did not work... I am new to SFML so this is really hurting my head to figure this out, also I do not mind criticism, please if you see something I could be doing better in terms of sprite movement, let me know! Thanks for your help.
Below is my player class constructor
#include "Player.h"
Player::Player(Texture* texture, Vector2u imageCount, float switchTime, float speed) :
// initializer list from animation.cpp
animation(texture, imageCount, switchTime)
{
this->speed = speed;
row = 0;
body.setSize(Vector2f(100.0f, 150.0f));
body.setTexture(texture);
//sets initial position for test sprite sheet
body.setPosition(550.0f, 900.0f);
}
Player::~Player()
{
}
void Player::Update(float deltaTime)
{
Vector2f movement(0.0f, 0.0f);
if (Keyboard::isKeyPressed(Keyboard::A))
{
//if A is pressed move to the left on the x axis
movement.x -= speed * deltaTime;
}
if (Keyboard::isKeyPressed(Keyboard::D))
{
//if D is pressed move to the right on the x axis
movement.x += speed * deltaTime;
}
if (Keyboard::isKeyPressed(Keyboard::W))
{
// if W is pressed move up on the y axis
movement.y += speed * deltaTime;
}
if (Keyboard::isKeyPressed(Keyboard::S))
{
// if S is pressed move down on the y axis
movement.y -= speed * deltaTime;
}
if (movement.x == 0.0f || movement.y == 0.0f)//for idle animation
{
row = 0;//idle row for now just using walking until I get idle animation
}
else if(movement.x > 0.0f)
{
row = 1; //walking to the left animation
}
else if (movement.x < 0.0f )
{
row = 3; //walking to the right animation
}
else if (movement.y > 0.0f)
{
row = 0; // walking to stright animation
}
else if (movement.y < 0.0f)
{
row = 2;// walking back animation
}
animation.Update(row, deltaTime);
body.setTextureRect(animation.uvRect);
body.move(movement);
}
void Player::Draw(RenderWindow& window)
{
window.draw(body);
}
Below is my player class initialization
#pragma once
#include <SFML/Graphics.hpp>
#include "Animation.h"
using namespace std;
using namespace sf;
class Player
{
public:
Player(Texture* texture, Vector2u imageCount, float switchTime, float speed);
~Player();
void Update(float deltaTime);
void Draw(RenderWindow& window);
private:
RectangleShape body;
Animation animation;
unsigned int row;
float speed;
};
Below Game while loop and the Player function call
Player player(&playerTexture, Vector2u(9, 4), 0.09f, 100.0);
//*************************************************************
//clock & Time
float deltaTime = 0.0f;
Clock clock;
while (window.isOpen())
{
deltaTime = clock.restart().asSeconds();
//*********************************************************
//Player Input
if (Keyboard::isKeyPressed(Keyboard::Escape))
{
window.close();
}
//**********************************************************
//draws everything
player.Update(deltaTime);
window.clear();
window.draw(spritestartingBG);
player.Draw(window);
window.display();
}
return 0;
}
if (movement.x == 0.0f || movement.y == 0.0f) will be true unless moving diagonally-- you probably want the || to be &&.
Similarly, your left/right animations are reversed--you're moving left when movement.x is < 0.0, not > 0.0.

How can I make angles not act weird?

I'm new to programming and SFML. I'm trying to make something like a canon. It's gonna fire balls that will be flying in an arc. Sounds like a very simple task to accomplish, yet I cannot seem to figure out how angles work in SFML. For example, with ang_const = 0.13 Rad (7.44 Deg), my balls flies in a beautiful arc. However, when I change the value of ang_const to 0.14 Rad (8.021 Deg), the ball flies in the opposite direction! If I change the angle to 0.19 Rad (10.88 Deg), it flies downwards for whatever reason.
So here's my code:
#include <SFML/Graphics.hpp>
#include <math.h>
int WIDTH = 1024, HEIGHT = 704;
class Ball {
private:
float radius = 16.00;
public:
sf::CircleShape shape;
Ball () {
shape.setPosition(0 + radius*2, HEIGHT - radius*2);
shape.setRadius(radius);
shape.setFillColor(sf::Color::Cyan);
shape.setOrigin(radius, radius);
}
void update() {
if (x() - radius > WIDTH) {
this->shape.setPosition(0 - radius, y());
}
if (x() + radius < 0) {
this->shape.setPosition(WIDTH + radius, y());
}
if (y() - radius > HEIGHT) {
this->shape.setPosition(x(), 0 - radius);
}
if (y() + radius < 0) {
this->shape.setPosition(x(), HEIGHT + radius);
}
}
float RadToDeg (float radian) {
double pi = 3.14159;
return radian * (180 / pi);
}
float x() { return shape.getPosition().x; }
float y() { return shape.getPosition().y; }
float getRadius() { return radius; }
};
int main()
{
// Create the main window
sf::RenderWindow window(sf::VideoMode(WIDTH, HEIGHT), "del");
// Some variables
float ang_const = 0.13;
float velX_const = 3.5, velY_const = 3.5;
float grav_const = -0.02;
float ang = ang_const;
float velX = velX_const, velY = velY_const;
float grav = grav_const;
// Text
int size_for_text = 64;
sf::Font f;
f.loadFromFile("Keyboard.ttf");
sf::Text text1;
text1.setFont(f);
text1.setCharacterSize(27);
text1.setFillColor(sf::Color::White);
text1.setPosition(size_for_text, size_for_text);
// Ball
Ball ball;
while (window.isOpen())
{
// Process events
sf::Event event;
while (window.pollEvent(event))
{
// Close window: exit
if (event.type == sf::Event::Closed) {
window.close();
}
// Escape pressed: exit
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) {
window.close();
}
// Restart
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Space) {
ang = ang_const;
velX = velX_const, velY = velY_const;
grav = grav_const;
ball.shape.setPosition(0 + ball.getRadius()*2, HEIGHT - ball.getRadius()*2);
}
}
// Ball movement
ball.update();
velY += grav;
ball.shape.move(velX * cos(ball.RadToDeg(ang)),
velY * -sin(ball.RadToDeg(ang)));
// Clear screen
window.clear(sf::Color(0,0,80,100));
// Draw ball
window.draw(ball.shape);
// Draw text
text1.setString("ang " + std::to_string(ang));
window.draw(text1);
// Update the window
window.display();
}
return EXIT_SUCCESS;
}
The main lines are these:
Variables:
float ang_const = 0.13;
float velX_const = 3.5, velY_const = 3.5;
float grav_const = -0.02;
Ball movement:
velY += grav;
ball.shape.move(velX * cos(ball.RadToDeg(ang)), velY * -
sin(ball.RadToDeg(ang)));
Radians to Degrees function:
float RadToDeg (float radian) {
double pi = 3.14159;
return radian * (180 / pi);
}
Could someone explain what's wrong with my code and how angles work in SFML? I'd be appreciated for your help guys.
All the trigonometric functions defined in <cmath> expect their parameters to be values representing angles in radians (see e.g. std::cos).
So, when you write something like
cos(ball.RadToDeg(ang))
where ang is equal to, say 0.13, RadToDeg will convert it to 7.44, but, even if your intention is to pass an angle in degrees, that value is interpreted by std::cos (and std::sin) as an angle of 7.44 radians (or 66.28°).
That leads to your unexpected results:
cosr(7.44) = 0.505 (instead of cosd(7.44°) = 0.993) and
cosr(8.021) = -0.166 (instead of cosd(8.021°) = 0.992)

Tiles being drawn in the wrong location

I've finally managed to get my tiles drawn on the screen somewhat in a correct way. Although the location is a bit off and I can't seem to figure out why...
I'm using SFML for drawing.
Tile.hpp:
#ifndef TILE_HPP
#define TILE_HPP
#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
#include "textureManager.hpp"
class Tile {
public:
Tile();
Tile(sf::Vector2i coord, int biome);
~Tile();
sf::Vector2i getCoord() const { return coord; };
int getBiome() const { return biome; };
void setCoord(sf::Vector2i coord) { this->coord = coord; };
void setBiome(int biome) { this->biome = biome; };
void draw(int x, int y, sf::RenderWindow* rw);
void update(sf::Texture& texture);
private:
sf::Vector2i coord;
int biome;
sf::Sprite sprite;
};
#endif
Tile.cpp
#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
#include "textureManager.hpp"
#include "tile.hpp"
Tile::Tile()
{}
Tile::Tile(sf::Vector2i coord, int biome) {
this->biome = biome;
this->coord = coord;
}
Tile::~Tile(){}
void Tile::draw(int x, int y, sf::RenderWindow* rw)
{
sprite.setPosition(x, y);
rw->draw(sprite);
}
void Tile::update(sf::Texture& texture)
{
switch (biome)
{
// Not important here
}
}
Now the more relevant part: the drawing
void StatePlay::draw(const float dt)
{
game->window.setView(view);
game->window.clear(sf::Color::Black);
sf::Vector2f offset = camera.getLocation();
int newX = (offset.x / map.getTileSize()) - (map.chunkSize / 2);
int newY = (offset.y / map.getTileSize()) - (map.chunkSize / 2);
for (int x = 0; x < map.chunkSize; x++)
{
for (int y = 0; y < map.chunkSize; y++)
{
Tile tile = map.getTile(newX + x, newY + y);
tile.draw((newX + x) * map.getTileSize(), (newY + y) * map.getTileSize(), &game->window);
}
}
return;
}
StatePlay::StatePlay(Game* game)
{
this->game = game;
sf::Vector2f pos = sf::Vector2f(game->window.getSize()); // 1366x768
view.setSize(pos);
pos *= 0.5f; // 688x384
view.setCenter(pos);
// Initialize map
map.init(game->gameTime, game->textureManager.getImage("tileset.png"));
float w = (float) map.getWidth(); // 500
float h = (float) map.getHeight(); // 500
w *= 0.5f; // 250
h *= 0.5f; // 250
w *= map.getTileSize(); // 250 * 32 = 8000
h *= map.getTileSize(); // 250 * 32 = 8000
// Move camera
// Uses view::move from sfml to move the view with w and h
// Also sets camera private to w and h values, return with camera::getLocation()
camera.setLocation(&view, sf::Vector2f(w, h));
}
The result is that I only see the ~10 tiles squared, in the bottom left corner of my screen, covering about 3/4.
The correct tiles are chosen, but the draw location is wrong... It should draw the center of 64x64 (x 32px each) tiles, as much as fit on the screen.
I have fixed the problem. It was a very stupid mistake...
At first without drawing anything, it is normal to center the view on 0.5f * sf::View::getSize() to get the view centered in your window. So the center was already at half of my window size. When using Camera::setLocation(), I used the sf::View::move() to move the view accordingly. So when trying to center it on the map, it added the x and y correctly, but also half of my window size. This resulted in having an offset which was incorrect. Substracting or leaving those values out has fixed this stupid problem.
Thank you for the help.

C++ SFML collision is not accurate

I'm making a 2D game with SFML in C++ and I have a problem with collision. I have a player and a map made of tiles. Thing that doesn't work is that my collision detection is not accurate. When I move player up and then down towards tiles, it ends up differently.
I am aware that source of this problem may be calculating player movement with use of delta time between frames - so it is not constant. But it smooths movement, so I don't know how to do it other way. I tried with constant speed valuses and to make collision fully accurate - speed had to be very low and I am not satisfied with that.
void Player::move() {
sf::Vector2f offsetVec;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::W))
offsetVec += sf::Vector2f(0, -10);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::S))
offsetVec += sf::Vector2f(0, 10);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A))
offsetVec += sf::Vector2f(-10, 0);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::D))
offsetVec += sf::Vector2f(10, 0);
this->moveVec += offsetVec;
}
void Player::update(float dt, Map *map) {
sf::Vector2f offset = sf::Vector2f(this->moveVec.x * this->playerSpeed * dt,
this->moveVec.y * this->playerSpeed * dt);
sf::Sprite futurePos = this->sprite;
futurePos.move(offset);
if (map->isCollideable(this->pos.x, this->pos.y, futurePos.getGlobalBounds())) {
this->moveVec = sf::Vector2f(0, 0);
return;
}
this->sprite.move(offset);
this->pos += offset;
this->moveVec = sf::Vector2f(0, 0);
return;
}
In player position update I create future sprite object, which is object after applying movement, to get it's boundaries and pass it to collision checker. To collision checker I also pass player pos, because my map is stored in 2d array of tile pointers, so I check only these in player range.
bool Map::isCollideable(float x, float y, const sf::FloatRect &playerBounds) {
int startX = int(x) / Storage::tileSize;
int startY = int(y) / Storage::tileSize;
Tile *tile;
for (int i = startX - 10; i <= startX + 10; ++i) {
for (int j = startY - 10; j <= startY + 10; ++j) {
if (i >= 0 && j >= 0) {
tile = getTile(i, j);
if (tile != nullptr && playerBounds.intersects(tile->getGlobalBounds()))
return true;
}
}
}
return false;
}
Full project on Github
My solution
I have changed if statement in update function to while statement, which decreases my offset vector till no collision is present. I still have to make some adjustments, but general idea is:
void Player::update(float dt, Map *map) {
int repeats = 0;
sf::Vector2f offset = sf::Vector2f(this->moveVec.x * this->playerSpeed * dt,
this->moveVec.y * this->playerSpeed * dt);
sf::Sprite futurePos = this->sprite;
while (map->isCollideable(this->pos.x, this->pos.y, futurePos, offset)) {
offset = 0.7f * offset;
repeats++;
if (repeats > 5) {
this->moveVec = sf::Vector2f(0, 0);
return;
}
}
this->sprite.move(offset);
this->pos += offset;
this->moveVec = sf::Vector2f(0, 0);
return;
}
I also had to rework isCollideable method a little, so it accepts sf::Sprite and offset vector so it can calculate boundaries on it's own.
When the player collides with a tile, you should calculate the penetration, that is, the value of "how much the player went into the tile". When you have this value, nudge your player back that much.
This is just a thought but you could have some inaccuracies in your collision detection when you typecast the float x, and y to integers and then divide them. This could cause problems because some of the data in the float could be lost. If the float was 3.5 or 3.3 or 3.9 then it would become 3 which throws off your collision calculations.