Using Timer to move Game at same speed on any machine

The place for any Hollywood tutorials
Post Reply
Bugala
Posts: 1168
Joined: Sun Feb 14, 2010 7:11 pm

Using Timer to move Game at same speed on any machine

Post by Bugala »

Reason why I am writing this tutorial is because i bumped into small problem with Timer into which i also found solution that I am going to show in this tutorial.


First of all, this is how you typically do movement:

Code: Select all

repeat
playerx = playerx + 1
playery = playery + 1
DisplayFrame(1, playerx, playery, 1)
until stop=1
This is the most basic way of moving for example player around. However, since there are no timings on this one, it means that everything will move at different speed on different machines.

If you for example have ever tried playing old Sierra games like Kings Quest 1, 2, 3, Police Quest 1, Leisure Suit Larry 1, Space Quest 1, 2 or 3 and you choose the speed as "Fastest", you will notice something.

Use Amiga 500 and the speed is quite nice as fastest. Use Turbocarded Amiga, And you cant control that guy anymore since it moves too fast.


This is what happens with this example too. It might be working nice and fine with your machine, but put it to slower or faster machine, and its either painstakingly slow, or simply too fast to play.

There are two solutions to this: easier, and better but bit more complex solution.

First the easier solution:

Code: Select all

repeat


StartTimer(1)


playerx = playerx + 1
playery = playery + 1
DisplayFrame(1, playerx, playery, 1)


WaitTimer(1, 40)
StopTimer(1)


until stop=1

Timer works following way. when you choose StartTimer(1), it will start timer number 1, this is like having one of those watches that you can use for timing. The number tells which one of the watches to start.

So if you have StartTimer(1), StartTimer(2) and StartTimer(3), it is like starting 3 different wrist watches to measure the time passed. And just like wrist watches, you can also stop these independently, that you can choose to StopTimer(2), while Timer 1 and 3 still continue going.

So StartTimer(number) starts the counting and StopTimer(number) stops it.

When Using WaitTimer(number, another number) command, it will tell program to halt until certain time have reached.

It works so that first number is the Timer (wrist watch) which to use, and second number tells to what point we want to use it for.

Command WaitTimer(2, 40) would with Wrist watch logic mean that program would wait until Wrist watch number 2 have reached 40 seconds, and after that continue forward.

In real version, time however is not seconds.

In Timer the time goes so that every 1 000 units is one second.

So if I want to wait for say 5 seoncds before continuing, i would use:

WaitTimer(1, 5000)

This would equal of waiting for Timer 1 to have been going for 5 seconds.


In the example program I used WaitTimer(1, 40). This means that code would be executed 25 times per second.

Good thing about this is, that no matter how fast machine you have, it would always wait until that certain time is reached before executing the next repeat and hence game plays same speed on any machine basically.


However, theres a draw back on this. First of all. If you have a fast machine, you get no benefit from it, since it still keeps refreshing the screen the same speed as well, and bigger draw back is that if ou have a game something like Dune II, it might happen that there comes so much stuff on screen at once, that slower machines wont be able to handle all the things in that 25th of a second time, and since this one forces you to go through every loop, it could become extremely slow and painful process to play it.



Hence there is the most times best solution although bit more complex:

Code: Select all

repeat

StartTimer(1)


playerx = playerx + ( (1/1000)  * timepassed )
playery = playery + ( (1/1000)  * timepassed )

DisplayFrame(1, playerx, playery, 1)


timepassed = GetTimer(1)


StopTimer(1)
until stop=1

New command here is GetTimer(1)

It also requires you to use some variable with it, like the timepassed variable I am using.

Idea is that by using the wristwatch analogy again this command means that we are going to watch the time that is on wristwatches display at that exact time and mark it up to that variable timepassed.

This makes it possible to have the game run at same speed on any machine regardless wether there comes lot of stuff to screen or not.

For fierst we store the timepassed value, which could be for example 20 (1/50th of a second)

We have now decided that playerx speed should be 1 pixel per second (extremely slow by the way)

Instead of using playerx = playerx + 1, we will now be using playerx = playerx + ( (1/1000) * timepassed)


Logic is folowing:

First we decide how fast we want player to move. In this example 1 pixel per second. For that reason we have to divide that speed by 1 000 (Timers one second)

After that we tell the program to move player forward that speed multiplied by timepassed.

Suppose we have very slow machine and it takes 2.5 seoncds for it to get to the loop again. that would mean that time had passed 2500 units.

Therefore it would move playerx 2.5 pixels forward.

Suppose we have very fast machine and it takes only 1/1000th of a secong. Then that means it would move playerx forward 0.001 pixel each loop, meaning that when moving one pixel, screen would be updated 1 000 times during that.



However, everything is not perfect yet.

For when you have a fast machine and not very heavy program, then it might happen that loop is executed faster than in 1 timeunit, in which case timepassed would result as 0, and hence everything would be moved multiplied by 0, meaning no movement at all.

Therefore we still need solution for this problem:

Code: Select all

repeat
StartTimer(1)
playerx = playerx + ( (1/1000)  * timepassed )
playery = playery + ( (1/1000)  * timepassed )
DisplayFrame(1, playerx, playery, 1)
timepassed = GetTimer(1)



If timepassed < 1
   continueprogram=0
    repeat
       timepassed = GetTimer(1)
       if timepassed => 1 then continueprogram=1
    until continueprogram=1
endif



StopTimer(1)
until stop=1


Now we finally have a program that basically works on any machine at same speed (there are some small exceptions but this should be good enough for practically all).
jalih
Posts: 276
Joined: Fri Jun 18, 2010 8:08 pm
Location: Finland

Re: Using Timer to move Game at same speed on any machine

Post by jalih »

I currently use the following loop to handle my game timing:

Code: Select all

Function gameloop(dt)
	Cls()
	secs = secs + dt 
	TextOut(#CENTER, #CENTER, secs)
EndFunction


Function gamerun(updaterate)
	StartTimer(1)
	StartTimer(2)

	Repeat
		; Try to lock the frame rate to game update rate
		WaitTimer(1, updaterate)
		; Get the actual time it took to update the frame.
		Local ftime = GetTimer(2)
		ResetTimer(2)
		; Delta time in seconds between the two last frames.
		Local dt = updaterate / 1000 * ftime / updaterate
		gameloop(dt)
	Forever
EndFunction


EscapeQuit(True)
Local fps = 60
Local gameupdaterate = 1000 / fps
secs = 0
gamerun(gameupdaterate)
Post Reply