Porting my AS3 game (engine) to Starling
I started working on my first Indie game really long ago. Probably even before Stage3d was announced. But my day job kept me busy and the development happened only on and off. Here are a few details about it
- - Has a resolution of 720 x 480 (3:2 aspect ratio, had old iPhone screens in mind)
- - Not based on Blitting like Flixel does. I used the display list.
- - A lot of procedural artwork with AS3 filters, but with cacheAsBitmap enabled.
- - Heavy usage of 'Graphics' class, to create the elements via code.
- - Also has usual PNG frame animated graphics.
Having worked on Robokill for the iOS, I got to read a lot of the original AS3 code. But little did I realize that even though that game had a resolution of 800 x 600, had lot of graphics and still ran smoothly, that game was designed in such a way that not the entire screen needed to be redrawn each frame. It never had parallax scrolling backgrounds, or full screen effects. It was written with a home-grown AS3 game engine and was not based on blitting like Flixel was. This is what influenced me to make the decisions for my game too. Everything went well until I added scrolling parallax backgrounds and the sound effects, then the frame rates dropped below 30 FPS. I realized my game was like a full blown platformer without tile based graphics, even though it looked deceptively simple to the end-user.
This is how it looked
Green/Red blocks are all created with code. There were more elements not shown in this level which made use of 'Graphics' class. I also had a few particle systems (again, based on display lists only). The top pole like element, the stars and the cube (square) in the middle are all animated and were loaded from images through Embed tags.
After I reached the FPS problem, and spending hours with the Flash builder profiler, I realized I needed to find a different solution, and it was a shame that I wasn't informed well about Stage3d. As AS3 development was not my full-time day job, I wasn't really updating myself on these new technologies. But after reading a little I could see Starling could be my saviour. The fullscreen demos looked great and ran at terrific speeds. But all the HUD, Buttons, Menus were all directly loaded from SWF, there is no way I can completely rely on Starling/Stage3d only. A post from Adobe knowledge base even recommended using the usual AS3 stuff for HUD and UI elements from the SWF on top of Stage3d graphics. So I finally created a new branch on GIT for the process and started!
Some relevant information about my so called Game engine
My basic gameObject class equivalent (which I called BasicThing) wasn't derived from Sprite. Instead it held a Sprite reference inside it. This was done to support some form of components based model. Each BasicThing held the following
- A Bitmap object to hold the current frame
- A Sprite object called m_container (used as the parent for everything under the BasicThing, including the bitmap) with protected scope. And a "get skin()" method which returned that.
- Getters and setters for x, y, width, height
- addChild(BasicThing), removeChild(BasicThing) - both of them added, removed m_container of the parameter as the child of m_container in the current object.
- Also a setFrame(int) and setFrames(Array) - Array used to be filled with BitmapData objects
Those were the main function, and like you see it had a lot of AS3 class dependencies like Bitmap, BitmapData, Sprite. Also whenever I subclassed BasicThing for various game entities, I loosely accessed m_container and added children to it like
public class CubeThing extends PhysicsThing
{
public function CubeThing()
{
super();
bd0 = (new Embeds.whitecube0_png() as Bitmap).bitmapData;
var bd1:BitmapData = (new Embeds.whitecube1_png() as Bitmap).bitmapData;
var bd2:BitmapData = (new Embeds.whitecube2_png() as Bitmap).bitmapData;
var bd3:BitmapData = (new Embeds.whitecube3_png() as Bitmap).bitmapData;
var bd4:BitmapData = (new Embeds.whitecube4_png() as Bitmap).bitmapData;
bdframes = new Array(bd1, bd2, bd3, bd4);
this.m_sprite.x = -12;
this.m_sprite.y = -12;
this.frames = bdframes;
this.setFrame(1);
}
public function cubeReset():void
{
this.skin.visible = true;
this.skin.alpha = 1.0;
}
In the above few snippets you can see how I have this horrible habit of breaking so many oops rules. But as a game developer I believe, you often just have to focus on getting it done than worrying about code structure and rules if you want to be really productive.
Also I had a BasicScene class extended from BasicThing, which served as the GameScene. And a SceneHolder class which took care of handling events and passing them down to each scene, attaching stuff to the flash Stage, taking care of transition when changing scene etc.
Keeping the factors discussed above, I had the following goals in mind
- - I wanted to support both normal AS3 classes and Starling classes in each BasicThing (gameObject)
- - The porting process should be progressive, I should be able to convert individual object to use Starling classes and see the output immediately to verify that part alone.
- - Which essentially means existing code shouldn't be disturbed much.
So that way I could stop when the necessary performance has been achieved and simply leave AS3 classes for the rest of them to save time.
The Starling-ification
First thing I did was modify the BasicThing class
1. It had both an AS3 Sprite and a Starling Sprite like this
protected var m_slsprite :starling.display.Sprite = new starling.display.Sprite();
protected var m_container :flash.display.Sprite = new flash.display.Sprite();
public function get slskin():starling.display.Sprite
{
return m_slsprite;
}
public function get skin():flash.display.Sprite
{
return m_container;
}
2. When adding and removing BasicThing children, I added and removed from both skin and slskin. Also the setters of x, y on BasicThing modified both skin and slskin. This way both of them co-exist peacefully.
3. Replace AS3 "BitmapData" with Starling "Texture". Replaced AS3 "Bitmap" with a combination of Starling "Image" and "Sprite" classes. This was done because, there were cases where I had the Bitmap created and added as child without any BitmapData in it. With Starling Image you had to definitely pass a Texture when creating it.
4. When setting frame, I assigned the Texture from the Array to the Image and called readjustSize each time.
5. Apart from this, in each class instead of creating Bitmap using somthing like "new Embeds.CubeSprite", I created a Starling Texture directly.
6. But I made a Caching factory for Texture through which I could load Starling Textures like this - TextureLoader.getTexture( Embeds.CubeSprite)
7. To handle the Stage attachment, I made a empty singleton StarlingHolder which extended from Starling Sprite, just to serve as a container for all the Starling content in the game.
3. Replace AS3 "BitmapData" with Starling "Texture". Replaced AS3 "Bitmap" with a combination of Starling "Image" and "Sprite" classes. This was done because, there were cases where I had the Bitmap created and added as child without any BitmapData in it. With Starling Image you had to definitely pass a Texture when creating it.
4. When setting frame, I assigned the Texture from the Array to the Image and called readjustSize each time.
5. Apart from this, in each class instead of creating Bitmap using somthing like "new Embeds.CubeSprite", I created a Starling Texture directly.
6. But I made a Caching factory for Texture through which I could load Starling Textures like this - TextureLoader.getTexture( Embeds.CubeSprite)
7. To handle the Stage attachment, I made a empty singleton StarlingHolder which extended from Starling Sprite, just to serve as a container for all the Starling content in the game.
public function BasicSceneHolder(stage:Stage)
{
super();
var _starling:Starling = new Starling(StarlingHolder, stage);
_starling.stage.addChild(this.slskin);
_starling.start();
this.m_stage = stage;
m_stage.addChild(this.skin);
}
P.S. BasicSceneHolder extends from BasicScene which extends from BasicThing. So it has a skin and slskin.
8. The final thing left to handle was converting the procedural content which made heavy usage of Graphics classes. I wanted to be sure I could do this, so I set out to make a quick sample to test it. Let me explain the process in detail in next section
Bringing Procedural content into Starling
For making the test sample this I took help from one of Tony Broyez old blog post (link seems down, lucky me, don't worry I made a gist for it!)
https://gist.github.com/quakeboy/7066901
After that I used the same code to convert what ever I draw with AS3 Graphics to Starling Textures. Feel free to comment and ask questions I would be glad to help.
Oh BTW, this is how the game looks now, in a land where AS3 classes and Starling classes live peacefully together with three layer starfield parallax background thanks for ScrollImage extension by Krecha, and cool particle effects thanks to another extension by Daniel to support ParticleEditor by 71squared.
8. The final thing left to handle was converting the procedural content which made heavy usage of Graphics classes. I wanted to be sure I could do this, so I set out to make a quick sample to test it. Let me explain the process in detail in next section
Bringing Procedural content into Starling
For making the test sample this I took help from one of Tony Broyez old blog post (link seems down, lucky me, don't worry I made a gist for it!)
https://gist.github.com/quakeboy/7066901
After that I used the same code to convert what ever I draw with AS3 Graphics to Starling Textures. Feel free to comment and ask questions I would be glad to help.
Oh BTW, this is how the game looks now, in a land where AS3 classes and Starling classes live peacefully together with three layer starfield parallax background thanks for ScrollImage extension by Krecha, and cool particle effects thanks to another extension by Daniel to support ParticleEditor by 71squared.
awesome dude!
ReplyDelete