Asteroids Ship Movement, Momentum Help.

I'm attempting to get a spacy movement system in an Asteroids Clone, I.E you don't instantly stop if you don't keep moving. That part 'works' but the actual movement based on angle bit is where I go wrong, I don't understand Trig. at all.

 
m_PlayerSprite.move(std::sin(3.14159265 * m_MomentumAngle / 180.f) * m_Momentum * deltaTime.asSeconds(), -1 * std::cos(3.14159265 * m_MomentumAngle / 180.f) * m_Momentum * deltaTime.asSeconds());


which was adapted from a piece of code I received on here and modified, which was

 
m_PlayerSprite.move(std::sin(3.14159265 * m_PlayerSprite.getRotation() / 180.f) * m_Speed * deltaTime.asSeconds(), -1 * std::cos(3.14159265 * m_PlayerSprite.getRotation() / 180.f) * m_Speed * deltaTime.asSeconds());


now, that piece of code worked fine for instant start-stop movement, it's hard to explain what my adapted version of it does so here's a download link to the release version of the game:

(WASD, Spacebar)

http://www.multiupload.co.uk/09MI1CQAA3

but let me try anyway, if you face right, move forward, face left, move forward, you do a sort of u-turn instead of slowing down.

I'm not even sure if that line of code is the problem, it could also be this block:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
        if (m_MomentumAngle > m_PlayerSprite.getRotation())
        {
            m_MomentumAngle -= ((m_MomentumAngle + m_PlayerSprite.getRotation()) / 2) * deltaTime.asSeconds();
            if (m_MomentumAngle < m_PlayerSprite.getRotation())
            {
                m_MomentumAngle = m_PlayerSprite.getRotation();
            }
        }
        else if (m_MomentumAngle < m_PlayerSprite.getRotation())
        {
            m_MomentumAngle += ((m_MomentumAngle + m_PlayerSprite.getRotation()) / 2) * deltaTime.asSeconds();
            if (m_MomentumAngle > m_PlayerSprite.getRotation())
            {
                m_MomentumAngle = m_PlayerSprite.getRotation();
            }
        }


Thanks in advance.
Last edited on
I'm not even sure if that line of code is the problem, it could also be this block:


That block doesn't make any sense to me, and I suspect that is where your problem lies.

It looks like that is where you are actually handling the ship rotation... but you are adding the rotation to itself, rather than just applying a fixed rate. Therefore the ship is going to rotate faster when its at a wider angle than it does at a narrower angle, which doesn't make any sense.

That is... assuming I'm reading the code correctly.


Really, you seem to be over-complicating this. You want the ship to have 3 things here:

1) position (coords, in the form of a vector)
2) direction (which way they're facing) (angle, in the form of a double)
3) velocity (speed) (coords, in the form of a vector)


When rotating, you simply adjust your direction by a fixed rate:


EDIT: note I'm using "m_MomentumAngle" here because it's what you had in your code, but really this is not the angle of momentum.. rather it's the angle that the ship is facing. You do not need to keep track of the angle of the actual ship movement. A better name would be m_FacingAngle or m_DirectionalAngle or something. /EDIT
1
2
3
4
5
6
7
8
9
10
// assuming positive rotation = counter-clockwise
if( user_wants_to_rotate_left )
  m_MomentumAngle += fixed_constant * your_time_scale;
if( user_wants_to_rotate_right )
  m_MomentumAngle -= fixed_constant * your_time_scale;

// and if you want to clip to make sure the angle is always between 0-360,
//  you can do something like this
while( m_MomentumAngle < 0 ) m_MomentumAngle += 360;
while( m_MomentumAngle > 360 ) m_MomentumAngle -= 360;



Once you have the angle, if the player moves forward, you apply force. Force simply adjusts the velocity. In your case, when they move forward, you want to apply force to push them in the direction they're currently facing:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// when the user wants to move forward:
double angle = m_MomentumAngle * pi / 180;

velocity.x += cos( angle ) * ship_accelleration_rate * your_time_scale;
velocity.y += sin( angle ) * ship_accelleration_rate * your_time_scale;

// here, to stop the ship from exceeding a maximum velocity, you can calculate the overall
//   speed and scale down the velocity appropriately

