how to animate picture without making it flicker using wxWidgets in erlang?

1.7k views Asked by At

I am trying to make images seems like moving. I clear the screen and put the images back on it, but the screen is flickering a lot. Is there a way to make it seems like moving without flicker that much?

the "draw_asteroids" is called every 100 ms since I want the movement of the image to be as much as continuous as possible, plus i have many other elements that move on screen as well (the code is similar to the spaceship).

my code:

start->
Wx = wx:new(),
Frame = wxFrame:new(Wx, -1, "Main Game Frame", [{size, {?max_x, ?max_y}}]),    
MenuBar = wxMenuBar:new(),
wxFrame:setMenuBar(Frame, MenuBar),
Panel = wxPanel:new(Frame),
wxFrame:connect(Panel, paint),
Image = wxImage:new("asteroid.png"),
wxFrame:show(Frame),
loop(Panel, {0,0}, Image).



loop(Panel, {X,Y},  Image)->
receive
after 100 ->
{NewX,NewY} =claculateNewPosition({X,Y}),
clearScreen(Frame, Panel),
draw_asteroids(Panel, {NewX,NewY}, Image),
loop(Panel, {NewX,NewY},  Image)
end.



draw_asteroids(Panel, {X, Y}, Image) ->

Pos = {round(X), round(Y)},
ClientDC = wxClientDC:new(Panel),
Bitmap = wxBitmap:new(Image),
wxDC:drawBitmap(ClientDC, Bitmap, Pos),
wxBitmap:destroy(Bitmap),
wxClientDC:destroy(ClientDC).


clearScreen(Frame, Panel)->

NewPanel = wxPanel:new(Frame),
wxWindow:setSize(Frame, {?max_x+1, ?max_y+1}),
wxWindow:setSize(Frame, {?max_x, ?max_y}),
NewPanel.
1

There are 1 answers

5
Pascal On

I have solved this in both windows and linux doing the following:

in the init function: create a bitmap of the expected size, and a memoryDC form it:

Bitmap = wxBitmap:new(W,H),
MemoryDC = wxMemoryDC:new(Bitmap),
...

I also define an area where to put the bitmap and I connect it to some event, at least paint:

Panel = wxPanel:new(Board, [{size,{W,H}}]),
wxPanel:connect(Panel, paint, [callback]),
wxPanel:connect(Panel, left_up, []),
wxPanel:connect(Panel, right_down, []),
wxPanel:connect(Panel, middle_down, []),

I use to store the modification request in a list which will be processed later.

then I use an external event to refresh the screen, that trigger window refresh The option {eraseBackground,false} is very important to avoid flickering:

do_refresh(Cb,Z,PMa,PFe,PM,BMa,BFe,BM,P,MemoryDC) ->
    wx:batch(fun ()  ->
        Cb1 = lists:reverse(Cb),
        [cell(MemoryDC,PMa,PFe,PM,BMa,BFe,BM,State,Cell,Sex,Z) || {State,Cell,Sex} <- Cb1],
        wxWindow:refresh(P,[{eraseBackground,false}])
    end).

I use this external event for 2 reasons:

  • have a regular refresh rate that I master
  • allow many processes to update independently the bitmap (storing the request in the list)

I use an external timer which call a refresh function which collect the list of modification asked by other processes, execute them in order in the memoryDC, and then trigs the paint event using the window:refresh/2 function. The whole function is executed in a batch to improve performances.

And in the redraw event I simply "blit" the panel (note that this function can refresh a part of the panel to reduce the execution time, I didn't use this optimization at first, and the result was fast and smooth enough so I kept my initial code) :

handle_sync_event(#wx{event = #wxPaint{}}, _wxObj, #state{panel=Panel, memoryDC=MDC, w=W, h=H}) ->
    redraw(Panel,MDC,W,H).

redraw(Panel, MemoryDC, W, H) ->
    DC = wxPaintDC:new(Panel),  
    wxDC:blit(DC,{0,0},{W,H},MemoryDC,{0,0}),
    wxPaintDC:destroy(DC).

It seems to be required to create and destroy the PaintDC.

This code do not manage the resize of the window (no sense in my application), but it should be easy to extend.