I've been switching to Flash 10 for my game experiments. Flash 10 offers new graphics APIs, and opens up new approaches for how to deal with game objects. I wanted to compare these approaches:
(Flash 9) Use multiple
Spriteobjects for hierarchical layers, filters, rotation, and translation. It's convenient to have one or more Sprites per game object, and then move and rotate them without having to manage them manually. For vector drawing, I can use
lineStylewith (Flash 10)
drawPath. For bitmap (textured) drawing, I can use
beginBitmapFill, or there's a special
Bitmapclass for rectangular bitmaps.
(Flash 10) Manage my own list of objects, and then build up a vector of
IGraphicsDataobjects, and pass that to
Graphics.drawGraphicsData. This puts the burden on me to handle translation and rotation, and I don't see a great way to add filters. Here too I can use either
beginBitmapFillfor vector or bitmap drawing.
(Flash 10) Manage my own list of objects, build up a vector of vertices and another vector of texture coordinates, and use
Graphics.drawTriangles. I have to handle translation and rotation of the vertices, but Flash will handle the translation, rotation, and scaling of the textures.
Note that there's actually a lot more flexibility than just three
options. There are three ways to draw polygons (
drawTriangles), two object management approaches (using
Sprite, or managing the list manually), and whether to defer
IGraphicsData. Which approach should I use for a game
like my spaceship
One of the things I love about Flash is that drawing is pretty
convenient, especially with
Sprite objects, bitmap handling,
translation/rotation, easy filters like drop shadow, and vector
drawing. For example:
var s:Sprite = new Sprite(); s.beginBitmapFill(texture, null, false, true); s.drawPath (Vector.<int>([1, 2, 2, 2]), Vector.<Number>([100, 0, 164, 0, 164, 64, 100, 64])); s.endFill();
To rotate or translate the game objects, set the
s.rotation fields. When you need a new game object, instantiate a
Sprite and insert it into the hierachy; when you want to remove one,
remove the shape. Flash handles the rest.
With Flash 10, the
IGraphicsData interface lets you store graphics
commands in objects, and then draw with them later. You can either
call commands similar to the Flash 9 API, or you can store graphics
data in a more direct form. Once you've assembled all the graphics
objects, you can draw them all using
v = new GraphicsBitmapFill(texture, null, false, true); v = new GraphicsPath (Vector.<int>([1, 2, 2, 2]), Vector.<Number>([100, 0, 164, 0, 164, 64, 100, 64])); v = new GraphicsEndFill(); graphics.drawGraphicsData(v);
You can draw each of these to a separate
Sprite, or you can assemble
them all into one big vector and make a single call to
Graphics.drawGraphicsData. The main value I think is to be able to
batch them up, so I decided to make a single call, but that means I
need to manage translation and rotation myself. Instead of creating
new graphics data objects, you can update them every frame. Each of
my game objects can keep a reference to where in the graphics data
array it is represented, and then when I want to update the game
object, I can change the corresponding
GraphicsPath object. For
rotation, I need to edit the path and also the bitmap rotation in the
Adding and removing game objects is a little trickier. Either I can rebuild the entire vector every time, or I can build something similar to a memory allocator, so that I can reuse parts of the vector. I haven't yet decided how I'm going to handle this (my tests so far have a fixed number of game objects).
The Flash 10
Graphics.drawTriangles interface is somewhat
inconvenient when working with 2D data. It requires that all polygons
be decomposed into triangles (not hard to do but it's an extra
step). The function takes a list of vertices, a list of indices that
point into the vertex vector, and texture coordinates.
graphics.beginBitmapFill(texture, null, false, true); graphics.drawTriangles (Vector.<Number>([100, 0, 164, 0, 164, 64, 100, 64]), Vector.<int>([0, 1, 2, 2, 3, 0]), Vector.<Number>([0, 0, 1, 0, 1, 1, 0, 1])); graphics.endFill();
Here too I need to handle translation and rotation myself, by updating
the vector. However I don't need to rotate the bitmaps because
drawTriangles takes care of this for me. Adding and removing
objects is similar to the previous case, in that I need to handle it
Overall, the traditional API is most convenient, but deferring the
drawPath by putting it into a
GraphicsDataPath is convenient
drawTriangles interface is least convenient, except for not
needing to rotate bitmaps.
I've found that my intuition hasn't been a great guide for
understanding Flash performance. My intuition told me that approach 1
Sprite objects) would be slower than approach 2 (global graphics
data list), and approach 3 (triangles) would be fastest of all.
I wrote a test program that drew 400–1000 square objects on the screen at once and moved them around. I also put in options for rotation (because many of the objects in the spaceship demo were rotating), use bitmap or vector graphics (I like noise in art and bitmaps seem to be the way to make that), using outlines for vector graphics (I like outlines!), and animating bitmaps.
My general conclusions, for this style of game:
- Vector graphics are faster than bitmap graphics (especially when rotation is involved), but that using outlines on the vector graphics slows them down, about as much as bitmaps do. Also, if the vector graphics have any additional complexity, the bitmaps end up being faster. Since I almost always want outlines or some other edge effect, bitmaps shouldn't cost me that much, and they'll give me a lot more options for adding details.
Using the separate
Spriteobjects is about the same speed as using a single
IGraphicsDatavector. Sprite objects are more convenient. However, once I want animation, the
IGraphicsDataapproach shines: I can swap in alternative paths and bitmaps at no cost, whereas with drawing directly, I have to redraw to get animation (it's possible that I can create lots of
Spriteobjects and then swap them out but I haven't tried this).
For consistent frame rates, it's best to minimize allocation, and reuse some objects. The
IGraphicsDataobjects, the vector of them, and the vectors fo vertex and index data all can be reused from frame to frame, with additional complexity to track where everything is.
With the triangle API, I can put all my textures into one big bitmap, and then access them using texture coordinates. This allows me to make a single
drawTrianglescall. However, using hundreds of of
GraphicsDrawPathobjects is suprisingly faster than using a single
drawTrianglescall. I was unable to make the triangle approach faster.
The performance of
drawTriangles (even when I used
GraphicsTrianglePath) was a disappointment. My thanks to Alex from Wild Shadow Studios for his help in understanding the APIs and performance.
It wasn't enough for
drawTriangles to be slower for this type of game. I've also had issues with the bitmap rendering. Here's a 64x64 test image drawn in several ways:
A polygon drawn with
Bitmapobject rotated 90 degrees clockwise.
A polygon drawn with
beginBitmapFill, rotated 90 degrees.
Two triangles drawn with
drawTriangles, and u/v range from 0 to 1.
- Same as 5, except u/v adjusted by 1/64.
- Same as 5, rotated 90 degrees.
- Same as 6, rotated 90 degrees.
Note that images 1 and 2 look the same. This is as it should be. If I believe the documentation, image 5 should look the same as 1 and 2. But it doesn't.
As far as I can tell,
drawTriangles is assigning a u/v coordinate of
1.0 to be pixel 62 instead of pixel 63. If you look closely, you can
see the gray pixels are getting blurred (stretched). A “fix” for this
is in image 6, where I set the u/v coordinate to 1.0 + 1/64, so
that it picks pixel 63. With this hack, the image matches the
reference image 1.
Images 3 and 4 are the same as 1 and 2, but rotated 90 degrees clockwise. Images 7 and 8 are the same as 5 and 6, rotated 90 degrees clockwise. Notice that 7 is incorrect (it should match 3), but it's incorrect in a different way than 5. Instead of losing the yellow and blue edges, it loses the green and yellow edges. The hack in image 6, when applied to 8, fails. That means it's not as obvious as pixel 62 instead of 63; there must be a weirder issue that I don't understand.
These issues with
drawTriangles rendering make it hard to recommend
for 2D graphics. I don't need everything to be exactly the same, but I
do plan to draw 1-pixel outlines around my sprites, and
drawTriangles can't preserve the outline. Even worse, as I rotate my
game's objects, the outlines will appear and disappear.
drawTriangles API also has some weird effects when using Flash's
built in zoom function (right click and choose Zoom In). It also
doesn't seem to be happy when using bitmap fills with
repeat set to
true. And it doesn't seem to handle texture scaling as well as the
regular APIs (it's possible they aren't using
For both convenience and performance for games like my spaceship demo,
I think I should have every game object store some
objects, and also store a vector of all of them. I had thought that
drawTriangles might be better, but it's both slower and less
convenient. It also doesn't look as good. I think I might take a look
drawTriangles for 3D perspective graphics, or if I had lots of
triangle data, but for 2D or orthographic 3D, I think it's probably
not the best choice for me right now.