double totalvelocity = sqrt( (velocity.x*velocity.x) + (velocity.y*velocity.y) );

if( totalvelocity > maximum_velocity )
{
  // ship is moving too fast.  Scale the velocity down
  velocity *= maximum_velocity / totalvelocity;
}



Now that you are applying force to the ship's velocity... all you have to do to move the ship is apply its velocity:

 
m_PlayerSprite.move( velocity );
Last edited on
Sorry, I should have explained better(I didn't explain it at all), there are two angles, the player angles (the angle it is facing) and the momentum angle (the angle at which momentum is being applied), this way the ship can move right (momentum angle = 90) while the player faces left (getRotation = 270).

the actual rotation of the ship, not the rotation of the momentum angle, is

 
m_PlayerSprite.setRotation(m_PlayerSprite.getRotation() + -1 * m_TurnSpeed * deltaTime.asSeconds());


which is what you suggested, also the angle is already 'clipped' to 0 and 360 by SFML so I don't have to handle that myself, although I don't think it has always clipped it.

as for velocity, that's calculated on-the-fly rather than stored anywhere.

1
2
3
4
double angle = m_MomentumAngle * pi / 180;

velocity.x += cos( angle ) * ship_accelleration_rate * your_time_scale;
velocity.y += sin( angle ) * ship_accelleration_rate * your_time_scale;


that's what this does

 
m_PlayerSprite.move(std::sin(3.14159265 * m_MomentumAngle / 180.f) * m_Momentum * deltaTime.asSeconds(), -1 * std::cos(3.14159265 * m_MomentumAngle / 180.f) * m_Momentum * deltaTime.asSeconds());


or, in your format

1
2
3
4
5
6
double angle = 3.14159265 * m_MomentumAngle / 180.f;

double velocityX = std::sin(angle) * m_Momentum * deltaTime.asSeconds();
double velocityY = -1 * std::cos(angle) * m_Momentum * deltaTime.asSeconds();

m_PlayerSprite.move(velocityX, velocityY);


warping from one side of the screen to the other, and movement, is handled in the players draw function.

The actual problem, is the way the momentums angle should change. the momevement should be just like in real life, in space if accelerate in a direction and then rotate your ship without accelerating, you will still be moving in the same direction as you were when you accelerated and that part works in my game, but in real life if you were to then face directly opposite to the direction you were facing, and accelerate, it should decelerate you, where as in my game it causes the ship to make a sharp u-turn.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#ifndef PLAYER_HPP
#define PLAYER_HPP

#include <vector>
#include <string>
#include <memory>

#include <SFML/Graphics.hpp>

#include "Bullet.hpp"

class Player
{
    public:
        Player();
        void handleKey(sf::Keyboard::Key, const sf::Time& deltaTime);
        void draw(sf::RenderWindow& Window, const sf::Time& deltaTime);
        ~Player();
    private:
        float m_Acceleration;
        float m_Momentum;
        float m_MomentumAngle;
        float m_TurnSpeed;
        sf::Clock m_timeSinceLastShot;
        sf::Texture m_PlayerTexture;
        sf::Sprite m_PlayerSprite;
        std::vector<Bullet*> m_Bullets;
        sf::Time m_ShotDelay;
};

#endif  


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
void Player::handleKey(sf::Keyboard::Key key, const sf::Time& deltaTime)
{
    switch(key)
    {
    case sf::Keyboard::Key::W:

        if (m_MomentumAngle > m_PlayerSprite.getRotation())
        {
            m_MomentumAngle -= ((m_MomentumAngle + m_PlayerSprite.getRotation()) / 2) * deltaTime.asSeconds();
            if (m_MomentumAngle < m_PlayerSprite.getRotation())
            {
                m_MomentumAngle = m_PlayerSprite.getRotation();
            }
        }
        else if (m_MomentumAngle < m_PlayerSprite.getRotation())
        {
            m_MomentumAngle += ((m_MomentumAngle + m_PlayerSprite.getRotation()) / 2) * deltaTime.asSeconds();
            if (m_MomentumAngle > m_PlayerSprite.getRotation())
            {
                m_MomentumAngle = m_PlayerSprite.getRotation();
            }
        }
        else
        {
            m_Momentum += m_Acceleration * deltaTime.asSeconds();
        }

        std::cout << "Momentum = " << m_Momentum << "\nMomentumAngle = " << m_MomentumAngle << "\n";
        //m_PlayerSprite.move(std::sin(3.14159265 * m_PlayerSprite.getRotation() / 180.f) * m_Speed * deltaTime.asSeconds(), -1 * std::cos(3.14159265 * m_PlayerSprite.getRotation() / 180.f) * m_Speed * deltaTime.asSeconds());
        break;
    case sf::Keyboard::Key::A:
        m_PlayerSprite.setRotation(m_PlayerSprite.getRotation() + -1 * m_TurnSpeed * deltaTime.asSeconds());
        break;
    case sf::Keyboard::Key::S:
        m_Momentum -= m_Acceleration * deltaTime.asSeconds();

        if (m_MomentumAngle > m_PlayerSprite.getRotation())
        {
            m_MomentumAngle -= ((m_MomentumAngle + m_PlayerSprite.getRotation()) / 2) * deltaTime.asSeconds();
            if (m_MomentumAngle < m_PlayerSprite.getRotation())
            {
                m_MomentumAngle = m_PlayerSprite.getRotation();
            }
        }
        else if (m_MomentumAngle < m_PlayerSprite.getRotation())
        {
            m_MomentumAngle += ((m_MomentumAngle + m_PlayerSprite.getRotation()) / 2) * deltaTime.asSeconds();
            if (m_MomentumAngle > m_PlayerSprite.getRotation())
            {
                m_MomentumAngle = m_PlayerSprite.getRotation();
            }
        }

        std::cout << "Momentum = " << m_Momentum << "\nMomentumAngle = " << m_MomentumAngle << "\n";
        //m_PlayerSprite.move(-1 * std::sin(3.14159265 * m_PlayerSprite.getRotation() / 180.f) * m_Speed * deltaTime.asSeconds(), std::cos(3.14159265 * m_PlayerSprite.getRotation() / 180.f) * m_Speed * deltaTime.asSeconds());
        break;
    case sf::Keyboard::Key::D:
        m_PlayerSprite.setRotation(m_PlayerSprite.getRotation() + m_TurnSpeed * deltaTime.asSeconds());
        break;
    case sf::Keyboard::Key::Space:
        if (m_timeSinceLastShot.getElapsedTime().asSeconds() > m_ShotDelay.asSeconds())
        {
            m_Bullets.push_back(new Bullet(m_PlayerSprite.getPosition().x, m_PlayerSprite.getPosition().y, m_PlayerSprite.getRotation() - 10));
            m_Bullets.push_back(new Bullet(m_PlayerSprite.getPosition().x, m_PlayerSprite.getPosition().y, m_PlayerSprite.getRotation()));
            m_Bullets.push_back(new Bullet(m_PlayerSprite.getPosition().x, m_PlayerSprite.getPosition().y, m_PlayerSprite.getRotation() + 10));

            std::cout << "Bullets: " << m_Bullets.size() << "\n";
            /*else //deletes first bullet and makes a new one
            {
                m_Bullets.erase(m_Bullets.begin());
                m_Bullets.push_back(new Bullet(m_PlayerSprite.getPosition().x, m_PlayerSprite.getPosition().y, m_PlayerSprite.getRotation()));
            }*/
            m_timeSinceLastShot.restart();
        }
        break;
    default:
        break;
    }
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
void Player::draw(sf::RenderWindow& Window, const sf::Time& deltaTime)
{
    int xPos = m_PlayerSprite.getPosition().x;
    int yPos = m_PlayerSprite.getPosition().y;
    int sizeX = m_PlayerTexture.getSize().x;
    int sizeY = m_PlayerTexture.getSize().x;
    int winX = Window.getSize().x;
    int winY = Window.getSize().y;

    if ( xPos > -1 * (sizeX / 2))
    {
        if (xPos < winX + sizeX / 2)
        {
            if ( yPos > -1 * (sizeY / 2))
            {
                if (yPos < winY + sizeY / 2)
                {
                    m_PlayerSprite.move(std::sin(3.14159265 * m_MomentumAngle / 180.f) * m_Momentum * deltaTime.asSeconds(), -1 * std::cos(3.14159265 * m_MomentumAngle / 180.f) * m_Momentum * deltaTime.asSeconds());
                }
                else
                {
                    m_PlayerSprite.setPosition(xPos, 0);
                    yPos = 0;
                }
            }
            else
            {
                m_PlayerSprite.setPosition(xPos, Window.getSize().y);
                yPos = winY;
            }
        }
        else
        {
            m_PlayerSprite.setPosition(0, yPos);
            xPos = 0;
        }
    }
    else
    {
        m_PlayerSprite.setPosition(winX, yPos);
        xPos = winX;
    }

    Window.draw(m_PlayerSprite);

    for (size_t i = 0; i < m_Bullets.size(); ++i)
    {
        if (m_Bullets[i]->suicide == true)
        {
            delete m_Bullets[i];
            m_Bullets.erase(m_Bullets.begin() + i);
        }
        else
        {
            m_Bullets[i]->draw(Window, deltaTime);
        }
    }
}
let me know if there is any other details you need, if you have a build system set up for SFML I can package up the source for you if you'd like to experiment with it, also sorry for the messy code, ATM I'm trying to get a working prototype and then reuse the better parts and design in version 0.03

Another angle problem you can see is if you hold W + A or D, the ship makes a spiral movement pattern, but it should be going around in circles.
Last edited on
there are two angles, the player angles (the angle it is facing) and the momentum angle (the angle at which momentum is being applied)


This is your problem. The momentum angle is worthless. Get rid of it. The angle of movement is implied by the velocity... you do not need to (and shouldn't) track it separately.

The fundamentals of physics:

1) Force modifies Velocity
2) Velocity modifies Position.

You should have an x and y velocity (I recommend keeping this in an sfml vector), and an x and y position (also in an sfml vector -- but it seems you are just using the sprite position for this, which works, as well)


You are implementing velocity as an angle and a speed (m_MomentumAngle and m_Momentum, respectively). That is much more problematic than just implementing it as independent x and y speeds (my 'velocity' vector in my example)


EDIT: my previous example will make more sense if you replace my usage of the momentum angle with the angle the player is facing.
Last edited on
If I'm understanding you correctly, that won't produce the results I want, my force is acceleration and it is applied on pressing W, my velocity is calculated when my player needs to move, and is used to modify the players position.

Now, if I get rid of m_MomentumAngle, and just use m_PlayerSprite.getRotation(), the ships direction will change when you rotate with A/D and then the velocity is applied directly to that angle, this isn't what I want, the ship should not move based on the ships heading, it should move based on the the heading of the ship when force was applied, just like a real-life space ship, if I turn and want to move that way I must apply force to my velocity, but velocity only moves a ship and doesn't dictate its heading, for that I need my momentum angle, so I then apply force (acceleration) to my velocity, and force (the average of my momentum angle and my current ship heading) to my momentum angle, is this not the right way of doing it? perhaps I don't fully understand velocity.
Last edited on
Nah, we're miscommunicating. Let me rephrase.

Get the idea of momentum and momentum angle out of your head. Those are flawed concepts for this problem.

You have 3 key elements:

1) a position
2) a velocity
3) a direction that the player is facing


