hi,
here's my game loop :-
1 | |
2 | while(!key[KEY_ESC] && play_state == PLAYING) |
3 | { |
4 | logic_calls = 0; |
5 | while(ticks == 0) |
6 | { |
7 | rest(100 / updates_per_second); |
8 | } |
9 | |
10 | while(ticks > 0) |
11 | { |
12 | old_ticks = ticks; |
13 | |
14 | ++logic_calls; |
15 | |
16 | if(hero.opening_door == OPENING) |
17 | { |
18 | if(door_opening_info.anim_delay_counter > 0) |
19 | { |
20 | --door_opening_info.anim_delay_counter; |
21 | door_opening_info.draw = DONT_DRAW; |
22 | } |
23 | else |
24 | { |
25 | door_opening_info.draw = DRAW; |
26 | door_opening_info.anim_delay_counter = door_opening_info.anim_ticks_per_frame; |
27 | } |
28 | } |
29 | |
30 | if(hero.anim_delay_counter > 0) |
31 | { |
32 | --hero.anim_delay_counter; |
33 | hero.update = 0; |
34 | } |
35 | else |
36 | { |
37 | hero.update = 1; |
38 | hero.anim_delay_counter = hero.anim_ticks_per_frame; |
39 | } |
40 | |
41 | |
42 | do_non_drawing(); |
43 | ticks--; |
44 | if(old_ticks <= ticks) break; |
45 | |
46 | } |
47 | |
48 | draw_everything_to_buffer(); |
49 | blit(buffer, screen, 0, 0, 0, 0, 640, 480); |
50 | |
51 | if(hero.leaving_level) play_state = LEAVE_LEVEL; |
52 | |
53 | } |
updates_per_second is 60.
I set hero.anim_ticks_per_frame to 1, so that aught to be the fastest option.
Anything bigger than 1 should slow him down.
The trouble is, as it is (set to 1) the character moves too slowly.
I'm not sure why it's so slow.
I thought perhaps I needed to change the updates_per_second value.
The timer gets installed like so :
install_int_ex(ticker, BPS_TO_TIMER(updates_per_second));
..and I don't understand the purpose of this bit of code in my game loop :
while(ticks == 0) { rest(100 / updates_per_second); }
I didn't write it myself, I copied it from the article on the allegro wiki about regulating game speed.
As you may have gathered I'm hoping to get some help on this matter.
Any help would be great.
..and I don't understand the purpose of this bit of code in my game loop :
If the drawing takes less than a full tick, we rest until the full tick has passed, allowing for an efficient utilization of the CPU.
It makes your game not use 100% of the CPU if it can help it.
And I am not sure what you mean by the character moving too slowly. In the code above you don't seem to ever move the character?
Yes apologies for that.
The function do_non_drawing()
is where the position of the character gets changed (if hero.update is 1).
To begin with, drop the old_ticks = ticks; and if(old_ticks <= ticks) break; lines - that's being taken care of by the loop condition and the ticks decrement.
And why isn't the animation stuff in do_non_drawing() as well?
It makes your game not use 100% of the CPU if it can help it.
The resting time is still completely arbitrary (1/10 of the update frequency? WTH?) though, so it might as well be replaced with rest(1).
To begin with, drop the old_ticks = ticks; and if(old_ticks <= ticks) break; lines - that's being taken care of by the loop condition and the ticks decrement.
No it isn't. That code prevents the logic from taking too long and freezing the computer. As soon as the logic step starts taking larger than a single tick, the loop will never exit because ticks will increase faster than it is decreased. It's optional for most games, but a truly general game loop will need something to take care of that.
The resting time is still completely arbitrary
rest(1) will work, but any value smaller than the update time slice will do.
If you want your character to move faster, then you also should increase his speed as well as decreasing the delay.
Okay thanks for the advice.
I'd like to say that I was hoping I could get him moving faster without resorting to increasing the x difference per frame.
The animation has 8 frames and each time the frame increases, he gets moved 3 pixels.
This means he moves 24 pixels each cycle which works well because my game area is effectively a grid of 24 * 24 tiles.
If I increased the x increment to 6 say then I'd only be able to use 4 frames.
I don't understand why it's so slow.
EDIT : if anyone has any further advice, please let me know.
EDIT : solved it myself
solved it myself
Awesome. Could you share what you did?
Yeah sure.
This timing business is all new to me so it gives me slightly the heebie jeebies when confronted with such a challenge.
Basically, because I had updates_per_second set to 60 the intervals between updates were quite big.
So if I set
hero.anim_ticks_per_frame
to just 1, the length of time between each update was 1/60 second all thins being equal and ignoring other things that might influence it.
So I realised that by increasing updates_per_second which is now 384, I could get more control over the speed of the updates. ie I could choose between multiples of
1/384 of a second instead of 1/60.
That's not a good idea for multiple reasons. First, the ticker controls the framerate, and anything about 60-70 Hz is a waste of CPU. Secondly, Allegro's timers are not accurate enough to support anything much above 100 Hz. The effect of your timer change is as if you did this:
for(int ii = 0; ii < 3; ii++) { do_non_drawing(); }
Which is almost the same as simply changing the speed parameter.
So I realised that by increasing updates_per_second which is now 384, I could get more control over the speed of the updates. ie I could choose between multiples of
1/384 of a second instead of 1/60.
If you measured your animation times in actual seconds, it wouldn't matter what your update rate is. All you would have to do is pass the amount of time passed since the last update to your animation object, which can be calculated easily from the number of updates in a second and the number of ticks passed.
I coded up an example class that takes care of doing these things. Take a look at the AnimBase::AdvanceTime function. All you have to do is pass it the elapsed time, and it takes care of updating the index into the BITMAP* array.
1 | // |
2 | |
3 | #ifndef FrameAnimations_H |
4 | #define FrameAnimations_H |
5 | |
6 | |
7 | #include <allegro.h> |
8 | #include <cstdlib> |
9 | |
10 | class AnimBase { |
11 | private : |
12 | BITMAP** frames; |
13 | unsigned int num_frames; |
14 | unsigned int cur_frame; |
15 | double frames_per_sec; |
16 | double total_anim_time; |
17 | double cur_frame_time; |
18 | unsigned int total_loops; |
19 | unsigned int cur_loop; |
20 | public : |
21 | |
22 | AnimBase() : frames(0) , num_frames(0) , cur_frame(0) , frames_per_sec(0.0) , |
23 | total_anim_time(0.0) , cur_frame_time(0.0) , total_loops(0) , cur_loop(0) {} |
24 | ~AnimBase() {FreeFrames();} |
25 | |
26 | /// x and y are the location where you want the top left of the frame to be drawn |
27 | void DrawTo(BITMAP* bmp , int x , int y) { |
28 | BITMAP* frame = 0; |
29 | if (frames && ((total_loops == 0) || (cur_loop < total_loops))) { |
30 | frame = frames[cur_frame]; |
31 | blit(frame , bmp , 0 , 0 , x , y , frame->w , frame->h); |
32 | } |
33 | } |
34 | |
35 | /// Returns the value of the CompletedAnimation function, so you know whether it is still playing |
36 | bool AdvanceTime(double deltatime) { |
37 | if (deltatime < 0.0) {deltatime = 0.0;} |
38 | cur_frame_time += deltatime; |
39 | if (cur_frame_time >= total_anim_time) { |
40 | unsigned int nloop_advance = (unsigned int)(cur_frame_time/total_anim_time); |
41 | cur_loop += nloop_advance; |
42 | cur_frame_time -= (double)nloop_advance*total_anim_time; |
43 | } |
44 | cur_frame = (unsigned int)((cur_frame_time/total_anim_time)*(double)(num_frames)); |
45 | return CompletedAnimation(); |
46 | } |
47 | |
48 | /// Returns true if the animation has finished playing the requested number of loops yet |
49 | /// A looping animation will always return false for this function. |
50 | bool CompletedAnimation() { |
51 | if (!total_loops) {return false;} |
52 | return (cur_loop < total_loops); |
53 | } |
54 | |
55 | void Restart() { |
56 | cur_frame = 0; |
57 | cur_frame_time = 0.0; |
58 | cur_loop = 0; |
59 | } |
60 | |
61 | /// nloops value : pass 0 to loop, or >0 for a specific number of loops |
62 | bool MakeForwardAnimation(BITMAP** frame_array , unsigned int nframes , double total_time , unsigned int nloops) { |
63 | if (!frame_array) {return false;} |
64 | if (total_time < 0.0) {return false;} |
65 | if (!MakeFrames(nframes)) {return false;} |
66 | for (unsigned int i = 0 ; i < nframes ; ++i) { |
67 | if (!frame_array<i>) {// invalid BITMAP* for one of the animation frames |
68 | FreeFrames(); |
69 | return false; |
70 | } |
71 | frames<i> = frame_array<i>; |
72 | } |
73 | total_anim_time = total_time; |
74 | frames_per_sec = (double)num_frames/total_anim_time; |
75 | total_loops = nloops; |
76 | // cur_loop, cur_frame, and cur_frame_time are already 0 |
77 | // (due to the MakeFrames call, which calls FreeFrames, which calls Clear) |
78 | return true; |
79 | } |
80 | |
81 | bool MakeFrames(unsigned int nframes) { |
82 | FreeFrames(); |
83 | if (!nframes) {return false;} |
84 | frames = (BITMAP**)malloc(sizeof(BITMAP*)*nframes); |
85 | if (!frames) {return false;} |
86 | for (unsigned int i = 0 ; i < nframes ; ++i) { |
87 | frames<i> = (BITMAP*)0; |
88 | } |
89 | num_frames = nframes; |
90 | return true; |
91 | } |
92 | |
93 | void Clear() { |
94 | frames = 0; |
95 | num_frames = 0; |
96 | cur_frame = 0; |
97 | frames_per_sec = 0.0; |
98 | total_anim_time = 0.0; |
99 | cur_frame_time = 0.0; |
100 | total_loops = 0; |
101 | cur_loop = 0; |
102 | } |
103 | |
104 | void FreeFrames() { |
105 | if (frames) { |
106 | free(frames); |
107 | } |
108 | Clear(); |
109 | } |
110 | }; |
111 | |
112 | |
113 | #endif // FrameAnimations_H |
114 | |
115 | // |
Then to use it all you have to do is call AdvanceTime once for each logic iteration and then display it. You can even produce a slow motion or hyper motion effect by using a multiplier on the elapsed time that you pass to the function. And that could also be used to vary the animation speed of a walking or running animation. In that case the time scaling factor that you use is :
This is assuming that the animation is one complete set of 2 strides, and that TotalAnimationTime is the time value passed to AnimBase::MakeForwardAnimation for the total_time parameter. WalkingSpeed is just the velocity of the player, and StrideLength should be the largest possible distance in pixels between the player's feet from the animation frames.
A usage example of the class :
1 | // |
2 | AnimBase clouds_anim; |
3 | clouds_anim.MakeForwardAnimation(cloud_frames , 120 , 4.0 , 0); |
4 | double time_warp_factor = 3.0;// render at 3X normal speed |
5 | |
6 | while(!quit) { |
7 | while(ticks < 1) {rest(1);} |
8 | time_passed = (double)ticks*secs_per_tick; |
9 | ticks = 0; |
10 | |
11 | // logic |
12 | clouds_anim.AdvanceTime(time_passed*time_warp_factor); |
13 | |
14 | // drawing |
15 | clouds_anim.DrawTo(buffer , 0 , 0); |
16 | |
17 | // display |
18 | blit(buffer , screen , 0 , 0 , 0 , 0 , buffer->w , buffer->h); |
19 | |
20 | } |
21 | |
22 | // |
If you need transitions between animations you could code another class on top of it that handles a queue of AnimBase* objects. It could turn off the looping of one AnimBase and queue another AnimBase to replace it when the current one finishes.
I realize this is in C++, but it should be relatively easy to turn it into C if you wish to, and it's far more useful to me as C++, so that's what I wrote it as. That way I can continue to use it, but hopefully it will still be helpful to you.
sorry I couldn't reply sooner. Had trouble with my internet connection.
Thanks very much Edgar,
I'll get to work on adapting that code. If I get stuck I might well have to ask a few things.
EDIT
I do need to ask a few things to make use of your help Edgar.
In this code ;
while(!quit) { while(ticks < 1) {rest(1);} time_passed = (double)ticks*secs_per_tick; ticks = 0;
I presume ticks is updated by an interrupt - is that right ?
...and why is it set to 0 ?
I presume ticks is updated by an interrupt - is that right ?
...and why is it set to 0 ?
Yes, ticks is incremented by an interrupt. It's set to zero because the amount of time passed is recorded in the time_passed variable there. I generally use delta time in my programs, and pass the amount of time passed on to functions that use it. In this fashion, frames are dropped automatically when the combined logic and rendering are taking too long. In this way, if you notice a low frame rate, you know that your cpu is having trouble doing everything requested of it.
Okay, great. Glad you're around to help!
I was just wondering if setting ticks to 0 might cause problems for other parts of the program using the ticks variable.
Could it just as easily use a copy of ticks ?
I was just wondering if setting ticks to 0 might cause problems for other parts of the program using the ticks variable.
Could it just as easily use a copy of ticks ?
If other parts of your program rely on the value of the ticks variable, then yes, changing the value will probably mess with things.
If you favor the tick decrementation loop, it can work with the class I put up just as easily :
1 | // |
2 | AnimBase clouds_anim; |
3 | clouds_anim.MakeForwardAnimation(cloud_frames , 120 , 4.0 , 0); |
4 | double time_warp_factor = 3.0;// render at 3X normal speed |
5 | |
6 | while(!quit) { |
7 | while(ticks < 1) {rest(1);} |
8 | |
9 | ticks_passed = ticks; |
10 | ticks = 0; |
11 | for (i = 0 ; i < ticks_passed ; ++i) { |
12 | time_passed = secs_per_tick; |
13 | |
14 | // logic |
15 | clouds_anim.AdvanceTime(time_passed*time_warp_factor); |
16 | } |
17 | |
18 | // drawing |
19 | clouds_anim.DrawTo(buffer , 0 , 0); |
20 | |
21 | // display |
22 | blit(buffer , screen , 0 , 0 , 0 , 0 , buffer->w , buffer->h); |
23 | |
24 | } |
25 | |
26 | // |
With this kind of logic processing you'll have to watch out for the runaway train effect though - if the logic and rendering are taking more than one tick total, then the number of ticks_passed between each iteration of the while(!quit) loop will steadily increase. Since just the logic portion generally takes less time than the rendering loop, this will offset the increase somewhat, but it's still something to be aware of.
With this kind of logic processing you'll have to watch out for the runaway train effect though - if the logic and rendering are taking more than one tick total, then the number of ticks_passed between each iteration of the while(!quit) loop will steadily increase. Since just the logic portion generally takes less time than the rendering loop, this will offset the increase somewhat, but it's still something to be aware of.
If you just use the algorithm in the wiki, all this would have already been taken care of.
If you just use the algorithm in the wiki, all this would have already been taken care of.
Do you mean this part?
1 | while(!quit) |
2 | { |
3 | while(ticks == 0) |
4 | { |
5 | rest(100 / updates_per_second);//rest until a full tick has passed |
6 | } |
7 | |
8 | while(ticks > 0) |
9 | { |
10 | int old_ticks = ticks; |
11 | |
12 | DoLogic(); |
13 | |
14 | ticks--; |
15 | if(old_ticks <= ticks)//logic is taking too long: abort and draw a frame |
16 | break; |
17 | } |
18 | |
19 | DrawEverything(); |
20 | } |
Resting 100/updates_per_second milliseconds is too long. When that line is executed, it could be just before the timer interrupt that increments ticks is called. That means it could waste exactly as much time is rested there before it gets around to taking care of the logic and drawing. This means that in the worst case, one tick of the logic loop has at most
seconds to execute the logic before ticks will equal old_ticks. That works out to
seconds allowed to execute the logic.
If you only rest(1) while (ticks < 0) then you are far more likely to start as close as possible to when the timer is incremented than you are with the other method.
This method gives the logic portion (SecondsPerUpdate - 0.001) seconds to execute (ignoring the granularity of an Allegro timer ms, of course).
So, which gives the logic more time to execute, based on SecondsPerUpdate?
If SecondsPerUpdate is > 0.01 (A timer running at less than 100Hz), then (SecondsPerUpdate - 0.001) (which is the time the logic has with rest(1)) is larger, and gives the logic more time to execute. Since the vast majority of timers for games are less than 100 Hz since they are usually synchronized with the monitor refresh rate, rest(1) makes more sense.
while(ticks > 0) { int old_ticks = ticks; DoLogic(); ticks--; if(old_ticks <= ticks)//logic is taking too long: abort and draw a frame break; }
This portion logically says "If ticks increments during the time just before and just after DoLogic() is called, then don't process any more logic before rendering a frame." So if the timer is incremented while DoLogic is processing, then it stops and draws a frame. Depending on the relative position of the current time to the next interrupt time, it will behave differently.
If logic and drawing combined are taking longer than the update interval (L + D > I), the best option is just to discard the extra ticks and let the program slow down. Since L + D is larger than I, between updates it is guaranteed to have at least one tick incrementation occur, and the updates will be at a fairly consistent rate.
On all computers fast enough to process it, there will be time left over before the next update and the cpu can be allowed to rest. On slower computers, they will run at 100% cpu at a consistent rate based on their processor power.
So I don't see any rationale that dissuades me from using something as completely simple as this :
while (!quit) { while (ticks < 1) {rest(1);} ticks = 0; /// Process one update at most, so count time passed as /// one tick, or seconds_per_update seconds. DoLogic(); Display(); }
If you don't want to use up 100% cpu on a slower computer, then you need to slow down your timer enough to allow the cpu rest time between updates. You can do this based on the number of milliseconds that you rest between updates. If it is 0, then you need to slow down your timer, and if it is greater than 0 then you can try to speed up your timer. You'd also have to test for fluctuating rate changes and stabilize the timer rate at the slower of the two fluctuating values.
stuff about rest
rest(100 / 60) = rest(1). But yeah, rest(1) is probably simpler there.
If logic and drawing combined are taking longer than the update interval (L + D > I), the best option is just to discard the extra ticks and let the program slow down. Since L + D is larger than I, between updates it is guaranteed to have at least one tick incrementation occur, and the updates will be at a fairly consistent rate.
Do you know the point of fixed step timing? The point of it is to update the logic of the game at a fixed rate relative to the time passed in the real world. You can't just lump L and D together. The point of fixed step timing is to keep them separate. There are three cases that an average game encounters:
1) In most games, D >> L. So, for every frame, multiple ticks will pass, usually around 1 or 2. The wiki algorithm measures these ticks, and then calls the DoLogic() the number of times that the ticks were incremented. That is the standard domain of operation.
2) If somehow D < 1, then the program can rest and not use 100% CPU. At this point, the ratio of logics to frames will be 1.
3) If somehow L > 1, then we enter the bad territory, and must escape it gracefully. Arguably, the best solution is to lock the ratio of the logic updates to frame updates to 1.
Those three cases are solved by the wiki algorithm. If you can think of something better, by all means suggest it. In my experimentation that was the simplest algorithm that fulfilled those three cases properly.
So I don't see any rationale that dissuades me from using something as completely simple as this :
It doesn't handle case 1.
If you don't want to use up 100% cpu on a slower computer, then you need to slow down your timer enough to allow the cpu rest time between updates. You can do this based on the number of milliseconds that you rest between updates. If it is 0, then you need to slow down your timer, and if it is greater than 0 then you can try to speed up your timer. You'd also have to test for fluctuating rate changes and stabilize the timer rate at the slower of the two fluctuating values.
What? You never ever touch the rate of your timer after you set it up. That would introduce non-determinism into the system. The timer always ticks at the same rate.
Do you know the point of fixed step timing? The point of it is to update the logic of the game at a fixed rate relative to the time passed in the real world.
Which cannot be done once the sum of one logic step and one drawing step take more than one timing interval.
You can't just lump L and D together. The point of fixed step timing is to keep them separate.
L and D are inseparable as far as timing is concerned. For every frame drawn, it needs at least one logic step.
1) In most allegro games, D >> L (at least, in Allegro land). So, for every frame, multiple ticks will pass, usually around 1 or 2. The wiki algorithm measures these ticks, and then calls the DoLogic() the number of times that the ticks were incremented. That is the standard domain of operation.
If more than one tick passes from the time logic and drawing start and conclude, then the computer is too slow to handle the amount of processing required to maintain that frame rate. If you process more than one logic tick at a time, you are dropping frames, and dropping information to present to the user.
3) If somehow L > 1, then we enter the bad territory, and must escape it gracefully. Arguably, the best solution is to lock the ratio of the logic updates to frame updates to 1.
You're contradicting yourself. If only one logic call can be executed for each frame, then that is exactly what I posted at the end of my last post. The algorithm from the wiki does not do this, as the ratio of logic updates to frame updates can vary.
Those three cases are solved by the wiki algorithm.
It handles 2) fine, but what sane algorithm wouldn't?
As for 3) :
while(ticks > 0) { int old_ticks = ticks; DoLogic(); ticks--; if(old_ticks <= ticks)//logic is taking too long: abort and draw a frame break; }
With this algorithm, ticks is allowed to pile up behind old_ticks, and as many ticks are processed as possible as long as one tick is processed faster than one timing interval. If one tick can't be processed during that interval then ticks starts to pile up. Like I said before, there's also the problem where the ticks counter is just about to increment when DoLogic() starts and increments before it finishes. That makes it exit and draw immediately, regardless of whether or not the DoLogic function was taking too long. What all that makes for is what sounds like a choppy update rate, where it starts and stops with an uneven drawing rate. Of course that will hardly be noticed on a computer that's fast enough to process L+D faster than the timing interval, but for slow computers I bet it could make a noticeable difference.
That's not fixed step timing. It is thrown out of the window immediately.
Actually, the code example I put up is fixed timing. What makes you think it isn't? ticks >= 1 makes for one tick and therefore one logic update and one drawing update. No visual information is lost, no frames are skipped, not logic frames, or drawing frames either. All it does is slow down on computers that aren't fast enough to handle it.
What? You never ever touch the rate of your timer after you set it up. That would introduce non-determinism into the system. The timer always ticks at the same rate.
You can still use your fixed step update with a slower timer. A slower timer just means more time has passed, and you can iteratively subtract your fixed time step from it if you like. Not to mention that you could use two timers, one fixed to a constant rate that you use to count how many logic frames should be processed, and a second timer that you use to determine whether you should be updating your display. In this fashion, you would wait on the second timer and proceed only if the first timer has ticks on it as well. That way you would still have exactly the same number of logic frames processed at the same fixed step, but the view onto the world could be in different time frames.
If you can think of something better, by all means suggest it.
// while (!quit) { while (ticks < 1) {rest(1);} ticks = 0; /// Process one update at most, so count time passed as /// one tick, or seconds_per_update seconds. DoLogic(); Display(); } //
Everyone on every computer would see exactly the same thing given the same platform peculiarities, and those with slower processors would just see it more slowly.
Everyone on every computer would see exactly the same thing given the same platform peculiarities, and those with slower processors would just see it more slowly.
You have funny ideas of what's "Better". A slower computer should just skip drawing the screen and get the logic up to date (to a point).
Maybe I'm out of touch, but doesn't rest(1) actually end up resting for at LEAST one scheduler time slot (in some OSs, thats 10ms a piece)? Seeing as you can't wait for less than one slot on fixed frequency timer schedulers. (linux with NO_HZ might be able to let an app sleep for 1ms at a time, but then, the actual sleep time depends on how much time other processes use, how long it takes to switch processes, if the process actually slept at all, etc)
edit:
Everyone on every computer would see exactly the same thing given the same platform peculiarities
Most people won't see the same exact platform peculiarities. Hardware versions, driver version, OS versions and other software (and various versions thereof) all lead to different characteristics on pretty much every PC.
That's not a good idea for multiple reasons. First, the ticker controls the framerate, and anything about 60-70 Hz is a waste of CPU. Secondly, Allegro's timers are not accurate enough to support anything much above 100 Hz. The effect of your timer change is as if you did this:
So updates_per_second shouldn't be more than 60.
if(hero.anim_delay_counter > 0) { --hero.anim_delay_counter; hero.update = 0; } else { hero.update = 1; hero.anim_delay_counter = hero.anim_ticks_per_frame; }
The fastest movement I can get is with anim_ticks_per_frame set to 1 but this is still too slow.
Does this mean I've no choice but to change my animation so it draws less frames per 24 pixel move ?
Most people won't see the same exact platform peculiarities. Hardware versions, driver version, OS versions and other software (and various versions thereof) all lead to different characteristics on pretty much every PC.
That's why I qualified it as 'given the same platform peculiarities'.
You have funny ideas of what's "Better". A slower computer should just skip drawing the screen and get the logic up to date (to a point).
Better for what purpose though?
The only real argument here is what should happen on computers that are too slow to handle your program. And the only two choices you have are to slow down game time relative to real time, or drop display frames.
That's a preference though, so maybe I should have clarified it as 'Better for some' as opposed to 'Better in general', but that doesn't fix the oddities I pointed out in the wiki algorithm.
The fastest movement I can get is with anim_ticks_per_frame set to 1 but this is still too slow.
You're running at 60Hz, and incrementing your animation's frame counter 1 for each update, and it is still too slow? That should be showing 60 fps for your animation. Show more code.
L and D are inseparable as far as timing is concerned. For every frame drawn, it needs at least one logic step.
That is true.
If you process more than one logic tick at a time, you are dropping frames, and dropping information to present to the user.
Dropping frames the the standard procedure. The game, however, does not slow down, since logic frames per second should remain constant.
You're contradicting yourself. If only one logic call can be executed for each frame, then that is exactly what I posted at the end of my last post. The algorithm from the wiki does not do this, as the ratio of logic updates to frame updates can vary.
Read what I said again. If L > 1 then the ratio is locked to 1. It is not always locked to 1, because that would violate case #1. To satisfy case 1 and 3 the ratio has to vary.
Your simplification fails to satisfy the first case, and as soon as your frame rate drops below 60, your game will slow down. This is not acceptable for a game. You are suggesting the equivalent using a fixed delta time in your favourite delta timing game loop. If you think that this idea is so good, by are you even calculating a delta time? Just assume it's 1/60 and anyone who can't match that can go upgrade their computers.
Of course that will hardly be noticed on a computer that's fast enough to process L+D faster than the timing interval, but for slow computers I bet it could make a noticeable difference
The choppiness will be preferable to an all across the board slowdown that your algorithm will necessitate. Moreover, my tests have shown that the choppiness is not very noticeable. Choppiness can be eliminated by forcing the ratio of logic updates to frame updates to be an integer number, but I have not figured out how to do this simply. If this somehow can be done simply, it'd be an even better algorithm than the one in the wiki.
Not to mention that you could use two timers, one fixed to a constant rate that you use to count how many logic frames should be processed, and a second timer that you use to determine whether you should be updating your display. In this fashion, you would wait on the second timer and proceed only if the first timer has ticks on it as well. That way you would still have exactly the same number of logic frames processed at the same fixed step, but the view onto the world could be in different time frames.
Yeah, that is a good idea... but the algorithm for that might be complicated. I'll need to see how that would work. In either case, the amount of game time that each logic tick simulates has to remain constant. If we can combine this with the integral ratio idea I mentioned above, it'd be nigh perfect algorithm.
That's why I qualified it as 'given the same platform peculiarities'.
You'd have to have identical hardware, with identical driver, os and software versions installed for that to happen. Not exactly something you can rely on.
Better for what purpose though?
I'd prefer the game to be somewhat playable on more machines than to be slow as molasses on more machines.
You'd have to have identical hardware, with identical driver, os and software versions installed for that to happen. Not exactly something you can rely on.
It's not exactly something I was depending on. Of course different machines behave differently, but it's mostly irrelevant to what I was talking about.
I'd prefer the game to be somewhat playable on more machines than to be slow as molasses on more machines.
Okay, so you'd prefer that objects teleport in your programs when the cpu can't handle it, that's fine. Like I said, that's your preference. It sounds like trying to play online first person games with packet lag. You can do it, but it doesn't sound like much fun to me.
Yeah, that is a good idea... but the algorithm for that might be complicated. I'll need to see how that would work. In either case, the amount of game time that each logic tick simulates has to remain constant. If we can combine this with the integral ratio idea I mentioned above, it'd be nigh perfect algorithm.
I'll see if I can come up with a prototype or two when I have some more time.
Yes I realize I just got through saying I prefer not to drop frames, but it pays to have the ability to be versatile, so it's still worth exploring to me.
William, since your animation is still running slower than it seems it should be, please show us more code, specifically all that relates to the logical processing and the struct you're using for your hero object.
You can do it, but it doesn't sound like much fun to me.
Nor does everything going slow.
William, since your animation is still running slower than it seems it should be, please show us more code, specifically all that relates to the logical processing and the struct you're using for your hero object.
Sure thing. I'll post my main() aswell :-
1 | int main() |
2 | { |
3 | int old_ticks; |
4 | |
5 | init(); |
6 | |
7 | get_shadow_colours(); |
8 | load_data(); |
9 | colour_codes_init(); |
10 | generate_shapes_info(); |
11 | create_and_organise_bitmaps(); |
12 | |
13 | temp_buffer = create_bitmap(640, 480); |
14 | |
15 | initialise_levels(); |
16 | |
17 | level_x = 4; |
18 | level_y = 5; |
19 | |
20 | current_level = level_numbers[level_y][level_x]; |
21 | |
22 | initialise_hero_for_first_level(); |
23 | |
24 | while(!key[KEY_ESC]) |
25 | { |
26 | //read_level(level_number); |
27 | |
28 | |
29 | initialise_level_variables(); |
30 | |
31 | initialise_water_tiles(); |
32 | initialise_nodes(); |
33 | |
34 | draw_first_frame(); |
35 | fade_in_level(); |
36 | blit(buffer, screen, 0, 0, 0, 0, 640, 480); |
37 | |
38 | play_state = PLAYING; |
39 | |
40 | while(!key[KEY_ESC] && play_state == PLAYING) |
41 | { |
42 | logic_calls = 0; |
43 | |
44 | while(ticks == 0) |
45 | { |
46 | rest(100 / updates_per_second); |
47 | } |
48 | |
49 | |
50 | while(ticks > 0) |
51 | { |
52 | old_ticks = ticks; |
53 | |
54 | ++logic_calls; |
55 | |
56 | if(hero.opening_door == OPENING) |
57 | { |
58 | if(door_opening_info.anim_delay_counter > 0) |
59 | { |
60 | --door_opening_info.anim_delay_counter; |
61 | door_opening_info.change_frame = 0;; |
62 | } |
63 | else |
64 | { |
65 | door_opening_info.change_frame = 1; |
66 | door_opening_info.anim_delay_counter = door_opening_info.anim_ticks_per_frame; |
67 | } |
68 | } |
69 | |
70 | |
71 | if(water_animation_info.anim_delay_counter > 0) { |
72 | water_animation_info.anim_delay_counter--; |
73 | water_animation_info.draw = DONT_DRAW; |
74 | } |
75 | else |
76 | { |
77 | water_animation_info.draw = DRAW; |
78 | water_animation_info.anim_delay_counter = water_animation_info.anim_ticks_per_frame; |
79 | } |
80 | |
81 | |
82 | if(hero.anim_delay_counter > 0) |
83 | { |
84 | --hero.anim_delay_counter; |
85 | hero.update = 0; |
86 | } |
87 | else |
88 | { |
89 | hero.update = 1; |
90 | hero.anim_delay_counter = hero.anim_ticks_per_frame; |
91 | } |
92 | |
93 | |
94 | do_non_drawing(); |
95 | ticks--; |
96 | if(old_ticks <= ticks) break; |
97 | |
98 | } |
99 | |
100 | |
101 | |
102 | draw_everything_to_buffer(); |
103 | blit(buffer, screen, 0, 0, 0, 0, 640, 480); |
104 | |
105 | if(hero.leaving_level) |
106 | { |
107 | hero.level_exit_x = hero.x; |
108 | hero.level_exit_y = hero.y; |
109 | |
110 | initialise_next_level(); |
111 | play_state = LEAVE_LEVEL; |
112 | } |
113 | |
114 | } |
115 | |
116 | } |
117 | |
118 | |
119 | fclose(logfile); |
120 | save_bitmap("buffer.bmp", buffer, NULL); |
121 | |
122 | allegro_exit(); |
123 | return 0; |
124 | } |
125 | END_OF_MAIN(); |
1 | void do_non_drawing(void) |
2 | { |
3 | /* if hero.animating is IN_ANIMATION then just need |
4 | to draw him in next position. |
5 | hero.animating needs to be changed to IN_ANIMATION when : |
6 | it's been determined that he'll move |
7 | when's this ? |
8 | when determine_heros_move is called. |
9 | On the other hand it has to be changed to NOT_IN_ANIMATION when : |
10 | he's finished moving or whatever he was doing. |
11 | |
12 | */ |
13 | |
14 | if(hero.update && hero.animating == NOT_IN_ANIMATION) |
15 | { |
16 | get_hero_input(); |
17 | /* assigns hero.direction_hero_wants_to_move |
18 | hero.trying_to_push |
19 | hero.trying_to_pull */ |
20 | |
21 | hero.what_hero_can_do = determine_heros_move(); |
22 | |
23 | |
24 | |
25 | if( hero.what_hero_can_do == MOVE_ONLY |
26 | || hero.what_hero_can_do == JUST_HOLD |
27 | || hero.what_hero_can_do == JUST_UNHOLD |
28 | || hero.what_hero_can_do == MOVE_AND_PULL |
29 | || hero.what_hero_can_do == MOVE_AND_PUSH |
30 | || hero.what_hero_can_do == REACH |
31 | || hero.what_hero_can_do == UNREACH) |
32 | { |
33 | hero.animating = IN_ANIMATION; |
34 | } |
35 | |
36 | update_hero_frame_and_position(); |
37 | } |
38 | |
39 | else if(hero.update) { |
40 | update_hero_frame_and_position(); |
41 | } |
42 | |
43 | /* shapes_info.shape_being_moved |
44 | needs to be changed to NOT_BEING_MOVED because draw_shapes() must only be called |
45 | when a shape needs moving on the "tops" bitmap. Can writing comments like this solve |
46 | problems ? That's what I want to find out by trying. */ |
47 | |
48 | |
49 | |
50 | if(outcast.level_has_outcast) update_outcast(); |
51 | |
52 | |
53 | if(hero.update && shapes_info.needs_drawing == NEEDS_DRAWING) update_moving_shapes(); |
54 | |
55 | |
56 | if(hero.opening_door == OPENING && door_opening_info.change_frame == 1) |
57 | { |
58 | ++door_opening_info.door_frame; |
59 | if(door_opening_info.door_frame == NUMBER_OF_FRAMES_FOR_DOOR_ANIMATION + 2) |
60 | { |
61 | --door_opening_info.door_frame; |
62 | } |
63 | } |
64 | |
65 | } |
That's my logic loop. Please forgive the crazy commenting.
The hero struct is :
1 | |
2 | struct hero { |
3 | BITMAP *hwh[NUMBER_OF_WH_FRAMES]; /* hero walking horizontally frames */ |
4 | BITMAP *hwd[NUMBER_OF_WALKING_DOWN_FRAMES]; |
5 | BITMAP *hs[NUMBER_OF_STILL_FRAMES]; /* hero still */ |
6 | BITMAP *hh[NUMBER_OF_HOLDING_FRAMES]; /* hero holding */ |
7 | BITMAP *hp[NUMBER_OF_PULLING_FRAMES]; |
8 | BITMAP *hrfu[NUMBER_OF_REACHING_FRAMES]; |
9 | BITMAP *hrfa[NUMBER_OF_REACHING_FRAMES]; |
10 | int moving; |
11 | int pushing; |
12 | |
13 | unsigned short animating; |
14 | int direction_hero_wants_to_move; |
15 | short trying_to_push; |
16 | short trying_to_pull; |
17 | int reaching_for_door; |
18 | short direction_facing; |
19 | short what_hero_can_do; |
20 | short distance_from_xy; |
21 | int x, y; |
22 | |
23 | int holding; |
24 | |
25 | int holding_frame; |
26 | int reaching_frame; |
27 | int opening_door; |
28 | int opening_door_frame; |
29 | |
30 | int leaving_level; |
31 | unsigned short increment_for_walking; |
32 | |
33 | int draw; |
34 | int anim_delay_counter; |
35 | int anim_ticks_per_frame; |
36 | int update; |
37 | |
38 | int level_exit_x, level_exit_y; |
39 | |
40 | |
41 | }; |
not all it's members are still in use but I've not gotten around to fleecing it yet.
This function :-
1 | |
2 | void initialise_hero_for_first_level(void) |
3 | { |
4 | int x, y; |
5 | |
6 | hero.x = hero.y = -1; |
7 | /* Find out where hero starts. */ |
8 | for(y = 0; y < LEVEL_HEIGHT; ++y) |
9 | for(x = 0; x < LEVEL_WIDTH; ++x) |
10 | if(levels_array[current_level - 1].level[x][y].level_map_colour == tc[PLAYER_START]) |
11 | { |
12 | hero.x = x; |
13 | hero.y = y; |
14 | } |
15 | if(hero.x == -1 || hero.y == -1) |
16 | { |
17 | print_error("In read_level.c : read_level() : PLAYER_START not found.\n\n"); |
18 | printf("Level number %d.", current_level - 1); |
19 | exit_game(); |
20 | } |
21 | |
22 | |
23 | hero.update = 1; |
24 | hero.anim_delay_counter = 1; |
25 | hero.anim_ticks_per_frame = 1; |
26 | |
27 | hero.animating = NOT_IN_ANIMATION; |
28 | hero.direction_facing = FACING_RIGHT; |
29 | hero.trying_to_push = NOT_TRYING; |
30 | hero.trying_to_pull = NOT_TRYING; |
31 | hero.holding = NOT_HOLDING; |
32 | hero.increment_for_walking = 0; |
33 | hero.reaching_frame = -1; |
34 | hero.reaching_for_door = NOT_REACHING; |
35 | hero.opening_door = NOT_OPENING; |
36 | |
37 | hero.leaving_level = 0; |
38 | } |
sets things up.
Really appreciate your willingness to help.
edit : ...and I'll be happy to post any more code that needs to be seen.
Give your project a profiling party. It will help you that much for finding where exactly you consume the most part of your cpu time.
what's a profiling party please ?
Example:
I compiled an old project with the following gcc flags for activating profiling: -pg
After what I launch my code one time, I play with it, and after exiting I call gprof.
Here are the commands and the result:
gcc -c -Wall -pedantic -pg -mwindows *.c gcc -pg -o test.exe MapEditor.o m_action.o m_grafic.o m_help.o m_ressource.o -lnilorea -lagl -lalleg -luser32 -lgdi32 -lopengl32 -lglu32 -lglaux -lwsock32 -lws2_32 -lpthreadGC2
1 | test.exe |
2 | Done ! |
3 | gprof test.exe |
4 | Flat profile: |
5 | |
6 | Each sample counts as 0.01 seconds. |
7 | % cumulative self self total |
8 | time seconds seconds calls Ts/call Ts/call name |
9 | 33.33 0.01 0.01 allegro_gl_is_extension_s |
10 | 33.33 0.02 0.01 draw_map |
11 | 33.33 0.03 0.01 split_color |
12 | 0.00 0.03 0.00 365 0.00 0.00 draw_gui |
13 | 0.00 0.03 0.00 3 0.00 0.00 clear |
14 | 0.00 0.03 0.00 1 0.00 0.00 at_exit_job |
15 | 0.00 0.03 0.00 1 0.00 0.00 create_new_map |
16 | |
17 | % the percentage of the total running time of the |
18 | time program used by this function. |
19 | |
20 | cumulative a running sum of the number of seconds accounted |
21 | seconds for by this function and those listed above it. |
22 | |
23 | self the number of seconds accounted for by this |
24 | seconds function alone. This is the major sort for this |
25 | listing. |
26 | |
27 | calls the number of times this function was invoked, if |
28 | this function is profiled, else blank. |
29 | |
30 | self the average number of milliseconds spent in this |
31 | ms/call function per call, if this function is profiled, |
32 | else blank. |
33 | |
34 | total the average number of milliseconds spent in this |
35 | ms/call function and its descendents per call, if this |
36 | function is profiled, else blank. |
37 | |
38 | name the name of the function. This is the minor sort |
39 | for this listing. The index shows the location of |
40 | the function in the gprof listing. If the index is |
41 | in parenthesis it shows where it would appear in |
42 | the gprof listing if it were to be printed. |
43 | ♀ |
44 | Call graph (explanation follows) |
45 | |
46 | |
47 | granularity: each sample hit covers 4 byte(s) for 33.33% of 0.03 seconds |
48 | |
49 | index % time self children called name |
50 | <spontaneous> |
51 | [1] 33.3 0.01 0.00 allegro_gl_is_extension_supported |
52 | ----------------------------------------------- |
53 | <spontaneous> |
54 | [2] 33.3 0.01 0.00 draw_map [2] |
55 | ----------------------------------------------- |
56 | <spontaneous> |
57 | [3] 33.3 0.01 0.00 split_color [3] |
58 | ----------------------------------------------- |
59 | 0.00 0.00 365/365 main [464] |
60 | [6] 0.0 0.00 0.00 365 draw_gui [6] |
61 | ----------------------------------------------- |
62 | 0.00 0.00 3/3 main [464] |
63 | [7] 0.0 0.00 0.00 3 clear [7] |
64 | ----------------------------------------------- |
65 | 0.00 0.00 1/1 main [464] |
66 | [8] 0.0 0.00 0.00 1 at_exit_job [8] |
67 | ----------------------------------------------- |
68 | 0.00 0.00 1/1 main [464] |
69 | [9] 0.0 0.00 0.00 1 create_new_map [9] |
70 | ----------------------------------------------- |
71 | |
72 | This table describes the call tree of the program, and was sorted by |
73 | the total amount of time spent in each function and its children. |
74 | |
75 | Each entry in this table consists of several lines. The line with the |
76 | index number at the left hand margin lists the current function. |
77 | The lines above it list the functions that called this function, |
78 | and the lines below it list the functions this one called. |
79 | This line lists: |
80 | index A unique number given to each element of the table. |
81 | Index numbers are sorted numerically. |
82 | The index number is printed next to every function name so |
83 | it is easier to look up where the function in the table. |
84 | |
85 | % time This is the percentage of the `total' time that was spent |
86 | in this function and its children. Note that due to |
87 | different viewpoints, functions excluded by options, etc, |
88 | these numbers will NOT add up to 100%. |
89 | |
90 | self This is the total amount of time spent in this function. |
91 | |
92 | children This is the total amount of time propagated into this |
93 | function by its children. |
94 | |
95 | called This is the number of times the function was called. |
96 | If the function called itself recursively, the number |
97 | only includes non-recursive calls, and is followed by |
98 | a `+' and the number of recursive calls. |
99 | |
100 | name The name of the current function. The index number is |
101 | printed after it. If the function is a member of a |
102 | cycle, the cycle number is printed between the |
103 | function's name and the index number. |
104 | |
105 | |
106 | For the function's parents, the fields have the following meanings: |
107 | |
108 | self This is the amount of time that was propagated directly |
109 | from the function into this parent. |
110 | |
111 | children This is the amount of time that was propagated from |
112 | the function's children into this parent. |
113 | |
114 | called This is the number of times this parent called the |
115 | function `/' the total number of times the function |
116 | was called. Recursive calls to the function are not |
117 | included in the number after the `/'. |
118 | |
119 | name This is the name of the parent. The parent's index |
120 | number is printed after it. If the parent is a |
121 | member of a cycle, the cycle number is printed between |
122 | the name and the index number. |
123 | |
124 | If the parents of the function cannot be determined, the word |
125 | `<spontaneous>' is printed in the `name' field, and all the other |
126 | fields are blank. |
127 | |
128 | For the function's children, the fields have the following meanings: |
129 | |
130 | self This is the amount of time that was propagated directly |
131 | from the child into the function. |
132 | |
133 | children This is the amount of time that was propagated from the |
134 | child's children to the function. |
135 | |
136 | called This is the number of times the function called |
137 | this child `/' the total number of times the child |
138 | was called. Recursive calls by the child are not |
139 | listed in the number after the `/'. |
140 | |
141 | name This is the name of the child. The child's index |
142 | number is printed after it. If the child is a |
143 | member of a cycle, the cycle number is printed |
144 | between the name and the index number. |
145 | |
146 | If there are any cycles (circles) in the call graph, there is an |
147 | entry for the cycle-as-a-whole. This entry shows who called the |
148 | cycle (as parents) and the members of the cycle (as children.) |
149 | The `+' recursive calls entry shows the number of function calls that |
150 | were internal to the cycle, and the calls entry for each member shows, |
151 | for that member, how many times it was called from other members of |
152 | the cycle. |
153 | |
154 | ♀ |
155 | Index by function name |
156 | |
157 | [1] allegro_gl_is_extension_supported [9] create_new_map [3] split_color |
158 | [8] at_exit_job [6] draw_gui |
159 | [7] clear [2] draw_map |
And now I know where my time was mostly spent.
My top two eating functions are allegro_gl_is_extension_s and draw_map. Obvious, but sometime it helps.
thanks Gull..
I tried it but I get :-
http://www.allegro.cc/files/attachment/597746
I appreciate your help.
You made sure to run your program before running it through gprof right?
Yeah, as stated before and repeated by Thomas, you must run it one time as usual before calling gprof on it.
So, let's first do a '17.exe' call, after what you will be able to launch gprof on it.
Just to be clear William, you meant that your animation speed was too slow, not your program speed, right?
This may be part of it :
// if(hero.anim_delay_counter > 0) { --hero.anim_delay_counter; hero.update = 0; } else { hero.update = 1; hero.anim_delay_counter = hero.anim_ticks_per_frame; } //
If you have the anim_ticks_per_frame set to 1, then your anim_delay_counter gets set to 1 when it is 0, and decremented when it is greater than zero.
So starting from hero.anim_delay_counter = 0, it is set to 1 on the first logic call. On the second call it will be decremented. On the third call it will reach zero again. This effectively means that hero.update would only be set to 1 on every other logic call. With this setup it means hero.update is only set to 1 on every (hero.anim_ticks_per_frame + 1)th frame.
What you want is to decrement your tick counter on every pass through the logic loop, and detect when it reaches zero that way. So something more like :
// --hero.anim_delay_counter; if (hero.anim_delay_counter <= 0) { // reached the time to update the hero's frame hero.update = 1; hero.anim_delay_counter = hero.anim_ticks_per_frame; } else { hero.update = 0; } //
This way when the counter starts at 1 at the beginning of the logic call, it will be decremented to zero which triggers the frame update and resets the delay counter to the number of ticks per frame.
Okay guys.
Thanks to Thomas, Edgar and Gulladriel I appreciate your patience.
I've got things to work on now, so I'll be absent for a while, while I work through the help.
I'll get back when I've done some work.
EDIT :
hi again. Trying to use profiling but with no luck.
I have : used the -pg compiler flag.
used -pg with the linker.
ran the program before gprof.
..but I still get gmon.out : no such file or directory.
Can anyone help ?
EDIT 2 :
actually I've got it now.
Here's the profile :
1 | Flat profile: |
2 | |
3 | Each sample counts as 0.01 seconds. |
4 | % cumulative self self total |
5 | time seconds seconds calls ms/call ms/call name |
6 | 30.00 0.06 0.06 _mangled_main |
7 | 15.00 0.09 0.03 117 0.26 0.26 get_hero_input |
8 | 10.00 0.11 0.02 404 0.05 0.17 do_non_drawing |
9 | 10.00 0.13 0.02 5 4.00 4.00 add_two_pixel_border |
10 | 5.00 0.14 0.01 202 0.05 0.05 update_hero_frame_and_position |
11 | 5.00 0.15 0.01 117 0.09 0.09 determine_heros_move |
12 | 5.00 0.16 0.01 1 10.00 10.00 fade_in_level |
13 | 5.00 0.17 0.01 1 10.00 10.00 get_shadow_colours |
14 | 5.00 0.18 0.01 1 10.00 30.00 grass_from_pattern |
15 | 5.00 0.19 0.01 1 10.00 10.00 make_a_pattern |
16 | 5.00 0.20 0.01 1 10.00 10.00 save_shadow_array |
17 | 0.00 0.20 0.00 22768 0.00 0.00 hline |
18 | 0.00 0.20 0.00 22528 0.00 0.00 random_number |
It's nice to see that you've got profiling working for you. You will probably want a sample size larger than running your program for 0.20 seconds though, and you might as well wait until you've got all your basic program elements running before you worry about which functions are taking the longest overall.
Did you try the fix for the animation rate that I pointed out in my last post?
Did you try the fix for the animation rate that I pointed out in my last post?
Not until I read the above. I was being neurotic about it. And I was having trouble getting on my feet today for some reason.
When I saw you'd replied, I cut and pasted the code you wrote in place of the old code in my main function and commented the old code out.
When I ran it, I couldn't believe it. The hero walks about really fast which is brilliant because that means I can actually choose how fast I want him to move by increasing hero.anim_ticks_per_frame which will slow him down a bit.
So I'm really pleased, really relieved and feeling more positive my project now.
Thanks very much for the help Edgar. You spotted exactly what the problem was.
Well, I kind of glossed over it first. I should have read it more closely.
So I'm really pleased, really relieved and feeling more positive my project now.
That's good. Sometimes a small victory is enough to carry the whole day.