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
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
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.
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();
}
}
}
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.
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.
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.backBuf fer, 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();
}
}
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...
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
North America
Europe, Middle East and Africa
Asia Pacific