'velocity' is similar to your 'momentum'. The key difference is that instead of just being an "overall" speed, it is two different speeds -- one along the x axis and one along the y axis. All you have to do to apply the velocity is add it to the player's position:

1
2
3
4
position += velocity;  // done -- assuming both position and velocity are vectors

// or if using sf::Sprite to keep track of the position:
yoursprite.move( velocity );



Note that the velocity is completely independent of the facing direction. You can have a velocity of 1,0 (moving east) when the ship is facing due north, or you can have a velocity of -1,0 (moving west) when the ship is facing east... or whatever.

The only thing that modifies the velocity is force.


---
quick example of the problem you're seeing:

Ship is moving west, player is facing east. Player applies force.

Desired result: ship slows down. Angle of movement does not change (ship still moving west, just at a slower rate)

With your momentum approach, this is very, very difficult to accomplish because you somehow have to recognize that the angle of movement is directly opposite the angle that the player is facing. For proper behavior here, m_MomentumAngle must not change. It's almost like you have to treat this as a special case -- even when it really isn't.


But anyway... throwing away the momentum angle and using the velocity approach... all we have to do to accomplish this result is adjust the velocity by applying force in the direction the player is facing.


So let's say that we're moving at full speed (10) west. So our velocity is -10,0. Then the player faces due east and pushes forward.

