SFML community forums

Help => Graphics => Topic started by: LucasShadow on January 02, 2012, 12:55:32 am

Title: Sprite Movement on Single MouseClick
Post by: LucasShadow on January 02, 2012, 12:55:32 am
Hello.  I was wondering if anyone had any suggestions as how to go about making a sprite move from Point A to Point B on a 2d plane.  I am able to do this, but the sprite simply warps from one point to another...I have looked into making a speed variable (dont want to use framerates because of the "faster on faster computer" thing) somehow.  I have also looked into vectors some, but have not been able to figure out how to set the sprite's position with the vector.

EDIT: Using SFML 1.6

Maybe some code will help too
Code: [Select]
if (App.GetInput().IsMouseButtonDown(sf::Mouse::Left)) {

            OldX = PlayerObj.GetPosition().x;
            OldY = PlayerObj.GetPosition().y;
            NewX = App.GetInput().GetMouseX();
            NewY = App.GetInput().GetMouseY();
            XVector = (OldX - NewX);
            YVector = (OldY - OldY);
        }
        //Vector...
        float OldPosition = ???;
        float NewPosition = ???;
        sf::Vector2f GetDir(sf::Vector2f OldPosition);
        sf::Vector2f RVector = NewPosition - OldPosition;
        float temp =(float) sqrt((RVector.x * RVector.x + RVector.y * RVector.y));
        RVector = RVector / temp;
        RVector = RVector * SPEED;
        //Set position...
        PlayerObj.SetPosition(RVector.x, RVector.y);
Title: Sprite Movement on Single MouseClick
Post by: LucasShadow on January 04, 2012, 04:56:00 pm
Bump?

If it helps, I just need to find a way to define OldPosition (where the sprite was) and a way to define NewPosition (where the sprite is going).
Title: Sprite Movement on Single MouseClick
Post by: Tex Killer on January 04, 2012, 07:55:57 pm
You must use clocks to control the time passed since the click.

Specify some duration for the animation, say one second, and, based on the time passed since the click, you correct the position accordingly.
Title: Sprite Movement on Single MouseClick
Post by: jmcmorris on January 05, 2012, 12:26:37 am
This is how I do a translations. First you need to do initialization. Specify the destination and duration as well as store the starting position and keep track of the elapsed time since the translation started. Then you can calculate the progress of the translation and calculate the current position. Here is some psuedo code.

Code: [Select]
//initialization of translation
sf::Vector2f destination(100, 100);
Uint32 duration = 5000; //5 seconds
Uint32 elapsed = 0;
sf::Vector2f start = sprite.GetPosition();
sf::Vector2f delta = destination - start

Code: [Select]
//update translation
elapsed += renderWindow.GetFrameTime(); //sf::RenderWindow
float progress = elapsed / float(duration);
sf::Vector2f current = start + delta * min(progress, 1);
sprite.SetPosition(current);


Note that this is an absolute destination. For it to be relative you need to add start to destination in the initialization part.
Title: Sprite Movement on Single MouseClick
Post by: LucasShadow on January 05, 2012, 01:09:06 am
Alright, I think I am able to follow what you guys are getting at. I tried to alter my code some, and the result was much better, but with one little hiccup.  No matter what the distance my sprite goes, it always covers it within 2 seconds.  How can I alter my current code so that the sprite covers any distance at a constant speed?

Code:
Code: [Select]
if (App.GetInput().IsMouseButtonDown(sf::Mouse::Left)) {
            NewX = App.GetInput().GetMouseX();
            NewY = App.GetInput().GetMouseY();
            Clock1.Reset();
        }
        //Clock
        float ElapsedTime = Clock1.GetElapsedTime();
        //Initialization of translation
        sf::Vector2f NewPosition(NewX, NewY);
        float duration = 50;
        sf::Vector2f OldPosition = PlayerObj.GetPosition();
        sf::Vector2f Movement = NewPosition - OldPosition;
        //Update translation
        ElapsedTime += App.GetFrameTime();
        float progress = ElapsedTime / (duration);
        sf::Vector2f Current = OldPosition + Movement * progress;
        PlayerObj.SetPosition(Current);
        App.Draw(PlayerObj);
Title: Sprite Movement on Single MouseClick
Post by: jmcmorris on January 05, 2012, 04:33:45 am
For a constant rate you will want use the frametime and a constant. Multiply the constant by the framerate divided by the constant's unit. Such as if you want your player moving at 40 pixels per second then you need it to be 40 * (framerate / 1000) assuming the framerate is in milliseconds, which it is here. Another thing to consider is that if it moves at a diagonal then it will be moving faster. You will need to do some trigonometry to get the correct movement vector.
Title: Sprite Movement on Single MouseClick
Post by: LucasShadow on January 05, 2012, 01:49:44 pm
Where would I implement the constant speed in the code?

And by trigonometry, I assume you mean something like this:
Code: [Select]
 float XDistance = sqrt((NewX - OldX) * (NewX - OldX));//always outputs positive number
        float YDistance = sqrt((NewY - OldY) * (NewY - OldY));
        sf::Vector2f NewPosition(NewX, NewY);
        sf::Vector2f OldPosition = PlayerObj.GetPosition();
        float duration = 50;
        sf::Vector2f Movement = sqrt(XDistance * XDistance + YDistance * YDistance);


However, I get the following error:
Code: [Select]
|146|error: conversion from 'double' to non-scalar type 'sf::Vector2f' requested|
Title: Sprite Movement on Single MouseClick
Post by: Tex Killer on January 05, 2012, 07:23:25 pm
Your code is very messed up... The error is hapening on the last line, but look at the first two: You are taking the square root of some square number... Result: the same number.

The error is due to you trying to put a number on a vector variable on the last line. A vector is not a number.

By the way, to get what you want, you must forget the distance at all, and stablish some constant speed. Do it as jmcmorris sugested.
Title: Sprite Movement on Single MouseClick
Post by: LucasShadow on January 05, 2012, 10:17:28 pm
Quote

...you must forget the distance at all...


Okay...thats a little confusing in my head but okay.

Quote
...and stablish some constant speed. Do it as jmcmorris sugested.


Alright, but how do I implement his coding suggestions? Or better phrased, where do I implement it?

Code: [Select]

40 * FrameRate / 1000
Title: Sprite Movement on Single MouseClick
Post by: Tex Killer on January 06, 2012, 12:38:11 am
Create some current position vector, and get the angle between the start position and the end position, in radians.

Then you can do this each frame:
Code: [Select]
float speed = 40 * FrameTime / 1000;
current_position.x += cos(angle) * speed;
current_position.y += sin(angle) * speed;

When the current position is bigger than the destination, make it be the destination and the transition is over.
Title: Sprite Movement on Single MouseClick
Post by: LucasShadow on January 06, 2012, 04:12:27 pm
How would you calculate the angle between the two vectors without using the distance? Is there some special function I cant find in the documentation.
Title: Sprite Movement on Single MouseClick
Post by: Tex Killer on January 06, 2012, 06:07:18 pm
Yeah, you must use the distances, but only to get the angle and only one time.
You can use the standard atan function.
Title: Sprite Movement on Single MouseClick
Post by: LucasShadow on January 06, 2012, 10:38:52 pm
Alright, I know this is probably frustrating to you continuing to correct me so this is my last reply...I believe I have implemented all of the coding suggestions, but the result is the same.  No matter the distance, the sprite covers it in the same amount of time.

Code: [Select]
       if (App.GetInput().IsMouseButtonDown(sf::Mouse::Left)) {
            NewX = App.GetInput().GetMouseX();
            NewY = App.GetInput().GetMouseY();
            Clock1.Reset();
        }
        //Clock
        float ElapsedTime = Clock1.GetElapsedTime();
        float FrameTime = App.GetFrameTime();
        //Initialization of translation
        sf::Vector2f NewPosition(NewX, NewY);
        float duration = 50;
        sf::Vector2f OldPosition = PlayerObj.GetPosition();
        sf::Vector2f Movement = NewPosition - OldPosition;
        //Update translation
        ElapsedTime += App.GetFrameTime();
        float progress = ElapsedTime / (duration);
        sf::Vector2f Current_Position = OldPosition + Movement * progress;
        float speed = 40 * FrameTime / 1000;
        float angle = atan((sqrt((NewX - OldX) * (NewX - OldX))) / (sqrt((NewY - OldY) * (NewY - OldY))));
        Current_Position.x += cos(angle) * speed;
        Current_Position.y += sin(angle) * speed;
        if(PlayerObj.GetPosition().x == NewX && PlayerObj.GetPosition().y == NewY);
        PlayerObj.SetPosition(Current_Position.x, Current_Position.y);


