C++ and SFML. Angles won't work, help!

Hey guys, 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:
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#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:
1
2
3
float ang_const = 0.13;                 
float velX_const = 3.5, velY_const = 3.5;
float grav_const = -0.02;               


Ball movement:
1
2
velY += grav;
ball.shape.move(velX * cos(ball.RadToDeg(ang)), velY * -sin(ball.RadToDeg(ang)));


Radians to Degrees function:
1
2
3
4
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.
Last edited on
Your lines here at least are wrong:
1
2
        ball.shape.move(velX * cos(ball.RadToDeg(ang)),
                        velY * -sin(ball.RadToDeg(ang)));


In any scientific programming language (including C++) the arguments sent to cos() and sin() will be in radians, not degrees.

But, further inspection shows that is not your only problem. You need to read a Mechanics textbook.
Last edited on
Thank you very much for your reply!
But, further inspection shows that is not your only problem. You need to read a Mechanics textbook.


What do you mean by that? I'd be grateful if you point other mistake in my code?
Hello, @Ashmor.
You can get an analytic solution for any time using the "suvat" equations, since this is constant-acceleration motion (in two dimensions). Google "suvat equations" or "projectile motion" to follow that up.

Alternatively, you can repeatedly step forward in time by a time increment delta_t. A simple, slightly inaccurate version is
   position(x,y) += velocity(vx,vy) * delta_t
   velocity(vx,vy) += acceleration(0,-g) * delta_t

Alternatively, more accurately here, use the suvat equations over a timestep:
   position(x,y) += velocity(vx,vy) * delta_t + 0.5 * acceleration(0,-g) * delta_t^2
   velocity(vx,vy) += acceleration(0,-g) * delta_t

With stepping you will also, ultimately, be able to add drag (due to friction) or lift (due to spin).


The equation which you appear to be trying to use is really only that for the INITIAL velocity components:
vx = V * cos(angle)
vy = V * sin(angle)
(The latter equation may have a minus sign if you take y positive downward).
Thereafter, you should be using the suvat equations. The angle will change (and be pretty useless).

I'd seriously suggest that you sorted the maths out first.



Last edited on
Thank you for the explanation @lastchance! It's very helpful!

I'd seriously suggest that you sorted the maths out first.
Yeah. I know. I'm currently reading a Physics book (chapters about Kinematics and Newton's laws of motion.) and trying to implement what I learn from the book.

I'm talking about this book:
https://cnx.org/contents/Ax2o07Ul@16.17:HR_VN3f7@6/Introduction-to-Science-and-the-Realm-of-Physics-Physical-Quantities-and-Units
@Ashmor,

If you want a more physics engine/game engine oriented text on the subject(s), consider either

Game Physics Engine Development, by Ian Millington

Or any of the "Wild Magic" series by David Eberly (along with his open source engine on physics and rendering).

The reason I suggest these is that simulation for games (and other more serious targets) is quite different than a scientific (and other more formal targets) approach to the subject of motion in real time systems.

The issues of display rate vs time is covered, issues regarding performance are covered, and a lot of material related to the computational limits of modern processors and how that impacts the calculations.

For example, there are various designs of representation, and many tend to "oscillate" or constantly "bounce" due to calculations that reach very small (or very large) values. This is something a science based text will not address, and you might end up trying to retrace the thought and design work to deal with that yourself.

It appears you're focused on 2D, and there are typical "specializations" for 2D physics. If you move to 3D physics, you'll probably not find the use of quaternions in a science based text. They are an important tool in 3D.

Of course, the authors of these (and related) texts have used science behind their work, so what I'm suggesting is a compliment to the study, if not a change of direction.
Last edited on
@Niccolo,

Wow! Thank you a lot for the clarification and book recommendations! I've been looking for a good game development book for a while now.
Glad you liked that @Ashmor,

I have another thought for you based on your last phrase.

Some study to make game engines, which is a bit different than game development in the modern era (last 10 to 20 years).

Physics engines are rather involved libraries. They're touchy, and a bit of a black art. Decades have gone into the research (Eberly is a PhD, consulting for the industry for many years).

