PureBasic Survival Guide VII - 2D Graphics II
PureBasic Survival Guide
a tutorial for using purebasic for windows 4.60b2

Part 0 - TOC
Part I - General
Part II - Converts
Part III - Primer I
Part IV - Primer II
Part V - Advanced
Part VI - 2D Graphics I
Part VI - 2D Graphics II
Part X - Assembly
Part XI - Debugger
Part XII - VirtualBox
Part XIII - Databases
Part XIV - Networking
Part XV - Regular Expressions
Part XVI - Application Data
Part XVII - DPI
Part XXVII - Irregular Expressions
Part XXIX - Projects
 

Part VII - 2D Graphics II
v0.09 10.04.2011

7.1 More...
7.2 Moving sprites
7.3 Orthagonal fixed speed movement
7.4 Vector based movement with inertia
7.5 Size frequently matters!

 

7.1 More...
 

To keep these pages manageable, I decided to split my old 2D Graphics page into two parts. You're now entering the second stage. As Ben Liebrand said: 'Escape while you can... Run run! Bwoaahahahaha!"

(The quote above may only mean something those those growing up in the Netherlands, listening on their transistor radios to the legendary Curry en Van Inkel...)


7.2 Moving sprites.


Framerate.

Your graphics card is sending an image to the monitor, a certain number of times per second. Unfortunately not all hardware configurations do so with the same frequency. The sample code of RotateSprite3D a little back can be used to show the problem. Typically LCD's will run at 60 frames per second, called the 'refresh rate', but you cannot count on that! Good ol' CRT's run at all kinds of frequencies, randing from 50 to 100 hz...

Using OpenScreen() with the #PB_Screen_WaitSynchronization or #PB_Screen_SmartSynchronization flag makes FlipBuffers() wait for the vertical blank, before it 'flips the buffer'. Duh. Vertical blank? The vertical blank is a brief 'pause' between two screen refreshes. If we change the screen in that period, the user will see a completely new frame, without any 'jerking' or 'tearing'. So, do we want it? Yes, we do want it.

When our program hits the Flipbuffers() statement it will wait for the next screen to be drawn (if the right option was used with OpenScreen() of course... So, if computer A has a refresh of 100 frames per second, and computer B has a refresh of 60 frames per second, then on computer A the program will start / stop a 100 times per second, and on computer B that will only happen 60 times. If our code is tied into the same loop as FlipBuffers(), the speed of our code would thus vary... In other words, on computer A the program will do it's calculations 100 times per second, whilst on the other machine it will only do 60 calculations per second.

So? Imagine you move a sprite 1 pixel per calculation, that means on computer A the sprite will move 100 pixels per second whilst on computer B it will only move 60 pixels per second. That may be fine for certain applications, but for games that's somewhat... euh... undesirable :-)

The example with RotateSprite3D() can be tweaked a little to show this. Uncomment these two lines:

; framerate = 120
; SetFrameRate(framerate)
We are now setting our own refresh speed at 120 frames per second. If we run the code again... nothing changed! That's because FlipBuffers() is still waiting for the vertical blank, check out the OpenScreen() command, and replace #PB_Screen_WaitSynchronization with #PB_Screen_NoSynchronization and run... Now we will get our chosen speed, ignoring the actual refresh rate of the screen.


Refreshrate vs. framerate.

Once more... An (old CRT) monitor displays it's image 'n' times per second. It draws horizontal line after horizontal line, then at the end jumps back to the start to do it all over again. That jumping back to the start is called the vertical blank. The number of times it updates the screen is called the refreshrate.

You can set the refresh rate for a full screen using:

SetRefreshRate()
...but if that actually does anything depends on your videocard and settings (and incorrect settings could eventually damage your (older) monitor). Thus I would suggest to only do these kind of things after asking the user's confirmation / permission.

You can detect the refreshrate (of monitor 0 ie. desktop 0) using:

ExamineDesktops()
refreshrate = DesktopFrequency(0)
The data on your screen is also updated a number of times per second, called the framerate. The framerate and the refreshrate don't have to be the same frequency. In the paragraph above we modified the code to run at a framerate unrelated to the refresh rate, by telling FlipBuffers() NOT to wait for the screen refreshes, but to wait for our own set framerate using SetFrameRate() and FlipBuffers(#PB_Screen_NoSynchronization).

Obviously, when our drawing is updated at a fixed rate, and we don't care about the actual screen refresh rate, we may run into an effect called 'tearing'. This happens becuase we change the image whilst it is being shown on the screen, so it's better to update the image during the vertical blank.

Many games show very high framerates, but no tearing. They draw their images at a higher frequency, but the image shown to the user is still matched to the vertical blank. In other words, the game's engine may draw 200 frames per second, but you are only seeing 60 of them, the rest is thrown away.

For now I'll keep the framerate tied to the refreshrate, ie.

ExamineDesktops()
refreshrate = DesktopFrequency(0)
framerate = refreshrate

Refreshrate independent sprite movement, framerate independent sprite speeds.

Ah, what a lovely sentence :-)

There are many ways to create framerate ie. refreshrate independent sprite movement. You could totally disconnect things-on-screen from things-behind-the-scenes, or you could calculate the movement per frame so the movement per second is the same on every system regardless of framerate. Let's try that approach first... (And I'm not so sure I can handle the latter, but perhaps I'll try it lateron...)

The trick is to express movement in distance per second and then figure out how much that is per frame. Let's say we move a sprite by 30 pixels each second, and our framerate is 60 frames per second:

framerate = 60
speedpersecond.f = 30
speedperframe.f = speedpersecond / framerate
So, if we would move our sprite with 30 pixels per second, and we have 60 frames per second, then we would have to move it 30 / 60 = 0.5 pixels per frame. (60 frames = 1 second = 60 x 0.5 = 30 pixels per second)

Let's start with the framework. We'll create a window, put a screen on it, and handle all events properly in an endles Repeat / Until loop. We'll set action.i to #f_exit to tell the program we want to quit, and we're checking the [Esc] key as well as [Alt]+[F4] (which causes a #PB_Event_CloseWindow). I like to use Enumeration to make sure that each window, gadget, image and action has its own unique number. It makes debugging (and thus life :-)) easier. (Yes, if you paid attention all those topics passed in the previous pages :-))

; survival guide 7_2_100 sprite engine start
; pb 4.51 directx9
;
EnableExplicit
;
InitSprite()
InitKeyboard()
;
Enumeration
  ;
  ; windows
  ;
  #w_main_nr
  ;
  ; actions
  ;
  #f_exit
  #f_none
  ;
  ; images
  ;
  #i_rocket
  ;
EndEnumeration
;
ExamineDesktops()
Global desktop_width.i = DesktopWidth(0)
Global desktop_height.i = DesktopHeight(0)
Global desktop_depth.i = DesktopDepth(0)
;
; open a maximized window the same size as the desktop and without a border
;
Global w_main_h.i = OpenWindow(#w_main_nr,0,0,desktop_width,desktop_height,"Sprite Engine",#PB_Window_BorderLess|#PB_Window_ScreenCentered|#PB_Window_Maximize)
;
; open a screen the same size as the window (ie. covers the whole desktop)
;
OpenWindowedScreen(w_main_h.i,0,0,desktop_width,desktop_height,0,0,0,#PB_Screen_SmartSynchronization)
;
; endless loop handling events
;
Global action.i = #f_none
Repeat
  Global event = WindowEvent()
  Select event
  Case #PB_Event_CloseWindow
    action = #f_exit
  Case 0
    ;
    ; there were no events, so let's do our graphical stuff
    ;
    ExamineKeyboard()
    If KeyboardPushed(#PB_Key_Escape)
      action = #f_exit
    EndIf
    ;
    ; and show things on the screen
    ;
    FlipBuffers()
    ;
  EndSelect
Until action = #f_exit
;
; smoothly close screen and window
;
CloseScreen()
CloseWindow(#w_main_nr)
If you run the above, the screen turns black, and nothing happens. Well, that's the idea, as we're not drawing anything on it yet!

We want to display multiple sprites, all floating around on the screen, all with different directions and speeds. To store information about each sprite, we'll create a structure that contains all information we need: its image, its speed, its direction, its position. As we want to deal with multiple sprites at the same time, we'll create an array to keep track of all of them and we'll check all elements using a For / Next loop. (Ah, more references to previous topics :-))

Let's expand the framework above, by creating an two images (one for a sprite, one for a rock). We'll then create four sprites, one of them our 'rocket', three of them our 'rocks'. For now, we'll just put them on a static spot on the screen. We'll dimension the array to contain a maximum of 100 objects, and we'll use a flag in the structure to tell our program which objects (sprites) are alive and which ones are not. Each object gets its own 3D sprite assigned so we can rotate them independently.

So...

; survival guide 7_2_200 sprite engine 4 static sprites
; pb 4.51 directx9
;
EnableExplicit
;
; *** initialize
;
InitSprite()
InitSprite3D()
InitKeyboard()
;
; make sure our gui elements, images etc. have unique numbers
;
Enumeration
  #w_main_nr                         ; main window
  ;
  #f_exit                            ; quit
  #f_none                            ; no action (keep looping)
  ;
  #i_vectoid                         ; rocket 'v' shaped image
  #i_rock                            ; rock image
  ;
EndEnumeration
;
; create a structure that will contain all information on each object (sprite)
;
Structure object
  alive.i                            ; set to #true to enable this object
  sprite3d_nr.i                      ; 3d sprite used
  a.f                                ; angle ie. object orientation
  z.f                                ; object size factor (1 = original size)
  x.f                                ; x-coordinate
  y.f                                ; y-coordinate
EndStructure
;
; create an array that can contain 100 objects
;
#objects_n = 100
Dim object.object(#objects_n)        ; so the array() is called 'object' and is of type 'object'
;
; *** get information on the desktop, open window and screen, build sprites
;
ExamineDesktops()
Global desktop_width.i = DesktopWidth(0)
Global desktop_height.i = DesktopHeight(0)
Global desktop_depth.i = DesktopDepth(0)
;
; open a maximized window the same size as the desktop and without a border
;
Global w_main_h.i = OpenWindow(#w_main_nr,0,0,desktop_width,desktop_height,"Sprite Engine",#PB_Window_BorderLess|#PB_Window_ScreenCentered|#PB_Window_Maximize)
;
; open a screen the same size as the window (ie. covers the whole desktop)
;
OpenWindowedScreen(w_main_h.i,0,0,desktop_width,desktop_height,0,0,0,#PB_Screen_SmartSynchronization)
;
; create the first image and sprite (i've given them the same number)
;
Global i_vectoid_h = CreateImage(#i_vectoid,64,64,32)
StartDrawing(ImageOutput(#i_vectoid))
  Box(0,0,63,63,RGB(0,0,0))
  FrontColor(RGB(0,255,0))
  LineXY(4,60,32,4)
  LineXY(32,4,60,60)
  LineXY(60,60,32,16)
  LineXY(32,16,4,60)
StopDrawing()
CreateSprite(#i_vectoid,64,64,#PB_Sprite_Texture)
StartDrawing(SpriteOutput(#i_vectoid))
  DrawImage(i_vectoid_h,0,0)
StopDrawing()
;
; create the second image and sprite (again with the same number)
;
Global i_rock_h = CreateImage(#i_rock,64,64,32)
StartDrawing(ImageOutput(#i_rock))
  Box(0,0,63,63,RGB(0,0,0))
  FrontColor(RGB(255,255,255))
  LineXY(4,60,32,4)
  LineXY(32,4,60,60)
  LineXY(60,60,38,16)
  LineXY(38,16,54,6)
  LineXY(54,6,43,34)
  LineXY(43,34,60,60)
  LineXY(60,60,10,35)
  LineXY(10,35,4,60)
StopDrawing()
CreateSprite(#i_rock,64,64,#PB_Sprite_Texture)
StartDrawing(SpriteOutput(#i_rock))
  DrawImage(i_rock_h,0,0)
StopDrawing()
;
; create four objects, first the player, then three rocks
;
; the player is object number 0
;
CreateSprite3D(0,#i_vectoid)
With object(0)
  \alive = #True                  ; yes, it's alive
  \sprite3d_nr = 0                ; which sprite did we use?
  \a = 0                          ; direction it faces
  \x = desktop_width / 2          ; in the middle of the screen
  \y = desktop_height / 2         ; in the middle of the screen
  \z = 1                          ; normal size
EndWith
;
; three rocks, objects 1 to 3
;
Global n.i
For n = 1 To 3
  CreateSprite3D(n,#i_rock)
  With object(n)
    \alive = #True                ; yes, it's alive
    \sprite3d_nr = 1              ; which sprite did we use?
    \a = 0                        ; direction it faces
    \x = desktop_width / 4*n      ; spread over the screen
    \y = desktop_height * 0.75    ; below the player object
    \z = 1                        ; normal size
  EndWith
Next n
;
; endless loop handling events
;
Global action.i = #f_none
Repeat
  Global event = WindowEvent()
  Select event
  Case #PB_Event_CloseWindow
    action = #f_exit
  Case 0
    ;
    ; there were no events, so let's do our graphical stuff
    ;
    ExamineKeyboard()
    If KeyboardPushed(#PB_Key_Escape)
      action = #f_exit
    EndIf
    ;
    ; process all objects
    ;
    ClearScreen(0)
    Start3D()
      For n = 0 To #objects_n
        With object(n)
          If \alive
            DisplaySprite3D(\sprite3d_nr,\x,\y)
          EndIf
        EndWith
      Next n
    Stop3D()
    ;
    ; and show things on the screen
    ;
    FlipBuffers()
    ;
  EndSelect
Until action = #f_exit
;
; smoothly close screen and window
;
CloseScreen()
CloseWindow(#w_main_nr)

7.3 Octagonal fixed speed movement.
 

Yes. I am perfectly aware that the title of this section is wrong, but I just couldn't come up with a better prase. (Chances like this are rare :-)) Think of Pacman, and all those shoot-em-ups. The player controls the character directly, the character moves at a constant speed, and there's no inertia. Sort of, but I think you got the picture.
 

<< werkpunt - this section is under construction >>


7.4 Vector based movement with inertia.
 

Now, let's move things 'asteroids' style. Assume every object is moving in a specific direction with a specific speed in pixels per second (called a 'vector'). From this we can deduct how many pixels the object should move in x- and y-direction, per frame. Let's go back to the code we ended with in 7.2 and expand our structure a little to contain information on the vector and the resulting movement, like this:

Structure object
  alive.i                            ; set to #true to enable this object
  sprite3d_nr.i                      ; 3d sprite used
  a.f                                ; angle ie. object orientation
  z.f                                ; object size factor (1 = original size)
  x.f                                ; x-coordinate
  y.f                                ; y-coordinate
  v_v.f                              ; speed in pixels per second
  v_a.f                              ; direction of movement
  d_x.f                              ; movement in x-direction in pixels per frame
  d_y.f                              ; movement in y-direction in pixels per frame 
EndStructure
So, let's say our oject moves 30 pixels per second, at an angle of 45 degrees. x- and y- movement per frame can be calculated like this:
v_v = 30
v_a = 45
d_x = v_v * Sin(v_a) / framerate
d_y = v_v * Cos(v_a) / framerate
Unfortunately we're dealing with a computer here, which means that position 0,0 is in the top left corner of the screen, and sinus and cosinus use radians, not degrees, so the final calculation in our code ends up like this:
\d_x = \v_v * Sin( \v_a / 180 * #PI ) / desktop_framerate
\d_y = 0 - \v_v * Cos( \v_a / 180 * #PI ) / desktop_framerate
Mmm. Whilst we're at it, we'd better add some additional information to that structure, which might make our life easier later on, such as which 3D sprite number we use, if it rotates and at what speed, and let's add the size as well so we can do proper screen wrapping (ie. if thinkgs disappear on the left side they should reappear on the right side). This blows up the structure like this:
Structure object
  alive.i                            ; set to #true to enable this object
  sprite_nr.i                        ; sprite used
  sprite3d_nr.i                      ; 3d sprite used
  width.i                            ; width of sprite
  height.i                           ; height of sprite
  a.f                                ; angle ie. object orientation
  r.f                                ; rotation speed in degrees per second
  d_r.f                              ; rotation in degrees per frame
  z.f                                ; object size factor (1 = original size)
  v_v.f                              ; speed in pixels per second
  v_a.f                              ; direction of movement
  d_x.f                              ; movement in x-direction in pixels per frame
  d_y.f                              ; movement in y-direction in pixels per frame
  x.f                                ; x-coordinate
  y.f                                ; y-coordinate
EndStructure
With all the other changes our sample program has become 247 lines... But I think you can handle it by now :-)
; survival guide 7_2_300 moving objects
; pb 4.51 directx9
;
EnableExplicit
;
; *** initialize
;
InitSprite()
InitSprite3D()
InitKeyboard()
;
; make sure our gui elements, images etc. have unique numbers
;
Enumeration
  #w_main_nr                         ; main window
  ;
  #f_exit                            ; quit
  #f_none                            ; no action (keep looping)
  ;
  #i_vectoid                        ; rocket 'v' shaped image
  #i_rock                            ; rock image
  ;
EndEnumeration
;
; create a structure that will contain all information on each object (sprite)
;
Structure object
  alive.i                            ; set to #true to enable this object
  sprite_nr.i                        ; sprite used
  sprite3d_nr.i                      ; 3d sprite used
  width.i                            ; width of sprite
  height.i                           ; height of sprite
  a.f                                ; angle ie. object orientation
  r.f                                ; rotation speed in degrees per second
  d_r.f                              ; rotation in degrees per frame
  z.f                                ; object size factor (1 = original size)
  v_v.f                              ; speed in pixels per second
  v_a.f                              ; direction of movement
  d_x.f                              ; movement in x-direction in pixels per frame
  d_y.f                              ; movement in y-direction in pixels per frame
  x.f                                ; x-coordinate
  y.f                                ; y-coordinate
EndStructure
;
; create an array that can contain 100 objects
;
#objects_n = 100
Dim object.object(#objects_n)        ; so the array() is called 'object' and is of type 'object'
;
; *** get information on the desktop, open window and screen, build sprites
;
ExamineDesktops()
Global desktop_width.i = DesktopWidth(0)
Global desktop_height.i = DesktopHeight(0)
Global desktop_depth.i = DesktopDepth(0)
Global desktop_framerate.i = DesktopFrequency(0)
;
; open a maximized window the same size as the desktop and without a border
;
Global w_main_h.i = OpenWindow(#w_main_nr,0,0,desktop_width,desktop_height,"Sprite Engine",#PB_Window_BorderLess|#PB_Window_ScreenCentered|#PB_Window_Maximize)
;
; open a screen the same size as the window (ie. covers the whole desktop)
;
OpenWindowedScreen(w_main_h.i,0,0,desktop_width,desktop_height,0,0,0,#PB_Screen_SmartSynchronization)
; OpenScreen(desktop_width,desktop_height,32,"Sprite Engine",#PB_Screen_SmartSynchronization)
;
; create the first image and sprite (i've given them the same number)
;
Global i_vectoid_h = CreateImage(#i_vectoid,64,64,32)
StartDrawing(ImageOutput(#i_vectoid))
  Box(0,0,63,63,RGB(0,0,0))
  FrontColor(RGB(0,255,0))
  LineXY(4,60,32,4)
  LineXY(32,4,60,60)
  LineXY(60,60,32,16)
  LineXY(32,16,4,60)
StopDrawing()
CreateSprite(#i_vectoid,64,64,#PB_Sprite_Texture)
StartDrawing(SpriteOutput(#i_vectoid))
  DrawImage(i_vectoid_h,0,0)
StopDrawing()
;
; create the second image and sprite (again with the same number)
;
Global i_rock_h = CreateImage(#i_rock,64,64,32)
StartDrawing(ImageOutput(#i_rock))
  Box(0,0,63,63,RGB(0,0,0))
  FrontColor(RGB(255,255,255))
  LineXY(4,60,32,4)
  LineXY(32,4,60,60)
  LineXY(60,60,38,16)
  LineXY(38,16,54,6)
  LineXY(54,6,43,34)
  LineXY(43,34,60,60)
  LineXY(60,60,10,35)
  LineXY(10,35,4,60)
StopDrawing()
CreateSprite(#i_rock,64,64,#PB_Sprite_Texture)
StartDrawing(SpriteOutput(#i_rock))
  DrawImage(i_rock_h,0,0)
StopDrawing()
;
; create four objects, first the player, then three rocks
;
; the player is object number 0
;
With object(0)
  \alive = #True                  ; yes, it's alive
  \sprite_nr = #i_vectoid         ; wich 2d sprite do we use
  \sprite3d_nr = 0                ; which 3d sprite will we use
  \a = 0                          ; direction it faces
  \x = desktop_width / 2          ; in the middle of the screen
  \y = desktop_height / 2         ; in the middle of the screen
  \z = 1                          ; normal size
  \v_v = 120                      ; move at 60 pixels per second
  \v_a = 30                      ; in 30 degrees (left up)
  \r = 0
EndWith
;
; three rocks, objects 1 to 3
;
Global n.i
For n = 1 To 3                    ; change to create more rocks
  With object(n)
    \alive = #True                ; yes, it's alive
    \sprite_nr = #i_rock          ; wich 2d sprite do we use
    \sprite3d_nr = n              ; which 3d sprite will we use
    \a = 0                        ; direction it faces
    \x = desktop_width / 4*n      ; spread over the screen
    \y = desktop_height * 0.75    ; below the player object
    \z = 1                        ; normal size
    \v_v = Random(200)            ; random speed
    \v_a = Random(360)            ; random direction
    \r = Random(360)-Random(360)  ; rotation speed in degrees per second
  EndWith
Next n
;
; check starting orientation and speeds for all objects, create the 3d sprites, and fill in some fields
;
For n = 0 To #objects_n
  With object(n)
    If \alive = #True
      ;
      ; here we calculate the movement per frame for each object
      ;
      \d_x = \v_v * Sin( \v_a / 180 * #PI ) / desktop_framerate
      \d_y = 0 - \v_v * Cos( \v_a / 180 * #PI ) / desktop_framerate
      ;
      ; it's rotation speed per frame
      ;
      \d_r = \r / desktop_framerate
      ;
      ; fill in the size
      ;
      \width = SpriteWidth( \sprite_nr )
      \height = SpriteHeight( \sprite_nr )
      ;
      ; and then create the 3d version and make sure it points in the right direction
      ;
      CreateSprite3D( \sprite3d_nr , \sprite_nr )
      RotateSprite3D( \sprite3d_nr , \v_a , #PB_Absolute)
      ;
    EndIf
  EndWith
Next n
;
; *** endless loop handling windows events and moving objects
;
Global action.i = #f_none
Repeat
  Global event = WindowEvent()
  Select event
  Case #PB_Event_CloseWindow
    action = #f_exit
  Case 0
    ;
    ; there were no events, so let's do our graphical stuff
    ;
    ExamineKeyboard()
    If KeyboardPushed(#PB_Key_Escape)
      action = #f_exit
    EndIf
    ;
    ; << insert here your controlling code >>
    ;
    ; process all objects
    ;
    ClearScreen(0)
    Start3D()
      For n = 0 To #objects_n
        With object(n)
          If \alive
            ;
            ; here we actually add the movement per frame to the sprites coordinates
            ; and we handle screen wraps ie. the object appears on the other side of
            ; the screen if it leaves the visible area
            ;
            ; first apply x / y speeds
            ;
            If \d_x <> 0
              \x = \x + \d_x
              If \x > desktop_width
                \x = \x - desktop_width - \width
              ElseIf \x < 0 - \width
                \x = \x + desktop_width + \width
              EndIf
            EndIf
            If \d_y <> 0
              \y = \y + \d_y
              If \y > desktop_height
                \y = \y - desktop_height - \height
              ElseIf \y < 0 - \height
                \y = \y + desktop_height + \height
              EndIf
            EndIf
            ;
            ; now rotate any rotating objects
            ;
            If \d_r <> 0
              \a = \a + \d_r
              If \a > 360
                \a = \a - 360
              ElseIf \a < 0
                \a = \a +360
              EndIf
              RotateSprite3D( \sprite3d_nr , \a , #PB_Absolute )
            EndIf
            ;
            ; and draw the actual sprite
            ;
            DisplaySprite3D(\sprite3d_nr,\x,\y)
          EndIf
        EndWith
      Next n
    Stop3D()
    ;
    ; and show things on the screen
    ;
    FlipBuffers()
    ;
  EndSelect
Until action = #f_exit
;
; smoothly close screen and window
;
CloseScreen()
CloseWindow(#w_main_nr)
The next challenge: add player control. Look in the code above for the line reading:
; << insert here your controlling code >>
It should be around line 184. Replace that line with the following code:
;
; player movement in two flavours
;
With object(0)
  ;
  ; adjust speed and angle etc. depending on pressed key
  ;
  If KeyboardPushed(#PB_Key_A)
    \r = \r - 1000 / desktop_framerate
  EndIf
  If KeyboardPushed(#PB_Key_D)
    \r = \r + 1000 / desktop_framerate
  EndIf
  If KeyboardPushed(#PB_Key_W)
    \d_x = \d_x + 10 * Sin( \a / 180 * #PI) / desktop_framerate
    \d_y = \d_y - 10 * Cos ( \a / 180 * #PI ) / desktop_framerate
  EndIf
  If KeyboardPushed(#PB_Key_S)
    \d_x = \d_x - 10 * Sin( \a / 180 * #PI) / desktop_framerate
    \d_y = \d_y + 10 * Cos ( \a / 180 * #PI ) / desktop_framerate
  EndIf
  ;
  ; move the object with a fixed speed and no inertia
  ;
  If KeyboardPushed(#PB_Key_Left)
    \d_x = 0
    \d_y = 0
    \x = \x - 500 / desktop_framerate
  EndIf
  If KeyboardPushed(#PB_Key_Right)
    \d_x = 0
    \d_y = 0
    \x = \x + 500 / desktop_framerate
  EndIf
  If KeyboardPushed(#PB_Key_Up)
    \d_x = 0
    \d_y = 0
    \y = \y - 500 / desktop_framerate
  EndIf
  If KeyboardPushed(#PB_Key_Down)
    \d_x = 0
    \d_y = 0
    \y = \y + 500 / desktop_framerate
  EndIf
  ;
  ; here's how to retrieve the new vector, although I'm not using
  ; it in this code
  ;
  \v_v = Sqr( \d_x * \d_x + \d_y * \d_y) * desktop_framerate
  \v_a = 90+ATan( \d_y / \d_x ) * 180 / #PI
  If \d_x < 0
    \v_a = \v_a + 180
  EndIf
  ;
  ; uncomment the next two lines if you want to verify my calculations above
  ; they would recalculate the new movements per frame based on the recalculated vector
  ;
  ;   \d_x = \v_v * Sin( \v_a / 180 * #PI ) / desktop_framerate
  ;   \d_y = 0 - \v_v * Cos( \v_a / 180 * #PI ) / desktop_framerate
  ;
  ; re-calculate movement / rotation per frame
  ;
  \d_r = \r / desktop_framerate
  ;
EndWith

7.5 Size frequently matters!
 

<< werkpunt - this section is under construction >>
 

We did miss some things. For example: screen resolution.... multi monitor setups...


Resolution.

We've now figured out that moving an object should be done in a constant pixels per second, and by figuring out the framerate we can now move it in pixels per frame. But there's a catch... What if our program needs to look and feel the same on every screen, not just those with a different refresh rate, but also those with a different resolution? Obviously you'd have to create graphics to match the different resolution, but you would also have to adjust the movement per frame.

So, instead of expressing the movement of our objects in pixels per second, we'd better express them in 'percentage of screen per second'.

Duh? Duh.

An example: on our 50 hz 1024 x 768 screen we want to move the object with 120 pixels per second, ie. we need to move it:

120 pixels per second at 50 hz ->
120 / 50 = 2.4 pixels per frame ->
from left to right would take 1024 / 2.4 = 426.6 frames or 8.53 seconds
If we would only look at the framerate, the same program on a 60 hz 800 x 600 screen would take:
120 pixels per second at 60 hz ->
120 / 60 = 2 pixels per frame ->
from left to right would take 800 / 2 = 400 frames or 6.66 seconds
Not good!

We could also express speed as a 'percentage of the screen size':

1024 x 768 screen at 50 hz:
120 pixels per second at 60 hz on a 1024 x 768 screen ->
120 / 1024 = 11.72% of the screen per second ->
11.72% x 1024 / 50 = 2.4 pixels per frame
from left to right would take 1024 / 2.4 = 426.6 frames or 8.53 seconds
800 x 600 screen at 60 hz:
11.72% x 800 / 60 = 1.56 pixels per frame
from left to right would take 800 / 1.56 = 512 frames or 8.53 seconds
Also, the object on screen 2 should be resized. If it's 64 pixels on the 1024 x 768 screen, it should be about 64 / 1024 x 800 = 46.9 pixels on the 800 x 600 screen.

This is why programming 'in the old days' of the C64, Atari 800 and so on was so much easier: all machines were more or less identical. The same thing applies to consoles: except for the NTSC vs. PAL issue they're all the same. It also explains why many games do not properly support widescreen, 100hz, or other variations... It's just more work for the programmers.


Multi-monitor setups.

Another interesting challenge is a multi monitor setup, where each monitor has its own refresh rate and its own screen resolution. If the window (ie. windowed screen) would be opened on the second monitor we should use that monitor's resolution and a suitable framerate...

Got a headache? Good! Now go out and write that award winning apllication or game! :-)