Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » timing animation again

This thread is locked; no one can reply to it. rss feed Print
 1   2 
timing animation again
William Labbett
Member #4,486
March 2004
avatar

hi,

here's my game loop :-

1 
2while(!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.

SiegeLord
Member #7,827
October 2006
avatar

Quote:

..and I don't understand the purpose of this bit of code in my game loop :

The Wiki said:

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?

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

William Labbett
Member #4,486
March 2004
avatar

Yes apologies for that.

The function do_non_drawing()

is where the position of the character gets changed (if hero.update is 1).

gnolam
Member #2,030
March 2002
avatar

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?

SiegeLord said:

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). :P

--
Move to the Democratic People's Republic of Vivendi Universal (formerly known as Sweden) - officially democracy- and privacy-free since 2008-06-18!

SiegeLord
Member #7,827
October 2006
avatar

Quote:

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.

Quote:

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.

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

William Labbett
Member #4,486
March 2004
avatar

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 :)

SiegeLord
Member #7,827
October 2006
avatar

Quote:

solved it myself

:D Awesome. Could you share what you did?

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

William Labbett
Member #4,486
March 2004
avatar

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.

SiegeLord
Member #7,827
October 2006
avatar

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.

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Quote:

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 
10class AnimBase {
11private :
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;
20public :
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 :

<math>TimeScaleFactor = \frac{TotalAnimationTime*WalkingSpeed}{2*StrideLength}</math>

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//
2AnimBase clouds_anim;
3clouds_anim.MakeForwardAnimation(cloud_frames , 120 , 4.0 , 0);
4double time_warp_factor = 3.0;// render at 3X normal speed
5 
6while(!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.

William Labbett
Member #4,486
March 2004
avatar

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 ?

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Quote:

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.

William Labbett
Member #4,486
March 2004
avatar

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 ?

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Quote:

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//
2AnimBase clouds_anim;
3clouds_anim.MakeForwardAnimation(cloud_frames , 120 , 4.0 , 0);
4double time_warp_factor = 3.0;// render at 3X normal speed
5 
6while(!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.

SiegeLord
Member #7,827
October 2006
avatar

Quote:

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. ::)

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

SiegeLord said:

If you just use the algorithm in the wiki, all this would have already been taken care of. ::)

Do you mean this part?

wiki page on Timers : Making use of the ticks said:

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
<math>SecondsPerUpdate - \frac{100}{UpdatesPerSecond}*\frac{1}{1000}</math>
seconds to execute the logic before ticks will equal old_ticks. That works out to
<math>\frac{9}{10}SecondsPerUpdate</math>
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?
<math>\frac{9}{10}SecondsPerUpdate <?> SecondsPerUpdate - 0.001</math>

<math>0.01 <?> SecondsPerUpdate</math>

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.

Quote:

                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.

SiegeLord
Member #7,827
October 2006
avatar

Quote:

stuff about rest

rest(100 / 60) = rest(1). But yeah, rest(1) is probably simpler there.

Quote:

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.

Quote:

So I don't see any rationale that dissuades me from using something as completely simple as this :

It doesn't handle case 1.

Quote:

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.

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Quote:

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.

Quote:

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.

Quote:

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.

Quote:

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.

Quote:

Those three cases are solved by the wiki algorithm.

It handles 2) fine, but what sane algorithm wouldn't?

As for 3) :

Quote:

             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.

Quote:

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.

Quote:

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.

Quote:

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. :D

Thomas Fjellstrom
Member #476
June 2000
avatar

Quote:

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. :D

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:

Quote:

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.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

William Labbett
Member #4,486
March 2004
avatar

Quote:

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 ?

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Thomas Fjellstrom said:

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'.

Thomas Fjellstrom said:

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.

William Labbett said:

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.

SiegeLord
Member #7,827
October 2006
avatar

Quote:

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.

Quote:

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.

Quote:

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.

Quote:

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.

Quote:

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.

"For in much wisdom is much grief: and he that increases knowledge increases sorrow."-Ecclesiastes 1:18
[SiegeLord's Abode][Codes]:[DAllegro5]:[RustAllegro]

Thomas Fjellstrom
Member #476
June 2000
avatar

Quote:

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.

Quote:

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.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

Quote:

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.

Quote:

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.

SiegeLord said:

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.

Thomas Fjellstrom
Member #476
June 2000
avatar

Quote:

You can do it, but it doesn't sound like much fun to me.

Nor does everything going slow.

--
Thomas Fjellstrom - [website] - [email] - [Allegro Wiki] - [Allegro TODO]
"If you can't think of a better solution, don't try to make a better solution." -- weapon_S
"The less evidence we have for what we believe is certain, the more violently we defend beliefs against those who don't agree" -- https://twitter.com/neiltyson/status/592870205409353730

 1   2 


Go to: