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
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().
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
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.
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(); } } }
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.
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.
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.
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?
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();
}
}
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
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).
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...
Copy link to clipboard
Copied
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