SFML sprite movement (think super meat boy's rocket levels)

This topic is 1842 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

Recommended Posts

I'm a complete newbie when it comes to game development and I'm catching up on my c++ (would consider myself below average in c++ programming), so I've been messing around with SFML 2.0 (which is amazing compared to my experience with SDL) and I've been playing with some code which takes two rects -> draws them on the screen and one rect is controlled using WASD and the other rect attempts to go in the direction of the other rect's current location on the screen.

Right now I've figured out with a little googling how to get the angle of a line of two points and then send the current rect to the other one eg.:

angle = (atan2(player.y - zombie.y, player.x - zombie.x) * 180) / M_PI;
x = cos(angle);
y = sin(angle); 

Then I could properly send the zombie to the player based on it's current position.

The Problem: Every time I move my player around the window, the zombie stops moving in place (im assuming because the angle is constantly being calculated in the game loop) until I stop moving my player on the screen then it proceeds to move towards the player again.

How do I make it so the zombie rect smoothly continues moving towards the player on the screen regardless of where I move the player on my current screen as it's moving on the screen?

I guess the best way to think of it is my code works when the player sprite isn't moving, but when it's moving it'll stop moving completely.  What I want to do is basically pull off what super meat boy did with their rockets and it'll keep chasing you regardless of the position: http://www.gamespot.com/super-meat-boy/videos/super-meat-boy-unforgiving-rocket-level-gameplay-movie-6282320/

Share on other sites

You should be multiplying the cos() and sin() by a velocity, and adding it to the x and y location every loop, like this:

  // Let's assume we've set float zombie.velocity = 5, which it seem is defined as pixels per frame   

angle = (atan2(player.y - zombie.y, player.x - zombie.x) * 180) / M_PI;
zombie.x += cos(angle)*zombie.velocity;
zombie.y += sin(angle)*zombie.velocity;

Eventually you should define the velocity to be in some units/second (like pixels/second), so it will move better with other machine speeds.

Share on other sites
You should be multiplying the cos() and sin() by a velocity, and adding it to the x and y location every loop, like this:


// Let's assume we've set float zombie.velocity = 5, which it seem is defined as pixels per frame

<pre>
angle = (atan2(player.y - zombie.y, player.x - zombie.x) * 180) / M_PI;
zombie.x += cos(angle)*zombie.velocity;
zombie.y += sin(angle)*zombie.velocity;</pre>

Eventually you should define the velocity to be in some units/second (like pixels/second), so it will move better with other machine speeds.

Thank you, I will try this ^_^

Share on other sites

So I attempted to do what you said and I had to make the speed the same for the player and the zombie.

Problem/Question 1: It seems I can't make both units on the screen different velocities (eg.: player1 = zombie with a speed of 2 and player2 = player with a speed of 3 [slightly faster]), if I do this and I go in a diagonal direction my player1 sprite will go in weird circular patterns unless I stop moving.

Problem/Question 2: It seems that if I allow diagonal movement (holding W and D or A & S, etc...) it will do this small curl up until I stop moving.  What's the best way to approach this problem?

Problem/Question 3: There's also the issue when holding just down or right (S or D) and the sprite will just sit on the X or Y axis until I stop moving the unit on the screen and then proceed to move towards my unit.  How do I sort this?

Example:

Source:

#include "GameApp.h"

void GameApp::Setup() {

//setup the game window
window.create(VideoMode(width, height), "Zoms");

//set framerate
window.setFramerateLimit(60);

player1.setSize(10, 20);
player1.setPos(10, 10);

player2.setSize(10,10);

//lets say theg ames running now
running 	= true;
}

void GameApp::Run() {

//Setup game
Setup();

//used to get the current angle of the line between the two points
//then we can properly move the sprite to the player
double angle, x, y, newx, newy;

//sprite speed
int speed 	= 2;

//are the wasd buttons pressed?
bool mw = false;
bool ma = false;
bool ms = false;
bool md = false;

//setup the player2 location
int ranx 	= 300;
int rany 	= 300;

player2.setPos(ranx, rany);

float timeprev, timecurr;

while(window.isOpen()) {

//loop through the active events (keyboard, mouse, etc...)
while(window.pollEvent(event)) {

if(event.type == Event::Closed) {
window.close();
}

if(event.type == Event::KeyPressed) {
switch(event.key.code) {
case Keyboard::W:
mw = true;
break;
case Keyboard::A:
ma = true;
break;
case Keyboard::S:
ms = true;
break;
case Keyboard::D:
md = true;
break;
}
}

if(event.type == Event::KeyReleased) {
switch(event.key.code) {
case Keyboard::W:
mw = false;
break;
case Keyboard::A:
ma = false;
break;
case Keyboard::S:
ms = false;
break;
case Keyboard::D:
md = false;
break;
}
}
}

//did we move the player?
if(mw) {
rany -= speed;
}

if(ma) {
ranx -= speed;
}

if(ms) {
rany += speed;
}

if(md) {
ranx += speed;
}

//get the angle of the line between both player locations (zombie/player)
angle 	= (atan2(rany - player1.sprite.getPosition().y, ranx - player1.sprite.getPosition().x) * 180) / M_PI;

//steps between movement
x 		= cos(angle) * speed;
y 		= sin(angle) * speed;

newx = player1.sprite.getPosition().x + x;
newy = player1.sprite.getPosition().y + y;

//move the sprites on the screen
player1.sprite.setPosition(Vector2f(newx, newy));
player2.sprite.setPosition(Vector2f(ranx, rany));

window.clear();
window.draw(player1.sprite);
window.draw(player2.sprite);

//update the screen
window.display();
}

}

Edited by G4MR

Share on other sites

I believe using a normalised unit vector for direction would benefit you here. It'll have a couple of benefits for you, including uniform speed on diagonals.

I'm pretty sure SFML has a Vector2 class you can use for this, although I don't think it has much in the way of helper methods baked in.

What I would do would be to update the players direction V2 depending on the key pressed, e.g.:

if(event.type == Event::KeyPressed) {
switch(event.key.code) {
case Keyboard::W:
dir.Y = -1;
break;
case Keyboard::A:
dir.X = -1;
break;
case Keyboard::S:
dir.Y = 1;
break;
case Keyboard::D:
dir. = 1;
break;
}
}

if(event.type == Event::KeyReleased) {
switch(event.key.code) {
case Keyboard::W:
dir.Y = 0;
break;
case Keyboard::A:
dir.X = 0;
break;
case Keyboard::S:
dir.Y = 0;
break;
case Keyboard::D:
dir. = 0;
break;
}
}

You would then normalize the Vector and then apply it to the position.

Might look something like this (forgive the bad code, I just threw this together quickly off the cuff):

Run()
{
//Code
//Set the direction from input
normalize(&dir);
runx += dir.X * speed;
runy += dir.Y * speed;
//Code
}

Vector2 normalize(const Vector2& source)
{
float length = sqrt((source.x * source.x) + (source.y * source.y));
if (length != 0)
return Vector2(source.x / length, source.y / length);
else
return source;
} 

Share on other sites

You have errors in your code.

1st, ranx and rany are supposed to be for player2, yet here:

  

1. //did we move the player?
2.  if(mw) { rany -= speed; }   if(ma) { ranx -= speed; }   if(ms) { rany += speed; }   if(md) { ranx += speed; } 

You are adjusting ranx/y when you press keys, which is supposed to move player1. Using your code you should set newx and newy to the current player1 location at the start, then, when keys are pressed, adjust newx and newy, not ranx/rany.

Also, you are adjusting newx and newy with the angle from player2 to player1 here:

  

1. //get the angle of the line between both player locations (zombie/player)
2.  angle = (atan2(rany - player1.sprite.getPosition().y, ranx - player1.sprite.getPosition().x) * 180) / M_PI;   //steps between movement x = cos(angle) * speed; y = sin(angle) * speed;   newx = player1.sprite.getPosition().x + x; newy = player1.sprite.getPosition().y + y; 
  

But you should be using ranx and rany there, not newx and newy.

Basically, you are mixing the coordinates and actions between player1 and player2.  Please take care with what you do.  ideally, you should be storing the coordinates in the Player class, not using local ranx/newx variables.

Good luck.

Share on other sites

While I also suggest you use a vector difference like Jutaris suggested (though sf::Vector2 doesn't have ANY functions to normalize),

I think your original code's error is quite simple.

You're converting the result of your atan2 into degrees, but sin and cos are designed to accept radians. Remove the * 180 / M_PI which converts your angle to degrees.

Storing your angle should only look like this:

angle = atan2(rany - player1.sprite.getPosition().y, ranx - player1.sprite.getPosition().x);

I'm not sure how accurate BeerNutts analysis is - I thought all of that works as you indended, but the human controlled player is player2, and the zombie is player1.

Best 'o luck to ya though.

Share on other sites
While I also suggest you use a vector difference like Jutaris suggested (though sf::Vector2 doesn't have ANY functions to normalize),

I think your original code's error is quite simple.

You're converting the result of your atan2 into degrees, but sin and cos are designed to accept radians. Remove the * 180 / M_PI which converts your angle to degrees.

Storing your angle should only look like this:

angle = atan2(rany - player1.sprite.getPosition().y, ranx - player1.sprite.getPosition().x);

I'm not sure how accurate BeerNutts analysis is - I thought all of that works as you indended, but the human controlled player is player2, and the zombie is player1.

Best 'o luck to ya though.

This worked :D

Share on other sites

[quote name='Milcho' timestamp='1357577905' post='5018636']
I'm not sure how accurate BeerNutts analysis is - I thought all of that works as you indended, but the human controlled player is player2, and the zombie is player1.
[/quote]

Doh!  I assumed human player was player1.  My bad.

Share on other sites

Another version someone posted on /r/gamedev:

while(game_is_running) {

handle_input_and_move_player();

//get vector
vx = player.x - zombie.x;
vy = player.y - zombie.y;

//get length of vector (distance from zombie to player)
length = sqrt(vx*vx + vy*vy);

//check length is above the hit distance from player to zombie here
//if it's below the distance then the zombie has eaten the player
//length must be above zero or the normalisation will error (divide by zero)

//normalise
vx = vx/length;
vy = vy/length;

zombie.x += vx*zombie.speed*frame_time;
zombie.y += vy*zombie.speed*frame_time;

//draw everything
draw_everything();
}

Which also works.