Page 1 of 3

Smooth animation / sprite movement

Posted: Sun Aug 24, 2008 3:34 am
by blueznl
I must have overlooked it, but is there a NON-API way to get smooth animation out of PureBasic? As of yet I've been pretty much unsuccesfull :-(

Edit: Euh. Just realized. What I mean is: how can I move 2D sprites smoothly over a WINDOWED screen, without resorting to WinApi?

Edit 2: Euh... just sprites in general are driving me totally bonkers! I want to do this properly, or not at all...

Posted: Sun Aug 24, 2008 8:51 am
by byo
How about SDL or OPENGL?

Posted: Sun Aug 24, 2008 11:36 am
by djes
Try to write your own ring 0 graphic driver and program graphic registers yourself. :twisted: You can test with linux with more success, I think :P

Posted: Sun Aug 24, 2008 12:04 pm
by Kaeru Gaman
how can I move sprites smoothly over the screen
sprites move smoothly, when opening a fullscreen and using the monitor's native refreshrate.

I suppose you mean the glitch when using a WindowedScreen?
I remember there was the problem that every second a frame stays for TWO VSyncs....

Posted: Sun Aug 24, 2008 12:33 pm
by HeX0R
I guess you mean something like this example ?

I also never had any success, so another project just went to the garbageman...

Posted: Sun Aug 24, 2008 1:36 pm
by blueznl
Ah yes. On a WINDOWED screen, forgot to mention that...

Posted: Sun Aug 24, 2008 9:27 pm
by blueznl
It's getting a bit worrying that nobody reacted. And my problems seem to be getting bigger (yeah, in the p*rn industry that's a good thing, not so sure it's the same case here...)

Please run the code below. Comments in the next post...

Code: Select all

Enumeration
  #w_main
  #i_vectoid
  #i_rock
  #spr_player
  #spr_rock
  ;
  #f_exit
  #f_none
EndEnumeration
;
Structure object
  x.l
  y.l
  w.l
  h.l
  dx.l
  dy.l
  sprite_nr.l
EndStructure
;
Global NewList objects.object()
;
screen_w.l = 1280
screen_h.l = 1024
;
InitSprite()
InitKeyboard()
OpenScreen(screen_w,screen_h,16,"Test")
;
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()
;
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(#spr_player,64,64)
CreateSprite(#spr_rock,64,64)
;
StartDrawing(SpriteOutput(#spr_player))
  DrawImage(i_vectoid_h,0,0)
StopDrawing()
StartDrawing(SpriteOutput(#spr_rock))
  DrawImage(i_rock_h,0,0)
StopDrawing()
;
AddElement(objects())
With objects()
  \x = Random(screen_w)
  \y = Random(screen_h)
  \w = 64
  \h = 64
  \dx = 1+Random(5)
  \dy = 1+Random(5)
  \sprite_nr = #spr_player
EndWith
For n = 1 To 5
  AddElement(objects())
  With objects()
    \x = Random(screen_w)
    \y = Random(screen_h)
    \w = 64
    \h = 64
    \dx = -1-Random(5)
    \dy = -1-Random(5)
    \sprite_nr = #spr_rock
  EndWith
Next n
;
SetFrameRate(60)
action = #f_none
Repeat
  ExamineKeyboard()
  If KeyboardPushed(#PB_Key_Escape)
    action = #f_exit
  EndIf
  ;
  ClearScreen(0)
  ;
  For n = 0 To 5
    SelectElement(objects(),n)
    With objects()
      If \x > screen_w - \w
        \dx = -1-Random(5)
      EndIf
      If \x < 0
        \dx = 1+Random(5)
      EndIf
      If \y > screen_h - \h
        \dy = -1-Random(5)
      EndIf
      If \y < 0
        \dy = 1+Random(5)
      EndIf
      \x = \x + \dx
      \y = \y + \dy
      DisplayTransparentSprite( \sprite_nr, \x , \y )
    EndWith
  Next n
  FlipBuffers(#PB_Screen_SmartSynchronization)
Until action = #f_exit
CloseScreen()

Posted: Sun Aug 24, 2008 9:36 pm
by blueznl
Results under 4.20, full screen:


DirectX9 + SmartSynchronization - smooth but SetFrameRate has no effect, average cpu load

DirectX9 + WaitSynchronization - running at max speed and SetFrameRate does nothing, max cpu load

DirectX9 + NoSynchronization - same thing, max cpu load

DirectX7 + SmartSynchronization - little tearing but SetFrameRate works, average cpu load

DirectX7 + WaitSynchronization - smooth, no tearing, SetFrameRate works, max cpu load

DirectX7 + NoSynchronization - very little tearing, SetFrameRate works, max cpu load


So far, so good. It looks like DirectX9 is not entirely ready yet, as framerate settings don't seem to work. Now the above is all done on full screen.

Unfortunately, this eats up CPU time. Appearently FlipBuffers() isn't smart enough to give timeslices back to other tasks, and the smart option sometimes misses a frame.


Under 4.2 in a window things look a little different: all show some tearing or lost frames, except for DirectX9 together with SmartSynchronization... In other words, we could do smooth windowed animation if we stick to the default framerate... hmmm... Not entirely what I had in mind :-)

Posted: Sun Aug 24, 2008 10:41 pm
by blueznl
In fact, the more I play with it, the more I am convinced there's something broken, or at least a bit inconsistent and I for one don't understand it.

If I run the code below using SpecialFX (software) sprites, it will show tearing again when using DirectX7, but not when using DirectX9. Why would it show tearing when doing software sprites here? I'm pretty sure my hardware is fast enough to even do software sprites...

Code: Select all

Enumeration
  #w_main
  #i_vectoid
  #i_rock
  #spr_player
  #spr_rock
  ;
  #f_exit
  #f_none
EndEnumeration
;
Structure object
  x.l
  y.l
  w.l
  h.l
  dx.l
  dy.l
  sprite_nr.l
EndStructure
;
Global NewList objects.object()
;
InitSprite()
InitKeyboard()
;
; screen_w.l = 640
; screen_h.l = 480
; w_main_h = OpenWindow(#w_main,0,0,screen_w,screen_h,"Test",#PB_Window_ScreenCentered)
; OpenWindowedScreen(w_main_h,0,0,screen_w,screen_h,0,0,0)
;
screen_w.l = 1280
screen_h.l = 1024
OpenScreen(screen_w,screen_h,16,"Test")
;
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()
;
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(#spr_player,64,64,#PB_Sprite_Memory)
CreateSprite(#spr_rock,64,64,#PB_Sprite_Memory)
;
; CreateSprite(#spr_player,64,64)
; CreateSprite(#spr_rock,64,64)
;
StartDrawing(SpriteOutput(#spr_player))
  DrawImage(i_vectoid_h,0,0)
StopDrawing()
StartDrawing(SpriteOutput(#spr_rock))
  DrawImage(i_rock_h,0,0)
StopDrawing()
;
AddElement(objects())
With objects()
  \x = Random(screen_w)
  \y = Random(screen_h)
  \w = 64
  \h = 64
  \dx = 1+Random(5)
  \dy = 1+Random(5)
  \sprite_nr = #spr_player
EndWith
For n = 1 To 5
  AddElement(objects())
  With objects()
    \x = Random(screen_w)
    \y = Random(screen_h)
    \w = 64
    \h = 64
    \dx = -1-Random(5)
    \dy = -1-Random(5)
    \sprite_nr = #spr_rock
  EndWith
Next n
;
SetFrameRate(60)
;
action = #f_none
Repeat
  ExamineKeyboard()
  If KeyboardPushed(#PB_Key_Escape)
    action = #f_exit
  EndIf
  ;
  ;
  StartSpecialFX()
  ;
  ClearScreen(0)
  For n = 0 To 5
    SelectElement(objects(),n)
    With objects()
      If \x > screen_w - \w
        \dx = -1-Random(5)
      EndIf
      If \x < 0
        \dx = 1+Random(5)
      EndIf
      If \y > screen_h - \h
        \dy = -1-Random(5)
      EndIf
      If \y < 0
        \dy = 1+Random(5)
      EndIf
      \x = \x + \dx
      \y = \y + \dy
      ;
      ; DisplaySprite( \sprite_nr, \x , \y )
      ;
      DisplayTranslucentSprite( \sprite_nr, \x, \y, 128)
    EndWith
  Next n
  ;
  StopSpecialFX()
  ;  
  FlipBuffers(#PB_Screen_SmartSynchronization)
  ;
Until action = #f_exit
CloseScreen()

Posted: Mon Aug 25, 2008 8:36 pm
by Rings
this is my testcode, testing all modes in one source.
pressing space to skip a test.

Code: Select all

Enumeration
  #w_main
  #i_vectoid
  #i_rock
  #spr_player
  #spr_rock
  ;
  #f_exit
  #f_none
EndEnumeration
;
Structure object
  x.l
  y.l
  w.l
  h.l
  dx.l
  dy.l
  sprite_nr.l
EndStructure
;
Global NewList objects.object()
;
screen_w.l = 1280
screen_h.l = 1024
;
InitSprite()
InitKeyboard()
OpenScreen(screen_w,screen_h,16,"Test")
;
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()
;
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(#spr_player,64,64)
CreateSprite(#spr_rock,64,64)
;
StartDrawing(SpriteOutput(#spr_player))
  DrawImage(i_vectoid_h,0,0)
StopDrawing()
StartDrawing(SpriteOutput(#spr_rock))
  DrawImage(i_rock_h,0,0)
StopDrawing()
;
AddElement(objects())
With objects()
  \x = Random(screen_w)
  \y = Random(screen_h)
  \w = 64
  \h = 64
  \dx = 1+Random(5)
  \dy = 1+Random(5)
  \sprite_nr = #spr_player
EndWith
For n = 1 To 5
  AddElement(objects())
  With objects()
    \x = Random(screen_w)
    \y = Random(screen_h)
    \w = 64
    \h = 64
    \dx = -1-Random(5)
    \dy = -1-Random(5)
    \sprite_nr = #spr_rock
  EndWith
Next n
;
SetFrameRate(60)
action = #f_none

s1=Second(Date())

Repeat
s2=Second(Date())
If s2<>S1
 s3+1
 If s3=10
  syncmode+1 
  s3=0
 EndIf
 s1=s2
EndIf 
  ExamineKeyboard()
  If KeyboardPushed(#PB_Key_Escape)
    action = #f_exit
  EndIf
  If KeyboardPushed(#PB_Key_Space)
    syncmode+1 
    Delay(300)
  EndIf
  
  If syncmode>3
     StartSpecialFX() 
  EndIf   
  ClearScreen(0)
  ;
  For n = 0 To 5
    SelectElement(objects(),n)
    With objects()
      If \x > screen_w - \w
        \dx = -1-Random(5)
      EndIf
      If \x < 0
        \dx = 1+Random(5)
      EndIf
      If \y > screen_h - \h
        \dy = -1-Random(5)
      EndIf
      If \y < 0
        \dy = 1+Random(5)
      EndIf
      \x = \x + \dx
      \y = \y + \dy
      If syncmode<4
       DisplayTransparentSprite( \sprite_nr, \x , \y )
      Else
       
       DisplayTranslucentSprite( \sprite_nr, \x, \y, 128) 
      EndIf
    EndWith
  Next n
  If syncmode>3
     StopSpecialFX() 
  EndIf   
  
  StartDrawing(ScreenOutput())
  DrawText(1,1,Str(syncmode))
  CompilerIf Subsystem("OpenGL")
    DrawText(30,1,"OpenGL Subsystem")
  CompilerEndIf
  CompilerIf Subsystem("DirectX9")
    DrawText(30,1,"DirectX9 Subsystem")
  CompilerEndIf
  CompilerIf Subsystem("DirectX7")
   DrawText(30,1,"DirectX7 Subsystem")
  CompilerEndIf
  CompilerIf Subsystem("NT4")
   DrawText(30,1,"NT4 Subsystem")
  CompilerEndIf

  Select syncmode
   Case 0,4
    DrawText(1,20,"FlipBuffers()")
   Case 1,5
    DrawText(1,20,"FlipBuffers( #PB_Screen_NoSynchronization )")
   Case 2,6
    DrawText(1,20,"FlipBuffers(#PB_Screen_SmartSynchronization) ")
   Case 3,7
   DrawText(1,20,"FlipBuffers( #PB_Screen_WaitSynchronization)")
  EndSelect 
  If syncmode>3
    DrawText(1,40,"DisplayTranslucentSprite()")
  Else
    DrawText(1,40,"DisplayTransparentSprite()")
  EndIf
  
  StopDrawing()
  
  Select syncmode
   Case 0
    FlipBuffers()
   Case 1
    FlipBuffers( #PB_Screen_NoSynchronization )
   Case 2
    FlipBuffers(#PB_Screen_SmartSynchronization) 
   Case 3
   FlipBuffers( #PB_Screen_WaitSynchronization)
   Case 4
    FlipBuffers()
   Case 5
    FlipBuffers( #PB_Screen_NoSynchronization )
   Case 6
    FlipBuffers(#PB_Screen_SmartSynchronization) 
   Case 7
    FlipBuffers( #PB_Screen_WaitSynchronization)   
   Default
    action = #f_exit
   EndSelect
  
  ;
  
Until action = #f_exit
CloseScreen() 

Posted: Wed Aug 27, 2008 2:08 pm
by blueznl
After exhausing the little brainpower I have left, I came to a horrid conclusion: PC's stink, and Windows is even worse, and some of that smell rubs off on PureBasic.

Hehe :-)

That's the hook.

(This is long and rambling, but I think worth a thought. Written on August 27, using PureBasic 4.20, knowing perfectly well that it will be improved in the future. Maybe this little thought experiment can help finding the right path :-) I'm not saying that PureBasic is to blame! I'm just saying that if PureBasic is not to blame, we have to get smart and find a way around the current limitations.)

Now let's think. Why is it, no, why do *I* find it so hard to write a game in PureBasic? Refresh. And CPU load.

Let's look at the current DirectX 7 / 9 implementation in PB, and some general limitations of developing games on Windows machines.


1. Tear free.

If you want to avoid 'tearing' on your screen, there are three possibilities...

- you only redraw your screen during 'vertical blanks' so the user won't see the updates
- you ramp up the framerate so much the user won't notice, or
- you 'obfuscate' the tearing by interlacing, smoothing edges, and similar tactics

The most logical approach is vsync, updating the image during vertical blanks. It's also (somewhat) implied in DirectX and PB, using the FlipBuffers() command.


2. DirectX and the FlipBuffers() bug.

FLipBuffers() has a few parameters. To go tearfree, we want to wait for vertical sync, so FlipBuffers(#PB_Screen_WaitSynchronization) would be a logical choice. Unfortunately, DirectX makes a mess of it (not PB's fault) and eats up all CPU cycles until it can flip the buffer.

That's a pain. Obvioiusly, it's no good to waste expensive CPU power on something like waiting. We could be processing other messages, prepare for the next level, load images, and even do totally non-game related things in the background.

FlipBuffers() has a variant called FlipBuffers(#PB_Screen_SmartSynchronization) which is supposed to reduce CPU wait time, but unfortunately it appears to be a bit broken. It causes a little jerking and tearing under DirectX7, and doesn't wait for anything with DirectX8.

So, that's a bug in PureBasic, but it's only half the problem.


3. Frame rate vs. Refresh rate.

Refresh rate = the number of times per second your monitor shows you an image.

Frame rate = the number of times per second your program draws a new image.

You DON"T control the refresh rate on the machine you run your game. You may THINK you do, but you don't. Sometimes you can change it, but often you cannot, and there is no guarantee that the physical monitor of the user can handle your requested refresh rate.

If you tie your program to the refresh rate, your program will perform differently on different machines with different settings, ie. on some machines the program will run faster than on others, depending on the refresh rate. Not good.

So, if you tie your program to the refresh rate (which is exactly what you do when using FlipBuffers(#PB_Screen_Waitsynchronization) your program is stuck to whatever the user set as a screen refresh rate, and in this case you can assume framerate = refresh rate.


4. The fractional solution.

Assume a player object should move 'n' pixels per second, then you could deduct how many pixels it should move per frame. Let's say the framerate = refreshrate = 60 / second, then:

speedperframe = speedpersecond / 60

Your engine would thus, each frame, have to calculate the movement of your object, adjusting it's position with speedperframe distance. To avoid fractions / roundings you should store coordinates in floats then, not in ints.


5. The threaded solution.

Another approach would be to spread your logic over multple threads. (A requirement for this is that FlipBuffers() is accurate and does not steal all CPU cycles). You could put your 'graphical' engine in one thread, and your game logic in another, etc.

Ah. All starts to make sense. I need to drink more... where's the bottle? :-)

Posted: Wed Aug 27, 2008 3:08 pm
by djes
blueznl wrote: 4. The fractional solution.

Assume a player object should move 'n' pixels per second, then you could deduct how many pixels it should move per frame. Let's say the framerate = refreshrate = 60 / second, then:

speedperframe = speedpersecond / 60

Your engine would thus, each frame, have to calculate the movement of your object, adjusting it's position with speedperframe distance. To avoid fractions / roundings you should store coordinates in floats then, not in ints.
No, you don't have to do that. You just have to separate the graphical update, and the main logic. Then, you can do the main logic several times, and the graphical refresh only once.

Posted: Wed Aug 27, 2008 8:26 pm
by blueznl
Euh... I think you still have to do, what is the use of running the same calculation multiple times, if you're still tied to the vertical blank? I mean, beyond increasing the accuracy?

Posted: Wed Aug 27, 2008 9:46 pm
by djes
It's not the same calculations. The main logic is the moves/user interactions; it must happen regularly; only the display is tied to a least accurate "vbl", or a network event :)

But with your "fractional solution" :
1) you have to compute each movement based on the speedperframe, so you have to *think* during development to this computation, and its consequences : you have to run after something that happened before (because you know there was a frame drop only after), and so on;
2) you forget the user, and its actions. In a game, the more (and the most regular) a user can act, the better is the interaction. So you *need* a regular (timer based at least) main logic.
3) You have to change too much things. If you separate the display and the main logic, you don't have to change your main code. Only put the main logic in a loop, and the frame display after.

Posted: Wed Aug 27, 2008 9:49 pm
by Kaeru Gaman
I think you mean what he called "5. The threaded solution."