If I take out the following code from the above code...
Code: [Select]
if(PlayerObj.GetPosition().x == NewX && PlayerObj.GetPosition().y == NewY);
It doesn't seem to have an effect.  In conjunction with this article: http://gamedev.stackexchange.com/questions/20175/sfml-moving-a-sprite-on-mouseclick, I think I have done something wrong since I don't seem to need to control it...
Title: Sprite Movement on Single MouseClick
Post by: Naufr4g0 on January 07, 2012, 12:00:11 am
I think you have to normalize  the "destination - origin" vector, that is the movement direction vector, on mouse click, with a code like this:

Code: [Select]

sf::Vector2f direction = Destination - Origin;
// calculate distance between origin and destination points
float distance = sqrt( (Destination.X - Origin.X) * (Destination.X - Origin.X) + (Destination.Y - Origin.Y) *  (Destination.Y - Origin.Y) );
// vector normalization (you obtain a unit vector = vector of lenght 1)
direction /= distance;


At each loop you could move your player sprite with:

Code: [Select]

Player.Move ( direction * speed );


So you have a movement not-dependent from distance. :)
You have also to manually check when sprite reach the destination point (within a certain error margin)
Title: Sprite Movement on Single MouseClick
Post by: LucasShadow on January 14, 2012, 04:24:54 am
I dont expect a reply right now since everything I need to now is probably right in front of me....

I have tried working on this for a few days alone now, and have the below code:
Code: [Select]
if (App.GetInput().IsMouseButtonDown(sf::Mouse::Left)) {
            NewX = App.GetInput().GetMouseX();
            NewY = App.GetInput().GetMouseY();
            Clock1.Reset();
        }
        sf::Vector2f Origin = PlayerObj.GetPosition();
        sf::Vector2f Destination(NewX, NewY);
        sf::Vector2f Direction = Destination - Origin;
        // calculate distance between origin and destination points
        float Distance = sqrt( (Destination.x - Origin.x) * (Destination.x - Origin.x) + (Destination.y - Origin.y) *  (Destination.y - Origin.y) );
        // vector normalization (you obtain a unit vector = vector of lenght 1)
        Direction /= Distance;
        float FrameTime = App.GetFrameTime();
        float Speed = 40 * FrameTime / 1000;
        PlayerObj.Move(Direction * Speed);


Problem is that when I run it, the sprite doesnt move at all. Thanks in advance to anyone willing to help me :)
Title: Sprite Movement on Single MouseClick
Post by: Tex Killer on January 14, 2012, 07:54:57 am
Take all the lines from
Code: [Select]
Direction /= Distance; up and put inside the if. You better make your Direction vector and your Distance variable outside the main loop as well, so that they remain on multiple iterations.

The last three lines I think you should change.
Make something like this:
Code: [Select]
float Speed = 40.f;
float DistanceMoved = Speed * Clock1.GetElapsedTime() / 1000.f;
if (DistanceMoved > Distance) {
    DistanceMoved = Distance;
}
PlayerObj.SetPosition(Origin + Direction * DistanceMoved);

If you want to change the speed, change the 40.f to something else.
Title: Sprite Movement on Single MouseClick
Post by: LucasShadow on January 14, 2012, 05:20:48 pm
Hey Tex Killer, I tried to implemented the changes you suggested, with declaring the Direction, Distance, and Origin vectors outside the main loop. However, I still have the same problem: The sprite doesn't move.

Code: [Select]
if (App.GetInput().IsMouseButtonDown(sf::Mouse::Left)); {
            NewX = App.GetInput().GetMouseX();
            NewY = App.GetInput().GetMouseY();
            Clock1.Reset();
            sf::Vector2f Origin = PlayerObj.GetPosition();
            sf::Vector2f Destination(NewX, NewY);
            sf::Vector2f Direction = Destination - Origin;
            // calculate distance between origin and destination points
            float Distance = sqrt( (Destination.x - Origin.x) * (Destination.x - Origin.x) + (Destination.y - Origin.y) *  (Destination.y - Origin.y) );
            // vector normalization (you obtain a unit vector = vector of lenght 1)
            Direction /= Distance;}
            float Speed = 50000.f;
            float DistanceMoved = Speed * Clock1.GetElapsedTime() / 1000.f;
            if (DistanceMoved > Distance) {
            DistanceMoved = Distance;
            }
        PlayerObj.SetPosition(Origin + Direction * DistanceMoved);
Title: Sprite Movement on Single MouseClick
Post by: Tex Killer on January 15, 2012, 12:26:38 am
I think there is something wrong in other part of your code. Debug it to see which position is being set at each iteration, and check how is the sprite being draw.
Title: Sprite Movement on Single MouseClick
Post by: LucasShadow on January 16, 2012, 04:27:20 pm
Everything seems to be correct, though I may have messed up with declaring the variables outside of the loop?
Code: [Select]
#include <SFML/Graphics.hpp>

 int main()
 {
     sf::RenderWindow App(sf::VideoMode(800, 600), "Sprite Test");

     sf::Image ForestTileImg;
     sf::Image PlayerImg;
     if(!ForestTileImg.LoadFromFile("forest_terrain_tile.png")) {
        return EXIT_FAILURE;
     }
     if(!PlayerImg.LoadFromFile("player.png")) {
        return EXIT_FAILURE;
     }

     ForestTileImg.SetSmooth(false);
     PlayerImg.SetSmooth(false);

     sf::Sprite PlayerObj;
     sf::Sprite ForestTileObj;

     ForestTileObj.SetImage(ForestTileImg);
     ForestTileObj.SetPosition(400,284);

    PlayerObj.SetImage(PlayerImg);
    PlayerObj.SetPosition(400, 284);

    float OldX = PlayerObj.GetPosition().x;
    float OldY = PlayerObj.GetPosition().y;
    float NewX = (OldX + 1);
    float NewY = (OldY - 1);
    sf::Clock::Clock(Clock1);

    sf::Vector2f Origin = PlayerObj.GetPosition();
    sf::Vector2f Destination(NewX, NewY);
    sf::Vector2f Direction = Destination - Origin;
    float Distance = sqrt( (Destination.x - Origin.x) * (Destination.x - Origin.x) + (Destination.y - Origin.y) *  (Destination.y - Origin.y) );

     while (App.IsOpened())
     {
         sf::Event Event;
         while (App.GetEvent(Event))
         {
             if (Event.Type == sf::Event::Closed)
                 App.Close();
         }
         App.Clear();

         if (App.GetInput().IsMouseButtonDown(sf::Mouse::Left)); {
            NewX = App.GetInput().GetMouseX();
            NewY = App.GetInput().GetMouseY();
            Clock1.Reset();
            sf::Vector2f Origin = PlayerObj.GetPosition();
            sf::Vector2f Destination(NewX, NewY);
            sf::Vector2f Direction = Destination - Origin;
            // calculate distance between origin and destination points
            float Distance = sqrt( (Destination.x - Origin.x) * (Destination.x - Origin.x) + (Destination.y - Origin.y) *  (Destination.y - Origin.y) );
            // vector normalization (you obtain a unit vector = vector of lenght 1)
            Direction /= Distance;}
            float Speed = 50000.f;
            float DistanceMoved = Speed * Clock1.GetElapsedTime() / 1000.f;
            if (DistanceMoved > Distance) {
            DistanceMoved = Distance;
            }
         PlayerObj.SetPosition(Origin + Direction * DistanceMoved);

         App.Draw(PlayerObj);
         App.Draw(ForestTileObj);
         App.Display();
     }
     return EXIT_SUCCESS;
 }
Title: Sprite Movement on Single MouseClick
Post by: Tex Killer on January 16, 2012, 10:53:25 pm
You are redeclaring all the variables inside the loop again. Just use the ones you declared before.
Title: Sprite Movement on Single MouseClick
Post by: LucasShadow on January 17, 2012, 02:19:45 am
Okay, but there is one problem. Dont I need to declare the NewX, NewY variables twice? Once for a defining them for the definitions of the vectors outside of the loop, and a second time within the if statement that collects data from the mouse position:

