• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
0

Simple animation using doublebufferd canvas is not smooth

Guest
Sep 07, 2012 Sep 07, 2012

Copy link to clipboard

Copied

Hi out there,

I try to develop a simple flash game using a doublebuffer to draw the animations.

The redraw happens when Event.ENTER_FRAME is fired.

The backbuffer used for doublebuffering is of type BitmapData.

The animation is very simple: I draw a 20x20-pixel Bitmap into the backbuffer with increasing x coordinate, so that it should move smoothly from the left to the right side of my canvas. This basically works fine, but if you look closely, you will see significant disruptions in this movement. This does not seem to be related to the frame rate, since its constantly over 60. The disruptions of the smooth moving are not acceptable for an animations, but I'm quite sure I've done nothing wrong with the doublebuffering, so I'm scared this could be a flash-player problem or something... I would be very relieved if this were not the case

Please have a look at the swf showing the simple animation:

https://dl.dropbox.com/u/55967135/test.swf

(Btw. the disruptions of the animation also don't disappear when the movement is based on the time between two frames - by now its constant 2 pixels per frame.)

I've uploaded a very lightweight flash builder project including the whole sourcecode for the swf above:

https://dl.dropbox.com/u/55967135/test.zip

This is the enter frame function, that draws the moving small image (20x20-pixel PNG):

        public function enterFrame():void               
          {
               
               // Calculate the time since the last frame  (NOT USED IN THE EXAMPL PROGRAM)                   
               var thisFrame : Date = new Date();               
               var dT : Number = (thisFrame.getTime() - lastFrame.getTime())/1000.0;               
               lastFrame = thisFrame;          
               
               // erase backBuffer
               backBuffer.fillRect(backBuffer.rect, 0xFFFFFFFF);
               
               
               // set new postion of the small testimage
               if (this.pos > 600 || this.pos < 0) {
                    this.direction = !this.direction;
               }
               
               // increase / decrease vertical position
               if (this.direction) {
                    this.pos += 2;
               } else {
                    this.pos -= 2;
               }
               
               //trace(pos);
               // draw small test image at postion "offset"
               backBuffer.copyPixels(     this.testGraphic.bitmap.bitmapData, 
                                             this.testGraphic.bitmap.bitmapData.rect, 
                                             new Point(pos, 0.0));               
          }

The enterFrame() is a method of my class GraphicsController, which handles the doublebuffering. It is launched by the applications enterFrame(event) method:

            public function enterFrame(event:Event):void
               {
                    GraphicsController.Instance.enterFrame();     
                    myCanvas.graphics.clear();
                    myCanvas.graphics.beginBitmapFill(GraphicsController.Instance.backBuffer, null, false, false);                              
                    myCanvas.graphics.drawRect(0, 0, this.width, this.height);                              
                    myCanvas.graphics.endFill();

               }     

Help would be GREATLY appreciated

Thank you

Gerd

TOPICS
ActionScript

Views

2.0K

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 07, 2012 Sep 07, 2012

Copy link to clipboard

Copied

i don't use flash builder so there could be a lot lost translating from fb to flash pro but the first problem i see in your code is the repeated use of new Data:

don't use new Date() as a timer.  it's 6 times slower than getTimer().

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guest
Sep 08, 2012 Sep 08, 2012

Copy link to clipboard

Copied

Hi kglad,

you're obviously right in the point that the "new Date()" calls each frame are redundant.

Unfortunately the use of "getTimer()" doen't fix the problem.

It seems to me that it's not the framerate but the backbuffer isn't actually drawn on each redraw event.

Any further suggestions?

Regards Gerd

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 08, 2012 Sep 08, 2012

Copy link to clipboard

Copied

pos should be an int, not a Number

this.testGraphic.bitmap.bitmapData.rect should be replaced by a variable that stores that rectangle reference

new Point() should be replaced by a variable whose x property is updated in your enterFrame function

and, you can increase your frame rate to improve smoothness of motion.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guest
Sep 08, 2012 Sep 08, 2012

Copy link to clipboard

Copied

Hi,

I have implemented all the suggestions and updated my uploaded files:

https://dl.dropbox.com/u/55967135/test.swf

https://dl.dropbox.com/u/55967135/test.zip

I don't think that performance is the problem, since the frame rate doesn't decrease when the interuptions appear.

It seems to me that the updated image is not displayed every frame.

Could it be a flash player inherent problem ? (the disruptions are only every 5-10 seconds but clearly disturbing)

This is the relevant code (also contained in the provided zip file).

Thanks for further suggestions.

test.mxml:

framerate set to 100, "holder" is an mx:UIComponent.

                    
          
     import test.GraphicsController;
          import test.ResourceManager;
          
          private var frontBuffer : Bitmap;

          public function enterFrame(event:Event):void
          {
               GraphicsController.Instance.enterFrame();     
          }                                   
          
          public function init():void
          {     
               GraphicsController.Instance.startup(myCanvas.width, myCanvas.height);
               frontBuffer = new Bitmap(GraphicsController.Instance.backBuffer);     
               holder.addChild(frontBuffer);

               addEventListener(Event.ENTER_FRAME, enterFrame);
          }

GraphicsController.as

package test
{
     import flash.display.*;
     import flash.events.*;
     import flash.geom.Point;
     import flash.geom.Rectangle;
     
     import mx.collections.*;
     import mx.core.*;
     
     import spark.primitives.Rect;
     
     public class GraphicsController          
     {
          // double buffer
          public var backBuffer:BitmapData;          
          
          // colour to use to clear backbuffer with          
          public var clearColor:uint = 0xFFFFFFFF;
          
          // static instance
          protected static var instance:GraphicsController = null;
          
          private var testGraphic : GraphicsResource = ResourceManager.testGraphic;
          private var direction : Boolean = true;
          private var pos : Point = new Point(0, 0);
          private var rect : Rectangle;
          private var bitmapData : BitmapData;
          
          
          static public function get Instance():GraphicsController
          {
               if(instance == null)
               {
                    instance = new GraphicsController();
               }
               return instance;
          }
          
          
          public function GraphicsController()
          {
               if(instance != null)
               {
                    throw new Error("Only one instance allowed.");
               }
          }
          
          public function startup(width : Number, height : Number):void          
          {               
               backBuffer = new BitmapData(width,height);     
               rect = this.testGraphic.bitmap.bitmapData.rect;
               bitmapData = this.testGraphic.bitmap.bitmapData;
          }

          public function enterFrame():void               
          {
               this.backBuffer.lock();

               // erase backBuffer
               this.backBuffer.fillRect(backBuffer.rect, 0xFFFFFFFF);
               
               // set new postion of the small testimage
               if (this.pos.x > 600 || this.pos.x < 0) {
                    this.direction = !this.direction;
               }
               
               // increase / decrease vertical position
               if (this.direction) {
                    this.pos.x += 2;
               } else {
                    this.pos.x -= 2;
               }
               
               // draw small test image at postion "offset"
               this.backBuffer.copyPixels(     this.bitmapData, 
                                                  this.rect, 
                                                  this.pos);               
               this.backBuffer.unlock();
          }
          
          
     }
}

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 08, 2012 Sep 08, 2012

Copy link to clipboard

Copied

performance is the issue.  just because your frame rate remains steady doesn't mean there will be no "hiccups" in your animation.

almost certainly you are measuring an average frame rate.  so, if you find your animation is a steady 24 fps, it's highly unlikely that your display is updated every 1000/24 ms. 

most likely you're getting an update between every 20 and 80 ms (and, it's possible because of your coding, you have an even wider range of display updates).

to test, create a startTime int and in your enterFrame function use:

trace(getTimer()-startTime);

startTime=getTimer();

run that code until you see a hiccup and then let me know what update range you saw.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guest
Sep 08, 2012 Sep 08, 2012

Copy link to clipboard

Copied

Hi,

first of all, thanks for you further interest on my problem...

I now calculate the framerate per frame (not averaged over one second) using

          public function enterFrame():void               
          {
               // Calculate the time since the last frame                         
               var thisFrame : Number = Number(getTimer());               
               var dT : Number = (thisFrame - lastFrame)/1000.0;               
               lastFrame = thisFrame;          
               trace(1.0/dT);
                        ....

I interrupted the trace output directly after these hicups occured. These are the fps values measured per frame:

https://dl.dropbox.com/u/55967135/fps.txt

The framereate was set to 1000 to allow fastest rendering. Although the framerate varies (as you said) down to ~30, this cannot explain these hicups in my opinion, since an fps of above 24 should be able to display continuous animation. Thanks again for your time.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 08, 2012 Sep 08, 2012

Copy link to clipboard

Copied

i'm not sure what to make of those numbers because i'm not familiar with fb. 

trying to duplicate your setup shows the frame rate drops below 6 when the animation stutters.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guest
Sep 08, 2012 Sep 08, 2012

Copy link to clipboard

Copied

"trying to duplicate your setup shows the frame rate drops below 6 when the animation stutters."

how do you duplicate it? How can I reprocuce your result?

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 08, 2012 Sep 08, 2012

Copy link to clipboard

Copied

i used the following in my document class (Main):

package {

    import flash.display.MovieClip;

    import test.GraphicsController;

    import flash.events.Event;

    public class Main extends MovieClip {

        var controller:GraphicsController;

        private var myCanvas:MovieClip;

        public function Main() {

            controller=GraphicsController.Instance;

            controller.startup(stage.stageWidth,stage.stageHeight,this);

            myCanvas = this;

            this.addEventListener(Event.ENTER_FRAME,enterFrame);

        }

        private function enterFrame(event:Event):void {

            GraphicsController.Instance.enterFrame();

            myCanvas.graphics.clear();

            myCanvas.graphics.beginBitmapFill(GraphicsController.Instance.backBuffer, null, false, false);

            myCanvas.graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight);

            myCanvas.graphics.endFill();

        }

    }

}