Game development, on the other hand, usually involves the use of a game engine. I've discovered some students, early in the study, tend to develop the viewpoint that this is some kind of cop-out.

If you intend to work toward the development of game engines themselves, that's one avenue. It naturally divides between physics and graphics (real time rendering). Both are relatively deep fields of study. To match even an older game engine (one of the big 4 or 5) would take several years to write.

The days when a team would write everything from blank page to finished game is over. The targets are simply too complex and varied. Just the API's for modern rendering are a major undertaking. Vulkan, DX12 and Metal (Apple) are the 3 primary API's now. Even though older DX versions and OpenGL (ES) are still somewhat in use, they will fade into history fairly soon (as in no hardware will bother to support them, or the platforms will loose support).

However, someone has to be able to engineer advancements on the existing engines, and maybe some new ones eventually. It is, certainly, a valid direction of study.

If, however, your intent is game development, then you may benefit some from a study of engine development (for a perspective), but you really would be well advised to switch directions and experiment with some of the major game engines.

Even if you do intend to study engine design and development, running through the tutorials on the big 3 or 4 Engines would provide a good view of the modern expectations.



@Niccolo,

Thanks for the answer. I'm planning to make an indie game at some point and maybe I'll even be able to accomplish it. I totally agree with you that it's best to use a game engine instead of trying to make one on your own which is an extremely difficult task. For now, I want to get game development basics before moving on to one of the game engines.
I improved my code, using some suvat formulas. It seems to be working just fine. Here's the code just in case someone's interested in it.

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#include <SFML/Graphics.hpp>
#include <math.h>
int WIDTH = 1024, HEIGHT = 704;

class Ball {
private:
    float radius = 16.00;
public:
    bool isGameStarted = false;
    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);     // deg = rad * (180 / pi)
    }
    float DegToRad (float degree) {
        double pi = 3.14159;
        return degree * pi / 180;       // rad = deg * pi / 180
    }
    
    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");
    
    // Ball
    Ball ball;
    
    // Some variables
    float angel_var = 70;
    float gravity_var = 9.8;
    float velocity_var = 5.0;
    float time_var = 0;
    
    float angel = angel_var;
    float gravity = gravity_var;
    float velocity = velocity_var;
    float t = time_var;
    
    float vx = velocity * cos(ball.DegToRad(angel));
    float vy = velocity * -sin(ball.DegToRad(angel));
    
    // 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);
    
    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();
            }
            
            // Angle
            if (event.type == sf::Event::KeyPressed &&
                event.key.code == sf::Keyboard::Up) {
                angel_var += 5;
            }
            if (event.type == sf::Event::KeyPressed &&
                event.key.code == sf::Keyboard::Down) {
                angel_var -= 5;
            }
            
            // Start movement
            if (event.type == sf::Event::KeyPressed &&
                event.key.code == sf::Keyboard::Space) {
                if (ball.isGameStarted) {
                    ball.isGameStarted = false;
                }
                else {
                    ball.isGameStarted = true;
                }
            }
        }
        
        // Ball movement
        if (ball.y() - ball.getRadius() > HEIGHT ||
            ball.x() - ball.getRadius() > WIDTH) {
            ball.isGameStarted = false;
        }
        
        if (ball.isGameStarted) {
            t += 0.002;
            ball.shape.move(vx * t, vy * t + (gravity/2)*t*t);
        }
        else {
            angel = angel_var;
            gravity = gravity_var;
            velocity = velocity_var;
            vx = velocity * cos(ball.DegToRad(angel));
            vy = velocity * -sin(ball.DegToRad(angel));
            t = time_var;
            ball.shape.setPosition(0 + ball.getRadius()*2, HEIGHT - ball.getRadius()*2);
        }
        
        // Clear screen
        window.clear(sf::Color(0,0,80,100));
        
        // Draw ball
        window.draw(ball.shape);
        
        // Draw text
        text1.setString("ang " + std::to_string((int)angel_var));
        window.draw(text1);
        
        // Update the window
        window.display();
    }
    
    return EXIT_SUCCESS;
}
Topic archived. No new replies allowed.