Uneven Terrain Clamping in Flash Games – AS3


This subject comes up a lot in flash game design. When I’ve got a character in a game I’m building, and I want him to be able to walk across uneven ground, and keep him mapped on the curves and slopes. It makes a flash game much more engaging, fun, and realistic when the character can go anywhere, rather than being just limited to flat planes floating around in space. While this demo and tutorial doesn’t cover the most comprehensive ground mapping for a character in flash, this is a quick and easy solution if you’re working on just a small game or some specific part. It also has the potential to be scaled up for a much larger scenario, which I’m sure you’ll be able to see.

So, without further adieu, here’s the type of flash game terrain clamping we’re going for:

Click the flash program to gain focus, and then use the arrow keys to move our character around. Left and Right WALK him along the corresponding directions, up JUMPS, and down will CROUCH. But the key focus of this flash as3 tutorial is the mapping to uneven ground.. Try walking our hero/character along the sloped hill, and see that he actually does. This is what we're going to achieve through the next couple of explanations and steps.

To show you how this effect is really done on the back-end, it is really quite simple. All I am using is a series of lines, horizontal and sloped, to make a ground collision space for our flash character.

After you setup your scenes, platformer levels, or what have you, you just need to go around on a new layer, drawing a movieclip-wrapped line over every entity you want your flash character to collide with. In the case for my picture above, I’ve got the four lines labeled as “line1″, “line2″, “line3″, and “line4″, all derive from a “line” class I created beforehand.

Every frame, I iterate over all available” lines”, run a hitTestObject() against my character, and reset the .y position accordingly.

 

What that code would look like is as follows:

    for(var i:Number = 1; i <= 4; i++){ //iterate over all lines    
        var _line:line = MovieClip(root)["line" + i];
        if(_line.height > 1){
            //we've got a SLOPED line
            if (hero_shell.hit.hitTestObject(_line)) {
                var ratio:Number = (hero_shell.x - _line.x) / (_line.width); //find our how far along we are
                if(hero_shell.y > _line.y - (_line.height * ratio)){
                    hero_shell.y = _line.y - (_line.height * ratio); //correct to that same ratio height
                    if(jump){ hero_shell.hero.play(); jump = false; } //we landed, show animation and turn off jump
                    break; //exit since we already found our collider
                }
            }
        }
        else{
            //we've got a HORIZONTAL LINE
            if (hero_shell.hit.hitTestObject(_line) && hero_shell.y > _line.y) {
                hero_shell.y = _line.y; //correct out hero.y height
                if(jump){ hero_shell.hero.play(); jump = false; } //we landed, show animation and turn off jump
                break; //exit since we already found our collider
            }
        }
    }

As general pseudo code, we loop across all available lines, first check if the height is only 1 pixel ( a horizontal line), or otherwise (a sloped line), and react accordingly.

If it’s a straight line,  simply set our chaacters height equal to line line, as height’s only 1 pixel, thereby pixel-perfect collision.

If it is sloped, first check the greedy hitTestObject(), and if it returns true, then calculate a ratio.  By ratio, I mean the following:\

Once we know what percentage of the distance we are horizontally over, it the same percentage of the height of the line, vertically.  This is exactly what we’re doing in this actionscript code:

 hero_shell.y = _line.y - (_line.height * ratio);

Of course we put the break; in after a collision is found to help optimize execution of the code. After the ground collision is found we can stop searching, we already found the only collision available, so move on to the next frame.

 

The way this code is written is actually very similar to what they do in 3D videogames as well.  Every frame, shoot a raycast down the -y axis, find collision with the ground plane, and remap the y distance for our character. If no collision is found, it must mean our character is below surface, so shoot a ray up the positive y-axis, and map using that collision instead.

 

**In the case I have a massive world with hundreds of collision lines integrated, there are ways to optimize.  Every frame, once I’ve found my collision, set a similar/close number to start  at for searching collision lines on next frame.  For example, if I collided with “line7″, set the start number to 6 next frame, as my likely next collision line will be “line6″ or “line8″, as those are the probable neighbors. This avoids the excess time needed searching through line indexes that aren’t likely to be hit. (Of course this assumed your lines are numbered left-to-right, lowest-to-highest.

Another good way to do collision detection, whether it be flash games or functional 3D worlds, is to use hierarchical volume bounding. The idea is to create a large bounding box or sphere around a complex object, and check for collision with that first. If that’s collided with, then check more specific bounding spaces within that. As so on and so forth, as much as you need to. A great example is in this picture below:

First check a large box around the character, then once that is detected, check the individual arm box, if that’s detected too, check specifically for the wrist within that arm. If that’s detexted, register the blood animation, take off damage, etc..

Checking large bounding boxes at firsts prevents me from checking the wrist, torso, each individual finger, etc.., EVERY SINGLE FRAME, drastically slowing down performance. Do quick, dirty calculations at first, and go down deeper once you’re sure; it’ll save time.

 

We could setup this same scenario for ground collision in flash games. Make large level test collision boxes, and once the player is inside specific zones, then check the specific lines. I promise it will really help performance.  But, of course.. If you’re building a massive framework for an adventure game, maybe it’s just best to go with the physics engine ;)

 

