Simple screenless animation

Share your advanced PureBasic knowledge/code with the community.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Simple screenless animation

Post by netmaestro »

Code updated for 5.20+

For simple animation such as card games etc., quite acceptable results can be obtained in the animation of simple objects without using a double-buffered screen. The trick is to use an image as a kind of "poor man's drawing buffer", do all drawing to it, and then draw the buffer image to the window in a step analogous to FlipBuffers() if you were using a screen.
Here is a basic example:

Code: Select all

Declare.l MouseCollision()
Global xoffset.l,yoffset.l,tilex,tiley,grabbing

Enumeration
  #window
  #cardimage
  #bufferimage
  #bkgimage
EndEnumeration

CreateImage(#cardimage,70,96)
CreateImage(#bkgimage,640,480)
StartDrawing(ImageOutput(#bkgimage))
  Box(0,0,640,480,RGB(170,120,0))
StopDrawing()
CreateImage(#bufferimage,640,480)
StartDrawing(ImageOutput(#cardimage))
  Box(0,0,70,96,#White)
  DrawText(20,40,"Card",#Red,#White)
StopDrawing()

OpenWindow(#window,0,0,640,480,"Simple Screenless Animation Demo",#PB_Window_SystemMenu|#PB_Window_MinimizeGadget)

tilex = 124
tiley = 124
Quit = 0

Repeat
  ev=WindowEvent()
  If ev=#WM_LBUTTONDOWN And MouseCollision()
    xoffset=WindowMouseX(#window)-tilex
    yoffset=WindowMouseY(#window)-tiley
    grabbing = #True
  EndIf
  If ev = #WM_LBUTTONUP
    grabbing=#False
   EndIf
  If Grabbing
    tilex=WindowMouseX(#window)-xoffset
    tiley=WindowMouseY(#window)-yoffset
  EndIf
  StartDrawing(ImageOutput(#bufferimage))
    DrawImage(ImageID(#bkgimage),0,0)
    DrawImage(ImageID(#cardimage),tilex,tiley)
  StopDrawing()
  StartDrawing(WindowOutput(#window))
    DrawImage(ImageID(#bufferimage),0,0)
  StopDrawing()
  Delay(1)
Until ev=#PB_Event_CloseWindow Or Quit = 1
End

Procedure.l MouseCollision()
  If WindowMouseX(#window)>tilex And WindowMouseX(#window)<tilex+70
    If WindowMouseY(#window)>tiley And WindowMouseY(#window) < tiley+96
      ProcedureReturn #True
    Else
      ProcedureReturn #False
    EndIf
  EndIf
EndProcedure 
Last edited by netmaestro on Wed Feb 22, 2006 2:38 am, edited 6 times in total.
BERESHEIT
Straker
Enthusiast
Enthusiast
Posts: 701
Joined: Wed Apr 13, 2005 10:45 pm
Location: Idaho, USA

Post by Straker »

Nice demo. Thanks.
8)
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

Here is a 1000-particle fireworks explosion:

Code: Select all

  
Structure shrapnel
  x.f
  y.f
  u.f
  v.f
  life.f
  s.f
EndStructure

Global NewList dot.shrapnel()

CreateImage(0,640,480)
StartDrawing(ImageOutput(0))
Box(0,0,640,480,$000000)
StopDrawing()

OpenWindow(0,0,0,640,480,"Boom",#PB_Window_ScreenCentered|#PB_Window_SystemMenu)

Procedure BlowemUp(spotx,spoty)
  For g=0 To 1000
    AddElement(dot())
    angle.f=Random(359)*3.141592/180
    dot()\life=1+Random(254)
    dot()\u=Cos(angle)*Random(128)/64
    dot()\v=Sin(angle)*Random(128)/64
    dot()\v=dot()\v-1 ;this will make it go up
    dot()\x=spotx
    dot()\y=spoty
    dot()\s=Random(15)/10
  Next g
EndProcedure

  Repeat
    ev=WindowEvent()
    StartDrawing(ImageOutput(0))
      Box(0,0,640,480,#Black)
      If ListSize(dot())<600
        BlowemUp(100+Random(480),100+Random(280))
      EndIf
      ForEach dot()
        dot()\x=dot()\x+dot()\u
        dot()\y=dot()\y+dot()\v
        dot()\v=dot()\v+0.05
        If dot()\x=>(0) And dot()\x<=(639) And dot()\y=>(0) And dot()\y<=(479)
          c=dot()\life
          c=c+c<<8+c<<16
          Plot(dot()\x,dot()\y,c)
        EndIf
        dot()\life=dot()\life-1:If dot()\life<=0:DeleteElement(dot()):EndIf
      Next
    StopDrawing()
    StartDrawing(WindowOutput(0))
      DrawImage(ImageID(0),0,0)
    StopDrawing()
    Delay(10)
  Until ev=#PB_Event_CloseWindow
End

Not bad for no directx, eh? Just straight 2d drawing on a window.
Last edited by netmaestro on Sun Jan 12, 2020 7:43 pm, edited 4 times in total.
BERESHEIT
rsts
Addict
Addict
Posts: 2736
Joined: Wed Aug 24, 2005 8:39 am
Location: Southwest OH - USA

Post by rsts »

pretty nice.

now I just have to add sound and I'll be ready for the 4th of July :)

thanks for sharing these.
User avatar
Psychophanta
Always Here
Always Here
Posts: 5153
Joined: Wed Jun 11, 2003 9:33 pm
Location: Anare
Contact:

Post by Psychophanta »

Good idea and fast way for table, cards, etc. games and games like tetris...
In fact that way works faster than i'd believe 8)

By the way, for card games and programs like the previous example i would suggest to use WaitWindowEvent() in the main loop.
http://www.zeitgeistmovie.com

while (world==business) world+=mafia;
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

WaitWindowEvent() can be problematic with animation. Unexpected stops sometimes happen, with WindowEvent() and a decent delay(), cpu is not hogged. But you definitely have to use a fair bit to do the explosions one. Cards should be ok, though, shouldn't it? Mouse is always moving when you want the card to move, generating events.
Last edited by netmaestro on Wed Feb 22, 2006 3:06 am, edited 3 times in total.
BERESHEIT
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Post by Rescator »

Alternatively instead of WindowOutput() you could just use a image gadget,
and each time the "image" is changed just change/update the gadgets state to show the new image.

The main benefit is that Windows will handle the redrawing for you,
as opposed to drawing directly on the window.

On a modern machine this works surprisingly well,
and quite high framerates can actualy be achieved.
And should be more than enough for most of the simpler 2D games.
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

An image gadget is an option, for sure. So far I can't find much performance difference, but there could be benefits if one experiments a bit with both ways of doing it.
Last edited by netmaestro on Wed Feb 22, 2006 2:40 am, edited 2 times in total.
BERESHEIT
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6166
Joined: Sat May 17, 2003 11:31 am
Contact:

Post by blueznl »

Rescator wrote: Alternatively instead of WindowOutput() you could just use a image gadget
like this...

Code: Select all

; purebasic survival guide - pb3.94
; timer_1.pb - 02.01.2006 ejn (blueznl)
; http://www.xs4all.nl/~bluez/datatalk/pure1.htm
;
; - speed independent programming
; - using timer and max accuracy of 1 msec
;
w = 400
h = 400
;
w_main_nr = 1
w_main_h = OpenWindow(w_main_nr,0,0,w,h,#PB_Window_SystemMenu|#PB_Window_ScreenCentered,"graphics1")
CreateGadgetList(w_main_h)
;
bitmap1_nr.l = 1                               ; image number 1
bitmap1_h.l = CreateImage(bitmap1_nr,w,h)      ; create the image and store the handle
;
gadget1_nr.l = 1
ImageGadget(gadget1_nr,0,0,w,h,bitmap1_h)      ; create an image gadget using image1
;
#interval = 1000/50                            ; 50 hz
#wrapalert = 2147483647-3*#interval
;
Dim x(1000)
Dim y(1000)
Dim d.f(1000)
;
x.f = 5000
y.f = 5000
v.f = 30
a.f = 0
;
nmax = 1000
;
r = 255
g = 255
b = 128

Repeat
  ;
  ; getickcount_() returns an increasing number, it's a counter in millisecs
  ; and could wrap around after 50 days or so :-)
  ;
  timer = GetTickCount_()
  If timer > #wrapalert
    ;
    ; ah, the 50 day wrap around
    ; we'll wait until we've passed the danger zone, actually this is ugly
    ; and will cause a 6 * interval time hickup once every 50 days... oh well,
    ; that ain't too bad :-)
    ;
    trigger = 0-#wrapalert
    ;
  ElseIf timer > trigger+#interval*2
    ;
    ; fresh start, wrap, or behind, we'll set trigger in such a way it will cause an action next time
    ;
    trigger = timer
    ;
    Debug "behind"
    nmax = nmax-1
  ElseIf timer > trigger
    ;
    ; ah time to do something
    ;
    ; the next trigger moment is based upon the old one plus interval
    ; we don't use the timer + interval as that may be later or earlier due to other
    ; things windows is doing in fore or background
    ;
    ; trigger = timer+#interval
    ;
    a.f = a + (Random(1)-Random(1))/3
    v.f = v + (Random(1)-Random(1))/5
    x.f = x + v*Sin(a)
    y.f = y + v*Cos(a)
    d.f = d + (Random(1)-Random(1))/2
    ;
    If x < 0
      x = 0
    ElseIf x > 10000
      x = 10000
    EndIf
    If y < 0
      y = 0
    ElseIf y > 10000
      y = 10000
    EndIf
    ;
    UseImage(bitmap1_nr)
    StartDrawing(ImageOutput())
      FrontColor(255,255,255)
      DrawingMode(0)
      Box(0,0,w,h)
      DrawingMode(4)
      FrontColor(r,g,b)
      ;
      n = 0
      While n < nmax
        n = n+1
        If d(n) = 0
          d(n) = 1
          x(n) = x/10000*w
          y(n) = y/10000*w
          n = 100
        EndIf
      Wend
      For n = 1 To 100
        If d(n) > 0
          d(n) = d(n)+1
          Circle(x(n),y(n),d(n))
          If d(n) > w*1.2
            d(n) = 0
          EndIf
        EndIf
      Next n
      ;
    StopDrawing()
    ;
    SetGadgetState(gadget1_nr,bitmap1_h)
    trigger = trigger+#interval
    ;
    ;
  EndIf
  ;
  event = WindowEvent()
  ;
Until event = #PB_Event_CloseWindow Or event = 514

End
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB)
( The path to enlightenment and the PureBasic Survival Guide right here... )
Post Reply