and this in GraphicsController:

package test

{

    import flash.display.*;

    import flash.events.*;

    import flash.geom.Point;

    import flash.geom.Rectangle;

    import flash.utils.getTimer;

    //import flash

    //import mx.collections.*;

    import mx.core.*;

    public class GraphicsController       

    {

        // double buffer

        public var backBuffer:BitmapData;       

        // colour to use to clear backbuffer with       

        public var clearColor:uint = 0xFFFFFFFF;

        //private var canvas:Bitmap;

        // static instance

        protected static var instance:GraphicsController = null;

        // the last frame time

        //protected var lastFrame:Date;

        private var testGraphic : GraphicsResource = ResourceManager.testGraphic;

        private var rect:Rectangle;

        private var pos : int = 0;

        private var pt:Point = new Point(0,0);

        private var direction : Boolean = true;

        private var startTime:int = getTimer();

        var lastFrame:int = 0;

        public function startup(width : Number, height : Number, tl:MovieClip):void       

        {           

            backBuffer = new BitmapData(width,height);   

            rect = this.testGraphic.bitmap.bitmapData.rect;

            //canvas = new Bitmap(backBuffer);

            //lastFrame = new Date();   

            //tl.addChild(canvas);

        }

        static public function get Instance():GraphicsController

        {

            if(instance == null)

            {

                instance = new GraphicsController();

            }

            return instance;

        }

        public function GraphicsController()

        {

            if(instance != null)

            {

                throw new Error("Only one instance allowed.");

            }

        }

        public function enterFrame():void           

        {

            // Calculate the time since the last frame                   

            //var thisFrame : Date = new Date();           

            //var dT : Number = (thisFrame.getTime() - lastFrame.getTime())/1000.0;           

            //lastFrame = thisFrame;       

            // erase backBuffer

            backBuffer.lock();

            backBuffer.fillRect(backBuffer.rect, 0xFFFFFFFF);

            // set new postion of the small testimage

            if (this.pos > 300 || this.pos < 0) {

                this.direction = !this.direction;

            }

            // increase / decrease vertical position

            if (this.direction) {

                this.pos += 2;

            } else {

                this.pos -= 2;

            }

            pt.x = pos;

            //trace(pos);

            // draw small test image at postion "offset"

            backBuffer.copyPixels(this.testGraphic.bitmap.bitmapData, rect, pt);           

            backBuffer.unlock();

              var thisFrame : Number = Number(getTimer());              

              var dT : Number = (thisFrame - lastFrame)/1000.0;              

              lastFrame = thisFrame;         

              trace(1.0/dT);

            //, (getTimer()-startTime)

            //trace(getTimer()-startTime);

            startTime=getTimer();

        }

    }

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guest
Sep 09, 2012 Sep 09, 2012

Copy link to clipboard

Copied

Thank you, I tried it out. However I still don't have an idea how the lack of performance could be explained, since I think there should be nothing very time consuming in the code. I'm hopefully looking forward to further suggestions.

Thanks

Gerd

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 09, 2012 Sep 09, 2012

Copy link to clipboard

Copied

in addition to the problems i mentioned before, you are also using beginBitmapFill which is going to be slower (in most circumstances) than copyPixels which is what you should be using to implement a blitting application (assuming that's what you're trying to do).

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guest
Sep 09, 2012 Sep 09, 2012

Copy link to clipboard

Copied

Hi kglad, in posting 4. I posted a version that doesnt use beginBitmapFill anymore. Also, all your suggestions have been implemented in that version - but the hiccup-problem remains the same.

I thought the doublebuffer technique to be a very common approach for flash games - lots of tutorials for this topic exist, e.g.

http://www.brighthub.com/internet/web-development/articles/11012.aspx

Thats why I wonder that this problem hasn't been discovered before.

I still hope, its just a mistake in my code, but comparing to existing tutorial sites, its quite the same.

Still looking forward to a solution...

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Sep 09, 2012 Sep 09, 2012

Copy link to clipboard

Copied

LATEST

i'm not an expert on flashbuilder or flex.

i can tell you that flash is prefectly capable of handling a lot more than 1 square moving across the stage without any hiccups and there's no need to use any buffer. 

simply drawing the display to a single bitmap is more than capable of handling what would otherwise be high-demand displays. 

here's an example with 10,000 squares moving and rotating using blitting which is the use of one stage-sized bitmap to display what is seen on stage:  www.kglad.com/Files/forums/blit_test2.html

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines