Handling time in C++ - c++

I am writing a simulation program. I have some difficulties when handling the time.
My program has a time period and a total elapsed time variables. I have traffic light objects. I want to change the color of them when the elapsed time is integer multiple of the period. For example, say total time starts from 0 and finishes in 60 seconds and the period is 10 seconds. So, when time is 10, 20, 30, etc. , the color should be changed.
I tried solving this issue with using simple math but nothing has been changed when I draw the objects. So, how can I handle the time to change their colors?

You can use the standard <chrono> library, but SFML has its own set of tools for handling time. You don't need any complicated calculations or threads. A simplified example of part of what you need:
#include <SFML/Graphics.hpp>
#include <vector>
class Light : public sf::CircleShape {
public:
Light(std::vector<sf::Color> cols, sf::Time period)
:colors{ cols }, colorIdx{ 0 }, changePeriod{ period }
{
setRadius(100);
}
void update(sf::Time deltaTime) {
elapsedTime += deltaTime;
while (elapsedTime >= changePeriod) {
elapsedTime -= changePeriod;
changeColor();
}
setColor();
}
protected:
void changeColor() {
if (++colorIdx == colors.size()) {
colorIdx = 0;
}
}
void setColor() {
setFillColor(colors[colorIdx]);
}
private:
std::vector<sf::Color> colors;
std::size_t colorIdx;
sf::Time changePeriod;
sf::Time elapsedTime;
};
int main() {
Light light1({sf::Color::Red, sf::Color::Yellow, sf::Color::Green}, sf::seconds(1));
Light light2({sf::Color::Red, sf::Color::Green}, sf::milliseconds(200));
light2.setPosition(300, 300);
light2.setRadius(20);
sf::RenderWindow window(sf::VideoMode(400, 400), "");
sf::Event event;
sf::Clock clock;
while (window.isOpen()) {
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) window.close();
}
sf::Time dt = clock.restart();
light1.update(dt);
light2.update(dt);
window.clear();
window.draw(light1);
window.draw(light2);
window.display();
}
}

You could use delay(milliseconds) with the header file #include<dos.h>. For example if you want your traffic lights to be changed every 10 seconds in 60 seconds you could do the following code:
for(int i = 0; i < 6; i ++)
{
changeColor();
delay(1000);
}
Also, you could use sleep(). You may find the answer to your question here:
http://www.cplusplus.com/forum/beginner/13906/

Related

SFML slow when drawing more than 500 shapes

I am new to SFML and I would like to implement a fluid boids simulation. However, I realized that when more than 500 shapes are drawn at the same time, the fps drop quickly.
How can I improve the code below to make it much faster to run?
#include <SFML/Graphics.hpp>
#include <vector>
#include <iostream>
sf::ConvexShape newShape() {
sf::ConvexShape shape(3);
shape.setPoint(0, sf::Vector2f(0, 0));
shape.setPoint(1, sf::Vector2f(-7, 20));
shape.setPoint(2, sf::Vector2f(7, 20));
shape.setOrigin(0, 10);
shape.setFillColor(sf::Color(49, 102, 156, 150));
shape.setOutlineColor(sf::Color(125, 164, 202, 150));
shape.setOutlineThickness(1);
shape.setPosition(rand() % 800, rand() % 600);
shape.setRotation(rand() % 360);
return shape;
}
int main() {
sf::Clock dtClock, fpsTimer;
sf::RenderWindow window(sf::VideoMode(800, 600), "Too Slow");
std::vector<sf::ConvexShape> shapes;
for (int i = 0; i < 1000; i++) shapes.push_back(newShape());
while (window.isOpen()) {
window.clear(sf::Color(50, 50, 50));
for (auto &shape : shapes) { shape.rotate(0.5); window.draw(shape); }
window.display();
float dt = dtClock.restart().asSeconds();
if (fpsTimer.getElapsedTime().asSeconds() > 1) {
fpsTimer.restart();
std::cout << ((1.0 / dt > 60) ? 60 : (1.0 / dt)) << std::endl;
}
}
}
I have the following performance:
Shapes FPS
10 60
100 60
500 60
600 60
700 55
800 50
900 45
1000 21
My goal is to have about 5k boids on the screen.
EDIT
I am building the project on Windows 11 under WSL2 with vGPU enabled. When testing natively on Windows 11 with Visual Studio I get much better performance (I can run 5k boids at 60 FPS)
The problems are a lot of draw calls. That is slow part of this program. In order to fix this, we can put all triangles into single vertex array and call draw upon only that array. That way we will speed up program. Problem with it is that you must implement your own rotate method. I implemented the method below and edited so the function returns triangles in single vertex array.
#include <SFML/Graphics.hpp>
#include <iostream>
//This function returns vertex array by given number of triangles
sf::VertexArray newShape(int numberOfTriangles) {
sf::VertexArray shape(sf::Triangles);
//we are going trough every point in each triangle
for (int i=0;i<3*numberOfTriangles;i++){
//creating points of triangles as vertexes 1, 2 and 3
sf::Vertex v1(sf::Vector2f(rand() % 800, rand() % 600));
sf::Vertex v2(sf::Vector2f(v1.position.x - 7, v1.position.y - 20));
sf::Vertex v3(sf::Vector2f(v1.position.x + 7, v1.position.y - 20));
//setting color
v1.color = v2.color = v3.color = sf::Color(49, 102, 156, 150);
//rotating for random angle
sf::Transform transform;
transform.rotate(rand()%90, (v2.position.x + v3.position.x) / 2,v1.position.y - 10);
v1.position = transform.transformPoint(v1.position);
v2.position = transform.transformPoint(v2.position);
v3.position = transform.transformPoint(v3.position);
//appending them into vertex array
shape.append(v1);
shape.append(v2);
shape.append(v3);
}
return shape;
}
//rotate function finds the middle of 3 vertexes and rotates them
void rotate(sf::VertexArray& array, double angle){
for (int i=0;i<array.getVertexCount();i+=3){
sf::Transform transform;
transform.rotate(angle, (array[i+1].position.x + array[i+2].position.x) / 2, (array[i].position.y + array[i+1].position.y)/2);
array[i].position = transform.transformPoint(array[i].position);
array[i+1].position = transform.transformPoint(array[i+1].position);
array[i+2].position = transform.transformPoint(array[i+2].position);
}
}
int main() {
sf::Clock dtClock, fpsTimer;
sf::RenderWindow window(sf::VideoMode(800, 600), "Too Slow");
//creating new array with 30000 triangles
sf::VertexArray shapes = newShape(30000);
window.setFramerateLimit(60);
while (window.isOpen()) {
//event to close window on close button
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
window.clear(sf::Color(50, 50, 50));
//no need for for now, as you can rotate them all in function and draw them together
rotate(shapes,5);
window.draw(shapes);
window.display();
float dt = dtClock.restart().asSeconds();
if (fpsTimer.getElapsedTime().asSeconds() > 1) {
fpsTimer.restart();
std::cout << ((1.0 / dt > 60) ? 60 : (1.0 / dt)) << std::endl;
}
}
}
The bottleneck now is not drawing but rotating, i tested this code with 100000 triangles and im getting around 45 fps. With 1000000 i am getting bad framerate because of rotation.

C++ SFML. How to create a diminishing(shrinking) circle

I have a class of circles that appear and disappear in the window for a while, there may be several, or maybe one. Currently drawn circles are stored in the vector_of_current_circles vector. I need to make them shrink to a certain size over time. How to do it?
window while loop:
while (window.isOpen()) {
// check all the window's events that were triggered since the last iteration of the loop
sf::Event event;
while (window.pollEvent(event)) {
// "close requested" event: we close the window
if (event.type == sf::Event::Closed)
window.close();
}
for (int i = 0; i < vector_of_circles.size(); i++) {
if (std::fabs(vector_of_circles[i].getBeginOfLife() - clock.getElapsedTime().asSeconds()) < 1e-2) {
if (!vector_of_circles[i].get_is_drawn()) {
window.clear();
window.draw(sprite);
vector_of_current_circles.push_back(vector_of_circles[i]);
for (const auto &item : vector_of_current_circles) {
item.print_circle(window);
}
window.display();
vector_of_circles[i].set_is_drawn();
}
}
if (std::fabs(vector_of_circles[i].getEndOfLife() - clock.getElapsedTime().asSeconds()) < 1e-2) {
if (vector_of_circles[i].get_is_drawn()) {
vector_of_current_circles.erase(vector_of_current_circles.begin());
vector_of_circles[i].set_is_drawn();
}
window.clear();
window.draw(sprite);
for (const auto &item : vector_of_current_circles) {
item.print_circle(window);
}
window.display();
}
}
}
Here is Circle code:
private:
sf::CircleShape circle_;
//sf::Clock clock;
float begin_of_life_;
bool is_drawn_ = false;
float end_of_life_;
//sf::RenderWindow& window_;
public:
Circle();
void print_circle(sf::RenderWindow&) const;
float get_radius() const;
void set_position(float, float);
void set_texture(sf::Texture&);
void setBeginOfLife(float);
void setEndOfLife(float);
double getBeginOfLife() const;
double getEndOfLife() const;
bool get_is_drawn() const;
void set_is_drawn();
To reduce equaly a circl in a certain time with a certain speed, you need:
speed (speed_) value: the speed of the reduice of radius by second,
radius (radius_) value: the initial value of the radius.
Your circle need to have setOrigin to the center.
// your function to reduce a certain circle (class member)
void reduce()
{
float elapsed_time = ; // your time elapsed from the last call
// Getting the position of your center
// if you haven't set the origin to the center of the circle this code doesn't work
sf::Vector2f pos = circle_.getPosition();
radius_ -= (speed_ * elapsed_time); // calculating the new radius
circle_.setRadius(radius_); // set the new radius
circle_.setOrigin(sf::Vector2f(radius_ / 2, radius_ / 2)); // update the origine to the center
circle_.setPosition(pos); // not 100% sure that this line is used
}
Now you have a circle that reduce at a certain speed and staying in the same place.

Write text input on the screen in SFML

So I'm creating a graphing calculator. I have an input string s. From the string, I can graph it using SFML. I start from the a MIN x-coordinate to a MAX x-coordinate, get the corresponding y from a EvaluateString() method, and all the coordinates to a VertexArray v. I wrote my method and the graphing method already and it all worked well.
However, I have a small issue. I want to input my string on the screen, such as "sin(cos(tan(x)))" like this. I'm struggling to find a way to do it. I kinda figured out it has to do with the event TextEntered, but still I can't find anything completely.
Please suggest me a way.
class Calculator{
public:
void main();
private:
WindowSize DefaultWindow;
sf::RenderWindow window;
Cartesian vertexX[2],vertexY[2];
sf::Vertex axis[4];
const double MAX = 10;
const double MIN = -10;
const double INCREMENT = 0.001;
};
int main(){
DefaultWindow.Max = Cartesian(10,10);
DefaultWindow.Min = Cartesian(-10,-10);
DefaultWindow.plane.width=1500;
DefaultWindow.plane.height=1500;
// Set up x and y-axis
vertexX[0] = Cartesian(-100,0);
vertexX[1] = Cartesian(100, 0);
vertexY[0] = Cartesian(0,-100);
vertexY[1] = Cartesian(0,100);
axis[0] = sf::Vertex(convertCartesiantoWindow(vertexX[0],DefaultWindow));
axis[1] = sf::Vertex(convertCartesiantoWindow(vertexX[1],DefaultWindow));
axis[2] = sf::Vertex(convertCartesiantoWindow(vertexY[0],DefaultWindow));
axis[3] = sf::Vertex(convertCartesiantoWindow(vertexY[1],DefaultWindow));
// Set up the window
window.create(sf::VideoMode(1500, 1500), "Graphing calculator");
// Input string
string s = "sin(cos(tan(x)))";
// Stack c contains all the Cartesian coordinate vertices
// Cartesian is a struct which contains x and y coordinates
Stack<Cartesian> c;
sf::VertexArray v;
// For a certain function in string s, I evaluate it
// and return the y_coordinate from the function EvaluateString (s, i)
// Push each (x,y) evaluated in the Stack c
for (double i = MIN; i <= MAX; i+= INCREMENT)
c.Push(Cartesian(i,EvaluateString(s,i)));
// v is VertexArray which contains all the vertices (x,y)
v = plot(DefaultWindow, c);
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
switch (event.type) {
case sf::Event::Closed:
window.close();
break;
}
}
}
// Draw the graph
window.clear(sf::Color::Black);
window.draw(axis,4,sf::Lines);
window.draw(v);
window.display();
}
As #super suggest, use a library would be a nice solution, and surely better than mine, but just in case this satisfies your needs, I implemented a super basic TextField class.
It may be plenty of errors, but it can gives you an idea on how to achieve that functionality.
A TextField is nothing more than a rectangle which contains a text. Since it will have a sf::Text, it must have a sf::Font. Additionally, I limit the number of characters that it will contain. In order for us to write inside the TextField, we have to know if it's selected, i.e. if it has the focus. So, a first approach could be:
class TextField : public sf::Transformable, public sf::Drawable{
private:
unsigned int m_size;
sf::Font m_font;
std::string m_text;
sf::RectangleShape m_rect;
bool m_hasfocus;
};
We need a constructor for this class:
class TextField : public sf::Transformable, public sf::Drawable{
public:
TextField(unsigned int maxChars) :
m_size(maxChars),
m_rect(sf::Vector2f(15 * m_size, 20)), // 15 pixels per char, 20 pixels height, you can tweak
m_hasfocus(false)
{
m_font.loadFromFile("C:/Windows/Fonts/Arial.ttf"); // I'm working on Windows, you can put your own font instead
m_rect.setOutlineThickness(2);
m_rect.setFillColor(sf::Color::White);
m_rect.setOutlineColor(sf::Color(127,127,127));
m_rect.setPosition(this->getPosition());
}
private:
unsigned int m_size;
sf::Font m_font;
std::string m_text;
sf::RectangleShape m_rect;
bool m_hasfocus;
};
We also need some basic methods, we want to get the text inside:
const std::string sf::TextField::getText() const{
return m_text;
}
and move it, placing it somewhere inside our window:
void sf::TextField::setPosition(float x, float y){
sf::Transformable::setPosition(x, y);
m_rect.setPosition(x, y);
}
this is a tricky one. We are overwritting setPosition method of sf::Transformable because we need to update our own m_rect.
Also, we need to know if a point is inside of the box:
bool sf::TextField::contains(sf::Vector2f point) const{
return m_rect.getGlobalBounds().contains(point);
}
pretty simple, we use cointains method of sf::RectangleShape, already in sfml.
Set (or unset) focus on the TextField:
void sf::TextField::setFocus(bool focus){
m_hasfocus = focus;
if (focus){
m_rect.setOutlineColor(sf::Color::Blue);
}
else{
m_rect.setOutlineColor(sf::Color(127, 127, 127)); // Gray color
}
}
easy one. For aesthetics, we also change the outline color of the box when focused.
And last, but not least, our TextField has to behave some way when input (aka an sf::Event) is received:
void sf::TextField::handleInput(sf::Event e){
if (!m_hasfocus || e.type != sf::Event::TextEntered)
return;
if (e.text.unicode == 8){ // Delete key
m_text = m_text.substr(0, m_text.size() - 1);
}
else if (m_text.size() < m_size){
m_text += e.text.unicode;
}
}
That delete key check is little dirty, I know. Maybe you can find better solution.
That's all! Now main looks like:
int main()
{
RenderWindow window({ 500, 500 }, "SFML", Style::Close);
sf::TextField tf(20);
tf.setPosition(30, 30);
while (window.isOpen())
{
for (Event event; window.pollEvent(event);)
if (event.type == Event::Closed)
window.close();
else if (event.type == Event::MouseButtonReleased){
auto pos = sf::Mouse::getPosition(window);
tf.setFocus(false);
if (tf.contains(sf::Vector2f(pos))){
tf.setFocus(true);
}
}
else{
tf.handleInput(event);
}
window.clear();
window.draw(tf);
window.display();
}
return 0;
}
Proof of concept:
std::string str;
sf::String text;
// In event loop...
if (event.Type == sf::Event::TextEntered)
{
// Handle ASCII characters only
if (event.Text.Unicode < 128)
{
str += static_cast<char>(event.Text.Unicode);
text.SetText(str);
}
}
// In main loop...
window.Draw(text);
This should create an sf::Event::TextEntered for input, and sf::String for output

Game loop with interpolation - weird step back

I have read about an interpolation applied to game loops and tried to implement it myself. It looks almost same as I expected, but when the object ends its movement weird step back takes place. I decided to paste here full source, because this problem may be caused by everything.
#include <SFML/Graphics.hpp>
#include <chrono>
sf::RenderWindow window(sf::VideoMode(800, 600), "Interpolation");
sf::Event event;
int fps = 10; // set to 10 for testing purpose
std::chrono::nanoseconds timePerFrame = std::chrono::seconds(1);
std::chrono::nanoseconds accumulator;
std::chrono::steady_clock::time_point start;
sf::RectangleShape shape1(sf::Vector2f(50, 50));
sf::RectangleShape shape2(sf::Vector2f(50, 50));
sf::Vector2f movement(0, 0);
sf::Vector2f position1(375, 100);
sf::Vector2f position2(375, 275);
void initialization();
void processInput();
void update();
void interpolate();
void render();
int main()
{
initialization();
while(window.isOpen())
{
start = std::chrono::steady_clock::now();
processInput();
while(accumulator >= timePerFrame)
{
update();
accumulator -= timePerFrame;
}
interpolate();
render();
accumulator += std::chrono::steady_clock::now() - start;
}
return 0;
}
void initialization()
{
timePerFrame /= fps;
shape1.setPosition(position1);
shape2.setPosition(position2);
}
void processInput()
{
while(window.pollEvent(event))
{
if(event.type == sf::Event::Closed) window.close();
}
}
void update()
{
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) movement = sf::Vector2f(-300, 0);
else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) movement = sf::Vector2f(300, 0);
else movement = sf::Vector2f(0, 0);
position1.x += movement.x / fps;
position2.x += movement.x / fps;
shape1.setPosition(position1);
shape2.setPosition(position2);
}
void interpolate()
{
double interpolationFactor = (double) accumulator.count() / timePerFrame.count();
shape2.setPosition(position2.x + (movement.x / fps * interpolationFactor), position2.y);
}
void render()
{
window.clear(sf::Color::Black);
window.draw(shape1);
window.draw(shape2);
window.display();
}
I do not know what may cause that kind of problem. I'm looking forward your help.
Your interpolate function can count some parts of the time interval multiple times.
Since accumulator only resets every timePerFrame ticks, a fast loop rate can add smaller intervals multiple times. If the main loop runs in 0.01 seconds, the first call to interpolate uses that 0.01 in the interpolation factor. The next time, it uses 0.02 (for a total add of 0.03). This continues until there is enough time accumulated for update to update the position using 0.1 seconds (the time step). Since interpolate added in more time than that, the object jumps back.
interpolate should only add in the time of the current step, and not the fully accumulated time. (Also, rather than calling now to get the start time every loop, the previous loop's now value used for the end time should be used as the start time for the next loop. Otherwise you'll lose occasional clock ticks when it changes between the end of one loop and the start of the next.)

SFML C++11 Trying to call a function in an object which is in a vector

So I am trying to make a simple shooter but so far i had no luck. I want to spawn bullets when the user presses right shift. And the bullets should fire at the top of the screen. The bullets do spawn but they dont move.
I created a vector to hold the created bullets named "bullets". Then I used the update function in my core struct to detect RShift presses and push an instance of the Bullet object to the vector.
Later on in my main class I iterate the vector and draw the bullets. But when I try calling the function it doesnt work.
My Code so far:
#include <SFML/Graphics.hpp>
#include <stdio.h>
const unsigned int ResX{480}, ResY{640};
struct Bullet {
sf::CircleShape shape;
Bullet(float sX, float sY, sf::Color color, float bullet_radius) {
shape.setRadius(bullet_radius);
shape.setOrigin(bullet_radius, bullet_radius);
shape.setFillColor(color);
shape.setPosition(sX, sY);
}
void update(float vel) {
sf::Vector2f velocity;
velocity.y = -abs(vel);
shape.move(velocity);
}
};
std::vector<Bullet> bullets;
struct Core {
const float core_width{64}, core_height{48}, core_velocity{0.2};
sf::RectangleShape shape;
sf::Vector2f velocity;
Core(float sX, float sY) {
shape.setSize({core_width, core_height});
shape.setPosition(sX, sY);
shape.setFillColor(sf::Color::White);
shape.setOrigin(core_width/2, core_height/2);
}
void update() {
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
velocity.x = -core_velocity;
else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
velocity.x = core_velocity;
else
velocity.x = 0;
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
velocity.y = -core_velocity;
else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
velocity.y = core_velocity;
else
velocity.y = 0;
shape.move(velocity);
if(sf::Keyboard::isKeyPressed(sf::Keyboard::RShift)) {
Bullet b(shape.getPosition().x, shape.getPosition().y, sf::Color::Red, 5);
bullets.push_back(b);
}
}
};
int main(int argc, char *argv[])
{
sf::RenderWindow window(sf::VideoMode(ResX, ResY), "Brick Breaker", sf::Style::None);
Core core(ResX / 2, ResY /2 + 200);
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed || sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))
window.close();
}
std::printf("VelocityX=%f, BulletVelocity=\n",core.velocity.x);
window.clear();
core.update();
window.draw(core.shape);
for(const auto& b : bullets)
window.draw(b.shape);
//this function is causing an error : method update could not be resolved!
b.update();
window.display();
}
return 0;
}
btw i feel the need to say i am very very unexperienced with programming in c++ I am a complete beginner so any advice is welcome. srry 4 bad english. thanks!
EDIT: I figured it out thx! (The problem was that i missed the curly brackets:
for(const auto& b : bullets)
{
window.draw(b.shape);
//this function is causing an error : method update could not be resolved!
b.update();
}
But i have another question aswell! It would be awesome if one of you explained how the iterator functions and what purpose the auto& and const keywords serve here. Thanks again!