Code: [Select]
   float NewX = 0;
    float NewY = 0;
    sf::Clock::Clock(Clock1);
    sf::Vector2f Origin = PlayerObj.GetPosition();
    sf::Vector2f Destination(NewX, NewY);
    sf::Vector2f Direction = Destination - Origin;
    float Distance = sqrt( (Destination.x - Origin.x) * (Destination.x - Origin.x) + (Destination.y - Origin.y) *  (Destination.y - Origin.y) );

     while (App.IsOpened())
     {
         sf::Event Event;
         while (App.GetEvent(Event))
         {
             if (Event.Type == sf::Event::Closed)
                 App.Close();
         }
         App.Clear();

         if (App.GetInput().IsMouseButtonDown(sf::Mouse::Left)); {
            NewX = App.GetInput().GetMouseX();
            NewY = App.GetInput().GetMouseY();
            Clock1.Reset();
            Direction /= Distance;}
            float Speed = 50000.f;
            float DistanceMoved = Speed * Clock1.GetElapsedTime() / 1000.f;
            if (DistanceMoved > Distance) {
            DistanceMoved = Distance;
            }
         PlayerObj.SetPosition(Origin + Direction * DistanceMoved);
Title: Sprite Movement on Single MouseClick
Post by: Tex Killer on January 17, 2012, 06:39:28 am
In your code, you are not declaring them twice. In fact, I think your Destination vector should start off with the same value as the Origin vector, so that it starts not moving.

Actually, NewX and NewY don't need to be even declared outside the if, as they are only temporary variables that you use to make the Destination vector... But hey, you are not updating the Destination vector inside the if! You must change the destination on the mouse click, right?

You must also update the Origin vector, the Direction vector and the Distance variable. All of this before this line:
Code: [Select]
Direction /= Distance;

If all of this is set right, I think the code should work... But keep in mind that with the current speed, the movement would be pratically instantaneous.
Title: Sprite Movement on Single MouseClick
Post by: LucasShadow on January 17, 2012, 07:11:23 am
I did ask you suggested.  The Destination vector in the beginning to the Origin vector outside of the loop.  The NewX and NewY are now inside the loop, and the Destination vector is now  updated inside the if statement. Everything else is updated as well.  

Code: [Select]
   sf::Clock::Clock(Clock1);
    sf::Vector2f Origin = PlayerObj.GetPosition();
    sf::Vector2f Destination = Origin;
    sf::Vector2f Direction = Destination - Origin;
    float Distance = sqrt( (Destination.x - Origin.x) * (Destination.x - Origin.x) + (Destination.y - Origin.y) *  (Destination.y - Origin.y) );

     while (App.IsOpened())
     {
         sf::Event Event;
         while (App.GetEvent(Event))
         {
             if (Event.Type == sf::Event::Closed)
                 App.Close();
         }
         App.Clear();

         if (App.GetInput().IsMouseButtonDown(sf::Mouse::Left)); {
            Clock1.Reset();
            float NewX = App.GetInput().GetMouseX();
            float NewY = App.GetInput().GetMouseY();
            Origin = PlayerObj.GetPosition();
            sf::Vector2f Destination(NewX, NewY);
            Direction = Destination - Origin;
            Direction /= Distance;}
            float Speed = 500.f;
            float DistanceMoved = Speed * Clock1.GetElapsedTime() / 1000.f;
            if (DistanceMoved > Distance) {
            DistanceMoved = Distance;
            }
         PlayerObj.SetPosition(Origin + Direction * DistanceMoved);


Bugs: One, the player does not show up on the screen on startup. I think it has something to do with this code:
Code: [Select]
   sf::Vector2f Destination = Origin;
    sf::Vector2f Direction = Destination - Origin;


Second, still nothing happens when the mouse is clicked. Perhaps related to the first bug?
Title: Sprite Movement on Single MouseClick
Post by: Tex Killer on January 17, 2012, 09:14:28 pm
Every time you put the type before the variable name, you are (re)declaring it. You didn't updated the Destination variable inside the if, you redeclared it. See:
Code: [Select]
sf::Vector2f Destination(NewX, NewY);
Also, you are still not updating the Distance variable inside the if. The distance can start with 0 at the beginning as well, as the origin and destination are the same.
Title: Sprite Movement on Single MouseClick
Post by: LucasShadow on January 17, 2012, 10:02:08 pm
How would I update the Destination and Distance vectors then?  Because doing:
Code: [Select]
Destination = (NewX, NewY);
Doesnt work (error).  And I can not find any other commands in the documentation that would allow for the vector to be updated within the if statement.
Title: Sprite Movement on Single MouseClick
Post by: Naufr4g0 on January 17, 2012, 10:14:52 pm
Quote from: "LucasShadow"
How would I update the Destination and Distance vectors then?  Because doing:
Code: [Select]
Destination = (NewX, NewY);
Doesnt work (error).  And I can not find any other commands in the documentation that would allow for the vector to be updated within the if statement.


The right code is:

Code: [Select]

Destination = sf::Vector2f (NewX, NewY);


Additionally, I think you have to check events for mouse inside the while statement, instead of using App.GetInput() method, cause they have a different behavior.
App.GetInput().IsMouseButtonDown() returns true continuously untill you release the button.
App.GetEvent() returns a true value only at the moment you press the button.
Title: Sprite Movement on Single MouseClick
Post by: LucasShadow on January 17, 2012, 10:48:53 pm
I got it to work now thanks to everyone. Just one little thing lol. The player continuously follows the cursor, even though everything is now within the if statement, it doesnt wait for an action from the mouse.

Code: [Select]
        if (App.GetInput().IsMouseButtonDown(sf::Mouse::Left));
            {
            Clock1.Reset();
            float NewX = App.GetInput().GetMouseX();
            float NewY = App.GetInput().GetMouseY();
            sf::Vector2f Origin = PlayerObj.GetPosition();
            sf::Vector2f Destination = sf::Vector2f (NewX, NewY);
            sf::Vector2f Direction = Destination - Origin;
            float Distance = sqrt( (Destination.x - Origin.x) * (Destination.x - Origin.x) + (Destination.y - Origin.y) *  (Destination.y - Origin.y) );
            Direction /= Distance;
            float Speed = .5f;
            PlayerObj.SetPosition(Origin + Direction * Speed);
            }
Title: Sprite Movement on Single MouseClick
Post by: Tex Killer on January 17, 2012, 11:45:52 pm
Sorry, only now I noticed the ; on the end of this line:
Code: [Select]
if (App.GetInput().IsMouseButtonDown(sf::Mouse::Left));
This makes your if empty, and all the code goes outside it. Get rid of the ;.

Overall, having a good identation helps you see the code better, so I sugest you format all your code.
Title: Sprite Movement on Single MouseClick
Post by: LucasShadow on January 18, 2012, 01:32:39 am
Ah, thank you for that lol Little embarrassing to miss a little colon. It is nearly perfect guys, thank you very much. However, the player now only moves if the mouse button is held down, even though the PlayerObj.SetPosition command is outside of the if statement:
Code: [Select]
   sf::Vector2f Origin = PlayerObj.GetPosition();
    sf::Vector2f Destination = Origin;
    sf::Vector2f Direction = Destination - Origin;
    float Speed = .5f;

     while (App.IsOpened())
     {
         sf::Event Event;
         while (App.GetEvent(Event))
         {
             if (Event.Type == sf::Event::Closed)
                 App.Close();
         }
         App.Clear();

         if (App.GetInput().IsMouseButtonDown(sf::Mouse::Left))
            {
            float NewX = App.GetInput().GetMouseX();
            float NewY = App.GetInput().GetMouseY();
            Origin = PlayerObj.GetPosition();
            Destination = sf::Vector2f (NewX, NewY);
            Direction = Destination - Origin;
            float Distance = sqrt( (Destination.x - Origin.x) * (Destination.x - Origin.x) + (Destination.y - Origin.y) *  (Destination.y - Origin.y) );
            Direction /= Distance;
            }
         PlayerObj.SetPosition(Origin + Direction * Speed);
Title: Sprite Movement on Single MouseClick
Post by: Tex Killer on January 18, 2012, 03:22:25 am
Obsviously, this is because you've put everyting inside the if a couple of posts before. Nobody said that it was a good thing to do.