Map Tiling in AS3 was the first post of my short-lived AS3 Game Engine series before I found Starling. While the core tile drawing code remains mostly the same, there have been some changes. We saw from yesterday’s post that the Play State creates an instance of the Map class and passes it the map JSON data Object. Let’s take a look at the JSON file that holds the map data, then we’ll look at the Map class and see what it does.
[toc]
Feel free to check out the finished product of my AS3 Starling TD Demo.
You can also find all of the code used in srcview.
Or you can download the whole project zipped up.
Or check out the repo on Bitbucket
I’m not sure if I’ve mentioned this yet, but I am using TexturePacker to create my spritesheets/tilesheets (is there a difference? semantics?). TexturePacker makes creating these tilesheets stupid easy and quick. If you can save your png images into a folder, and if you can remember that folder so you can find that very same folder again… you can make tilesheets using TexturePacker. They have tons of tutorials for using the app, and Lee Brimlow makes a three-part excellent spritesheet/basics-of-blitting tutorials over at GotoAndLearn (part1, part2, part3)
Map JSON Data
We’re going to look at the map data from the second map I made. Here’s a screenshot of the map actually in the game, all drawn out, tile-by-tile.
These are all tiles I made out of 24×24 tiles in Photoshop. Here’s a sample of the tiles that are in my tile sheet.
This has clearly been enlarged and is a little pixelated, but it’s 24×24 pixel art, there’s really not a whole lot of room for smooth lines. This image is here to show that I’ve basically grouped my tiles in groups of 3. For example, the first three tiles are all the same road piece, left-to-right, but with varying “dirt” sorts of pixels along the road piece. This lets me have the long straight roads for the enemies to travel down, and every tile doesn’t look like the exact same tile. Sure, only 3 variations isn’t really a lot of difference. You could make curved pieces, or “broken road” sorts of pieces, anything really.
Then there’s the single grass tile. Hey, it works right? The single green tile, as we’ll see in the JSON data is our background tile. I set the Map tiler up to take a single background image and make a whole “layer” of this same tile as the background layer. Any further layers are kept in the JSON array below in “tiles” each “layer” consists of however many “row”s there are on the screen. So looking at the “row” info below you can clearly see how it syncs up with the actual drawn map image above. This is a “row” of data, the first two tiles are {} empty, so we draw nothing for this layer in those tiles. The third tile in this row is a vertical road piece. If you go back to the map image above and look in the top left corner, 2 green tiles in, you see a vertical road piece. I’ve structured my JSON data and set up my Map class so that if there is nothing in a tile (“{}”) we skip that tile so we do not overwrite the background in any way. This lets me add layers and layers of detail if I wanted. Which I clearly don’t take advantage of here in this tutorial as there is just a single “layer” on top of the background that just shows a simple road. Rock tiles, trees, level details could all be easily added in subsequent layers to provide an infinite amount of customization to each map.
I won’t go into too much more detail about the JSON data, but I just want to make sure you can look at the JSON here and visualize at least “something” of a structure that you should expect to see in the actual drawn out map. Here are the first three rows of the map, you can look at the image above and come back to the data. The first row has a single vertical road piece in it. The second row has a single vertical road piece in it. Then the third row has the connecting road pieces for the vertical road to connect with the long horizontal road we’re building here.
Quick note about the way I’ve named my textures and images here. You’ll notice the “src” attribute contains something that looks like ‘roads/road1_202’. If you’ve downloaded the project zip file, or checked it out from my bitbucket repo you’ll see that you can go to src/assets/images/tiles and I’ve got all my tile images that I want to go in my spritesheet. So when it references ‘roads/road1_202’ if you go to src/assets/images/tiles/roads/road1_202.png you’ll find the single 24x24px image tile that I’m referencing, and that tile gets drawn into the src/assets/images/atlas.png spritesheet. I named these files this way for a reason, so all of these brown dirt roads in this set belong to my “road1” (“road2” might be snow roads?) set and they’re numbered 1-7 for the different directions so road1_1 for the first direction and the last number is a variation number. So ‘roads/road1_103’ would be the road1 set, the first direction, and the 3rd shading variation of that horizontal tile. That’s just the way I did it, and why I did it that way. Feel free to adopt your own file naming conventions.
{
"mapName": "Second Map!",
"numRows": 22,
"numCols": 22,
"numLayers": 1,
"bkgdSrc": "misc/grass1_001",
"drawBkgd": 1,
"tileWidth": 24,
"tileHeight": 24,
"startHP": 10,
"startGold": 15,
"enemyWaveData": { ....removed.... },
"enemyData": { ....removed.... },
"tiles": [{
"layer": [{
"row": [
{},
{},
{ "src": "roads/road1_202", "isWalkable": 1, "wp": 1, "wpGroup": "wpGroup1", "wpIndex": 1, "sp": 1, "spDirection": "up" },
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{}
]},
{
"row": [
{},
{},
{ "src": "roads/road1_201", "isWalkable": 1, "groupStartIcon": "t" },
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{}
]},
{
"row": [
{},
{},
{ "src": "roads/road1_503", "isWalkable": 1, "wp": 1, "wpGroup": "wpGroup1", "wpIndex": 2 },
{ "src": "roads/road1_101", "isWalkable": 1 },
{ "src": "roads/road1_103", "isWalkable": 1 },
{ "src": "roads/road1_103", "isWalkable": 1 },
{ "src": "roads/road1_101", "isWalkable": 1 },
{ "src": "roads/road1_103", "isWalkable": 1 },
{ "src": "roads/road1_101", "isWalkable": 1 },
{ "src": "roads/road1_102", "isWalkable": 1 },
{ "src": "roads/road1_101", "isWalkable": 1 },
{ "src": "roads/road1_101", "isWalkable": 1 },
{ "src": "roads/road1_102", "isWalkable": 1 },
{ "src": "roads/road1_102", "isWalkable": 1 },
{ "src": "roads/road1_101", "isWalkable": 1 },
{ "src": "roads/road1_103", "isWalkable": 1 },
{ "src": "roads/road1_101", "isWalkable": 1 },
{ "src": "roads/road1_103", "isWalkable": 1 },
{ "src": "roads/road1_102", "isWalkable": 1 },
{ "src": "roads/road1_302", "isWalkable": 1, "wp": 1, "wpGroup": "wpGroup1", "wpIndex": 3 },
{},
{}
]},
- Line 2 – mapName lets us enter a name for the map so we can display it later in the game.
- Line 3 – numRows is the number of rows this map will contain
- Line 4 – numCols is the number of columns this map will contain
- Line 5 – numLayers is the number of layers (Excluding the background!) that will show up in the data
- Line 6 – bkgdSrc is the source/id name given to the background tile inside the spritesheet
- Line 7 – drawBkgd is a 1 or 0 if we want to use the bkgdSrc tile and draw a whole background layer with that single tile
- Line 8 – tileWidth is the width of a tile
- Line 9 – tileHeight is the height of a tile
- Line 10 – startHP is the starting hit points we should give the player for the map
- Line 11 – startGold is the starting gold we should give the player for the map
- Line 12 – enemyWaveData was removed as it will be discussed in the enemy section
- Line 13 – enemyData was removed as it will be discussed in the enemy section
- Line 14 – tiles contains an array of layers which contain an array of rows which contain an array of tile objects.
So that’s an except from the src/assets/json/maps/map2.json file, feel free to check it out in its entirety in the project. Let’s move on to how that data is actually used.
Man, quick formatting note, my need for properly indented code is fighting with the fact that the site is only so wide. In the actual files in the project, the public class Map extends Sprite line is properly a tab inside of the package. But as that’d add even more tabs to the actual code (that we really actually care about) I’ve just dropped everything back a tab or two. /OCD-Disclaimer
Map.as
package com.zf.objects.map
{
import com.zf.core.Assets;
import com.zf.core.Config;
import com.zf.managers.WaypointManager;
import flash.geom.Point;
import flash.geom.Rectangle;
import starling.display.Image;
import starling.display.Sprite;
import starling.textures.Texture;
import com.zf.objects.tower.Tower;
public class Map extends Sprite
{
public var halfTileWidth:Number;
public var halfTileHeight:Number;
public var mapWidth:Number;
public var mapHeight:Number;
public var tileWidth:int;
public var tileHeight:int;
public var mapOffsetX:int;
public var mapOffsetY:int;
private var _mapData:Object;
private var _tileData:Array;
private var _wpMgr:WaypointManager;
// Tile data flattened down without layers, aggregating isWalkable data
private var _flatTileData:Array;
private var _mapWidthOffset:Number;
private var _mapHeightOffset:Number;
public function Map(data:Object, wpMgr:WaypointManager, mOffsetX:int, mOffsetY:int) {
_mapData = data;
_wpMgr = wpMgr;
mapOffsetX = mOffsetX;
mapOffsetY = mOffsetY;
tileWidth = _mapData.tileWidth;
tileHeight = _mapData.tileHeight;
halfTileWidth = _mapData.tileWidth >> 1;
halfTileHeight = _mapData.tileHeight >> 1;
mapWidth = _mapData.tileWidth * _mapData.numCols;
mapHeight = _mapData.tileHeight * _mapData.numRows;
_mapWidthOffset = mapWidth + mapOffsetX;
_mapHeightOffset = mapHeight + mapOffsetY;
// initial layer level
_tileData = [];
_flatTileData = [];
_parseMapDataIntoTileData();
_drawMap();
}
private function _parseMapDataIntoTileData():void {
var rowCt:int = 0,
colCt:int = 0,
layerCt:int = 0,
mapLayerCt:int = 0;
if(_mapData.drawBkgd) {
var data:Object = {"src": _mapData.bkgdSrc, "isWalkable": false };
_createBkgdLayerTileData(data);
layerCt++;
} else {
mapLayerCt = layerCt;
}
for(layerCt; layerCt <= _mapData.numLayers; layerCt++)
{
_tileData[layerCt] = [];
for(rowCt = 0; rowCt < _mapData.numRows; rowCt++)
{
_tileData[layerCt][rowCt] = [];
for(colCt = 0; colCt < _mapData.numCols; colCt++)
{
// map data doesnt have the bkgd layer
var tileData2:TileData = new TileData(_mapData.tiles[mapLayerCt].layer[rowCt].row[colCt]);
_tileData[layerCt][rowCt][colCt] = tileData2;
if(_flatTileData[rowCt][colCt] is TileData)
{
if(tileData2.isWalkable)
{
// if any tiledata is walkable, set the position to true
_flatTileData[rowCt][colCt].isWalkable = true;
_flatTileData[rowCt][colCt].srcImageName = tileData2.srcImageName;
}
}
else
{
_flatTileData[rowCt][colCt] = tileData2;
}
}
}
mapLayerCt++;
}
}
private function _createBkgdLayerTileData(data:Object):void {
var rowCt:int = 0,
colCt:int = 0;
// create the first empty row array
_tileData[0] = [];
for(rowCt; rowCt < _mapData.numRows; rowCt++)
{
_tileData[0][rowCt] = [];
if(_flatTileData[rowCt] == undefined) {
_flatTileData[rowCt] = [];
}
for(colCt = 0; colCt < _mapData.numCols; colCt++)
{
var bkgdTile:TileData = new TileData(data);
_tileData[0][rowCt][colCt] = bkgdTile;
// get another tile for _flatTileData
bkgdTile = new TileData(data);
_flatTileData[rowCt][colCt] = bkgdTile;
}
}
}
- Line 37 - _mapData contains our map JSON file data in Object form
- Line 38 - get a reference to Play State's WaypointManager as we'll need that later
- Line 40-41 - get our 36px map offsets so we can draw the map properly on the stage inside the UI elements
- Line 44-45 - bitwise dividing tileWidth and tileHeight by two, shifting the bit to the right by 1 (dividing by power of 2)
- Line 46-47 - get the total map width and height by the number of tiles times their width/height
- Line 48-49 - more math, getting the map width/height with the offset added in to save us cycles later
- Line 55 - we're about to see what _parseMapDataIntoTileData does, but basically, it parses the map data... into tile data! 🙂
- Line 56 - after we've parsed the map data into tile data in line 55, now lets go draw the map!
- Line 60-63 - declaring some vars outside of the loop for efficiency
- Line 65 - if the map JSON data's "drawBkgd" param was 1/true, we want to draw a background layer
- Line 66 - creating a skeleton tile data object for the background tile
- Line 67 - send the background tile data object into _createBkgdLayerTileData and we'll see in a minute what it does (spoiler, it creates the background grass layer!)
- Line 68 - increment our layer counter as we've created a background layer (One map layer, ah ah ah! ... the Count... from seseme street?)
- Line 70 - if we didn't say to draw a background layer from the background tile... do this
- Line 73 - start the big outer loop of layers. The more layers, the more loops this would go through.
- Line 75 - begin building the _tileData Object which is a large nested set of arrays. This creates a new array by layerCt for the rows array
- Line 76 - loop over the rows
- Line 78 - add another nest to _tileData for the column array
- Line 79 - loop over the individual tile data objects (columns)
- Line 82 - PRO TIP! This was a huge source of confusion and an hour or two of aggravation. Being the efficiency-minded coder that I am, I had originally declared tileData2 up there so I wouldn't be creating a whole new TileData object inside these nested loops, over and over. I was going to use the same TileData object and keep the whole thing neat and tidy. For over an hour I was stepping through insane numbers of lines, tracing out every variable and every property of most of those variables so much that my console became almost an unreadable mess. I was clearly passing in a new set of data from my JSON file, but the map seemed like everything was getting set to the last tile it drew... everything was crazy man! So, veterans will already know this but ECMAScript and AS3 both do Objects by reference, and when I was reusing this object and passing in new data, it was just overwriting the data in all of the tiles that had come before, because it was the same damn Object. So, after throwing a new TileData() here, I was able to create individual TileData Objects that aren't linked in any way!
- Line 82 (again) - new TileData(_mapData.tiles[mapLayerCt].layer[rowCt].row[colCt]); is the long string that i'm passing in, so basically what i'm doing is going to the _mapData object and saying from the "tiles" array, I want the mapLayerCt layer, and from the "layer" array I want the rowCt layer, and from the "row" array, I want the colCt Object
- Line 84 - pass that new TileData Object into the _tileData array
- Line 86 - here's where I start in with _flatTileData. _flatTileData is basically a "flattened _tileData" array. I remove the layer array so it's like if you're actually looking directly at the visual map you've drawn, you don't see the individual layers. I just want a flat row/column array of an aggregation of all tiles in all rows for that specific row/column tile. This comes in incredibly helpful when it comes to placing Towers onto the map. I check against this _flatTileData array that has already been processed and knows if anything in a single row/column tile isWalkable. And if anything in that tile isWalkable, then we can't place a tower there.
- Line 86 (again) - so imagine this loop is going through layer by layer and hitting these same row/column orders in this array. The very first time through here _flatTileData[rowCt][colCt] wont be set yet, so I check to see if _flatTileData[rowCt][colCt] is a TileData Object (meaning something is already there) and if it is, we continue to the next line.
- Line 88 - if the tileData2 Object I just created isWalkable, then I want to set this whole row/col tile set to isWalkable true. So the idea is, if i'm looking at a single row/col tile square, a 24x24px slice of my map, layer by layer, if any single one of those layers isWalkable == true, then set the whole tile to true.
- Line 97 - if no TileData Object is here yet, push the current tileData2 object into this slot
- Line 105 - start creating that background layer from the background tile object being passed in as the param data
- Line 110 - creating the first "layer" _tileData[0] array
- Line 122-123 - take that background tile data and create a new TileData Object then push that to my _tileData array as the 0th layer, rowCt row, and colCt column
- Line 126-127 - create a totally Different TileData Object so that these background tiles have nothing at all in common except the same data properties. But no memory addresses! Gotta keep em separated.
Taking a quick break so we can keep the scrolling down a little... Ok continuing on in Map.as
private function _drawMap():void {
var currentLayer:int = 0,
maxLayers:int = _mapData.numLayers;
if(_mapData.drawBkgd) {
maxLayers++;
}
for(currentLayer; currentLayer < maxLayers; currentLayer++) {
_drawLayer(currentLayer);
}
// flatten this map once it has been drawn
this.flatten();
// Map is done drawing and adding waypoints, so lets clean up WaypointManager
_wpMgr.handleEndpointAndSort();
}
private function _drawLayer(layer:int):void {
var srcImage:Image,
destPt:Point = new Point(),
destRect:Rectangle = new Rectangle(0, 0, _mapData.tileWidth, _mapData.tileHeight),
rowCt:int = 0,
colCt:int = 0,
tileData:TileData,
pt:Point = new Point();
for(rowCt; rowCt < _mapData.numRows; rowCt++)
{
for(colCt = 0; colCt < _mapData.numCols; colCt++)
{
tileData = _tileData[layer][rowCt][colCt];
// if this tile does not have a source image name for this layer, skip drawing it
if(tileData.srcImageName == '') {
continue;
}
var tmpTexture:Texture = Assets.ta.getTexture(tileData.srcImageName),
xOffset:int = (_mapData.tileWidth - tmpTexture.width) >> 1,
yOffset:int = (_mapData.tileHeight - tmpTexture.height) >> 1,
rect:Rectangle = new Rectangle(-xOffset, -yOffset, _mapData.tileWidth, _mapData.tileHeight),
texture:Texture = Texture.fromTexture(tmpTexture, null, rect);
srcImage = new Image(texture);
srcImage.x = pt.x = (colCt * _mapData.tileWidth);
srcImage.y = pt.y = (rowCt * _mapData.tileHeight);
addChild(srcImage);
if(tileData.isWaypoint) {
pt.x += mapOffsetX;
pt.y += mapOffsetY;
_wpMgr.addWaypoint(tileData, pt, halfTileWidth, halfTileHeight);
}
if(tileData.groupStartIconDir != '') {
// Adding mapOffsets again here because isWaypoint and groupStartIconDir
// will never exist on the same tile
pt.x += mapOffsetX;
pt.y += mapOffsetY;
_wpMgr.addGroupStartPosition(tileData.groupStartIconDir,
pt, halfTileWidth, halfTileHeight);
}
}
}
}
public function destroy():void {
removeFromParent(true);
}
public function checkCanPlaceTower(p:Point):Object {
var retObj:Object = {};
retObj.canPlace = false;
retObj.point = new Point(-1, -1);
// make sure we clicked inside the actual map before further checks
if(clickedInsideMap(p)) {
// p is a reference to coordinates from the stage, we want to remove the offset
// to get just the coordinates relative to the map itself for the array keys
var rowCt:int = int((p.y - mapOffsetX) / _mapData.tileHeight),
colCt:int = int((p.x - mapOffsetY) / _mapData.tileWidth),
t:TileData = _flatTileData[rowCt][colCt];
// If isWalkable == true or isTower == true, then canPlace = false,
// we cannot place tower on walkway/tower
retObj.canPlace = ((_flatTileData[rowCt][colCt].isWalkable == false)
&& (_flatTileData[rowCt][colCt].isTower == false));
if(retObj.canPlace) {
retObj.point.x = colCt * _mapData.tileWidth;
retObj.point.y = rowCt * _mapData.tileHeight;
}
}
return retObj;
}
public function placedTower(t:Tower):void {
var rowCt:int = int((t.y - mapOffsetX) / _mapData.tileHeight),
colCt:int = int((t.x - mapOffsetY) / _mapData.tileWidth);
_flatTileData[rowCt][colCt].isTower = true;
}
public function removeTower(t:Tower):void {
var rowCt:int = int((t.y - mapOffsetX) / _mapData.tileHeight);
colCt:int = int((t.x - mapOffsetY) / _mapData.tileWidth);
_flatTileData[rowCt][colCt].isTower = false;
}
public function clickedInsideMap(p:Point):Boolean {
var clickedMap:Boolean = false;
if(p.x > 0 && p.x < _mapWidthOffset && p.y > 0 && p.y < _mapHeightOffset)
{
clickedMap = true;
}
return clickedMap;
}
public function get paddedBounds():Rectangle {
var paddedBoundsRect:Rectangle = new Rectangle();
paddedBoundsRect.x -= tileWidth - mapOffsetX;
paddedBoundsRect.y -= tileHeight - mapOffsetY;
paddedBoundsRect.width = this.width + tileWidth + mapOffsetX;
paddedBoundsRect.height = this.height + tileHeight + mapOffsetY;
return paddedBoundsRect;
}
public function get startHP():int {
return _mapData.startHP;
}
public function get startGold():int {
return _mapData.startGold;
}
}
}
- Line 1 - function that draws the map
- Line 3 - maxLayers is set to the numLayers from the mapData. Remember though this does not include the background layer.
- Line 6 - if we chose to draw the background, then increment maxLayers to account for the added background layer
- Line 10 - here we're looping over each layer and calling _drawLayer, passing in the layer index to draw
- Line 14 - after all the layers and rows and columns and tiles have been drawn onto this Map Sprite, call this.flatten(). It's basically a Starling equivalent to Flash's cacheAsBitmap. Read more on Flattened Sprites from the Starling manual.
- Line 17 - make a call to the WaypointManager to let it know that we're done drawing the map, done adding waypoints, Map is done and needs its services no longer, so WaypointManager can clean up and handle making endpoints and spawnpoints out of the waypoints Map sent it.
- Line 20 -
- Line 29-32 - loop over map rows and columns
- Line 33 - get the TileData Object at this layer/row/col location
- Line 37 - if this TileData object at this location has a srcImageName property equal to '', then this is one of those tiles from the JSON that looked like "{}", it is empty and we don't want to draw anything in this tile, so continue lets us skip the rest of the code in this loop, and go on to the next loop iteration.
- Line 40 - get the tile image by passing the srcImageName for the tileData into Assets.ta.getTexture() this returns a Texture. As a reminder, srcImageName would look something like 'roads/road1_101'
- Line 41-42 - I am basically making sure that the tile image gets drawn into the exact center of the 24x24 tile. So if my road1_101 is really only 24x20px when you shave off the transparency, I don't want those 4 pixels lost by Starling trying to draw my horizontal road tile smushed up to the top of the 24x24px box. So I need to find the x/y offsets. So if my horizontal road tile is 24x20px the _mapData.tileWidth is 24. tmpTexture.width is 24 so that's a 0 for the offset. However with height: _mapData.tileHeight is 24, and tmpTexture.height is 20. (24 - 20) is 4 and then with the ">> 1" bitwise dividing that by two, our yOffset is now 2. So to center that tile, we need to offset the tile texture's x by 0 and y by 2.
- Line 43 - PROTIP! It took me FOREVER to figure out how to do this in Starling. In Flash's BitmapData you could just copy pixels with transparency and you'd turn out alright. After combing the Starling Forums for a long time and finding a lot of people with helpful posts, but none seemed to actually work. Maybe those posts were from older versions of the framework when that did work or something? Anyways, once again I wish I had saved these great solution posts I found so I could give credit, but I didn't. After trying to do this with Matrix class transformations and insane stuff, I just did this. Create a rectangle with a negative x/y using the offsets, and give the proper tile width/height you want then...
- Line 44 - Texture's fromTexture function takes a 3rd parameter, "frame" which is a Rectangle. If you check out the top of the Texture docs (not anywhere near the fromTexture function nor the frame param helping to explain what those do) there's a little paragraph there on Texture Frame that explains how this works. Basically this lets me draw anything inside my standard 24x24 tile with proper transparency and offsets.
- Line 46 - now that I've got the texture from line 43 that is a 24x24 image properly offset with transparency, I can pass that texture into new Image() to create a proper source image for the tile.
- Line 47-48 - set srcImage.x and pt.x (Point) to the colCt times tileWidth, so this gets the proper x (and y on the next line) coordinate for that srcImage should be drawn on, and pt gets passed later on... eh we'll look at that later
- Line 50 - srcImage has its proper x/y set, add it to this Map Sprite
- Line 52-55 - if this particular tile is a waypoint tile, add the offsetX and Y to the proper x/y values on pt. Remember, the mapOffsetX/mapOffsetY values were passed in as 36 because of the 36px left-of-the-map area and the 36px area above the map where the UI elements are. This makes sure our waypoints match up with the correct stage x/y coordinates when taking into account that Map might not be sitting at 0,0 on the stage. Then pass these params to WaypointManager's addWaypoint function to create a new waypoint here.
- Line 58 - I added a groupStartIconDir attribute to the map JSON tile data. This lets me specify a specific tile where I want an Enemy group start icon to show up. This is the on-map "next wave" type button right next to where the enemies will be coming from
- Line 71 - hmmm... with everything that's happening in this Map class, surely my destroy() should be bigger than this. I might need to look into that more.
- Line 75-77 - checkCanPlaceTower turns an Object that holds two different properties, canPlace which is a Boolean "can you place this here yes or no" and 'point'. The 'point' property just passes back (if the player can actually place a Tower here) the x/y coordinate of where the tower should be placed.
- Line 79 - do a quick check to make sure that the p:Point that was passed in is actually inside the x/y coordinates of the map, if the player is dragging the tower around the UI parts of the stage for instance, then clickedInsideMap would be false and we don't have to check anything else
- Line 82-84 - get the row count by taking the point's y value minus the offset, then divide that by the tileHeight. int() lets me truncate decimals so i'm getting a whole number that should be the current row index. Same for colCt getting the column index, then t:TileData is me getting the TileData object from those indices
- Line 88 - this is a bit unreadable here. Basically I'm checking if isWalkable is false (there's no road tile here) and isTower is false (there's no tower here) then canPlace is true. Otherwise, if either of those are true, canPlace will be false.
- Line 91-94 - if canPlace is true, set the point x/y properly and then return the retObj
- Line 99-104 - once the player places a tower, get the x/y coords from the t:Tower object, find the appropriate indices on _flatTileData and update that TileData's isTower with true so players can't place multiple towers on the same tile.
- Line 106-111 - exactly the opposite of the last function. When a player sells a Tower, that Tower gets passed in here so we can get its x/y coords, and set isTower = false so another Tower could be placed here.
- Line 113-121 - a point gets passed in and we simply check if that point is within the bounds of the map.
- Line 123-130 - this is a Getter function for paddedBounds() and it returns a Rectangle. This Rectangle defines the bounds of the map with the x/y offsets added in.
- Line 132-138 - Getter functions to access the private _mapData var's startHP and startGold properties
So there's the Map class, that's how I handle map tile data from JSON to "blitting" from a tilesheet in Starling.
As always, feel free to check out the finished product of my AS3 Starling TD Demo.
You can also find all of the code used in srcview.
Or you can download the whole project zipped up.
Or check out the repo on Bitbucket
Until next time, thanks for reading and I hope this was helpful
-Travis
3 Comments