Game timer

hello, i'm making a text based strategy game in C++ 17 using SFML for audio and ncurses for the screen, but i'm getting really confused with how to implement a simple game clock that doesn't stop the player from moving around the screens.

i've looked at various example using std::chrono but they all seem very complex, what I want to do is in my maain game loop is every 5 seconds update a tick counter and run a bunch of functions. I'm not sure whether to have a class, a global variable, or something in main like the below pseudo code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main()
{
	while (1)
	{
		auto gameClock = std::chrono::steady_clock::now();
		if (6 seconds have passed since last tick)
		{
			update background functions();
		}

		initscr();
		createMap();
		currentGameScreen();
	}
	getchar();
	return 0;
}


any advice would be appreciated
I think it would be easiest just to show you a working example, and then explain it a bit.

Start with a baseline that you should be able to run if you have SFML set up:
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
#include <SFML/Graphics.hpp>

int main()
{
    int width  = 800;
    int height = 600;
    sf::RenderWindow window(sf::VideoMode(width, height), "SFML Test!");

    sf::RectangleShape rectangle;
    rectangle.setSize(sf::Vector2f(100, 50));
    rectangle.setOutlineColor(sf::Color::Red);
    rectangle.setOutlineThickness(5);
    rectangle.setPosition(0, height/2);

    window.setVerticalSyncEnabled(true);

    bool left_pressed = false;
    bool right_pressed = false;

    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();
            else if (event.type == sf::Event::KeyPressed)
            {
                if (event.key.code == sf::Keyboard::Left)
                {
                    left_pressed = true;
                }
                else if (event.key.code == sf::Keyboard::Right)
                {
                    right_pressed = true;
                }
            }
            else if (event.type == sf::Event::KeyReleased)
            {
                if (event.key.code == sf::Keyboard::Left)
                {
                    left_pressed = false;
                }
                else if (event.key.code == sf::Keyboard::Right)
                {
                    right_pressed = false;
                }
            }
        }

        sf::Vector2f delta_pos;
        if (left_pressed)
        {
            delta_pos.x -= 5;
        }
        if (right_pressed)
        {
            delta_pos.x += 5;
        }

        // move rectangle
        rectangle.setPosition(rectangle.getPosition() + delta_pos);

        window.clear(sf::Color::Blue);
        window.draw(rectangle);
        window.display();

    }

    return 0;
}


If you run this, you'll be able to use the Left and Right arrow keys to move the rectangle. Nothing too much, but it works.

Now let's say we want to incorporate time. Let's make it so, after 6 seconds, the box turns magenta.

SFML already has its own sf::Time and sf::Clock functionality, but you could convert this to use the C++ standard library if you really wanted to.
https://www.sfml-dev.org/tutorials/2.5/system-time.php

All we need to do is add an sf::Clock, and then query the clock to see what the elapsed time is.
We convert this time into seconds, and if the time is >6 seconds, set the rectangle to be magenta.
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
#include <SFML/Graphics.hpp>

int main()
{
    int width  = 800;
    int height = 600;
    sf::RenderWindow window(sf::VideoMode(width, height), "SFML Test!");

    sf::RectangleShape rectangle;
    rectangle.setSize(sf::Vector2f(100, 50));
    rectangle.setOutlineColor(sf::Color::Red);
    rectangle.setOutlineThickness(5);
    rectangle.setPosition(0, height/2);

    window.setVerticalSyncEnabled(true);

    sf::Clock clock;

    bool left_pressed = false;
    bool right_pressed = false;

    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
                window.close();
            else if (event.type == sf::Event::KeyPressed)
            {
                if (event.key.code == sf::Keyboard::Left)
                {
                    left_pressed = true;
                }
                else if (event.key.code == sf::Keyboard::Right)
                {
                    right_pressed = true;
                }
            }
            else if (event.type == sf::Event::KeyReleased)
            {
                if (event.key.code == sf::Keyboard::Left)
                {
                    left_pressed = false;
                }
                else if (event.key.code == sf::Keyboard::Right)
                {
                    right_pressed = false;
                }
            }
        }

        sf::Vector2f delta_pos;
        if (left_pressed)
        {
            delta_pos.x -= 5;
        }
        if (right_pressed)
        {
            delta_pos.x += 5;
        }

        sf::Time elapsed = clock.getElapsedTime();
        if (elapsed.asSeconds() > 6.0f)
        {
            rectangle.setFillColor(sf::Color::Magenta);
        }

        // move rectangle
        rectangle.setPosition(rectangle.getPosition() + delta_pos);

        window.clear(sf::Color::Blue);
        window.draw(rectangle);
        window.display();

    }

    return 0;
}


By the way, once you think you've understood basic timing (or maybe even now), I suggest reading this article: https://gafferongames.com/post/fix_your_timestep/
It might help fix future stutter in your game's frames.

________________________________________________

If you need to use C++ <chrono> instead of sf::Clock/sf::Time, you can.


Change the initial sf::Clock logic to:
 
auto start_time = std::chrono::system_clock::now();


Change the loop logic to:
1
2
3
4
5
6
        auto end_time = std::chrono::system_clock::now();
        std::chrono::duration<double> diff = end_time - start_time;
        if (diff.count() > 6.0)
        {
            rectangle.setFillColor(sf::Color::Magenta);
        }



________________________________________________

PS: I used graphics since that was the easiest to demonstrate, but of course you could use the same clocks and logic for a text-based thing too.
Last edited on
wow thank you very much for the detailed explanation Ganado, that's helped a lot.

