Smooth animation / sprite movement

Advanced game related topics
User avatar
djes
Addict
Addict
Posts: 1806
Joined: Sat Feb 19, 2005 2:46 pm
Location: Pas-de-Calais, France

Post by djes »

I just don't like threads ;)
Synchronize a graphical display and a game logic running at different intervals is (as far as I know) almost impossible!
User avatar
Kaeru Gaman
Addict
Addict
Posts: 4826
Joined: Sun Mar 19, 2006 1:57 pm
Location: Germany

Post by Kaeru Gaman »

hu? how do you mean that?

anyhow, if you want the game logic well timed, you'll have to depart it from the graphical display, and threads are best for this.
just keep the graphics in the Mainthread, that means, in your mainprogram, not an extra-thread.
oh... and have a nice day.
User avatar
djes
Addict
Addict
Posts: 1806
Joined: Sat Feb 19, 2005 2:46 pm
Location: Pas-de-Calais, France

Post by djes »

I think it's simpler to keep everything in the main program to have accurate values to display. If your graphic display occurs when you're computing, let's say, a collision check, you'll display something the user don't want to see. Or you can try to display only finished operations, but then, you'll be never synced, and you'll have to copy every positions to have the "in calculation" ones, and the final ones.
User avatar
Kaeru Gaman
Addict
Addict
Posts: 4826
Joined: Sun Mar 19, 2006 1:57 pm
Location: Germany

Post by Kaeru Gaman »

gray is every theory...

are you aware that depending on what you calculate you will sometimes need double memory?
e.g. something like Conways GameOfLife...

when you don't need doublemem for calculation, you will normally not need it for display.
oh... and have a nice day.
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6172
Joined: Sat May 17, 2003 11:31 am
Contact:

Post by blueznl »

Fractional frame based. The one thing missing is detection of the (natural) framerate. But it shows the idea... Shouldn't be too hard to turn it into a full fledged asteroids...

Edit: found how to check for the natural framerate. Changed the code a little and updated it here.

Code: Select all

Enumeration
  #w_main
  ;
  #f_exit
  #f_none
  ;
  #i_arrow                           ; image used for player
  ;
EndEnumeration
;
Global i_arrow_h.l
;
Structure spr
  alive.l                            ; set to #false to disable this sprite
  sprite2d_nr.l                      ; 2d sprite used
  sprite3d_nr.l                      ; 3d sprite used
  a.f                                ; object orientation
  rotation.f                         ; rotation speed in degrees / second, does not affect vector
  z.f                                ; object size factor (1 = original size)
  x.f                                ; x-coordinate
  y.f                                ; y-coordinate
  w.l                                ; width in pixels
  h.l                                ; height in pixels
  v_angle.f                          ; vector angle in degrees
  v_speed.f                          ; speed in pixels per second
  v_mode.l                           ; #true to use vector, #false to use x / y components
  v_x.f                              ; horizontal speed in pixels per second
  v_y.f                              ; same vertical