Also, if you guys were curious, he’s the entire code for the demo I created above, animations and all (it actually isn’t that much code at all). I won’t release the *.fla only because I don’t have copyright for the art, but it is simple enough for you guys to find your own assets.

//keycodes
var left:uint = 37;
var up:uint = 38;
var right:uint = 39;
var down:uint = 40;

var left_b:Boolean = false;
var up_b:Boolean = false;
var right_b:Boolean = false;
var down_b:Boolean = false;

var jump_force:Number = 0;
var jump:Boolean = false;

stage.addEventListener(KeyboardEvent.KEY_DOWN,kd);
stage.addEventListener(KeyboardEvent.KEY_UP,ku);

stage.addEventListener(Event.ENTER_FRAME,ef);

function kd(e:KeyboardEvent):void{
    if (e.keyCode==left){
        left_b = true;
    }
    if (e.keyCode==up){
        up_b = true;
    }
    if (e.keyCode==right){
        right_b = true;
    }
    if (e.keyCode==down){
        down_b = true;
    }
}

function ku(e:KeyboardEvent):void{
    if (e.keyCode==left){
        left_b = false;
    }
    if (e.keyCode==up){
        up_b = false;
    }
    if (e.keyCode==right){
        right_b = false;
    }
    if (e.keyCode==down){
        down_b = false;
    }
}

function ef(e:Event):void{
    if (left_b && !down_b){
        hero_shell.x-=4;
        hero_shell.scaleX = 1;
        if(hero_shell.hero.currentFrame < 34 || hero_shell.hero.currentFrame > 83 && !jump){
            hero_shell.hero.gotoAndPlay("WALK");
        }
    }
    else if (right_b && !down_b){
        hero_shell.x+=4;
        hero_shell.scaleX = -1;
        if(hero_shell.hero.currentFrame < 34 || hero_shell.hero.currentFrame > 83 && !jump){
            hero_shell.hero.gotoAndPlay("WALK");
        }
    }
    else{
        hero_shell.hero.gotoAndStop("IDLE");
    }
   
   
    for(var i:Number = 1; i <= 4; i++){
        var _line:line = MovieClip(root)["line" + i];
        if(_line.height > 1){
            //we've got a SLOPED line
            if (hero_shell.hit.hitTestObject(_line)) {
                var ratio:Number = (hero_shell.x - _line.x) / (_line.width); //find our how far along we are
                if(hero_shell.y > _line.y - (_line.height * ratio)){
                    hero_shell.y = _line.y - (_line.height * ratio); //correct to that same ratio height
                    if(jump){ hero_shell.hero.play(); jump = false; } //we landed, show animation and turn off jump
                    break; //exit since we already found our collider
                }
            }
        }
        else{
            //we've got a HORIZONTAL LINE
            if (hero_shell.hit.hitTestObject(_line) && hero_shell.y > _line.y) {
                hero_shell.y = _line.y; //correct out hero.y height
                if(jump){ hero_shell.hero.play(); jump = false; } //we landed, show animation and turn off jump
                break; //exit since we already found our collider
            }
        }
    }
   
    if (up_b && !jump){
        hero_shell.hero.gotoAndPlay("JUMP");
        jump = true;
        jump_force -= 30;
        hero_shell.y -= 10;
    }
   
    if (down_b && !jump){
        hero_shell.hero.gotoAndStop("CROUCH");
    }
   
    if(jump){ hero_shell.y += jump_force; jump_force+=2; if(jump_force > 10){ jump_force = 10;} }
    else{ jump_force = 0; }
   
    hero_shell.y += 10;
   
    if(hero_shell.x < 10){ hero_shell.x = 10; }
    else if(hero_shell.x > 630){ hero_shell.x = 630; }
}

Thanks for reading!

About Dave

Hey Guys! I'm Dave, an Ohio State CSE alumni who just recently started work in the industry. I'm a flash hobbyist at heart, and love making flash games and tutorials when the time permits. Check out what flash tutorials are available on the site, and don't forget to "Like Us!" on Facebook!