SFML 2.1 how to make one sprite face another sprite - c++

I am trying to make a shooter game, and while trying to code the enemies
to face towards the player, I tried to use trigonometry to find the necessary rotation, but the code didn't work, and the enemy rotated erratically. This is the code:
void face(sf::Sprite& target, sf::Sprite& subject){
int adjacent = subject.getPosition().x - target.getPosition().x;
int opposite = target.getPosition().y - subject.getPosition().y;
if (opposite == 0){
opposite++;
}
if (adjacent == 0){
adjacent++;
}
//if (adjacent < 0){
//adjacent += 180;
//}
float result=atan(/*opposite / adjacent*/adjacent/opposite)*180/PI;
subject.setRotation(result);
}
Any advice would be appreciated!

You must use float with adjacent and opposite. And change result with this:
float angle = atan(adjacent / opposite) * 180 / PI;
if (opposite > 0)
angle += 180;

Related

tilemap collision detection not working properly on slower computers

notes:
room1Collisions is a 1D array containing 1s and 0s representing collidable tiles.
+/- 0.1 is used so that the player can still move when against a collidable tile.
size is an SFML vector that holds the width and height of the player.
I understand that this code is not in great shape, but I'm trying to get collisions working to then refactor.
The issue (described below) occurs more frequently when the player collides diagonally
code:
void Player::Update(float dt) {
// 0 is a collidable tile
// change room1Collisions to a pointer of current level
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A))
{
// a is top left and b is bottom left
sf::Vector2u a(position.x/tileSize.x, (position.y+size.y/2)/tileSize.y);
sf::Vector2u b(position.x/tileSize.x, (position.y+size.y-0.1)/tileSize.y);
int tileNumberA = room1Collisions[(a.x) + a.y*(tilemapBounds.x/tileSize.x)];
int tileNumberB = room1Collisions[(b.x) + b.y*(tilemapBounds.x/tileSize.x)];
if (tileNumberA != 0 && tileNumberB != 0 && position.x >= 0) {
position.x -= speed * dt;
//animation.resumeAnimation();
}
direction = LEFT;
//animation.resumeAnimation();
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::D))
{
sf::Vector2u a((position.x+size.x)/tileSize.x, (position.y+size.y/2)/tileSize.y);
sf::Vector2u b((position.x+size.x)/tileSize.x, (position.y+size.y-0.1)/tileSize.y);
int tileNumberA = room1Collisions[(a.x) + a.y*(tilemapBounds.x/tileSize.x)];
int tileNumberB = room1Collisions[(b.x) + b.y*(tilemapBounds.x/tileSize.x)];
if (tileNumberA != 0 && tileNumberB != 0 && position.x+size.x <= tilemapBounds.x) {
position.x += speed * dt;
//animation.resumeAnimation();
}
direction = RIGHT;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::W))
{
sf::Vector2u a((position.x+0.1)/tileSize.x, (position.y+(size.y/3))/tileSize.y);
sf::Vector2u b((position.x+size.x-0.1)/tileSize.x, (position.y+(size.y/3))/tileSize.y);
int tileNumberA = room1Collisions[(a.x) + a.y*(tilemapBounds.x/tileSize.x)];
int tileNumberB = room1Collisions[(b.x) + b.y*(tilemapBounds.x/tileSize.x)];
if (tileNumberA != 0 && tileNumberB != 0 && position.y >= 0) {
position.y -= speed * dt;
//animation.resumeAnimation();
}
direction = UP;
//animation.resumeAnimation();
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::S))
{
sf::Vector2u a((position.x+0.1)/tileSize.x, (position.y+size.y)/tileSize.y);
sf::Vector2u b((position.x+size.x-0.1)/tileSize.x, (position.y+size.y)/tileSize.y);
int tileNumberA = room1Collisions[(a.x) + a.y*(tilemapBounds.x/tileSize.x)];
int tileNumberB = room1Collisions[(b.x) + b.y*(tilemapBounds.x/tileSize.x)];
if (tileNumberA != 0 && tileNumberB != 0 && position.y+size.y <= tilemapBounds.y) {
position.y += speed * dt;
//animation.resumeAnimation();
}
direction = DOWN;
}
//animation.setAnimation(direction);
//animation.Update(dt, 0.2f);
//animation.setPosition(position);
box.setPosition(position);
}
The problem is that when the game is run on slower computers, sometimes the player will skip (?) a collision check and it becomes stuck in a collidable tile, preventing it from moving orthogonally with respect to the collidable tile. On faster computers this issue is not present unless the player speed (currently 30) is increased significantly.
Below is an image showing the player inside a collidable tile. Please note that in this example the player is not able to move left/right since those directions are perpendicular to the collidable tile, but can still move up.
What you need is to regulate the FPS so that it is consistent for all computers.
Frame rate regulation example(This uses the SDL2 library for SDL_GetTicks and SDL_Delay but there are probably alternatives to that depending on your graphics library):
const int FPS = 60;
const int frameDelay = 1000/FPS;
Uint32 frameStart;
int frameTime;
//Main game loop
while (true)
{
//Gets the amount of milliseconds elapsed since the program was first run
frameStart = SDL_GetTicks();
//YOUR MAIN GAME LOOP HERE
//Gets the amount of milliseconds that it took to run the game loop
frameTime = SDL_GetTicks() - frameStart;
//Checks if the game loop was run faster than the max frame time
if(frameDelay > frameTime)
{
//Delays the game loop so that it takes that the frame time always is the same
SDL_Delay(frameDelay - frameTime);
}
}
(Keep in mind that this is just an example there are lots of ways to regulate the FPS)
After implementing FPS regulation you might need to tweak the speed and collision sensitivity a little but once you have it it should be consistent on ALL computers

