We have all done it. Hacked out a simple program or demo without any timer based movement code, and with little regard to how fast or slow the user's computer will run it. Which is ok, because it accomplished whatever we were attempting to show to those people.
When we move on to making an actual game for release, it becomes more important to consider the speed of the user's computer.
This is not a discussion on Timer Based Movement. I think we've already covered that well enough, and if not I'll be happy to write an in-depth tutorial for it.
This is a discussion on how to get more performance out of your main loop, while maintaining a good framerate.
What we typically see in the forums is something like this.
Objects = 1000
VSync = 0 ` Set to 0 to turn VSync off 1 to turn it on
set display mode 1024, 768, 32, VSync
set window position display width()/2-512, 0
sync on : sync rate 60
` Object data
type planet_data
ang as float
dist as float
speed as float
endtype
dim Planets(Objects+1) as planet_data
` Build the demo world
for x = 1 to Objects
make object sphere x, 10
r = rnd(255): g = rnd(255): b = rnd(255)
color object x, rgb(r, g, b)
Planets(x).ang = 0.0
Planets(x).dist = rnd(1990)+10
Planets(x).speed = (rnd(2500)+500) / 1000.0
next x
` Make second camera
CamImg as integer = 1
make camera 1
set camera to image 1, CamImg, 200, 200
` Position main camera
set current camera 0
autocam off
position camera 0, 0, 500, 0
point camera 0, 0, 0, 0
` Main loop variables
P as integer = 0 ` Chosen planet to display
while not escapekey()
text 10, 10, "DBPro Reported FPS : "+str$(screen fps())
` Move the objects along their orbit path
for x = 1 to Objects
Planets(x).ang = Planets(x).ang + Planets(x).speed
px# = sin(Planets(x).ang) * Planets(x).dist
pz# = cos(Planets(x).ang) * Planets(x).dist
position object x, px#, 0, pz#
next x
` Detect the selection of an object
if mouseclick() = 1
P = pick object(mousex(), mousey(), 1, Objects)
endif
` Position second camera on the selected object and show it's image
if P > 0
position camera 1, object position x(P), object position y(P)+5, object position z(P)-10
point camera 1, object position x(P), object position y(P), object position z(P)
paste image CamImg, screen width()-200, screen height()-200
endif
sync
endwhile
end
There's no Timer Based Movement,
sync rate is set to 60, and it does it's thing at an inconsistent rate from computer to computer. Setting VSYNC ON instead of setting
sync rate has basically the same effect, but with the benefit of not taking up all of the CPU's cycles.
Another approach is to turn VSYNC OFF and set
sync rate to 0 and just let it fly as fast as it can, but again that is going to run at different speeds on different computers, and eats up all available CPU cycles.
This example shows this with Timer Based Movement included just to keep things running at an even pace. Take notice of the FPS that DBPro reports. This isn't just how many times the screen is drawn, but also how many times your main loop runs.
Objects = 1000
VSync = 0 ` Set to 0 to turn VSync off 1 to turn it on
set display mode 1024, 768, 32, VSync
set window position display width()/2-512, 0
sync on : sync rate 0
` Timer Based Movement variables
global Factor# as float
global FLast as float
` Object data
type planet_data
ang as float
dist as float
speed as float
endtype
dim Planets(Objects+1) as planet_data
` Build the demo world
for x = 1 to Objects
make object sphere x, 10
r = rnd(255): g = rnd(255): b = rnd(255)
color object x, rgb(r, g, b)
Planets(x).ang = 0.0
Planets(x).dist = rnd(1990)+10
Planets(x).speed = (rnd(2500)+500) / 1000.0
next x
` Make second camera
CamImg as integer = 1
make camera 1
set camera to image 1, CamImg, 200, 200
` Position main camera
set current camera 0
autocam off
position camera 0, 0, 500, 0
point camera 0, 0, 0, 0
` Main loop variables
P as integer = 0 ` Chosen planet to display
while not escapekey()
TBM()
text 10, 10, "DBPro Reported FPS : "+str$(screen fps())
` Move the objects along their orbit path
for x = 1 to Objects
Planets(x).ang = Planets(x).ang + (Planets(x).speed * Factor#)
px# = sin(Planets(x).ang) * Planets(x).dist
pz# = cos(Planets(x).ang) * Planets(x).dist
position object x, px#, 0, pz#
next x
` Detect the selection of an object
if mouseclick() = 1
P = pick object(mousex(), mousey(), 1, Objects)
endif
` Position second camera on the selected object and show it's image
if P > 0
position camera 1, object position x(P), object position y(P)+5, object position z(P)-10
point camera 1, object position x(P), object position y(P), object position z(P)
paste image CamImg, screen width()-200, screen height()-200
endif
sync
endwhile
end
function TBM()
t = hitimer()
Diff# = t - FLast
FLast = t
Factor# = Diff# / 1000.0
endfunction
On a fast computer you can get quite high framerates with that, but again, it eats up all the available CPU cycles.
The solution I've come up with, although I'm sure I'm not the first, is to
Separate the Game Loop and Display Loop and let them run independently.
The main objective here is to put in the
Display Loop only those things that need to be drawn, created, modified, grabbed, or pasted to the screen right before the
sync, which is the only time they are needed. Everything in the
Game Loop should be the rest of the game that needs constant updating, such as object movement, calculations, collision detection, networking, etc..
The following example aims for an FPS of 30. So every 33 milliseconds it executes the
Display Loop. The
Game Loop is set to run at 4 times that rate. If the computer is fast enough, it will achieve this, and the Overall Loop Count will be quite high. If it's not, then the Game Loop slows to match up with the Display Loop, but no matter how fast the Game Loop runs, the Display Loop will always run at the desired framerate if it can. If things slow down to where they are at the same rate, even if the Display Loop rate drops below 30, the Game Loop rate will stay with it because it's trying to run faster, but can't. Timer Based Movement keeps the objects moving at the same speed no matter what the FPS or LPS.
Objects = 1000
VSync = 0 ` Set to 0 to turn VSync off 1 to turn it on
set display mode 1024, 768, 32, VSync
set window position display width()/2-512, 0
sync on : sync rate 0
` Timer Based Movement variables
global Factor# as float
global FLast as float
` Object data
type planet_data
ang as float
dist as float
speed as float
endtype
dim Planets(Objects+1) as planet_data
` Build the demo world
for x = 1 to Objects
make object sphere x, 10
r = rnd(255): g = rnd(255): b = rnd(255)
color object x, rgb(r, g, b)
Planets(x).ang = 0.0
Planets(x).dist = rnd(1990)+10
Planets(x).speed = (rnd(2500)+500) / 1000.0
next x
` Make second camera
CamImg as integer = 1
make camera 1
set camera to image 1, CamImg, 200, 200
` Position main camera
set current camera 0
autocam off
position camera 0, 0, 500, 0
point camera 0, 0, 0, 0
` Main loop variables
global P as integer = 0 ` Chosen planet to display
global LPS as integer = 0 ` Number of overall loops per second
global LPS$ as string
global GPS as integer = 0 ` Number of game loops per second
global GPS$ as string
global DPS as integer = 0 ` Number of display loops per second
global DPS$ as string
global LT as integer = hitimer() ` Loop Timer
global GT as integer = LT ` Game Loop Timer
global DT as integer = LT ` Display Loop Timer
global TV as integer = LT ` Timer Value
while not escapekey()
TV = hitimer()
inc LPS
if TV - LT >= 1000
LT = TV
LPS$ = str$(LPS)
LPS = 0
GPS$ = str$(GPS)
GPS = 0
DPS$ = str$(DPS)
DPS = 0
endif
if TV - DT >= 33 ` Rounded 33.3333 milliseconds for 30 FPS desired framerate
DT = TV
gosub DisplayLoop
inc DPS
endif
if TV - GT >= 8 ` Set for approximately four times the desired framerate
GT = TV
gosub GameLoop
inc GPS
endif
endwhile
end
GameLoop:
TBM()
` Move the objects along their orbit path
for x = 1 to Objects
Planets(x).ang = Planets(x).ang + (Planets(x).speed * Factor#)
px# = sin(Planets(x).ang) * Planets(x).dist
pz# = cos(Planets(x).ang) * Planets(x).dist
position object x, px#, 0, pz#
next x
` Detect the selection of an object
if mouseclick() = 1
P = pick object(mousex(), mousey(), 1, Objects)
endif
return
DisplayLoop:
text 10, 10, "DBPro Reported FPS : "+str$(screen fps())
text 10, 30, "Overall Loops Per Second : "+LPS$
text 10, 50, "Game Loops Per Second : "+GPS$
text 10, 70, "Display Loops Per Second : "+DPS$
` Position second camera on the selected object and show it's image
if P > 0
position camera 1, object position x(P), object position y(P)+5, object position z(P)-10
point camera 1, object position x(P), object position y(P), object position z(P)
paste image CamImg, screen width()-200, screen height()-200
endif
sync
return
function TBM()
t = hitimer()
Diff# = t - FLast
FLast = t
Factor# = Diff# / 1000.0
endfunction
Remember the FPS shown in the second example? That was your code running flat out. What we've done is made better use of that processing power by only requiring the display to be updated when it's needed, and giving the power over to the real meat of the program, where the more important stuff happens.
Using this method on the server app for Worlds Apart Online, it runs the Game Loop at around 23,000 loops per second with a Display Loop rate of 5 FPS. Perfect for a server that needs to handle lots of network traffic but doesn't need much of a display.
The client for Worlds Apart Online is able to maintain 40 FPS with a Game Loop rate of 80-90 loops per second.
I'm sharing this to help others who are pushing DBPro to it's limits and need more performance out of it while still achieving a good framerate.
I hope it helps.
Questions? Ask away...
[edit]
I've updated the code to use the HITIMER() function from IanM's Matrix1Utils plugin, since it is more accurate and reliable than the Windows TIMER() function.
[/edit]