We calculate the force they applied by using our famous sin/cos pair. Given an accelleration rate of '1', this means the force they applied by moving due east is 1,0. To apply this force, we just add it to the velocity.

velocity -10,0 + force 1,0 = new velocity -9,0.

Mission accomplished! We're still moving due west, but at a slower rate. As more force is applies, the ship will slow down and eventually reverse direction.


But the thing is it works with any angle. Say they're moving slightly north-west with a velocity of -5.6,-1.2. They point due east and push forward. Resulting in the same force 1,0. After applying that force, we have a new velocity of -4.6,-1.2.

With continued application of that force, the ship will eventually start moving north-east instead of north-west.




So that's the concept of force vs. velocity. With that in mind, let's revisit my previous example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//  This is code that is performed when the player fires their engines ('W' pressed)
sf::Vector2f force;  // we need to calculate the force to apply to the ship

double angle = angle_the_ship_is_facing * pi / 180;

force.x = cos( angle );
force.y = sin( angle );

force *= ship_accelleration_rate * your_time_scale;

// now we have our desired force.  Apply it to our velocity:
ship_velocity += force;  // <- it's as easy as this!


// only other thing we have to do is stop the player from moving too fast.
//   we can do this by examining the overall rate of the velocity (by applying pythagorean
//   theorum)

