Be a Supporter!

As3: Tile-Based Mechanics

  • 4,273 Views
  • 1 Reply
New Topic Respond to this Topic
MSGhero
MSGhero
  • Member since: Dec. 15, 2010
  • Online!
Forum Stats
Supporter
Level 16
Game Developer
As3: Tile-Based Mechanics Mar. 22nd, 2012 @ 07:04 PM Reply

As3: Tile-Based Mechanics
--------------------------------------
As3: Main
-------------

*** This tutorial requires a decent understanding of AS3. It is the basics of a tile-based engine, not a copy and paste cutout.

This tutorial is going to teach you how to create the basics for a tile-based game. Tile-based games are easy to program since they limit the freedom of the player, which makes coding easier for us :).

In a 2D XY tile-based game, the most noticeable thing is the grid. There is a specific width and height that each tile in the grid follows. Since everything in the game must be on (at least) one tile, an array within an array can be used to access each tile and any information it contains.

Code: Since each tile's width and height are the same size and the number of rows and columns doesn't change, these all can be set as constants.

private static const TILE_SIZE:uint = 50;
	private static const GRID_WIDTH:uint = 10;
	private static const GRID_HEIGHT:uint = 10;

The grid array should contain 0s if there is nothing on that tile at the start. But since you can add obstacles and other fun stuff, it helps to have the array written out somewhere in your code. It can be arrays within an array or vectors within vectors.

grid = [
	[0,0,0],
	[0,0,0]
	]
	//or
	grid = Vector.<Vector.<uint>>([
	             Vector.<uint>([0, 0, 0]),
	           Vector.<uint>([0, 0, 0])
			   ]);

Now to actually see your grid, we are going to create a sprite and use its graphics properties to draw the grid. Tweak these values as you wish.

private var gridSprite:Sprite = new Sprite();
	...
	grid.graphics.lineStyle(1);  //black line with a width of 1 pixel
	
	for (var i:uint = 0; i < GRID_WIDTH; i++) {
		grid.graphics.moveTo(i * TILE_SIZE, 0);
		grid.graphics.lineTo(i * TILE_SIZE, stage.height);  //vertical lines
	}
	for (i = 0; i < GRID_HEIGHT; i++) {
		grid.graphics.moveTo(0, i * TILE_SIZE);
		grid.graphics.lineTo(stage.width, i * TILE_SIZE);  //horizontal lines
	}
	
	grid.graphics.endFill();

Now for stuff on the grid: tile-based mechanics allow us to easily access a tile. If the sprite on the grid is at x = 54.35 and y = 181.85, this complicates accessing the information around the sprite. So we round everything's coordinates down to the nearest multiple of TILE_SIZE (unless that object is moving).

private function roundedDown(n:Number):int {
		return Math.floor(n / TILE_SIZE) * TILE_SIZE;
	}

If we do this for each value when it's necessary, our lovely sprite is now at x = 50 and y = 150 if TILE_SIZE is 50. Which means that in our grid array, it's at grid[1][3] or grid[3][1] depending on how you set the grid up (50 / TILE_SIZE, 150 / TILE_SIZE). For now, we will assume the sprite is a 50 x 50 square to make things even easier.

So our sprite is at grid[1][3]. What can we do with this information? For one, collisions. Let's say in our grid array, a 1 represents a tile that the sprite can't access, i.e. an obstacle. If we set grid[2][3] = 1, the sprite cannot move down (or some other direction depending on the setup) since the obstacle is blocking it's path of movement. If 2 sprites on the grid can't be overlapping, you can update the grid so that other sprites are considered obstacles. So for movement and collisions: "if the value of the next tile is 1, I can't move here."

if (grid[roundedDown(sprite.x) / TILE_SIZE + 1][roundedDown(sprite.x) / TILE_SIZE] == 1) {
		// if the tile below sprite's current position is 1...
		cantMoveHere();
	}
	
	else {
		canMoveHere();
	}

You know there can't be an x or y value less than 0 or greater than or equal to GRID_WIDTH and GRID_HEIGHT. The code above will break if the sprite's position + 1 is greater than or equal to GRID_WIDTH since grid[10] doesn't exist (grid[0] through grid[9] if GRID_WIDTH equals 10). So you will have to add a check to ignore cases when the code would otherwise break. So before the previous code:

if (grid[roundedDown(sprite.x) / TILE_SIZE < GRID_WIDTH - 1) {
	//if the sprite isn't on the last tile in the row, check the tile to the right
		previous code;
        }

I personally suggest using a tweening engine for movement since you know exactly where the sprite will end up. If the sprite is moving to the tile above it, the final result will be that your sprite's y value is TILE_SIZE less.

Since any tile on the grid is at an easy to access point, you can do fancy things such as highlighting a tile on a MOUSE_OVER event or have the sprite move to where you click. Just call roundedDown on mouseX and mouseY to get the position of the tile that the mouse is over. Then drawRect over the tile or do something with event.target, this part is open to whatever you please.

If you've played a Facebook game lately, it was probably a different kind of tile-based game, where the grid was rotated by 45 degrees. There is a library to make games like this called as3isolib. It's a pain to implement DefaultIsometricTransformation to everything you already have, so this tool is very useful and works the exact same as the rest of this tutorial. Plus since it's an XYZ grid rather than just XY, you can add height to certain tiles to create buildings, platforms, mountains, etc. There are a few tutorials on their site, so I won't go into anything here.

Any questions, ask away :)

Spysociety
Spysociety
  • Member since: Dec. 30, 2009
  • Offline.
Forum Stats
Member
Level 21
Blank Slate
Response to As3: Tile-Based Mechanics Mar. 22nd, 2012 @ 07:21 PM Reply

Nice tutorial MSGHero. Pretty clear.

By the way, if someone is interested to create a tile based engine with movie clips, you can create only one single tile clip, make all the different tiles inside keyframes and set the class as Tile for example. Then in a function you can do:

function buildMap(a:Array):void
{
          for(var i:int=0;i<a[0].length;i++)
          {
                        for(var j:int=0;j<a.length;j++)
                        {
                                   var tile:Tile=new Tile();
                                   tile.x=TILE_SIZE*i;
                                   tile.y=TILE_SIZE*j;
                                   tile.gotoAndStop(a[j][i]-1);
                                   addChild(tile);
                       }
        }
}