Tuesday, March 23, 2010

AS3 Dynamic Terrain




Ah the joys of Flash. Recently I needed to come up with a way of dynamically generating a realistic looking landscape for an isometric tile based game I am working on. After a bit of investigation into techniques that people have used I eventually successfully integrated an effective terrain generating algorithm in to the afore-mentioned game.

But how was this done? Once again through the beauty of BitmapData and it's widely ignored PerlinNoise method.

The beauty of this is that you are able to utilize PerlinNoise to generate what is called a "heightmap" which is then applied to the isometric tiles.

Firstly lets generate the perlin noise that we will use to generate a height map... 


var pmap:BitmapData = new BitmapData(400, 400);
var _seed:uint = Math.round(Math.random()*100);
pmap.perlinNoise(400,400, 6, _seed, true, false, 1, true);


This will generate a 400x400 BitmapData object that when rendered will look like the Photoshop "Clouds" filter (see image below). The beauty of Perlin Noise is that it produces gradual peaks and troughs which can be used to generate natural looking variants in terrain height.

In this example, we will be applying these values to a 7x7 isometric grid. Let's initialize where we will be storing this data:

// Set the grid size
var grid_width:uint = new uint(7);

// Initialize the heightmap
var heightmap:Array = new Array();


for (var i:uint=0; i < grid_width; i++) {
 
     heightmap[i] = new Array();
for (var j:uint=0; j < grid_width; j++) {
heightmap[i][j] = new uint();
}
}


Using the perlin map we've generated, let's divide the BitmapData object in to a 7x7 grid and examine the top-left pixel in each segment. We will then report the brightness of it to the heightmap array. (Darker pixels report a lower value, whilst brighter pixels report a higher value.)

We will also want to separately store the darkest pixel value for level correction in a later step...


var pixelPoint:Point = new Point();
var darkest_pixel:Number = 1;

//Divide the map in to a 7x7 grid and take data at each interval
for (i=0; i < grid_width; i++) {
 

    for (j=0; j < grid_width; j++) {
pixelPoint.x = Math.round((i/grid_width) * pmap.width)+1;
pixelPoint.y = Math.round((j/grid_width) * pmap.height)+1;
heightmap[i][j] = pmap.getPixel(pixelPoint.x, pixelPoint.y);
        heightmap[i][j] /= 0xffffff;

if (heightmap[i][j] < darkest_pixel) {
darkest_pixel = heightmap[i][j];
}
}
}


Now we will have a heightmap populated with pixel brightness data from the perlin noise. Not to mention a separate variable which contain the darkest pixel value. We will now use this lower limit value to stretch the heightmap values in to range between 0 and 1...

var brightest_pixel:Number = 0;

//Adjust values to a min of 0

for (i=0; i < grid_width; i++) {
for (j=0; j < grid_width; j++) {
heightmap[i][j] -= darkest_pixel;

        if (heightmap[i][j] > brightest_pixel) {
            brightest_pixel = heightmap[i][j];
        }
}
}

//Adjust values to highest value of 1
for (i=0; i < grid_width; i++) {



for (j=0; j < grid_width; j++) {
        heightmap[i][j] /= brightest_pixel;
    }
}


Now that we have a fully formed heightmap, we can apply it to change the height of the relevant cell in the isometric grid to produce natural looking terrain! (The following code assumes you have already got a flat isometric 7x7 grid on the stage called myTile[row#][column#])


var height_range:Number = 5;
for (i=0; i < grid_width; i++) {

for (j=0; j < grid_width; j++) {
  myTile[i][j].y -= heightmap[i][j]*height_range;
    }
}






Nice little effect, eh?

5 comments:

  1. Hey Ben, I'm a big fan of the exmortis series and I followed you on twitter and I found this page. Is this a page only for people that deal with flash games or something? If that's so, then maybe I should just stick to twitter. Please write back.

    ReplyDelete
  2. Dude, check your comments, I'm still waiting for my question to be answered. Please answer it. If you don't like the question or something, then tell me.

    ReplyDelete
  3. Dude, if you had of clicked your "Page Down" button just a couple of times on the front page, you would have your answer by now...

    How about I make it easy for you... go here...

    http://goandgetflucked.blogspot.com/2010/03/blogistential-angst.html

    ReplyDelete
  4. What happend with convertion on screen ( mouse) coordinates to iso co-ordinates ? If you use Sprite at Tile won't be problem, but when you have flat image (bitmapdata ) you didn't know when the user click.

    ReplyDelete
  5. Hey,
    Since I'm a little nub at as3. How can I implement this. Is there a complete code example?
    Thanks in advance!

    ReplyDelete