|
Implementing game loop |
almbfsek
Member #12,778
April 2011
|
I've been reading this. It explains couple of game loop types. I'm mostly interested in implementing the last one: "Constant Game Speed independent of Variable FPS" While implementing it I wanted to use allegro 5's timer functions so I came up with this: 1 //Start timer
2#include <stdio.h>
3#include <allegro5/allegro.h>
4
5#define WIDTH 960
6#define HEIGHT 640
7#define LOGIC_FPS 5
8
9int main(int argc, char **argv)
10{
11 ALLEGRO_DISPLAY *display;
12 ALLEGRO_EVENT_QUEUE *eventQueue;
13 ALLEGRO_TIMER *timer;
14
15 bool isGameRunning = true;
16 bool updateGameLogic = true;
17
18 //Various inits
19 if(!al_init()) {
20 fprintf(stderr, "failed to initialize allegro\n");
21 return EXIT_FAILURE;
22 }
23
24 timer = al_create_timer(1.0 / LOGIC_FPS);
25 if(!timer) {
26 fprintf(stderr, "failed to create timer!\n");
27 return EXIT_FAILURE;
28 }
29
30 display = al_create_display(WIDTH, HEIGHT);
31 if(!display) {
32 fprintf(stderr, "failed to create display!\n");
33 al_destroy_timer(timer);
34 return EXIT_FAILURE;
35 }
36
37 eventQueue = al_create_event_queue();
38 if(!eventQueue) {
39 fprintf(stderr, "failed to create eventQueue!\n");
40 al_destroy_timer(timer);
41 al_destroy_display(display);
42 return EXIT_FAILURE;
43 }
44
45 //Tie events to queue
46 al_register_event_source(eventQueue, al_get_display_event_source(display));
47 al_register_event_source(eventQueue, al_get_timer_event_source(timer));
48
49 //Start timer
50 al_start_timer(timer);
51
52 //Main loop
53 while(isGameRunning)
54 {
55 ALLEGRO_EVENT e;
56 al_wait_for_event(eventQueue, &e);
57
58 //Input
59 if(e.type == ALLEGRO_EVENT_TIMER)
60 updateGameLogic = true;
61 else if(e.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
62 isGameRunning = false;
63
64 //Update game logic
65 if(updateGameLogic && al_is_event_queue_empty(eventQueue)) {
66 updateGameLogic = false;
67 fprintf(stdout, "Logic updated\n");
68 }
69
70 //Update display
71 //Calculate interpolation here
72 al_clear_to_color(al_map_rgb(50,123,1));
73 al_flip_display();
74 fprintf(stdout, "Display updated\n");
75 }
76
77 //Cleaning
78 al_destroy_timer(timer);
79 al_destroy_display(display);
80 al_destroy_event_queue(eventQueue);
81
82 return EXIT_SUCCESS;
83}
But it doesn't seem to work. For some reason logic update and display update happens at the same time with a rate of 5 times per second (LOGIC_FPS). I need advice. Thanks in advance. |
Desmond Taylor
Member #11,943
May 2010
|
I'd suggest using al_wait_for_event_until. This is how I would do it. 1 ALLEGRO_TIMEOUT timeout;
2 al_init_timeout( &timeout, 0.6 );
3
4 al_wait_for_event_until( this->queue, &this->event, &timeout );
5
6 switch ( this->event.type )
7 {
8 case ALLEGRO_EVENT_TIMER:
9 {
10 this->ticked = true;
11 }
12 break;
13 }
That's taken strait from my code so don't just copy and paste. It's only an example so will not work without the rest of my class. |
AMCerasoli
Member #11,955
May 2010
|
Here is what I use to separate the logic from the drawing... Obviously isn't complete, I haven't started any project which needs to separate the logic from the drawing.. If you're creating a simple game probably you don't need it, but it's good to understand and work this way from the beginning. 1#include <allegro5/allegro.h>
2#include <allegro5/allegro_native_dialog.h>
3
4int main(int argc, char **argv)
5{
6 ALLEGRO_DISPLAY *display = NULL;
7 ALLEGRO_EVENT_QUEUE *event_queue = NULL;
8 ALLEGRO_EVENT ev;
9 ALLEGRO_TIMER *FPS = NULL; //Frames
10 ALLEGRO_TIMER *LPS = NULL; //Logic
11 bool redraw = true;
12
13 if(!al_init()) {
14 al_show_native_message_box(display, "Error", "Error", "Failed to initialize allegro!", NULL, ALLEGRO_MESSAGEBOX_ERROR);
15 return -1;
16 }
17
18 FPS = al_create_timer(1.0 / 5); //Frames
19 LPS = al_create_timer(1.0 / 10); //Logic
20
21 display = al_create_display(640, 480);
22
23 event_queue = al_create_event_queue();
24
25 al_register_event_source(event_queue, al_get_display_event_source(display));
26
27 al_register_event_source(event_queue, al_get_timer_event_source(FPS));
28
29 al_register_event_source(event_queue, al_get_timer_event_source(LPS));
30
31 al_clear_to_color(al_map_rgb(0,0,0));
32
33 al_flip_display();
34
35 al_start_timer(FPS);
36 al_start_timer(LPS);
37
38 while(1)
39 {
40
41 al_wait_for_event(event_queue, &ev);
42
43 if(ev.timer.source == LPS) { //LOGIC
44 Beep(500,10);
45 }
46
47 else if(ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
48 break;
49 }
50
51 else if(ev.timer.source == FPS) { //DRAW
52 al_clear_to_color(al_map_rgb(0,0,0));
53 Beep(100,10);
54 al_flip_display();
55 }
56 }
57
58 al_destroy_timer(FPS);
59 al_destroy_timer(LPS);
60 al_destroy_display(display);
61 al_destroy_event_queue(event_queue);
62
63 return 0;
64}
You can compile it right away. The Beep() function is just to make a "beep"... I may be wrong but actually you should separate the input from the logic and the drawing, that is what I'm trying to do. With Allegro 5 this is very easy to do. But to do this, each object (if you're using C++, which I hope, since is the best for game programming) should have: - A Draw Function and.. - A Input function (depending if that object is controlled by the user.) Anyway, I'm not pretty sure about all this, I still learning.
|
almbfsek
Member #12,778
April 2011
|
Desmond I guess it doesn't have anything to do with my game loop question but I'm eager to know what advnatage would I have by using a timeout? I originally used al_wait_for_event_until as seen in the wiki tutorials but then I thought the simple the better and got rid off the timeout. Thanks AMCerasoli, I tested it and it sucessfully separates game logic and FPS but still the FPS is pre determined. As suggested in the link I gave why run at 25 FPS when its possible to run it at 300 FPS it's wasting clock cycles. My code basically does the same thing except it doesn't put a limit on the FPS. I still don't see why it doesn't work though. any ideas? EDIT: Ok here is an hybrid between AMCerasoli's and my code. Someone please tell me why "Limitless update" gets written at the same rate with "Display updated"? Change the FPS value and see it for your self. What am I doing wrong? 1#include <stdio.h>
2#include <allegro5/allegro.h>
3
4#define WIDTH 960
5#define HEIGHT 640
6#define LPS 1
7#define FPS 4
8
9int main(int argc, char **argv)
10{
11 ALLEGRO_DISPLAY *display;
12 ALLEGRO_EVENT_QUEUE *eventQueue;
13 ALLEGRO_TIMER *lpsTimer, *fpsTimer;
14 ALLEGRO_EVENT e;
15
16 bool isGameRunning = true;
17
18 //Various inits
19 if(!al_init()) {
20 fprintf(stderr, "failed to initialize allegro\n");
21 return EXIT_FAILURE;
22 }
23
24 lpsTimer = al_create_timer(1.0 / LPS);
25 if(!lpsTimer) {
26 fprintf(stderr, "failed to create lpsTimer!\n");
27 return EXIT_FAILURE;
28 }
29
30 fpsTimer = al_create_timer(1.0 / FPS);
31 if(!fpsTimer) {
32 fprintf(stderr, "failed to create fpsTimer!\n");
33 return EXIT_FAILURE;
34 }
35
36 display = al_create_display(WIDTH, HEIGHT);
37 if(!display) {
38 fprintf(stderr, "failed to create display!\n");
39 al_destroy_timer(lpsTimer);
40 al_destroy_timer(fpsTimer);
41 return EXIT_FAILURE;
42 }
43
44 eventQueue = al_create_event_queue();
45 if(!eventQueue) {
46 fprintf(stderr, "failed to create eventQueue!\n");
47 al_destroy_timer(lpsTimer);
48 al_destroy_timer(fpsTimer);
49 al_destroy_display(display);
50 return EXIT_FAILURE;
51 }
52
53 //Tie events to queue
54 al_register_event_source(eventQueue, al_get_display_event_source(display));
55 al_register_event_source(eventQueue, al_get_timer_event_source(lpsTimer));
56 al_register_event_source(eventQueue, al_get_timer_event_source(fpsTimer));
57
58 //Start timers
59 al_start_timer(lpsTimer);
60 al_start_timer(fpsTimer);
61
62 //Main loop
63 while(isGameRunning)
64 {
65 al_wait_for_event(eventQueue, &e);
66
67 switch(e.type) {
68 case ALLEGRO_EVENT_DISPLAY_CLOSE:
69 isGameRunning = false;
70 break;
71
72 case ALLEGRO_EVENT_TIMER:
73 if(e.timer.source == lpsTimer) {
74 fprintf(stdout, "Logic updated\n");
75 }
76 else if(e.timer.source == fpsTimer) {
77 al_clear_to_color(al_map_rgb(50,123,1));
78 al_flip_display();
79 fprintf(stdout, "Display updated\n");
80 }
81 break;
82 }
83
84 fprintf(stdout, "Limitless update\n");
85 }
86
87 //Cleaning
88 al_destroy_timer(lpsTimer);
89 al_destroy_timer(fpsTimer);
90 al_destroy_display(display);
91 al_destroy_event_queue(eventQueue);
92
93 return EXIT_SUCCESS;
94}
|
Edgar Reynaldo
Major Reynaldo
May 2007
|
AMCerasoli said:
else if(ev.timer.source == FPS) { //DRAW
You are checking the timer field of the event without knowing the type of the event. ev.timer and ev.timer.source could have absolutely any value. Stop it. My take on separating logic and drawing : 1const float LOGIC_PER_SECOND = 120.0f;
2const float FRAMES_PER_SECOND = 60.0f;
3
4ALLEGRO_TIMER* logic_timer = al_create_timer(LOGIC_PER_SECOND);
5ALLEGRO_TIMER* display_timer = al_create_timer(FRAMES_PER_SECOND);
6
7// Event loop
8while (!quit) {
9 while (1) {
10 ALLEGRO_EVENT ev;
11 al_wait_for_event(event_queue , &ev);
12 if (ev.type == ALLEGRO_EVENT_TIMER) {
13 if (ev.timer.source == logic_timer) {
14 DoLogic(1.0/LOGIC_PER_SECOND);
15 } else if (ev.timer.source == display_timer) {
16 redraw = true;
17 }
18 }
19 if (al_is_event_queue_empty(event_queue)) {break;}
20 }
21 if (redraw) {
22 Redraw();
23 }
24 // clean up
25}
With this simple logic you can implement any logic per second, and any frames per second, with frames being dropped if they are taking too long. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
almbfsek
Member #12,778
April 2011
|
Edgar, that's what I came up with too. Can you solve my problem though? edit: also, is it still logical to use interpolation with this method? |
Edgar Reynaldo
Major Reynaldo
May 2007
|
almbfsek said: As suggested in the link I gave why run at 25 FPS when its possible to run it at 300 FPS Drawing more frames than the number of Hz that your monitor runs at is pointless, as you will never see more than Hz number of frames anyway. If you're talking about logic per second that's different though. almbfsek said: Someone please tell me why "Limitless update" gets written at the same rate with "Display updated"? Because you output 'Limitless update' each time any event occurs - whether it's a logic event or a display event. almbfsek said:
Can you solve my problem though? You need to exhaust all of the events that are in the queue before you update your display so that all the logic events have been processed. if (al_is_event_queue_empty(event_queue)) {break;} This line will break out of the event loop once there are no events left to process. If there are events left, it will go to the beginning of the loop and check the next event. If you want to adjust the rate that each timer runs, you can do that with al_set_timer_speed. Edit for your edit almbfsek said: edit: also, is it still logical to use interpolation with this method? Well, you could do it with ALLEGRO_EVENT.timestamp and al_get_time. I don't see the point of using interpolation at this level though. It just complicates things needlessly. I also don't see the point of running logic at a different rate than the display. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
almbfsek
Member #12,778
April 2011
|
Edgar Reynaldo said: Because you output 'Limitless update' each time any event occurs - whether it's a logic event or a display event. Aah I see. It's because of al_wait_for_event(eventQueue, &e) right? It waits until an event occurs thus "Limitless update" happens exactly at the same speed of the fastest occurring event. How can I truly make it separate though? I don't want to limit my FPS. I want to make it run as fast as possible while limiting game logic to 25 lps for example? NVM I better use a good old get_tick like routine. since timers in allegro are tied to the events what I'm asking is not possible. |
Edgar Reynaldo
Major Reynaldo
May 2007
|
almbfsek said: I don't want to limit my FPS. It is physically impossible to render more frames per second than the refresh rate of the monitor. So set your display timer to the refresh rate of the monitor and forget about it. almbfsek said: I want to make it run as fast as possible while limiting game logic to 25 lps for example? Is your logic really so expensive that you can't run it at the same rate as your display? My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
almbfsek
Member #12,778
April 2011
|
Edgar I agree that "FPS dependent on Constant Game Speed" type of game loop is simpler and works most of the time but I want to try new methods and see the difference for my self. Also there might be scenarios where the refresh rate of the monitor is more than the computer can handle. For example my monitor runs at 75 Hz but 75 fps might be too much for my old GPU. In this case if game logic and fps are not separated game logic will slow down too. It's something that I especially not want in a network game. It's easier to sync players if the game logic has its own rate. |
Edgar Reynaldo
Major Reynaldo
May 2007
|
You can easily skip logic or frames with the algorithm I suggested, and you don't have to make your game speed the same as your frame rate, but it will then look choppy if you don't interpolate. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
almbfsek
Member #12,778
April 2011
|
that's what I use now. you're right that "fps as fast as possible" is not really needed. for interpolation I use something like this: 1float getLogicInterpolation() {
2 return (al_get_timer_count(lpsTimer) % LPS + 1.0) / (float)LPS;
3}
and the main loop now looks like this 1while(isGameRunning)
2{
3 //Event handling
4 while(!al_is_event_queue_empty(eventQueue)){
5 ALLEGRO_EVENT e;
6 al_wait_for_event(eventQueue, &e);
7
8 switch(e.type) {
9 case ALLEGRO_EVENT_DISPLAY_CLOSE:
10 isGameRunning = false;
11 break;
12
13 case ALLEGRO_EVENT_TIMER:
14 if(e.timer.source == lpsTimer)
15 isLogicUpdateNeeded = true;
16 else if(e.timer.source == fpsTimer)
17 isDisplayUpdateNeeded = true;
18 break;
19 }
20 }
21
22 //Game logic
23 if(isLogicUpdateNeeded) {
24 isLogicUpdateNeeded = false;
25 //updateLogic();
26 }
27
28 //Rendering
29 if(isDisplayUpdateNeeded) {
30 isDisplayUpdateNeeded = false;
31 //updateDisplay();
32 }
33}
thanks everyone for your help |
AMCerasoli
Member #11,955
May 2010
|
Edgar Reynaldo said: You are checking the timer field of the event without knowing the type of the event. ev.timer and ev.timer.source could have absolutely any value. Stop it. Damn, is true I forgot to changed the last time!. You should separate the drawing from the logic because:
almbfsek said: In this case if game logic and fps are not separated game logic will slow down too. It's something that I especially not want in a network game. Well, I don't know what system are you using to create your network game, may be Peer-to-peer or client-server, but I think synchronizing the game completely is not a good option, I haven't created an only game, but I use to think about it, and in a Client-Server game I think the Server program doesn't need any logic per second, let alone FPS. I think it should be something like: the client send info to the server the server check if that info is correct and then send it to the other clients. so the Server just need receive as many request it's possible, and you don't need to time out the LPS of the Server... Anyway, read this web page, is poor gold.
|
Peter Wang
Member #23
April 2000
|
Edgar Reynaldo said: You are checking the timer field of the event without knowing the type of the event. ev.timer and ev.timer.source could have absolutely any value. Stop it. Actually in this case it's fine, as all event types have the three common fields: type, source, timestamp. It would be better style to use ev.any.source though. AMCerasoli said: After 30 FPS, the human eye can't notice the difference between 30 FPS or 3000 FPS. This old myth must die.
|
Arthur Kalliokoski
Second in Command
February 2005
|
AMCerasoli said: After 30 FPS, the human eye can't notice the difference between 30 FPS or 3000 FPS. Actually I can quite easily see the successive images of a mountain sticking up out of the terrain as I spin a camera around in place, at least on a CRT. Even at 85 fps. But 30 fps is generally good enough. I can also wave my finger around between my eye and the screen and see separate silhouettes, one for each refresh. They all watch too much MSNBC... they get ideas. |
|