How to Implement Collision Detection

I'm creating a program which simulates a robot moving around a map. I have an environment class which holds the robot and obstacles the robot could run into. At the moment I have class objects for my robot, and obstacles and I have a function which tells me if they collide (returns true/false). I am just not sure how to put it into the movement function for the robot.
The robot is a square and has a center point (x,y) a width, length and some orientation in degrees (fyi, the environment class is a friend of the robot class). The obstacles are circles with a center point (x,y) and a radius.
class Environment{
Robot robot;
vector<Obstacle> obstacles;
//random obstacle generation function
bool collision_circle(Obstacle obstacle) {
//Check if the circle intersects any of the corners of the robot
std::vector<Point> points;
points.push_back(robot.top_right);
points.push_back(robot.top_left);
points.push_back(robot.bottom_right);
points.push_back(robot.bottom_left);
Point obst_center(obstacle.return_x(), obstacle.return_y());
for (int i = 0; i < points.size(); i++) {
points[i].set_distance(obst_center);
if (points[i].distance <= obstacle.return_radius()) { return true; }
}
//Sort the points by distance away from the obstacle
std::sort(points.begin(), points.end(), less_than());
//Use the two closest to the obstacle to create a line
double m = (points[0].x - points[1].x) / (points[0].y -
points[1].y);
double b = points[0].y - (m * points[0].x);
//Determine a line perpendicular which intersects the obstacle's
center
double m_perp = 1 / m;
double b_perp = obst_center.y - (m * obst_center.x);
Point on Robot closest to obstacle
double new_x = (b - b_perp) / (m_perp - m);
double new_y = m_perp * new_x + b_perp;
distance between points
double diff_x = obst_center.x - new_x;
double diff_y = obst_center.y - new_y;
double distance = sqrt(pow(diff_x, 2) + pow(diff_y, 2));
if (distance <= obstacle.return_radius()) { return true; }
else { return false; }
}
Environment(Robot& t_robot): robot(t_robot) {}
void forward(double num_inches){
robot.y += num_inches * sin(robot.orientation * convert_deg);
//Convert_deg is a global variable = PI/180
robot.x += num_inches * cos(robot.orientation * convert_deg);
}
//void backward, left, right, etc.
}
I tried having the forward function check for intersection with each obstacle (up to 15 on the map) after a certain increment of distance, but that froze up my program or would have required thousands of calculations for each inch covered. Am I even on the right track in how to execute this? I am also using SFML for graphics, but, from what I know, it only supports bounding box collision detection. Also, I want the graphics to be something secondary to the program. I am writing this so that I can create and test an program for the robot's movement and would eventually like to just run the sample and be told if it worked and watch the replay if I want.

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.

Create a simple 2D AI C++

I would like to receive some insight as to how I can make an AI, that can walk smoothly around the map(between window size). Like, if the AI reached that defined spot, then it will walk to another spot.
Here is what I have tried,
First, I get a random float number from 0.0f to 608.0f because my window size is 640,640.
void AIntelligence::GenRandom()
{
MapX = static_cast <float> (rand()) / (static_cast <float> (RAND_MAX / 608.0f));
MapY = MapX;
}
Then,I pass in the current position of my sprite to this function
void AIntelligence::RandomMove(float PosX, float PosY)
{
this->PosX = PosX;
this->PosY = PosY;
if (PosX == MapX || PosY == MapY) //If the current is the same as the generated random, then
{ generate it again.
GenRandom();
}
else
{
if (PosX < MapX || PosY < MapY) //If not then I see if the position less than the
{ generated and translate it.
this->PosX += 8.0f;
this->PosY += 8.0f;
}
else if (PosX > MapX || PosY > MapY)
{
this->PosX -= 8.0f;
this->PosY -= 8.0f;
}
else
this->PosX += 0.0f;
this->PosY += 0.0f;
}
}
In my message loop, here is how I call the method
while (GetMessage(&Msg, NULL, 0, 0))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
Inputs->GetInput(); //Not related
Moving->RandomMove(PosX,PosY);
D3DXVECTOR2 SpritePos = D3DXVECTOR2(Moving->getPosX(), Moving->getPosY());
PosX = Moving->getPosX();
PosY = Moving->getPosY();
Graphic->ClearBegin(); //Begin the direct3d scene
Sprite->Begin(D3DXSPRITE_ALPHABLEND);
float Radian = D3DXToRadian(Rotation);
D3DXMatrixTransformation2D(&Mat, NULL, 0.0f, &SpriteScaling, &SpriteCenter, Radian, &SpritePos); // This is where the transformation is set.
Sprite->SetTransform(&Mat);
Sprite->Draw(Texture, NULL, NULL, NULL, D3DCOLOR_XRGB(255, 255, 255));
Sprite->End();
Graphic->EndPresent();
}
The sprite did move but only moving downward right. And once it reached the same certain spot, it will only stay and vibrate there.... Sorry if my explanation is not clear enough or did not provide enough information needed.
Here are a few things that should help you:
1) In RandomMove, your last else doesn't have braces, since you're performing two operations, you should wrap both of them in braces like you did elsewhere
2) float comparison is tricky. It's very unlikely that your PosX == MapX || PosY == MapY will ever trigger. A better idea would be to calculate the distance between your current position and the random position and then execute the code if the distance is less than an epsilon (small value). Here is a pretty detailed post about float comparison (link)
3) GenRandom always assigns the same value to MapX and MapY. You should try to execute two random calls instead (and probably use a const float to define your max value or make it configurable instead of hardcoding that width
4) Your RandomMove method is a bit misleading. It's not performing random movement, it's going towards MapX and MapY. You should separate the calls to GenRandom from your movement code.
5) Your movement code is meant to work only in diagonals since you always increment or decrement your position in both axes at the same time, in the same direction.
Here is a suggestion (not tested) of what your code could look like:
void AIntelligence::GenRandom(const float in_MaxValueX, const float in_MaxValueY)
{
MapX = in_MaxValueX * (float)rand() / (float)RAND_MAX;
MapY = in_MaxValueY * (float)rand() / (float)RAND_MAX;
}
bool AIntelligence::MoveTowards(const float in_PosX, const float in_PosY)
{
// how far are we from our objective
const float distX = in_PosX - PosX; // by calculating the distance from the target position, it makes our speed calculations easier later on
const float distY = in_PosY - PosY;
// tolerance in pixels
const float tolerance = 1.0f;
const float absDistX = abs(distX);
const float absDistY = abs(distY);
if(absDistX <= tolerance && absDistY <= tolerance) // destination reached
return true;
else
{
// here, normally, you would use a desired speed AND a delta time (your message loop is not really good for that though) to compute how much movement you can execute in a given frame
const float movement = min(10.f, absDistX + absDistY); // by using min, we're making sure not to overshoot our destination
// compute how this movement is spread on each axis
const float movementX = movement * distX / (absDistX + absDistY);
const float movementY = movement * distY / (absDistX + absDistY);
PosX += movementX;
PosY += movementY;
}
return false;
}
// in your loop
if(Moving->MoveTowards(MapX, MapY))
{
Moving->GenRandom(608.f, 608.f); // you should definitely not hardcode these values
}
Feel free to comment if there's parts you don't quite understand

Bullet algorithm having trouble with rotation on the X

Here is what I'm trying to do. I'm trying to make a bullet out of the center of the screen. I have an x and y rotation angle. The problem is the Y (which is modified by rotation on the x) is really not working as intended. Here is what I have.
float yrotrad, xrotrad;
yrotrad = (Camera.roty / 180.0f * 3.141592654f);
xrotrad = (Camera.rotx / 180.0f * 3.141592654f);
Vertex3f Pos;
// get camera position
pls.x = Camera.x;
pls.y = Camera.y;
pls.z = Camera.z;
for(float i = 0; i < 60; i++)
{
//add the rotation vector
pls.x += float(sin(yrotrad)) ;
pls.z -= float(cos(yrotrad)) ;
pls.y += float(sin(twopi - xrotrad));
//translate camera coords to cube coords
Pos.x = ceil(pls.x / 3);
Pos.y = ceil((pls.y) / 3);
Pos.z = ceil(pls.z / 3);
if(!CubeIsEmpty(Pos.x,Pos.y,Pos.z)) //remove first cube that made contact
{
delete GetCube(Pos.x,Pos.y,Pos.z);
SetCube(0,Pos.x,Pos.y,Pos.z);
return;
}
}
This is almost identical to how I move the player, I add the directional vector to the camera then find which cube the player is on. If I remove the pls.y += float(sin(twopi - xrotrad)); then I clearly see that on the X and Z, everything is pointing as it should. When I add pls.y += float(sin(twopi - xrotrad)); then it almost works, but not quite, what I observed from rendering out spheres of the trajector is that the furthur up or down I look, the more offset it becomes rather than stay alligned to the camera's center. What am I doing wrong?
Thanks
What basically happens is very difficult to explain, I'd expect the bullet at time 0 to always be at the center of the screen, but it behaves oddly. If i'm looking straight at the horizon to +- 20 degrees upward its fine but then it starts not following any more.
I set up my matrix like this:
void CCubeGame::SetCameraMatrix()
{
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotatef(Camera.rotx,1,0,0);
glRotatef(Camera.roty,0,1,0);
glRotatef(Camera.rotz,0,0,1);
glTranslatef(-Camera.x , -Camera.y,-Camera.z );
}
and change the angle like this:
void CCubeGame::MouseMove(int x, int y)
{
if(!isTrapped)
return;
int diffx = x-lastMouse.x;
int diffy = y-lastMouse.y;
lastMouse.x = x;
lastMouse.y = y;
Camera.rotx += (float) diffy * 0.2;
Camera.roty += (float) diffx * 0.2;
if(Camera.rotx > 90)
{
Camera.rotx = 90;
}
if(Camera.rotx < -90)
{
Camera.rotx = -90;
}
if(isTrapped)
if (fabs(ScreenDimensions.x/2 - x) > 1 || fabs(ScreenDimensions.y/2 - y) > 1) {
resetPointer();
}
}
You need to scale X and Z by cos(xradrot). (In other words, multiply by cos(xradrot)).
Imagine you're pointing straight down the Z axis but looking straight up. You don't want the bullet to shoot down the Z axis at all, this is why you need to scale it. (It's basically the same thing that you're doing between X and Z, but now doing it on the XZ vector and Y.)
pls.x += float(sin(yrotrad)*cos(xrotrad)) ;
pls.z -= float(cos(yrotrad)*cos(xrotrad)) ;
pls.y += float(sin(twopi - xrotrad));