double totalvelocity = sqrt( (ship_velocity.x*ship_velocity.x) + (ship_velocity.y*ship_velocity.y) );

// here, 'totalvelocity' is equivilent to your 'momentum' value.  It's the overall speed of the
//  ship... without any indication of what direction it's moving towards.

if( totalvelocity > maximum_velocity )  // if it's moving faster than a given maximum speed
{
  // we can slow the ship down by scaling down the velocity
  velocity *= maximum_velocity / totalvelocity;
}
Last edited on
Wow, thanks for the amazing explanation and help.

I had to change the cos/sin around for x/y and reverse the sign of the result of cos, but it works!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    case sf::Keyboard::Key::W:
    {
        m_Force.x = 0;
        m_Force.y = 0;

        double angle = (3.14159265 * m_PlayerSprite.getRotation()) / 180;

        m_Force.x = std::sin(angle);
        m_Force.y = -1 * std::cos(angle);

        m_Force *= m_Acceleration * deltaTime.asSeconds();
        m_Velocity += m_Force;
        std::cout << m_Force.x << "/" << m_Force.y << "\n" << m_Velocity.x << "/" << m_Velocity.y << "\n\n";
        break;
    }


1
2
3
4
5
6
7
8
9
10
11
12
if (yPos < winY + sizeY / 2)
                {
                    double totalVelocity = std::sqrt((m_Velocity.x * m_Velocity.x) + (m_Velocity.y + m_Velocity.y));

                    if (totalVelocity > 100.f)
                    {
                        m_Velocity.x *= 100.f / totalVelocity;
                        m_Velocity.y *= 100.f / totalVelocity;
                    }

                    m_PlayerSprite.move(m_Velocity);
                }


All I need to do now is tweak my acceleration a bit, thanks again.
I had to change the cos/sin around for x/y and reverse the sign of the result of cos, but it works!


sin/cos operate on the assumption that "angle 0" is due east, and that it moves counter clockwise.

So:
0 deg = east
90 deg = north
180 deg = west
270 deg = south


Based on your tweakage... it looks like you might have angle 0 as due north, rather than due east.. and are rotating clockwise rather than counter-clockwise (which might be partly SFML's fault -- the fact that it treats positive Y as down instead of up still causes a great deal of confusion for me)

But whatever works. Congrats.
yeah, that's how SFML has it set up, I'm not sure of the reasons behind it.
Topic archived. No new replies allowed.