04.07
I have just started using the pushbutton engine which went into public beta recently. It’s a flex/flash game engine that is designed around modularity between entities so that you can share code. Unfortunately, their documentation is rather sparse and can be hard to find. The best reference, the API documentation is very difficult to get to since it’s hosted in their google code project and not directly linked to on their website.
Everything they give you is set up for a single player experience that is built around xml files describing the components and their entities. So what if you want to dynamically load information? I did a lot or tracing and here is how I loaded a tile map that I would be able to dynamically create on the server and pass to client as xml. FYI I’m using the Lost Garden PlanetCute tiles since they’re not only Creative Commons, but really good looking.
Since I’m going to already have all the graphics embedded on the client, I can use the xml loader that the pushbutton documentation uses. I also included my Simple Spatial Manager and Scene components, which handle the placement and drawing, respectively.
<things version="1"> <entity name="SimpleSpatial"> <component type="PBLabs.Rendering2D.BasicSpatialManager2D" name="Manager"/> </entity> <entity name="Scene"> <component type="PBLabs.Rendering2D.Scene2DComponent" name="Scene"> <SpatialDatabase componentReference="SimpleSpatial" componentName="Manager"/> <SceneViewName>MainView</SceneViewName> <Position> <x>0</x> <y>0</y> </Position> <RenderMask childType="String"> <_0>Renderable</_0> </RenderMask> </component> </entity> <entity name="brown"> <component type="PBLabs.Rendering2D.SpriteSheetComponent" name="SpriteSheet"> <Image filename="../Assets/Images/Level/Brown Block.png"/> </component> </entity> <entity name="dirt"> <component type="PBLabs.Rendering2D.SpriteSheetComponent" name="SpriteSheet"> <Image filename="../Assets/Images/Level/Dirt Block.png"/> </component> </entity> <entity name="grass"> <component type="PBLabs.Rendering2D.SpriteSheetComponent" name="SpriteSheet"> <Image filename="../Assets/Images/Level/Grass Block.png"/> </component> </entity> <entity name="plain"> <component type="PBLabs.Rendering2D.SpriteSheetComponent" name="SpriteSheet"> <Image filename="../Assets/Images/Level/Plain Block.png"/> </component> </entity> <entity name="stone"> <component type="PBLabs.Rendering2D.SpriteSheetComponent" name="SpriteSheet"> <Image filename="../Assets/Images/Level/Stone Block.png"/> </component> </entity> <entity name="water"> <component type="PBLabs.Rendering2D.SpriteSheetComponent" name="SpriteSheet"> <Image filename="../Assets/Images/Level/Water Block.png"/> </component> </entity> <entity name="wood"> <component type="PBLabs.Rendering2D.SpriteSheetComponent" name="SpriteSheet"> <Image filename="../Assets/Images/Level/Wood Block.png"/> </component> </entity> <group name="SpriteData"> <objectReference name="SimpleSpatial"/> <objectReference name="Scene"/> <objectReference name="brown"/> <objectReference name="dirt"/> <objectReference name="grass"/> <objectReference name="plain"/> <objectReference name="stone"/> <objectReference name="water"/> <objectReference name="wood"/> </group> </things> |
And I embed the images in the swf, again, following the documentation
package com.enigmatic { import PBLabs.Engine.Resource.ResourceManager; import PBLabs.Rendering2D.ImageResource; import flash.utils.ByteArray; public class LevelTiles { //Embeds [Embed(source="../Assets/Images/Level/Brown Block.png", mimeType='application/octet-stream')] private var _brownBlock:Class; [Embed(source="../Assets/Images/Level/Dirt Block.png", mimeType='application/octet-stream')] private var _dirtBlock:Class; [Embed(source="../Assets/Images/Level/Grass Block.png", mimeType='application/octet-stream')] private var _grassBlock:Class; [Embed(source="../Assets/Images/Level/Plain Block.png", mimeType='application/octet-stream')] private var _plainBlock:Class; [Embed(source="../Assets/Images/Level/Stone Block.png", mimeType='application/octet-stream')] private var _stoneBlock:Class; [Embed(source="../Assets/Images/Level/Water Block.png", mimeType='application/octet-stream')] private var _waterBlock:Class; [Embed(source="../Assets/Images/Level/Wood Block.png", mimeType='application/octet-stream')] private var _woodBlock:Class; public function LevelTiles() { ResourceManager.Instance.RegisterEmbeddedResource("../Assets/Images/Level/Brown Block.png", ImageResource, new _brownBlock() as ByteArray); ResourceManager.Instance.RegisterEmbeddedResource("../Assets/Images/Level/Dirt Block.png", ImageResource, new _dirtBlock() as ByteArray); ResourceManager.Instance.RegisterEmbeddedResource("../Assets/Images/Level/Grass Block.png", ImageResource, new _grassBlock() as ByteArray); ResourceManager.Instance.RegisterEmbeddedResource("../Assets/Images/Level/Plain Block.png", ImageResource, new _plainBlock() as ByteArray); ResourceManager.Instance.RegisterEmbeddedResource("../Assets/Images/Level/Stone Block.png", ImageResource, new _stoneBlock() as ByteArray); ResourceManager.Instance.RegisterEmbeddedResource("../Assets/Images/Level/Water Block.png", ImageResource, new _waterBlock() as ByteArray); ResourceManager.Instance.RegisterEmbeddedResource("../Assets/Images/Level/Wood Block.png", ImageResource, new _woodBlock() as ByteArray); } } } |
Then, to load this, we just use the level manager. Note the last argument to AddLevelFileReference, which is true. The means that the sprite data will be persistent, and we won’t have to rebuild the basic entities of the level every time we load a new level.
package com.enigmatic { public class LevelLoader { import PBLabs.Rendering2D.Scene2DComponent; import PBLabs.Rendering2D.BasicSpatialManager2D; import PBLabs.Rendering2D.SpriteSheetComponent; import PBLabs.Engine.Core.LevelManager; public function LevelLoader() { var _scene2DComponent:Scene2DComponent; var _managerComponent:BasicSpatialManager2D; var _spriteSheet:SpriteSheetComponent; LevelManager.Instance.AddLevelFileReference("../Assets/Level/Sprites.xml",0,true); LevelManager.Instance.AddGroupReference("SpriteData",0); } } } |
And finally, to complete the circle, the level itself, in xml.
<level> <row> <tile name="wood"/> <tile name="dirt"/> <tile name="grass"/> <tile name="plain"/> <tile name="stone"/> <tile name="water"/> <tile name="brown"/> <tile name="wood"/> <tile name="dirt"/> <tile name="grass"/> <tile name="plain"/> <tile name="stone"/> <tile name="water"/> <tile name="brown"/> </row> <row> <tile name="dirt"/> <tile name="grass"/> <tile name="plain"/> <tile name="stone"/> <tile name="water"/> <tile name="brown"/> <tile name="wood"/> <tile name="dirt"/> <tile name="grass"/> <tile name="plain"/> <tile name="stone"/> <tile name="water"/> <tile name="brown"/> <tile name="wood"/> </row> <row> <tile name="grass"/> <tile name="plain"/> <tile name="stone"/> <tile name="water"/> <tile name="brown"/> <tile name="wood"/> <tile name="dirt"/> <tile name="grass"/> <tile name="plain"/> <tile name="stone"/> <tile name="water"/> <tile name="brown"/> <tile name="wood"/> <tile name="dirt"/> </row> </level> |
And now comes the fun part. I simply use ActionScript’s ability to work with XML to loop through the level data and add a tile at a time. Each tile is made of two basic components. A SpriteRenderComponent to display, and a SimpleSpatialComponent to handle placement. the order you add components don’t matter, even if they reference other components. Pushbutton handles that kind of stuff.
The way pushbutton works, you call a global function AllocateEntity() which returns an IEntity component. Then you manipulate the entity from there. The engine handles all the messy work of resource allocation and whatnot.
var newTile:IEntity = AllocateEntity(); |
Now that we have an entity, we add components to it. Since I built this off the documented level xml file, I created the Render Component First.
var render:SpriteRenderComponent = new SpriteRenderComponent(); render.SpriteSheet = NameManager.Instance.Lookup(tile.attribute("name").toString()).LookupComponentByType(SpriteSheetComponent) as SpriteSheetComponent; render.PositionReference = new PropertyReference("@Spatial.Position"); render.RotationReference = new PropertyReference("@Spatial.Rotation"); render.SizeReference = new PropertyReference("@Spatial.Size"); newTile.AddComponent(render,"Render"); |
this is the same as the xml:
<component type="PBLabs.Rendering2D.SpriteRenderComponent" name="Render"> <SpriteSheet componentReference="<tilename>"/> <PositionReference>@Spatial.Position</PositionReference> <RotationReference>@Spatial.Rotation</RotationReference> <SizeReference>@Spatial.Size</SizeReference> </component> |
The trickiest part was converting the xml line for the sprite sheet to code. That’s where the NameManager comes into play. All named entities are stored there, and if you refer back to the Sprites.xml file, you’ll see that the entities are named based on the type. Once you get the entity, you can pull the type up easily by using the LookupComponent commands. You can use name or type. The Serializer handles the sprite sheet lookup for you if your using xml, which is why there was a extra level of depth not in the xml.
Next is the spatial manager that handles the placement of the tile.
var spatial:SimpleSpatialComponent = new SimpleSpatialComponent(); spatial.SpatialManager = (NameManager.Instance.Lookup("SimpleSpatial")).LookupComponentByName("Manager") as ISpatialManager2D; spatial.Position = new Point(x * 50, y * 40); spatial.QueryMask = new ObjectType(); spatial.QueryMask.TypeNames = ["Floor","Renderable"]; spatial.Size = new Point(50, 85); newTile.AddComponent(spatial,"Spatial"); |
this is the same as the xml:
<component type="PBLabs.Rendering2D.SimpleSpatialComponent" name="Spatial"> <SpatialManager componentReference="SimpleSpatial" componentName="Manager"/> <QueryMask childType="String"> <_0>Floor</_0> <_1>Renderable</_1> </QueryMask> <Position> <x>50</x> <y>50</y> </Position> <Size> <x>50</x> <y>85</y> </Size> </component> |
Again, the Spatial Manager was the most complicated, but the extra level was also included in the xml. The other item to note is the QueryMask. This is what the RenderMask is referring to in the Scene from Sprite.xml. If you are using a Box2DSpatialComponent instead, the RenderMask matches up against the CollidesWithTypes, which is what the demo program is using.
Here is the completed function. Make sure you wait until the level loading is complete (simply add an even listener for a LevelEvent.LOADED_EVENT) or you’ll run into null references due to a partially loaded level.
public function buildLevel(xml:XML):void { var x:int = 0; var y:int = 0; for each (var row:XML in xml.*) { for each (var tile:XML in row.*) { var newTile:IEntity = AllocateEntity(); var render:SpriteRenderComponent = new SpriteRenderComponent(); render.SpriteSheet = NameManager.Instance.Lookup(tile.attribute("name").toString()).LookupComponentByType(SpriteSheetComponent) as SpriteSheetComponent; render.PositionReference = new PropertyReference("@Spatial.Position"); render.RotationReference = new PropertyReference("@Spatial.Rotation"); render.SizeReference = new PropertyReference("@Spatial.Size"); newTile.AddComponent(render,"Render"); var spatial:SimpleSpatialComponent = new SimpleSpatialComponent(); spatial.SpatialManager = (NameManager.Instance.Lookup("SimpleSpatial")).LookupComponentByName("Manager") as ISpatialManager2D; spatial.Position = new Point(x * 50, y * 40); spatial.QueryMask = new ObjectType(); spatial.QueryMask.TypeNames = ["Floor","Renderable"]; spatial.Size = new Point(50, 85); newTile.AddComponent(spatial,"Spatial"); x += 1; } x = 0; y += 1; } } |
Here is the final result with source code. It’s not much to look at, but it should get you started.
The next step is to create a custom TilemapRendererComponent that would do a lot of the heavy lifting for me. Things like positioning of the tiles based on the grid and having to work with each individual tile. Of course, the point of this post wasn’t tiles, but the entities that happened to be tiles, so that’s a whole other topic to cover later
Thanks for the great article, I really enjoyed your method of providing code, links to download and explanation. A real help for the push button development experience.
interesting!
Nice tutorial! Very helping!
Thanks for posting your code, it’s helpful since like you said – the Pushbutton docs are pretty lacking.