I've tried both sf::Time and chrono and gone with chrono for the time being even though they both worked great

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
/****************
**** GLOBALS ****
****************/

std::unique_ptr<std::vector<sector>> g_map = std::make_unique<std::vector<sector>>();
unsigned int g_gameTick;

int main()
{
	auto start_time = std::chrono::system_clock::now();
	initscr();
	createMap();
	gameMenu();
	gameTick();

	while (1)
	{
		auto end_time = std::chrono::system_clock::now();
		std::chrono::duration<double> diff = end_time - start_time;
		if (diff.count() > 6.0)
		{
			start_time = end_time;
			g_gameTick++;
			gameTick();
		}
	}

	return 0;
}


i'll probably have to put it in some kind of timer object as thinking about it theres going to be timers running all over the place for the player and AI building ships etc. Thanks!
Just further to the above, could I get some advice on how to manage different timers all over the program? In addition to the main game loop timer, which classes a 'game tick' as 5 seconds, every action by the player or AI will take a set number of ticks, such as building something, moving somewhere. I was thinking that I need to make a timer class like below, and embed one in my objects (a timer may need to be paused for resource shortages etc). but i'm half way through figuring out the timer class and im wondering if i'm over complicating it, and every object should just determine at what game tick count in the future it will be done, would that be recommended?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class jtimer {
public:
	jtimer(const int ticks) : m_ticks(ticks), m_paused(true) {}
	void		pause()  { m_paused = true; }
	void		resume() { m_paused = false; }
	bool		paused() const { return m_paused; }
	void        start()
	{
		clock = std::chrono::system_clock::now();
		m_paused = false;
	}
	void		tick()
	{
		//comparison between ticks and ticks remaining here
	}
private:
	int			m_ticks;
	int			m_ticks_remaining;
	bool		m_paused;
	std::chrono::time_point<std::chrono::system_clock> clock;
};


a timer would then go into each object, like below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class building {
public:
	building(const std::string& name, const int cost, const int ticks, std::initializer_list<std::pair<resource, int>>& requiredResources) : m_name(name), m_cost(cost), m_ticks(ticks)
	{
		for (auto& p : requiredResources)
		{
			m_requiredResources.push_back(p);
		}
	}
	std::string									getName() const { return m_name; }
	int											getCost() const { return m_cost; }
	int											getTicks()const { return m_ticks; }
	std::vector<std::pair<resource, int>>&		getRequiredResources() { return m_requiredResources; }

	building()		= delete;
	~building()	    = default;
private:
        jtimer                                                                       m_timer;
	std::string								m_name;
	int										m_cost;
	int										m_ticks;
	std::vector<std::pair<resource, int>>	m_requiredResources;
};


or rather than a timer in each, it should just add N ticks onto the current game master tick, and check for completion in a loop?
Last edited on
Any suggestions?
typically the point of a timer is self management? Its usually event-driven, in that after X amount of time, it triggers some activity. Often, this is done with threading... you kick off your timer thread, and it just runs, and every so often it does what you wanted to happen? For example, say you were making a word processor, every 5 min you save the file, your timer pops a thread, sleeps for 1 min, is it 5 min mark? no, ... no... yes, write a snapshot backup file... no ... no ... no... etc

if you are doing things on a specific time in a always running loop, you don't need all this mess.
just get the wall clock time once before the loop starts. inside the loop, check current time vs saved time, has it been long enough to do activity? If yes, reset saved time, do activity, else skip that part.. each loop would 'manage' itself. You can wrap the chrono:: stuff in a class, but its tiny, and I do not see any need for that, apart from avoiding 2 repeated lines of code in the loops, its not saving much.

if you are counting loop iterations instead of wall clock, that is a simple swap of the same idea.
Last edited on
I would say just experiment and figure out what you think is the cleanest or most maintainable solution. I don't have much experience in this, so don't take what I saw as scripture, but personally I would only have one global clock that you keep close to your main loop.

If you have a bunch of things building independently, one option would be to check the state of each building process every game loop iteration. Each building process remembers when it started to build, e.g. start time = 1234 ms;

When you loop through all the building processes, you see which ones have been building for >= the time required [ (current_time - start_time) >= required_time ] for that building, and if so, finish the building process.

(As far as I know, SFML by itself doesn't have an event-driven timer. Windows API can handle this, but not sure how compatible that would be with SFML.)
Last edited on
I used the visual/ windows timers with graphics for a long time. I see no reason they would not work if you are not seeking portability, but I don't know SFML either.
thanks very much for the responses, i'll try a few approaches. Most likely i'll try checking it all against the game clock, so if the current tick is '10', and a building takes 20 ticks to construct, i'll save a tick completion int at 30, and have all these objects checked in the main loop
ok. That sounds like a good plan. what about something along these lines... peseudoc++ish ..:
you can set up the inheritance however ... I assumed has-a for this example but you probably want real inherit...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class microiterationcounter
{
   static int current; 
   int start;
   int duration;
   bool doneyet() {return ((current-start) >= duration);}
}

mainloop
{
      if(somebuilding.myiterationcounter.doneyet())
           {
              blah;
           }
     .... things
    microiterationcounter::current++; //all objects share this.  but each has its own start and duration.
}


when you make an item, eg a building, the ctor (that I didnt write above) might set start = current and duration = parent class's value passed down, if all buildings took 30 and all farms took 10 and whatever. Its handy to wrap it up, maybe, but don't overthink it, keep this class small, efficient, and simple.
Last edited on
Topic archived. No new replies allowed.