View Full Version : Smooth Animation and Windows Interference
I am writing a 2D game and am having problems with achieving smooth animation. When Windows does something in the background, it interferes with my animation. I have tried setting a higher priority on my main game thread, but if I set it too high it will not work on XP. My FPS stays the same most of the time, but occasionally it drops when Windows dumps memory or some other task.
I noticed Dexterity games have smooth animation and the sprite never hesitates. Is this a trade secret? I can't find the information on how to do this in any book or online programming forums. Most demo games that are in books hesitate from time to time also as well as the MSDN DirectX demos.
Could someone please recommend a book or site or anything that would help me solve this problem? Or if you know how to stop Windows from interfering with a program. please post the solution.
Thanks.
Fenix Down
06-23-2003, 07:47 AM
Are you using framerate based movement (locked FPS) or time based movement (framerate independent)? I'm using time based, and I had a problem with things jerking when the framerate changed. I wrote a little algorithm for averaging the framerate over the last X frames in real time, so that if the framerate changed it would just get averaged in with the last X frames and wouldn't screw things up. If you're using time based movement I can explain how it works.
I use time based movement. Sure I'd like to hear what you do. Thanks.
Fenix Down
06-23-2003, 10:51 AM
Ok just to make sure we're on the same page first. For time based movement, you have to calculate a "motion factor" which is the number you multiply your movement amount by (which is X pixels per second you want to move) to get the proper movement for the current frame. The motion factor is calculated based on the framerate in the fashion
motion_factor=(time it took for the last frame in milliseconds)/1000
which will convert the number into seconds, and this will be the percentage of a pixels/second value you want to move this frame. Then if you want to move an object say 400 pixels per second you calculate the delta value as
amount_to_move=400*motion_factor
I'm assuming you're doing something along those lines. Anyway, the problem with this method is that you're calculating the motion factor every frame based on the last frame. This means that if the framerate changes from frame to frame your movement will come out uneven because your motion factor will change many times per second.
So here's what I did as the solution. I made a linked list (I'm using STL) of motion factors for the last X frames (X can be whatever number you need to get smooth movement). Every frame, I remove the first element of the list and add to the end the latest motion factor. Then I iterate through the list adding up all the motion factors, and divide the sum by the total number of them to get the average motion factor over the last X frames. This average changes in real time every frame, but because it's an average of the last X motion factors the difference in the average factor isn't nearly as great as between two frames. So if you get a spike or drop in the framerate for one or two frames it won't screw things up.
Here's some code (called every frame). The frame counter is just a counter I use to find out when to start removing the first element of the list. This happens when the counter gets to how many frames you want to keep an average for (stored in rolling_average_count). frame_time is the time it took to draw the last frame in milliseconds.
Hope this helps, let me know if you don't understand something.
frame_times_list.push_back(frame_time/1000.0f); //add the motion factor from the last frame
if(frame_counter < rolling_average_count)
{
frame_counter++;
}
else
{
frame_times_list.pop_front(); //if the list has rolling_average_count motion factors already, take out the first one
}
FloatIter float_iter=frame_times_list.begin();
while(float_iter!=frame_times_list.end())
{
sum+=(*float_iter);
++float_iter;
}
average_factor=sum/frame_counter; //calculate the average motion factor. THIS is what you'll use to calculate movement deltas
Scorpio
06-23-2003, 01:00 PM
You might also first try to determine if the problem is your time-based movement code...or, if it's something to do with how you are updating you main game loop in your Windows app.
You mentioned your "main game thread". Are you using lots of threads in your app? Any threads?
What is driving your main game loop? A thread? A peak-message loop?
Are you using page-flipping?
Are you calling Sleep() to do any time-limiting/time-wasting? (never use Windows Sleep() function for this)
-Scorpio
princec
06-23-2003, 01:54 PM
Hey TJM, contrary to popular belief:
- never use time-based movement in a 2D game; it looks awful - really awful. Alway mandate a minimum specification that can achieve a particular framerate and then tune your animation to run at that frequency.
- never use threads in a game. Thou shalt have but one thread, and it should do all the thinking and drawing.
- attempt to delegate as much drawing to hardware as possible, then the CPU getting interrupted won't matter so much
- if drawing to a window, expect all manner of other things to grab hold of the graphics card at inopportune moments and generally make things jerky. In fullscreen nothing gets in the way; you've got the whole graphics card to yourself, and additionally you can sync to the monitor which gives you nice consistent timing without having to busy-loop on a hires timer.
Beyond this there is nothing you can do to prevent Windows making things jerky now and again. Plug time: Alien Flux (http://www.puppygames.net/downloads/SetupAlienFluxDemo.exe) is a handy example of what happens when you follow these four rules.
Cas :)
sordith
06-23-2003, 02:13 PM
Originally posted by Scorpio
Are you calling Sleep() to do any time-limiting/time-wasting? (never use Windows Sleep() function for this)
-Scorpio
What would you sugest instead of Sleep()? I found that I needed to add a sleep in order to get my game thread to work nicely with other threads.
Scorpio
06-23-2003, 02:37 PM
If you're using Sleep to yield control to other threads, just remember that it's not guaranteed to return in exactly the time you specify. So, if you ask to sleep for 12 ms, it may not return for 26 ms. Or, if no other threads are ready to run, it could return immediately.
So, if you need to waste an *exact* amount of time (on the order of 10s of milliseconds), Sleep() is not acceptable (see below for Snooze function).
I can't remember if there's a function to give control to a specific thread. I think you can call SuspendThread (and ResumeThread) to yield control. The main thing is to avoid making a call that could give the CPU to another running app.
If you do need to just kill an exact amount of time, I usually write a function called Snooze() that looks something like:
void Snooze(uint32 uSnoozeTime)
{
timeBeginPeriod(1);
uint32 uNowTime = timeGetTime();
uint32 uEndTime = uNowTime + uSnoozeTime;
while (uNowTime <= uEndTime)
{
uNowTime = timeGetTime();
}
timeEndPeriod(1);
}
Note that this is for dealing with very short delays (e.g., 18ms). If you need to delay for multiple seconds (or longer), you should use a different method since you will need to process user-input and windows messages most likely.
Using a peak-message loop, we've never had any problems getting smooth gameplay...regardless of whether we are using page-flipping or manually limiting the frame-rate ourselves using the above Snooze function in our update loop (unless of course a disk deframenter is running in the background :) . TJM, hopefully there's something going on in your app this is a simple fix for you. Good luck with the problem.
-Scorpio
Fenix Down
06-23-2003, 03:03 PM
Originally posted by princec
Hey TJM, contrary to popular belief:
- never use time-based movement in a 2D game; it looks awful - really awful. Alway mandate a minimum specification that can achieve a particular framerate and then tune your animation to run at that frequency.
- never use threads in a game. Thou shalt have but one thread, and it should do all the thinking and drawing.
What's wrong with time based movement in 2D games? Works fine for me. And as far as threads go, how can you make a multiplayer game without a network dedicated thread? You'll be losing packets if you don't have one.
Scorpio
06-23-2003, 03:54 PM
Originally posted by Fenix Down
What's wrong with time based movement in 2D games? Works fine for me. And as far as threads go, how can you make a multiplayer game without a network dedicated thread? You'll be losing packets if you don't have one.
I have done both time-based and frame-based and both worked fine for me (YMMV). Time-based is more of a pain IMO...but it can allow for a "smoother" frame-rate since the user can run at 60, 85, 100 fps or whatever their card/monitor will do.
Regarding threads for multiplayer games...a thread can be nice...but it's definitely not required. The packets will queue up and as long as you grab them all once per frame, you should be fine. There's been lot's of multiplayer games made on platforms that didn't support threading. :)
-Scorpio
Fenix Down
06-23-2003, 04:07 PM
Originally posted by Scorpio
Regarding threads for multiplayer games...a thread can be nice...but it's definitely not required. The packets will queue up and as long as you grab them all once per frame, you should be fine. There's been lot's of multiplayer games made on platforms that didn't support threading. :)
-Scorpio
Hmm yeah I guess you're right. I really shouldn't be saying anything about multiplayer games because I haven't actually programmed any only read about them. :o
Mike Boeh
06-23-2003, 04:10 PM
There's yet another way to do this... Much simpler, framerate independent, and smooth.
Most 2D games do very little to tax the computer, except drawing. So you can run your game at an extremely high frame rate internally, say 250 fps.
Bugatron, Z-Ball, Dx-Ball, DX-Ball 2, and Pocket Tanks all run using this method.
Here's a simple algorithm for doing it:
//set a first value for LastTime (it will be used in the main loop)
LastTime = MilliSecs()/4
While In main game loop
//for our purposes, a tick is 1/250th of a second, just divide millisecs by 4 :)
//get in the time and make sure at least 1 tick expired
Do
TmpMS = MilliSecs()/4
ElapsedTicks = TmpMS - LastTime
While ElapsedTicks == 0
LastTime = TmpMS
//now we do the logic loop
For i = 0 To ElapsedTicks
//ALL GAME LOGIC GOES IN HERE. BUT DO NOT DRAW IN HERE.
Next
//DRAW ALL IMAGES HERE
Send to screen and flip
Wend
Originally posted by Fenix Down
What's wrong with time based movement in 2D games? Works fine for me. And as far as threads go, how can you make a multiplayer game without a network dedicated thread? You'll be losing packets if you don't have one.
Well... I'm not Cas, but I like to reply anyways :)
The animations in 2D games are made with a specific framerate in mind - therefore a different framerate will look a bit odd.
Let's say you have a 30fps walk animation and display it with 45fps... the result is that you display half of the animation twice (1,2,2,3,4,4,5,6,6...) and that will look odd for sure. If you get a much higher framerate let's say 100 it will be less noticeable, because it's 3vs4 instead of 1vs2.
The weakness of time based 2D animations aren't that obvious with faster machines but they get painfull on machines wich are able to run the game with "only" 100-175% of the speed (fluctuation between a 1 or 2 frame duration per animation phase).
The solution is simple: frame capping.
But it isn't that easy if you want to sync it (you won't believe how much I hate tearing effects ;))
120hz/4=30
100hz/4=25
85hz/3=28.3333
75hz/3=25
72hz/3=24
60hz/2=30
Too bad that 60hz is usually the only sure shot refreshrate a display supports.
Well it always depends on the genre and on the animation system itself. Eg a breakout game or a jump'n'run with characters wich consists out of a lot of images wich are just moved and rotated should (obviously) use time based rendering.
And threads... Cas said "[...]one thread, and it should do all the thinking and drawing."
And that's absolutly true. If you use different threads for rendering, gamelogic, ai and whatsoever you are just screaming for trouble. On the other hand using threads for background actions such as networking (It's done with NIO in Java. It is like a seperate thread but not a "thread" in Java terms.) or loading of data (levels, images, ...) is ok.
For example rendering, gamelogic and ai need the same copy of the world to work with. Let's say it's a first person shooter and you shoot a rocket. The level and the rocket has to be drawn, the gamelogic determines what you have hit and the ai has to react in some way (awareness, dodge, fire back etc).
If you have different threads for all these tasks you will run into alot of problems for sure.
elund
06-23-2003, 04:45 PM
I'm using variable frame-rate based movement. I loop through all active sprites and ask them if they're ready to draw by passing the current time in milliseconds. Each sprite looks at the elapsed time and decides if it's time to draw or not. If any sprites draw, the frame is copied to the screen. Except for particles, most of my major sprites share the same rate so most drawing happens at the same time. I can have each cel of each sprite animation use their own rate, but until I make an animation tool it's a bear to do by hand. I considered using a time-based method but I figured the game would run good enough on most computers (it's a puzzle game), and I'd rather have a minor slowdown than have my main character's walking animation look jerky as it skips cels. I'm also using a dirty rectangle system on both bg -> fg, and fg->screen, which helps to reduce unnecessary data traffic. I elected not to use page flipping to make it easier to support both windowed and full-screen modes, and since I added some alpha-blending I think this was the right decision.
As for threads, I'm only using one but I will be adding one more that does very little. I'm using compressed mods for music (in my case, MO3 (http://www.un4seen.com)), and they can take a moment or two to decompress. When I switch songs, there's a noticeable pause. I'm going to create a jukebox thread whose only purpose is to sleep a lot and every few seconds check if the song is over, loading the next song as necessary.
Originally posted by Fenix Down
average_factor=sum/frame_counter; //calculate the average motion factor. THIS is
what you'll use to calculate movement deltas
I'm not sure how you are applying the average_factor after you calculate it. Does it
replace your motion_factor?
Originally posted by Scorpio
You mentioned your "main game thread". Are you using lots of threads in your app?
Any threads?
What is driving your main game loop? A thread? A peak-message loop?
Are you using page-flipping?
Are you calling Sleep() to do any time-limiting/time-wasting? (never use Windows
Sleep() function for this)
-Scorpio
Currently I have one thread that uses peekmessage. I am using page-flipping with DirectX.
I never use sleep.
Originally posted by princec
In fullscreen nothing gets in the way; you've got the whole graphics card to yourself,
and additionally you can sync to the monitor which gives you nice consistent timing
without having to busy-loop on a hires timer.
I'm using fullscreen mode in DirectX, but I've never been able to change the video card's
refresh rate successfully. So I've always had to accept whatever rate it is which is why I
went to the time based movement. I never use busy-loop. But I do sync to the frame rate
my waiting on DirectX pageflip.
Originally posted by Mike Boeh
Most 2D games do very little to tax the computer, except drawing. So you can run
your game at an extremely high frame rate internally, say 250 fps.
It appears to me that I'm stuck with whatever framerate DirectX will give me. Doing
anything faster than that will mean I will draw a screen that will never be seen.
----------------------------------------------
To everyone:
I can get Windows to quit interfering by using SetPriorityClass and this works on
Windows 9x, but XP will be unresponsive after a minute or so because it appears to set
the priority too high. You can't do a small increment. SetThreadPriority allows you to do
it in small increments and appears to work if I only increase it by .1. This improves
performance to an almost acceptable level. It then only becomes a problem if there is
something overtaxing Windows. An example of this is if a computer is normally hooked
up to a network, but isn't, then Windows appears to be polling the network card and
taking a lot of CPU cycles. When the computer was hooked up to the network again, the
program worked fine. This is an extreme situation, but it magnifies the problem to a level
that you can easily see. Periodic hits to the hard disk cause interruption of the animation
on slower computers.
I tested several games on the computer that was not hooked up to the network and was
polling. Starcraft handled it fine. A couple of other games slowed. I'd like to get the game
I'm working on to a very smooth level. I would like the game only to slow in extreme
situations and otherwise run smoothly even during hard disk hits and other normal
Window operations.
From this messages in this post, I'm getting the impression that I may be as close as I can
get to smooth animation. The SetThreadPriority improves the performance to where it will
only drop a few FPS during hard disk hits. So perhaps I cannot expect any more than this.
Mike Boeh
06-23-2003, 05:20 PM
Originally posted by TJM
It appears to me that I'm stuck with whatever framerate DirectX will give me. Doing
anything faster than that will mean I will draw a screen that will never be seen.
Trust me, it works correctly :)
Fenix Down
06-23-2003, 05:39 PM
Originally posted by oNyx
Well... I'm not Cas, but I like to reply anyways :)
The animations in 2D games are made with a specific framerate in mind - therefore a different framerate will look a bit odd.
Let's say you have a 30fps walk animation and display it with 45fps... the result is that you display half of the animation twice (1,2,2,3,4,4,5,6,6...) and that will look odd for sure. If you get a much higher framerate let's say 100 it will be less noticeable, because it's 3vs4 instead of 1vs2.
You see my animation system is not based on frames per second. It's based on "how much time should pass between each frame" much like a GIF image works just realized that it would work for a "frames per second" based system too since it converts to that anyway. Each animation has a delay between frames, measured in milliseconds. Thus, if the delay is say 100 ms, this would be "10 frames per second" with your system.
My system adds up the percentage of 1 frame which has passed the last frame until it gets to 100% and then changes the frame. Sounds confusing but basically what it does is it uses the Motion Factor to keep adding a % amount to a % counter based on how much of a single animation frame has passed since the last game frame (this can be calculated if you know how many frames per second you want, which is 1000/delay since delay is in milliseconds). I think that makes more sense. Anyway, when the counter gets to 100% the frame is changed and the counter reset. Thus, the animation is purely independent of the framerate, same as the movement.
@TJM yes, average_factor replaces the motion factor. Essentially, average_factor is the average of the last X number of motion factors, say 20. This value is relatively stable and will reduce the effects of a framerate spike or drop by the law of averages. Since you have a lot of similar values, one drastically different value in 20 won't make a big difference in the average. That's the whole theory behind my method.
svero
06-23-2003, 06:25 PM
Originally posted by Mike Boeh
Trust me, it works correctly :)
Yes Mike's method is great. It's framerate independent and gives you the smoothest possible motion on whatever card you're using. I've been using time based mechanisms... but it's tempting to switch to this system, especially for arcade games.
- Steve
PaulNZ
06-23-2003, 08:04 PM
Originally posted by svero
Yes Mike's method is great. It's framerate independent and gives you the smoothest possible motion on whatever card you're using. I've been using time based mechanisms... but it's tempting to switch to this system, especially for arcade games.
Yes, it is The One True Way(tm). I have always used a similar system (not quite as high as 250fps - usually just whatever the 'base' frame rate is) and always seperate the updating from the drawing so that if things get slow you can update multiple times to keep things moving/animating the correct amount.
mg_mchenry
06-24-2003, 03:07 AM
I was considering using time-based animation for my game until I realized that I wanted to be able to record demos and replays for debug purposes and that I'm planning on adding a multiplayer feature later, so the game state has to be reliable and predictable and rounding errors need to be consistent.
I was planning on implementing a low fixed frame rate for AI/Physics/collisions/logic, etc and a time-based system for display. You might see this in a professional FPS.
This requires a whole other set of variables for display position.
After reading Mike's post, I realized I was nuts and giving myself a big headache for no good reason.
Running the game logic at a high fixed rate and displaying whatever frames the machine can handle makes much more sense.
Mike, what's the advantage to running 250 FPS rather than just 60, anyhow?
luggage
06-24-2003, 04:07 AM
Most games I've worked on have always worked out the difference in seconds between frames and passes that through as a delta. Someone mentioned this technique.
One of the cool things you can do is divide your delta down and you get 'bullet time' effects or set it to zero to just freeze everything.
If you do this you can spot the stuff that's not frame rate independant.
princec
06-24-2003, 04:33 AM
...but I have to stress that all this stuff is exactly perfect and correct in 3D... but completely wrong for 2D.
Cas :)
luggage
06-24-2003, 04:35 AM
oh. why? Surely it's just another dimension?
I'm worried now because the 2d stuff I'd started worked along the lines of a delta for each frame.
Guardian_Light
06-24-2003, 04:42 AM
Although this doesn’t apply to animation and time based movement. (which is actually vector based movement) It's related, and if you aren't using it, it could be considered a trade secret :cool:
When you are drawing your frame people will often experience "sluggish" controls on slower computers if you are using polling based input. To resolve this problem completely, call your input polling function in between individual drawing calls. For example:
Message Loop
{
Logic Fn()
Input Fn()
Render Fn()
}
Render Fn()
{
ClearScreen()
Input()
DrawBackground()
Input()
etc...
}
It seems like bad design in a sense (at least to me) but it solves having two separate threads for input and rendering.
Finally having separate threads for logic, input, and rendering is the what I consider the best solution, but it's also the most difficult to do.
Michael Sikora
Guardian Light Studios (http://www.guardianlightstudios.com)
i too use mikes method. It works great for me. I found it in some forum around a year or so back. It is the best as far as i can tell because it runs as smooth as possible in the target computer. Trust him and he said he had used them in his games.
@Mike - i tried all your games. Fantastic work, especially the best friends. Very cool. Keep up the good work. I have set best friends' quality and type of gameplay as the target for my 3d game. It is not hardcore and yet very entertaining. Very well done.
princec
06-24-2003, 05:52 AM
Were I a university lecturer and setting my game programming students this question, I'd illustrate the point by getting them to write a simple platform run & jump game in 2D using time-based interpolation.
We'd then run the game on a machine that had an arbitrary random delay in between each frame (to simulate varying amounts of stuff happening in different frames slowing down the machine like a particle effect going off or a bunch of aliens turning up etc), to show the interpolation working.
What they would discover is though the movement is technically correct the human brain has already compensated for the slowdown. You'd basically fail to make any successful jumps because your brain would compensate for the motion and then your game would overcompensate for it. The lesson here is that in the Z=0 plane, the human brain works in a very different way to when it's working in the X & Y planes (3D). I don't actually know the physical neorology behind it but it's basically a given.
Another thing you'd have to think about in 2D is that you have a fixed number of animation frames, which will only look good at one speed. If you compensate by missing out frames or adding duplicates to cover for it, you stand a very real chance of "aliasing", where, say, a walking sprite will end up with its legs in a constant position and never animating.
And finally you'll find that collision detection becomes vastly more complicated when you've got to work out where something collides with something else. On a slow machine using interpolation you might find a fast moving object like a bullet just passes straight past an alien, which means you need to resort to much more complex collision routines which themselves have to interpolate. Agh!
This is why the best solution in 2D is to pick a minimum spec framerate and tune for it to run at exactly that framerate or easy multiples thereof. (Alien Flux runs at 60Hz but has a 30Hz mode so it can run on <500Mhz computers at apparently full speed - it just gets twice as long to do all its thinking and rendering, and I double the number of ticks that elapse in each frame).
It's also worth pointing out that you're wasting your time over 60Hz as no-one - not even those who claim otherwise - can actually tell the difference, animation-wise, above this rate. It's no coincidence that TV runs at 60Hz (in America). Actually 50Hz is entirely adequate too but monitors don't usually go down to PAL frequency. Again - this is a 2D oddity; it doesn't apply in 3D. Scientific explanations welcome...
Cas :)
Mike Boeh
06-24-2003, 06:14 AM
Originally posted by mg_mchenry
Mike, what's the advantage to running 250 FPS rather than just 60, anyhow?
The advantage comes into play when people have higher refresh rates than 60 hz. If you are only moving an object 60 times per second on a monitor that's refreshing at 85 hz, you WILL notice it not being 100% smooth. Actually, 240 fps internally is what we have settled on. Another beneficial side effect to doing it this way is very accurate collision detection.
Bugatron actually runs at 1000 fps internally. And all it does for collisions is compare 3 random lines from each model. At that high of an internal frame rate, it always detects a collision. Cheap and easy collision detection that isn't sphere based :)
However, for most 3D games, like Best Friends, running a high internal frame rate isn't as good as a tweening system. For example, Best Friends runs at 30 fps internally, and smooths between each frame so you don't notice. This tweening technique is very common. Support for it is actually built in to Blitz3D.
@gana: thanks!
Punchey
06-24-2003, 06:25 AM
Can you explain what you mean by "tweening technique"? Your input sounds really great, so I'm curious what this term means. Thanks!
Mike Boeh
06-24-2003, 07:00 AM
Originally posted by Punchey
Can you explain what you mean by "tweening technique"? Your input sounds really great, so I'm curious what this term means. Thanks!
Tweening is similar to what others were talking about, calling it "time based interpolation"- but not quite :)
Tweening runs your game at a set frame rate that is typically much lower than the refresh rate- usually somewhere between 15-30 fps. But the game still renders as fast as the PC will let you, calculating a best guess as to where stuff will be between logic updates. This also produces silky-smooth results. The logic for it looks something like this:
fps = 30
period=1000/FPS
time = MilliSecs()
While in main game loop
elapsed=MilliSecs()-time
ticks=elapsed/period
;main loop
For k=1 To ticks
time=time+period
Update 3D World and game logic
Next
//this gets a fractional part of the number for how much
//time has actually elapsed.
float tween=(elapsed Mod period)/period
//this function will interpolate the rotation, scale, movement, and alpha of an object based on the fraction passed in
RenderWorld(tween)
Wend
This also has the advantage of isolating time away from the main game logic, but is more complicated than "high internal frame rate" method I discussed earlier- mainly because you have to write that tweening function for all aspects of a model. It also has the disadvantage of being slightly more sluggish in terms of response time. But for a true 3D game, I think it's still the best method.
Dan MacDonald
06-24-2003, 07:26 AM
I do the exact same thing as mike, cept with a nested while loop instead of a for loop, it works great.
freeman
06-24-2003, 08:44 AM
Dan MacDonald
I do the exact same thing as mike, cept with a nested while loop instead of a for loop, it works great.
Same here, I´ve tried quite a few different approaches and this one gave me the best result.
Dexterity
06-24-2003, 10:40 AM
With Dweep I let the game loop as quickly as possible, capturing input, processing game logic, and then rendering everything. I limit the minimum frame length to 20ms (i.e. 50fps) for the game logic though, so if less than 20ms has elapsed since the last frame, the game logic step is skipped. Everything in the game logic updates based on a 20ms tick then. Since there's no high-speed animation in the game, it doesn't look any better running at higher frame rates. However, if the rest of the game can run faster than 50fps, it will. This allows the cursor movement to be very smooth, since the cursor will be drawn faster than 50fps. Player input can also be captured faster than 50fps, but the game won't use it until the next time the game logic functions run.
I also use a dirty rectangle animation system. If the game logic loop hasn't executed (because less than 20ms elapsed since the last frame), then when it comes time to do the rendering step, none of the game sprites will be re-rendered. Only the cursor's dirty rectangle will be updated, on both the back buffer and the screen surface. On most frames where the game logic does execute, most of the screen hasn't changed, so only the dirty rectangles are rendered on both the back buffer and the screen surface.
I don't use page flipping... just a back buffer (which can be in system or video memory) and an active screen buffer (always in video memory). For each frame I do the dirty-rectangle rendering on the back buffer, but I rarely blank out the back buffer except during a scene change. The back buffer only renders what has changed since the last frame. Then these changes are blitted to the screen (not the whole screen, just the dirty rectangles). This makes the animation very fast and smooth (far faster than the artwork's frame rate), since very little drawing is actually done each frame. On a typical frame less than 10% of the screen is being animated.
As a result of being so stingy with blitting operations, the game will run just fine on a Pentium 90 with a 512K video card. The frame rate may drop when the whole screen is exploding with bombs, but under normal conditions there's very little drawing going on, so the game runs at its maximum frame rate usually, with the cursor animation as smooth as the hardware will allow. Even if there are a lot of animated sprites on the screen, most sprites animate at very low frame rates, like 12fps, so they don't create dirty rectangles every frame. You can fill a whole level with animated fans or lasers, and the game will still run fine. These low frame rates look ok because aside from Dweep, the game sprites don't move around -- they just animate in place. They're small too -- each one fits on a 40x40 tile.
I know this isn't a good approach for every game, but it was an engine designed for this specific game, and it works extremely well in this case.
What I don't understand about either the high framerate (Mike's - 240 fps) method or Dexterity's dirty rectangle method is how do you draw to the screen buffer without synching to the vertical retrace. In the 240 fps method Mike indicated he used page flipping. I page flip then wait for the page flip to complete before going on, thus synching to the screen refresh rate. Do you just skip the wait or is there some other way of doing this?
As for Dexterity's method, it would seem that if you blitted to the screen without checking on the vertical retrace you stand a chance of getting tearing. But maybe you check before blitting?
I ran Dweep on a 350 mhz computer and it hesitates slightly when Windows hits the hard disk. I ran Z-Ball on a 600 mhz and it hesitates rather frequently. It seems like most hesitation problems are solved with faster computers. These are the problems I'm having. I usually test my games on slower computer to be able to see small problems. Also some our customers have slower computers. But if I can get them to work well on slow computers, usually there's no problems on the fast ones.
Thanks for all input on smooth animation. And thanks Felix Down for clearing up your method for me. I think I'm on the right track. I just need to experiment a little.
Punchey
06-25-2003, 05:29 AM
If you or the system is hitting the disk, you're more than likely eating up most of your available resources. Hitting an IDE disk eats up GOBS of resources. When this is done, there's virtually nothing you can do to keep your game from getting a stuttuer of sorts. I mean, if the computer is busy doing things other than running your game, there's no way to FORCE it to render frames while it's busy elsewhere. To do that you'd need a dedicated CPU along with other subsystems.
Just be sure that YOU'RE not the one hitting the disk, and make sure your memory footprint is such that you're not making the player's system have to swap memory from disk, and you should be fine most of the time. If the player has defrag running in the background for instance, there's pretty much nothing you can do to make your game run smoothly under those curcumstances.
mg_mchenry
06-25-2003, 07:21 AM
Cas - let me know if I'm getting you right here or not.
- A 2d game which animates by moving and rotating images looks fine with variable frame rate.
- A 2d game which animates by flipping through a series of images (avatar walking) is bad with a time-compensating variable frame rate.
You would rather that if the system became overloaded (too many exploding sprites) the internal game frame rate should slow down and every frame would be displayed.
Instead of the game keeping a consistent rate for input and movement, but only displaying a portion of those frames?
My game has independent internal and display rates. It displays frame updates so long as the system is keeping up, but if they system is fast enough, it tweens animation between frames to any frame rate. It's a space shooter, so this will work fine.
Animated Gifs don't get tweened. I just don't believe that showing one frame of the animation for even an extra 50 milliseconds is going to throw anyone off... or even be noticable.
Perhaps I'll make a side-scroller with this engine so you can show me exactly what's wrong with it... because I'm not seeing the problem.
I will admit that every sidescroller I've ever played actually slowed down time every time too many things got on the screen. Perhaps there is a reason for this beyond the fact that it was easier to program that way.
Dexterity
06-25-2003, 07:57 AM
I didn't bother with retrace synching for Dweep because the game background is static and doesn't scroll. The individual sprites are so small and their movement so slow, that it would be tough to perceive any tearing even if you looked for it.
Any game will suffer if your PC is accessing the hard drive. Dweep has a very small memory footprint and shouldn't need to access the hard drive at all while a game is in progress. Windows, of course, has a mind of its own, and the OS sometimes decides it wants to do things in the background while the game is running.
princec
06-25-2003, 08:55 AM
mg - not quite; what I do is design for a minimum specification that's guaranteed to manage 60fps. So in my case, a 500Mhz processor or so, and a TNT2. It rarely drops below that but when it does it's over so quickly it's soon back to 60fps again and it's not so bothersome.
I've got a little feature in Alien Flux which has proved quite popular - it detects when you're running below par and suggests which options to change to make it run at a consistent rate. One of these is the 30Hz option, which gives it twice as long to think and render - so even a weedy computer can manage a consistent framerate.
The good bit about the 30Hz rate is I can effectively halve my stated minimum specs. It's not as slick but it's still 100% playable 100% of the time.
Cas :)
Lizardsoft
06-25-2003, 09:25 AM
There's a lot of great drawing methods presented here. There's some technical limitations of the system that you have to look at here as well though. Windows is not a realtime OS, meaning ultimately you don't receive any absolute execution time guarentees. If Windows pauses your process you are at its mercy. There's some very good explanations of how Windows handles threads and processes, how priorities work, etc. A good one to start you off is:
http://www.codeproject.com/system/simpletime.asp
It talks specifically about the problem of reliable timing, and as a result it covers why you cannot rely on your code to run at any given time. From that you should go and read the high ranking articles in the Processes/Threads/IPC section of Code Project. I've managed to get huge speed boosts in my Windows application (not a game I'm afraid) just by strategically multithreading the loading process and setting thread priorities correctly. I also actually managed to slow the program down several-fold the first time I attempted this so it's worth experimenting and not giving up right away. The next time I write a game, I will definitely use multithreading. In the case of my application, a lot of time was being wasted waiting for data from the disk and other operations that didn't take much CPU but halted the application until they were complete. By multithreading my app, I was able to perform other loading tasks while others were waiting on slower hardware. By understanding what's going on I'm sure you can help minimize the problem and understands what variables can and cannot be controlled by you.
I find a good general rule of thumb to be is the more different types of tasks being done in one thread, the more likely that one of them is getting hung up waiting for something when the other tasks could be executing. I don't want to be too specific since I'm not sure exactly what things are affected by Windows spasms, but here's an example. Say Windows has to swap out some memory. A reasonable (although not necessarily correct) assumption is that code which needs to read from memory that is being swapped, or that needs to access the disk while Windows is writing to it, must wait. A task like drawing a frame (assuming all your graphics are in video memory) or polling for player input probably could continue unhindered. I've never done a comprehensive investigation of what can and can't be done when Windows is doing "x" task, but splitting up different tasks into multiple threads definitely works well.
princec
06-25-2003, 10:27 AM
Agh! No! Avoid threads at all costs!
The only certainty about threads is that you cannot be certain when they will run. We've had countless n00bs at JGO who try to use threads all over the place and then plague us with "why doesn't my game run smoothly" questions.
What we tell them is to do everything in one thread - effectively meaning they are timeslicing their tasks manually instead of at the whim of the operating system. This is particularly important even in Windows, because the DOS based Windows systems behave quite differently from NT based ones; and multiprocessor based systems behave completely differently still. By using only one thread, we make sure that the code works across all operating systems in the same way.
The best way to achieve parallelism in a game is to offload work to graphics hardware. The task of, say, drawing a few hundred sprites in OpenGL involves simply pointing the driver at your vertex data and letting the graphics card suck it out of AGP ram while you get on with doing something else.
Other than that there's only one good place to use threads in a game, and by no coincidence it's the use that threads were originally designed for: doing disk I/O to load new resources whilst the game continues running. If you've got a particularly massive world it's handy to segment it and load in sectors in the background when it looks like the player is heading in that direction.
Even doing that is adding a massive load of complexity to your application with contention and synchronisation. (Even in Java with the oh-so-easy thread support built in to the very language it's still nontrivial).
Cas :)
Lizardsoft
06-25-2003, 11:39 AM
Certainly if you can offload the work elsewhere then by all means go for it. You do have to be careful with threads, your entirely right, but I definitely disagree that they can't be used effectively in a game. I think I gave the wrong idea and advocated too many threads. I like to use threads where response time is critical, since with clever prioritizing and thread synchronization you can get very nice results, such as user input. It does get really complicated since you have to sync your threads and overlap their functionality to make it really work. I tend to get carried away playing with this stuff ;)
I did stray away from my original point, which is that you have no guarentee that your code will be run by Windows (the same reason you suggest not using threads), meaning that background tasks can lag up your game by literally not giving it any CPU time for a while.
The thread thing is a tradeoff, you can gain back some control Windows yanks from you and make better use of CPU, but you have to adopt a different approach to your "game loop". I also agree that in 3D the situation becomes very different because of the card's ability to work away on its own.
Punchey
06-26-2003, 04:06 AM
I'm currently using threads to read and decompress textures in my new screensaver that's underdevelopment. It's hard to see how one could chop up something like image decompression and spread it out over multiple frames. Yes, there's some convoluted and twisted way of doing it, but it surely isn't elegant or easy to implement. Normally you'd do something like, "LoadTexture()" and then this routine doesn't return until it's done doing its job. So if it takes 1 whole second to finnish its job, then your entire game loop get stalled for 1 whole second -- unacceptable!
So what I'm doing is loading them in their own threads and it works great! My render loop doesn't get noticably slowed down even when loading and decompressing huge images.
But, I agree that threads should have very limited application in games. But I don't think they should just be written off either since they can definitely solve some otherwise perplexing problems.
mg_mchenry
06-29-2003, 11:30 AM
I made my 2D animation fully frame-rate independent, and it jerks every second or so. You win for now, Cas. ;) But it's because of a mistake I made, not because it shouldn't be done.
Wow, do I feel like a n00b!
I really should have known better. I know that timer interrupts have a limited precision. Anyone who is thinking of doing time-based animation and doesn't know this should read the article posted earlier in this thread by Lizardsoft:
http://www.codeproject.com/system/simpletime.asp
All Important Quote:
Windows is not a real-time operating system. Windows is not a real-time operating system. Windows is not a real-time operating system.
Somehow, Macromedia Director is giving me extremely accurate millisecond values, and I let it lull me into a false sense of security.
My system(in psuedocode):
internalFPS = 60
internalFrameTime_ms = 1000 / internalFPS // milliseconds per game frame
tweensEnabled = true
main loop (up to 999 times per second)
now_ms = time in milliseconds
while now_ms > nextGameUpdate_ms then
gameTime_ms = nextGameUpdate_ms
now_ms = gameTime_ms
input.update()
game.update()
graphics.update()
nextGameUpdate_ms+= internalFrameTime_ms
wend
if tweensEnabled then
if now_ms > gameTime_ms then
elapsedTime_ms = now_ms - gameTime_ms
Graphics.tween(elapsedTime_ms)
end if
end if
graphics.display(now_ms)
end main loop
So the internal game frame rate is constant. If it has to run several internal frames between updating the screen, it does.
But if your computer is fast enough to display frames between internal updates, it uses the positions and velocities of objects a the last update to tween a new frame.
I can set my internal frame rate to 10, and everything is still amazingly smooth moving. Except for about once a second, everything hesitates. This hesitation rate seems to be independent of the internal frame rate. Even if I only let my main loop execute at 30 FPS, I still see the once/second hesitation. Even if I disable tweening.
Visually, it's as if time flows perfectly smooth for most of a second and then pauses for about 1/30th of a second. It pauses just long enough to be noticed.
During the writing of this post, I have flip-flopped between thinking I know the reason behind this and having no clue about 4 times.
So far, I'm tracking these statistics:
1) in a second, how many internal frames are processed
2) in a second, how many frames are displayed
3) Average number of times game.update() had to be run in the game update loop. I'm calling this average frame step.
I realized that internalFrameTime_ms had to be a float or I ended up with 62 internal FPS when I started tracking 1. (16 ms/frame = 62.5 FPS; 16.6 ms/fame = 60)
As long as the main loop was running faster than internalFPS, framestep should be 1. But it's 1.04, which means that every 25 runs through the main loop, it runs 26 game updates.
My hunch is that I won't be able to eliminate this problem as long as I'm testing now_ms > nextGameUpdate_ms before each game frame update and the internal framerate is higher than the system timer accuracy will allow.
I'm at a loss trying to figure out how to make this system work and maintain frame rate independence.
I ripped my game engine completely apart to do this, and I was better off before I did.
I would consider allowing an arbitrary number of ms to pass between each game update and using an average over time similar to Fenix Down's average_factor.
So my internal rate would be variable, just like the display rate.
But I'm planning on networking the game, and I'd much rather that the game updates at discreet intervals...
mg_mchenry
06-29-2003, 11:39 AM
*Idea*
I could refrain from doing two game updates in a row without displaying unless the game is more than two frames behind...
That might do it.
mg_mchenry
06-29-2003, 12:16 PM
Or, instead of getting Director's version of the current milliseconds at the beginning of the main loop, I could:
1) Keep track of how many milliseconds pass per time through the main loop on *average*.
2) update the game that many milliseconds every frame, even though I know it's wrong on a small scale, it should smooth out over a longer period of time.
3) if the game time starts to lag behind real time, adjust the average.
If I make these averages over longer periods of time like 500-600 milliseconds.... maybe that does it.
mg_mchenry
06-29-2003, 04:09 PM
OK, I've tried all of my own suggestions.
Finally, I went back to the old version without any timing code in it whatsoever and found it had the exact same problem.
Director seems to stutter about once a second.
Also, I checked the millisecond values I was getting. I can get a difference of 1 millisecond before and after calling a function. It seems impossibly accurate. I don't know how.
Maybe it synchronizes it's internal millisecond counter once a second and that's what I'm seeing.
I think it might also be related to refresh rate. Director doesn't seem to be waiting for a vsync, and I can get it to generate artifacts if I animate at several hundred frames per second.
I might never have noticed this stutter if I didn't have such a smooth scrolling star field in the back ground... Loosing my confidence in using Director for this project, and I'm way too far into it.
princec
06-29-2003, 09:24 PM
It could be a garbage collection.
Cas :)
mg_mchenry
06-30-2003, 02:41 AM
Interesting suggestion - you must run into that with Java?
Director doesn't have destructors, so no code should run as a result of garbage collection. (I have to explicitly run any de-init or cleanup code before I release my last reference to an instance.) Additionally, I'm not creating or releasing anything throughout most of the code.
What's more - I got the same results in a movie that has no Lingo code, just score-based animation. There should be no garbage collection going on.
I've gone back and checked other director movies I've made in the past. Most don't have a constant smooth motion in them, so the stutter is not noticable. But if I just create a simple animation with one smooth moving sprite crossing the screen, I can see the stutter. If I export the movie to quicktime, quicktime plays it smoothly without any stutter.
I tried varying resolutions and refresh rates. I tried saving as a projector and using full screen.
I tried running my project in a window next to Starscape running in a window. The two apps are similar in that the background is mostly black and there are scrolling star fields. Both put together use 50% of my cpu (my app was set at 120 FPS display max).
Starscape scrolled smoothly on one side of my screen while my app stuttered about once a second.
I've got 2 months left to finish this game, and it's way too late to choose another platform. I really don't want to release with this kind of defect...
My game will have:
1) quality game play
2) quality graphics
3) intuitive menu system
4) friendly sales pitch
5) mildly annoying stutter.
Well, now that I'm sure that it's a Director problem, I'll hit the Director user groups a little harder. And Google.
princec
06-30-2003, 05:26 AM
You don't need destructors for garbage collection. If it's creating any internal data for anything under the covers it'll periodically need to throw it away, and the chances are this is what happens, every second or so I bet. It might not be GC of course, but a high-level system like Director almost certainly does use this mechanism to allocate memory and data.
Cas :)
Scorpio
06-30-2003, 05:49 AM
You might try writing a little test app that creates a bunch of stuff every frame and see if you get the same pausing problem (then reduce the object creation to see if it goes away). This might help you determine if it is garbage-collection (or other data management going on in the background) that is causing the problem.
If there's not a way to profile, you might try putting timing code around the major sections of your main loop and spit out a message if the execution time ever spikes above the norm. This might give you an extra clue or two as well.
Good luck! :)
-Scorpio
princec
06-30-2003, 06:23 AM
Just thought I'd throw this in: http://www.pinds.com/software/new-director
An amusing rant about Director ;)
Given that a large proportion of a game's development resources goes on the design and art / sound, exactly how much work is involved in shifting it to something like, say, Blitz? (Which I'm reliably informed by Chaz is a piece of piss).
Cas :)
mg_mchenry
06-30-2003, 06:32 AM
The reason I brought up destructors is that in my experience in the past with Java (feel free to tell me if I'm nuts, Cas), is that my destructor would run some time between when I no longer had a reference to the object and when the garbage collector got rid of it. Not neccessarily right away.
It's hard to imagine why garbage collection in and of itself would take up so much time unless large quantities of things were being dumped.
And the reason I'm pretty sure it's not gc is that even if I prevent any of my code from running in the first place and just have a square moving around, animated completely by Director, it still stutters at the same frequency.
Why would gc take so long, anyway? It's not like it has to look at every object and see if something has a reference to it, right? The objects have reference counters and when they reach 0, it's flagged for collection.
I can't wait to see if I have the same problem with Shockwave3D...
You might try writing a little test app that creates a bunch of stuff every frame...
Even creating nothing every frame, it's still doing it. I might try the profiling though... But theoretically, I can't see how it will work.
No program on the PC should be able to give me time accuracy greater than 10ms, yet I seem to be getting 1ms accuracy from Director.
One thought I had on this is that it's counting processor cycles or some such and once per second, fixing it's time value and time just stands still for 30ms or something.
I didn't even notice this problem until Cas brought up issues with 2D time-based animation. Then I started to look extra close. Now that I know that it's there, I can't get past it.
princec
06-30-2003, 06:57 AM
You're thinking of finalizers, and because of the way GC works, they are a a world of evil and should basically never, ever be used. This is the dark side of Java :P
I suspect that Director does indeed create thousands of objects every frame it ticks for one reason or another, filling up a little pool of memory. It will be scheduled to clear them down every so often, when the pool of memory runs out. This will tend to occur very regularly in a typical game loop as more or less the same thing happens every frame.
If it's a proper garbage collector it will indeed have to scan through every live object to make sure it's still referenced by something; reference counting simply doesn't work - you can easily get object leaks with reference counting and run out of memory in no time at all.
So the more live objects you've got when it runs out, the longer it'll take to scan through them all as well. Yikes! Sadly there's not many solutions to this problem. First and foremost, don't create objects in rendering loops. A few objects every frame won't hurt. Secondly, know what things are causing garbage generation under the covers and avoid them if you can.
Your last option which would be incremental concurrent garbage collection is most likely unavailable to Director ;)
Now: with respect to timer resolution, my timer has a resolution of a few nanoseconds I think - so it's hardly surprising Director manages 1 millisecond!
Something you might want to consider is further capping your framerate so that the pauses are less noticeable anyway. But that'll feel a bit crummy on a swanky nice 2ghz machine.
Cas :)
bstone
06-30-2003, 09:16 AM
Trying to see whether the effect takes place on another box/configuration might add to the overall confusion also :)
mg_mchenry
06-30-2003, 11:20 AM
Originally posted by princec
Something you might want to consider is further capping your framerate so that the pauses are less noticeable anyway. But that'll feel a bit crummy on a swanky nice 2ghz machine.
Cas :)
Yes, indeed, reducing to a jerky 15 FPS does make it impossible to notice the once per second jerk. I'll make that an option... I'll call it "stutter elimination through constant stutter".
But I don't necessarily have to limit it to 15 FPS. I can stick with 120 FPS and add in plenty of artificial stutter.
If I can't get this jerkyness to go away, I'm just going to have to release my first version of the game that way. And never make another game in Director again.
I'm basically using it as sprite library anyway. I should have gone with SDL...