EndStructure
;
InitSprite()
InitSprite3D()
InitKeyboard()
;
ExamineDesktops()
framerate = DesktopFrequency(0)
framelength.f = 1 / framerate        ; time per frame in seconds
; Debug framerate
;
screen_w.l = 1280                    ; screen width
screen_h.l = 1024                    ; screen height
screen_z.l = 32                      ; color depth
OpenScreen(screen_w,screen_h,screen_z,"SpriteEngine")
SetFrameRate(framerate)
;
; sorry, no luck with windowed screens yet...
;
;   screen_w.l = 800
;   screen_h.l = 600
;   screen_z.l = 32 
;   w_main_h.l = OpenWindow(#w_main,10,10,screen_w,screen_h,"SpriteEngine",#PB_Window_ScreenCentered)
;   OpenWindowedScreen(w_main_h,0,0,screen_w,screen_h,0,0,0)
;   SetFrameRate(framerate)
;
Sprite3DQuality(0)
;
; player sprite
;
#spr_player = 0
;
i_arrow_h = CreateImage(#i_arrow,64,64,32)
StartDrawing(ImageOutput(#i_arrow))
  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(#spr_player,64,64,#PB_Sprite_Texture)
StartDrawing(SpriteOutput(#spr_player))
  DrawImage(i_arrow_h,0,0)
StopDrawing()
CreateSprite3D(#spr_player,#spr_player)
;
#spr_max = 10
Dim spr.spr(#spr_max)
;
With spr(0)
  \alive = #True
  \sprite2d_nr = #spr_player
  \sprite3d_nr = #spr_player
  \a = 0
  \rotation = 0
  \z = 1
  \x = screen_w / 2
  \y = screen_h / 2
  \w = SpriteWidth( \sprite2d_nr)
  \h = SpriteHeight( \sprite2d_nr)
  \v_angle = 0
  \v_speed = 0
  \v_mode.l = #False
  \v_x.f = 0
  \v_y.f = 0
EndWith
;
spr_number.l = 1                      ; number of sprites to process
;
action = #f_none
Repeat
  ;
  ; calculate movement etc.
  ;
  n = 0
  While n < spr_number
    With spr(n)
      If \alive
        \a = \a + \rotation
        RotateSprite3D( \sprite3d_nr , \a , 0)
        If \v_mode
          \v_x = \v_speed * Sin( \v_angle / 180 * #PI )
          \v_y = 0 - \v_speed * Cos( \v_angle / 180 * #PI )
        EndIf
        \x = \x + \v_x * framelength
        \y = \y + \v_y * framelength
        ;
        ; wrap outside screen borders
        ;
        If \x > screen_w + \w
          \x = 0 - \w
        ElseIf \x < 0 - \w
          \x = screen_w + \w
        EndIf
        If \y > screen_h + \h
          \y = 0 - \h
        ElseIf \y < 0 - \h
          \y = screen_h + \h
        EndIf
        ;
      EndIf
    EndWith
    n = n+1
  Wend
  ;
  ; display all active sprites
  ;
  ClearScreen(0)
  Start3D()
  n = 0
  While n < spr_number
    With spr(n)
      If \alive
        DisplaySprite3D( \sprite3d_nr , \x , \y , 255)
      EndIf
    EndWith
    n = n+1
  Wend
  Stop3D()
  ;
  FlipBuffers(#PB_Screen_WaitSynchronization)
  ;
  ExamineKeyboard()
  If KeyboardPushed(#PB_Key_Escape)
    action = #f_exit
  EndIf
  With spr(0)
    ;
    ; move the player object using A / W / S / D / UP / DN / LFT / RGHT
    ;
    If KeyboardPushed(#PB_Key_Left)
      \rotation = \rotation - 10 * framelength         ; with inertia
      ; \a = \a - 300 * framelength                    ; without inertia
    ElseIf KeyboardPushed(#PB_Key_Right)
      \rotation = \rotation + 10 * framelength
      ; \a = \a + 300 * framelength
    ElseIf KeyboardPushed(#PB_Key_Up)
      \v_x = \v_x + 500 * Sin( \a / 180 * #PI ) * framelength
      \v_y = \v_y - 500 * Cos( \a / 180 * #PI ) * framelength
    ElseIf KeyboardPushed(#PB_Key_Down)
      \v_x = \v_x - 100 * Sin( \a / 180 * #PI ) * framelength
      \v_y = \v_y + 100 * Cos( \a / 180 * #PI ) * framelength
    ElseIf KeyboardPushed(#PB_Key_A)
      ; \x = \x - 100 * framelength                    ; without inertia
      \v_x = \v_x - 100 * framelength                  ; with inertia
    ElseIf KeyboardPushed(#PB_Key_D)
      ; \x = \x + 100 * framelength
      \v_x = \v_x + 100 * framelength
    ElseIf KeyboardPushed(#PB_Key_W)
      ; \y = \y - 100 * framelength
      \v_y = \v_y - 100 * framelength
    ElseIf KeyboardPushed(#PB_Key_S)
      ; \y = \y + 100 * framelength
      \v_y = \v_y + 100 * framelength
    EndIf
  EndWith
  ;
Until action = #f_exit
CloseScreen()
Last edited by blueznl on Thu Aug 28, 2008 9:34 pm, edited 1 time in total.
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB - upgrade incoming...)
( The path to enlightenment and the PureBasic Survival Guide right here... )
User avatar
djes
Addict
Addict
Posts: 1806
Joined: Sat Feb 19, 2005 2:46 pm
Location: Pas-de-Calais, France

Post by djes »

blueznl wrote:The one thing missing is detection of the (natural) framerate.
It's exactly why it can't work without a major headache... If you just have a linear move, and a constant frame rate, it's not difficult to obtain the right fractional step : if you have 3 steps just multiply by 3. But for others movements, user based, or following non linear curves, how do you obtain the new (and right!) position by calculus?
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6172
Joined: Sat May 17, 2003 11:31 am
Contact:

Post by blueznl »

... by thinking differently, and IGNORING the actual framerate.

Give every object a speed and an angle, or a horizontal and a vertical speed, for example in pixels per second.

Then add a fraction of the moved distance every frame to the position of the object.

That's what I do in the code above. Change the FlipBuffers() parameter above to 'no wait' and play around with the framerate, the objects should still move in the same speed, regardless of the framerate you set.

For everyframe:

newx = oldx + distancetomovepersocond / framespersecond
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB - upgrade incoming...)
( The path to enlightenment and the PureBasic Survival Guide right here... )
User avatar
djes
Addict
Addict
Posts: 1806
Joined: Sat Feb 19, 2005 2:46 pm
Location: Pas-de-Calais, France

Post by djes »

Do am I the only one to see that if you have a variable frame rate, if the first time the user press a key he's moving by two pixels, and the second time he's moving by four pixels, it will not make sense to him? When you press one time a key, you expect to have the very same effect, whatever is the frame rate!
Maybe I'm wrong. I've tried to modify your code to make a ball bouncing with varying frame rate to demonstrate how difficult it could be. But really it is! :lol:
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6172
Joined: Sat May 17, 2003 11:31 am
Contact:

Post by blueznl »

:-)

The code actually is not using a variable framerate :-)

It's 'fractional' ie. IF it would be using another framerate, the effect of pressing a key would be different.

But framerates do not change on a machine (except when you miss a frame because your processing takes too much time) when you tie in your code to the vertical sync with FlipBuffers() :-)
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB - upgrade incoming...)
( The path to enlightenment and the PureBasic Survival Guide right here... )
User avatar
djes
Addict
Addict
Posts: 1806
Joined: Sat Feb 19, 2005 2:46 pm
Location: Pas-de-Calais, France

Post by djes »

I was thinking it was the kind of thing you were trying to avoid. :)
So if the frame rate is defined at the start, and constant, that's OK. Sorry to have taken your time...
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6172
Joined: Sat May 17, 2003 11:31 am
Contact:

Post by blueznl »

No, it's not what I want to avoid :-)

Actually, I'm just trying to find two solutiosn:

1. how to eat up less CPU time with FlipBuffers() (which inherently would also allow multi threading / task seperation)

and

2. how to get smooth anymation in a window

Anything else is a report of all my stumbling during the journey :-)
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB - upgrade incoming...)
( The path to enlightenment and the PureBasic Survival Guide right here... )
User avatar
Kaeru Gaman
Addict
Addict
Posts: 4826
Joined: Sun Mar 19, 2006 1:57 pm
Location: Germany

Post by Kaeru Gaman »

blueznl wrote: how to eat up less CPU time with FlipBuffers()
remember my timer example?
you can change it to a fixed Framerate of 30, using SetFramerate() and FlipBuffers with waiting,
and it will spend most waiting time in the Delay and only few ms in the FlipBuffers.
oh... and have a nice day.
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6172
Joined: Sat May 17, 2003 11:31 am
Contact:

Post by blueznl »

Kaeru, what sample?

Oh, by the way, I updated the code above a little (to detect the framerate). The next challenge is properly detect if a user Alt+Tab's out of the game. Anyone a good suggestion?
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB - upgrade incoming...)
( The path to enlightenment and the PureBasic Survival Guide right here... )
User avatar
Kaeru Gaman
Addict
Addict
Posts: 4826
Joined: Sun Mar 19, 2006 1:57 pm
Location: Germany

Post by Kaeru Gaman »

the sample in the other topic where you asked if it was smooth....

I altered it with SetFrameRate, and nor it is NOT smooth any more, strange.
seems it needs some correction...
but at least, this is an example how to theoretically time a fixed FPS without 100%CPU...

btw:
I wrote the old example down because this kind of timing with native PB commands
is alike the timing described in this article: http://www.geisswerks.com/ryan/FAQS/timing.html

Code: Select all

; *********
; Demo for Timing on 30 FPS

EnableExplicit

Define.l FPS, Timer, TimerDuration, TimeGap
Define.l n, t, col
Define.l PX, PY
Define.l EXIT

InitSprite()
InitKeyboard()
OpenScreen(1024,768,32,"TimerDemo")

; ** create a sprite
CreateSprite(0,128,128)
StartDrawing(SpriteOutput(0))
  For n=0 To 127
    col = RGB(64-n/2,128-n,255-n)
    LineXY( 63-n, 63  , 63  , 63-n, col)
    LineXY( 64+n, 64  , 63  , 63-n, col)
    LineXY( 63-n, 63  , 64  , 64+n, col)
    LineXY( 64+n, 64  , 64  , 64+n, col)
  Next
StopDrawing()

FPS = 30
TimerDuration = 1000 / FPS
SetFrameRate(FPS)

PX = 512-64
PY = 384-64

Repeat

  ; ** buffer timer of loop start
  timer = ElapsedMilliseconds()
  ; **

  ExamineKeyboard()
  If KeyboardPushed(#PB_Key_Escape)
    EXIT = 1
  EndIf
  If KeyboardPushed(#PB_Key_Left)
    PX - 4
  EndIf
  If KeyboardPushed(#PB_Key_Right)
    PX + 4
  EndIf
  If KeyboardPushed(#PB_Key_Up)
    PY - 4
  EndIf
  If KeyboardPushed(#PB_Key_Down)
    PY + 4
  EndIf

  ClearScreen($201008)

  DisplaySprite(0,PX,PY)

  ; ** calculate difference of wanted time ( TimerDuration )
  ; ** and really elapsed time
  TimeGap = TimerDuration - (ElapsedMilliseconds() - Timer)
  ; ** if rest is bigger than 16ms, we wait with delay
  If TimeGap > 16
   ; ** for 10ms less
    Delay( TimeGap - 10 )
  EndIf
  ; ** the rest will wait FlipBuffers
  FlipBuffers(#PB_Screen_WaitSynchronization)
Until EXIT = 1
oh... and have a nice day.
User avatar
Demivec
Addict
Addict
Posts: 4282
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Post by Demivec »

Kaeru Gaman wrote:the sample in the other topic where you asked if it was smooth....

I altered it with SetFrameRate, and nor it is NOT smooth any more, strange.
seems it needs some correction...
but at least, this is an example how to theoretically time a fixed FPS without 100%CPU...
@Kaeru: I noticed that SetFrameRate seems to be more useful for windowed screens rather than a fullscreen. I commented out the SetFramerate, the timer section and changed FlipBuffers(#PB_Screen_WaitSynchronization) to FlipBuffers(). It works much smoother.

It's strange because I thought that was the purpose of all of those things in the first place. Why would it run better without them? Maybe it only happens with certain display frequencies.

Thanks for the link to the timing methods. I was looking for some improvements using API and can see that you adapted them well to PureBasic, though they don't seem to be performing as they were intended.

Being able to display and animate a screen by making display updates during the vsync or vertical blank seems like such a basic element. I'm surprised this isn't a working part of PureBasic yet. I am currently trying to do it through API, mainly Direct3D or something similar.
Post Reply