In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

Destructible Terrain

/**
 * Copyright devon_o ( http://wonderfl.net/user/devon_o )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/3xCz
 */

package 
{
    import com.bit101.components.Label;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Loader;
    import flash.display.LoaderInfo;
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.events.Event;
    import flash.net.URLRequest;
    import flash.system.LoaderContext;
    
    /**
     * From the fantastic tutorial / example here: http://gamedev.tutsplus.com/tutorials/implementation/coding-destructible-pixel-terrain/
     * Most of the original comments left intact
     * 
     * 
     * Arrow keys to move left and right and jump.
     * Mouse down to shoot
     * 
     * 
     * @author Devon O.
     */
    
    [SWF(width='465', height='465', backgroundColor='#000000', frameRate='60')]
    public class Main extends Sprite 
    {    
        private var player:Player;
        
        private var canvas:BitmapData;
        private var terrain:Bitmap;
        private var background:Bitmap;
        
        private var numItemsToLoad:int = 2;
        private var numItemsLoaded:int = 0;
        
        public function Main():void 
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        private function init(e:Event = null):void 
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;
            stage.showDefaultContextMenu = false;

            loadTextures();
        }
        
        private var loadText:Label;
        private function loadTextures():void
        {
            loadText = new Label(this, 50, 50, "Loading Textures...");
            
            var tLoader:Loader = new Loader();
            tLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onTerrainLoaded);
            tLoader.load(new URLRequest("http://assets.wonderfl.net/images/related_images/5/50/50ed/50ed97ea2bf41c27aa797fb95b12d239d1d89905"), new LoaderContext(true));
            
            var bgLoader:Loader = new Loader();
            bgLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onBgLoaded);
            bgLoader.load(new URLRequest("http://assets.wonderfl.net/images/related_images/e/e2/e248/e24840461132639617484f03fad8f40cc45e26b0"), new LoaderContext(true));
        }
        
        private function onTerrainLoaded(event:Event):void
        {
            var l:Loader = (event.currentTarget as LoaderInfo).loader;
            l.contentLoaderInfo.removeEventListener(Event.COMPLETE, onTerrainLoaded);
            terrain = l.content as Bitmap;
            
            numItemsLoaded++;
            if (numItemsLoaded == numItemsToLoad)
            {
                start();
            }
        }
        
        private function onBgLoaded(event:Event):void
        {
            var l:Loader = (event.currentTarget as LoaderInfo).loader;
            l.contentLoaderInfo.removeEventListener(Event.COMPLETE, onBgLoaded);
            background = l.content as Bitmap;
            
            numItemsLoaded++;
            if (numItemsLoaded == numItemsToLoad)
            {
                start();
            }
        }
        
        private function start():void
        {
            removeChild(loadText);
            
            canvas = new BitmapData(stage.stageWidth, stage.stageHeight, false, 0x000000);
            addChild(new Bitmap(canvas));
            
            player = new Player(100, 100);
            Controls.init(player, stage, terrain);
            
            addEventListener(Event.ENTER_FRAME, draw);
        }
        
        private function draw(event:Event):void
        {
            // update physics
            Controls.getPhysics().update();
              
            // load changes into the terrain
            Controls.getTerrain().update();

            /* Rendering */
            canvas.lock();
            canvas.fillRect(canvas.rect, 0x000000);
            
            // draw the background
            canvas.draw(background);

            // draw the terrain
            Controls.getTerrain().draw(canvas);

            // draw everything else
            Controls.getRenderer().draw(canvas);
            
            canvas.unlock();
        }
    }
}

interface IPhysicsObj 
{
    function getX():Number;
    function getY():Number;
    function getVX():Number;
    function getVY():Number;
    function setX(value:Number):void;
    function setY(value:Number):void;
    function setVX(value:Number):void;
    function setVY(value:Number):void;
    function checkConstraints():void;
}

import flash.display.BitmapData;
interface IRenderObj 
{
    function draw(canvas:BitmapData):void;
}


import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.display.Bitmap;
import flash.display.Stage;
import flash.ui.Keyboard;
class Controls {
    
    private static var player:Player;
    private static var stage:Stage;
    
    private static var terrain:Terrain;
    private static var physics:Physics;
    private static var renderer:Renderer;
    
    public static function init(p:Player, s:Stage, tImg:Bitmap):void
    {
        player = p;
        stage = s;
        
        terrain = new Terrain(tImg, 4);
        physics = new Physics();
        renderer = new Renderer();
        
        physics.add(player);
        renderer.add(player);

        stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
        stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
        stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
        stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
    }
    
    private static function onKeyDown(event:KeyboardEvent):void
    {
        if (event.keyCode == Keyboard.UP)
            player.jump();
        if (event.keyCode == Keyboard.LEFT)
            player.moveLeft();
        if (event.keyCode == Keyboard.RIGHT)
            player.moveRight();
    }
        
    private static function onKeyUp(event:KeyboardEvent):void
    {
        if (event.keyCode == Keyboard.LEFT)
            player.stopLeft();
        if (event.keyCode == Keyboard.RIGHT)
            player.stopRight();
    }
    
    private static function onMouseDown(event:MouseEvent):void
    {
        player.shoot();
    }
        
    private static function onMouseUp(event:MouseEvent):void
    {
        player.stopShooting();
    }
        
    public static function getMouseX():Number
    {
        return stage.mouseX;
    }
        
    public static function getMouseY():Number
    {
        return stage.mouseY;
    }
    
    public static function getTerrain():Terrain
    {
        return terrain;
    }
    
    public static function getPhysics():Physics
    {
        return physics;
    }
    
    public static function getRenderer():Renderer
    {
        return renderer;
    }
    
    /* RayCast */
    // Uses Bresenham's line algorithm to efficiently loop between two points, and find the first solid pixel
    // This particular variation always starts from the first point, so collisions don't happen at the wrong end.
    // returns an int array
    //       ||| x = ([0],[1]) point in empty space before collision point
    //       ||| o = ([2],[3]) collision point
    //(end)--||ox------- (start)
    //       |||
    // using http://www.gamedev.net/page/resources/_/reference/programming/sweet-snippets/line-drawing-algorithm-explained-r1275
    public static function rayCast(startX:int, startY:int, lastX:int, lastY:int):Vector.<int> 
    {
        var deltax:int = int(Math.abs(lastX - startX));        // The difference between the x's
        var deltay:int = int(Math.abs(lastY - startY));     // The difference between the y's
        var x:int = startX;                                        // Start x off at the first pixel
        var y:int = startY;                                        // Start y off at the first pixel
        var xinc1:int, xinc2:int, yinc1:int, yinc2:int;
        if (lastX >= startX)         // The x-values are increasing
        {               
            xinc1 = 1;
            xinc2 = 1;
        } else {                         // The x-values are decreasing
            xinc1 = -1;
            xinc2 = -1;
        }
  
        if (lastY >= startY)             // The y-values are increasing
        {                
            yinc1 = 1;
            yinc2 = 1;
        } else {                         // The y-values are decreasing
            yinc1 = -1;
            yinc2 = -1;
        }
        
        var den:int, num:int, numadd:int, numpixels:int;
        if (deltax >= deltay)            // There is at least one x-value for every y-value
        {        
            xinc1 = 0;                  // Don't change the x when numerator >= denominator
            yinc2 = 0;                  // Don't change the y for every iteration
            den = deltax;
            num = deltax / 2;
            numadd = deltay;
            numpixels = deltax;         // There are more x-values than y-values
        } else {                        // There is at least one y-value for every x-value
            xinc2 = 0;                          // Don't change the x for every iteration
            yinc1 = 0;                  // Don't change the y when numerator >= denominator
            den = deltay;
            num = deltay / 2;
            numadd = deltax;
            numpixels = deltay;         // There are more y-values than x-values
        }
        var prevX:int = startX;
        var prevY:int = startY;
  
        for (var curpixel:int = 0; curpixel <= numpixels; curpixel++) 
        {
            if (terrain.isPixelSolid(x, y))
                return new <int>[ prevX, prevY, x, y ];
                
            prevX = x;
            prevY = y;  
    
            num += numadd;              // Increase the numerator by the top of the fraction
    
            if (num >= den)             // Check if numerator >= denominator
            {             
                num -= den;               // Calculate the new numerator value
                x += xinc1;               // Change the x as appropriate
                y += yinc1;               // Change the y as appropriate
            }
    
            x += xinc2;                 // Change the x as appropriate
            y += yinc2;                 // Change the y as appropriate
        }  
        return new <int>[];
    }
    
    public static function randRange(max:Number, min:Number = 0, decimals:int = 0):Number {
        if (min > max) return NaN;
        var rand:Number = Math.random() * (max - min) + min;
        var d:Number = Math.pow(10, decimals);
        return ~~((d * rand) + 0.5) / d;
    }
    
    public static function explode(xPos:int, yPos:int, radius:Number):void
    {
        var radiusSq:Number = radius * radius;
  
        // loop through every x from xPos-radius to xPos+radius
        for (var x:int = xPos - int(radius); x < xPos + int(radius); x += terrain.destructionRes) 
        {
    
            // first make sure that the x is within terrain's boundaries
            if (x > 0 && x < terrain.width()) 
            {
                
                // next loop through every y pos in this x column
                for (var y:int = yPos - int(radius); y < yPos + int(radius); y += terrain.destructionRes) 
                {
                    if (y > 0 && y < terrain.height())
                    {
                        // first determine if this pixel (or if any contained within its square area) is solid
                        var solidX:int = 0;
                        var solidY:int = 0;
                        var solid:Boolean = false;
                        // loop through every pixel from (xPos,yPos) to (xPos + destructionRes, yPos + destructionRes)
                        // to find whether this area is solid or not
                        for (var i:int = 0; i < terrain.destructionRes && !solid; i++) 
                        {
                            for (var j:int = 0; j < terrain.destructionRes && !solid; j++) 
                            {
                                if (terrain.isPixelSolid(x + i, y + j)) 
                                {
                                    solid = true;
                                    solidX = x + i;
                                    solidY = y + j;
                                }
                            }
                        }
                        
                        if (solid) { // we know this pixel is solid, now we need to find out if it's close enough
                            var xDiff:Number = x - xPos;
                            var yDiff:Number = y - yPos;
                            var distSq:Number = xDiff * xDiff + yDiff * yDiff;
                            if (distSq < radiusSq)
                            {
                                // finally calculate the distance
                                var distance:Number = Math.sqrt(distSq);
                                
                                // the speed will be based on how far the pixel is from the center
                                var speed:Number = 800 * (1 - distance / radius);
                                if (distance == 0)
                                    distance = .001;    // prevent divide by zero in next two statements
                                
                                // velocity
                                var velX:Number = speed * (xDiff + randRange(10, -10, 2)) / distance;
                                var velY:Number = speed * (yDiff + randRange(10, -10, 2)) / distance;
                              
                                // create the dynamic pixel
                                var pixel:PhysicsPixel = new PhysicsPixel(terrain.getColor(solidX, solidY), x, y, velX, velY, terrain.destructionRes);
                                pixel.stickiness = 800;
                                physics.add(pixel);
                                renderer.add(pixel);
                              
                                // remove the static pixels
                                for (i = 0; i < terrain.destructionRes; i++) 
                                {
                                    for (j = 0; j < terrain.destructionRes; j++) 
                                    {
                                        terrain.removePixel(x + i, y + j);
                                    }
                                }
                            }
                        }
                    }    
                }
            }
        }
    }
}



import flash.display.BitmapData;
import flash.geom.Matrix;
import flash.geom.Rectangle;
class Bullet implements IPhysicsObj, IRenderObj
{
    private var x:Number;
    private var y:Number;
    private var lastX:Number;
    private var lastY:Number;
    private var velX:Number;
    private var velY:Number;
        
    public function Bullet(xpos:Number, ypos:Number, vx:Number, vy:Number) 
    {
        x = xpos;
        y = ypos;
        velX = vx;
        velY = vy;
    }
        
    /* INTERFACE IPhysicsObj */    
    public function getX():Number {return x;}    
    public function getY():Number {return y;}    
    public function getVX():Number {return velX;}    
    public function getVY():Number {return velY;}    
    public function setX(value:Number):void {x = value;}    
    public function setY(value:Number):void {y = value;}    
    public function setVX(value:Number):void {velX = value;}    
    public function setVY(value:Number):void {velY = value;}
        
    public function checkConstraints():void 
    {
        var collision:Vector.<int> = Controls.rayCast(int(lastX), int(lastY), int(x), int(y));
        if (collision.length > 0) 
        {
            Controls.getRenderer().remove(this);
            Controls.getPhysics().remove(this);
            Controls.explode(collision[2], collision[3], 30);
        }
        if (x > Controls.getTerrain().width() || x < 0 || y > Controls.getTerrain().height())
        {
            Controls.getRenderer().remove(this);
            Controls.getPhysics().remove(this);
        }
        lastX = x;
        lastY = y;
    }
        
    /* INTERFACE IRenderObj */
    private var r:Rectangle = new Rectangle();
    public function draw(canvas:BitmapData):void 
    {
        r.height = 8;
        r.width = 8;
        r.x = x - 4;
        r.y = y - 4;
        canvas.fillRect(r, 0xFF0000);
    }    
}



import flash.utils.getTimer;
class Physics 
{
    private var previousTime:Number;
    private var currentTime:Number;
    private var fixedDeltaTime:int = 16;
    private var fixedDeltaTimeSeconds:Number = Number(fixedDeltaTime) / 1000.00;
    private var leftOverDeltaTime:int = 0;
        
    private var objects:Vector.<IPhysicsObj>;
        
    public function Physics() 
    {
        objects = new Vector.<IPhysicsObj>();
    }
        
    public function add(obj:IPhysicsObj):void
    {
        objects[objects.length] = obj;
    }
        
    public function remove(obj:IPhysicsObj):void
    {
        var idx:int = objects.indexOf(obj);
        if (idx < 0) return;
        objects.splice(idx, 1);
    }
        
    public function update():void
    {
        // This game uses fixed-sized timesteps.
        // The amount of time elapsed since last update is split up into units of 16 ms
        // any left over is pushed over to the next update
        // we take those units of 16 ms and update the simulation that many times.
        // a fixed timestep will make collision detection and handling (in the Player class, esp.) a lot simpler
        // A low framerate will not compromise any collision detections, while it'll still run at a consistent speed.

        currentTime = getTimer();
        var deltaTimeMS:Number = currentTime - previousTime; // how much time has elapsed since the last update
        previousTime = currentTime; // reset previousTime
            
            
        // Find out how many timesteps we can fit inside the elapsed time
        var timeStepAmt:int = int(Number(deltaTimeMS + leftOverDeltaTime) / Number(fixedDeltaTime));
            
        // Limit the timestep amount to prevent freezing
        timeStepAmt = Math.min(timeStepAmt, 1);
            
        // store left over time for the next frame
        leftOverDeltaTime = int(deltaTimeMS) - (timeStepAmt * fixedDeltaTime);
            
            
        for (var iteration:int = 1; iteration <= timeStepAmt; iteration++) {
            for (var i:int = 0; i < objects.length; i++) { // loop through every PhysicsObj
              
                var obj:IPhysicsObj = objects[i];
                // get their velocity
                var velX:Number = obj.getVX();
                var velY:Number = obj.getVY();
             
                // add gravity
                velY += 980 * fixedDeltaTimeSeconds;
                obj.setVY(velY);
            
                // Always add x velocity
                obj.setX(obj.getX() + velX * fixedDeltaTimeSeconds);
                
                // if it's a player, only add y velocity if he's not on the ground.
                if (obj is Player) {
                    if (!(Player(obj).onGround && velY > 0))
                        obj.setY(obj.getY() + velY * fixedDeltaTimeSeconds);  
                    
                } else {
                    obj.setY(obj.getY() + velY * fixedDeltaTimeSeconds);
                }
                
                // allow the object to do collision detection and other things
                obj.checkConstraints();
            }
        }
    }
}


import flash.display.BitmapData;
import flash.geom.Rectangle;
class PhysicsPixel implements IPhysicsObj, IRenderObj
{
    private var x:Number;
    private var y:Number;
    private var lastX:Number;
    private var lastY:Number;
    private var velX:Number;
    private var velY:Number;
    private var bounceFriction:Number = 0.50;
    private var col:uint;
    private var size:int = 1;
    
    public var stickiness:Number = 1500;
        
    public function PhysicsPixel(c:uint, xx:Number, yy:Number, vX:Number, vY:Number, s:int) 
    {
        col = c;
        x = xx;
        y = yy;
        velX = vX;
        velY = vY;
        size = s;
    }
        
    /* INTERFACE IPhysicsObj */
    public function getX():Number {return x;}
    public function getY():Number {return y;}
    public function getVX():Number { return velX; }
    public function getVY():Number {return velY;}
    public function setX(value:Number):void {x = value;}
    public function setY(value:Number):void {y = value;}
    public function setVX(value:Number):void {velX = value;}
    public function setVY(value:Number):void {velY = value;}
        
    public function checkConstraints():void 
    {
        // Find if there's a collision between the current and last points
        var collision:Vector.<int> = Controls.rayCast(int(lastX), int(lastY), int(x), int(y));
        if (collision.length > 0) 
            collide(collision[0], collision[1], collision[2], collision[3]);
      
        // reset last positions
        lastX = x;
        lastY = y;
    
        // Boundary constraints... only remove the pixel if it exits the sides or bottom of the map
        if (x > Controls.getTerrain().width() || x < 0 || y > Controls.getTerrain().height())
        {
            Controls.getRenderer().remove(this);
            Controls.getPhysics().remove(this);
        }
    }
        
    /* INTERFACE IRenderObj */
    private var r:Rectangle = new Rectangle();
    public function draw(canvas:BitmapData):void 
    {
        r.width = size;
        r.height = size;
        r.x = x - (size >> 1);
        r.y = y - (size >> 1);
        canvas.fillRect(r, col);
    }
        
        
    /* Collide */
    // called whenever checkConstraints() detects a solid pixel in the way
    public function collide(thisX:int, thisY:int, thatX:int, thatY:int):void
    {
        // first determine if we should stick or if we should bounce
        if (velX * velX + velY * velY < stickiness * stickiness) // if the velocity's length is less than our stickiness property, add the pixel
        { 
            // remove this dynamic pixel
            Controls.getRenderer().remove(this);
            Controls.getPhysics().remove(this);
        } else { // otherwise, bounce
                
            // find the normal at the collision point
              
            // to do this, we need to reflect the velocity across the edge normal at the collision point
            // this is done using a 2D vector reflection formula ( http://en.wikipedia.org/wiki/Reflection_(mathematics) )
              
            var pixelNormal:Vector.<Number> = Controls.getTerrain().getNormal(int(thatX), int(thatY));
              
            var d:Number = 2 * (velX * pixelNormal[0] + velY * pixelNormal[1]);
              
            velX -= pixelNormal[0] * d;
            velY -= pixelNormal[1] * d;
              
            // apply bounce friction
            velX *= bounceFriction;
            velY *= bounceFriction;
              
            // reset x and y so that the pixel starts at point of collision
            x = thisX;
            y = thisY;
        }
    }
}


import flash.display.BitmapData;
import flash.geom.Rectangle;
import flash.utils.getTimer;
class Player implements IPhysicsObj, IRenderObj
{
    private var x:Number;
    private var y:Number;
    private var velX:Number;
    private var velY:Number;
    private var goLeft:Boolean;
    private var goRight:Boolean;
    private var shooting:Boolean;
    private var lastShot:Number;
    private var topBlocked:Boolean;
        
    private var playerWidth:int;
    private var playerHeight:int;
    
    public var onGround:Boolean;
        
    public function Player(xx:int, yy:int) 
    {
        x = xx;
        y = yy;
        velX = 0; 
        velY = 0; // set the initial velocity to 0
    
        // initialize the player as a 15x15 px box
        playerWidth = 15; 
        playerHeight = 15;
    }
        
    // Shooting toggles
    public function shoot():void
    {
        shooting = true;
    }
    public function stopShooting():void
    {
        shooting = false;  
    }
        
    // jump
    public function jump():void
    {
        if (onGround && !topBlocked && velY > -500)
            velY -= 500;
    }
        
    // moving toggles
    public function moveLeft():void
    {
        goLeft = true;
    }
    public function moveRight():void
    {
        goRight = true;
    }
    public function stopLeft():void
    {
        goLeft = false;
    }
    public function stopRight():void
    {
        goRight = false;
    }
        
    /* INTERFACE IRenderObj */
    private var r:Rectangle = new Rectangle();
    public function draw(canvas:BitmapData):void 
    {
        r.width = playerWidth;
        r.height = playerHeight;
        r.x = x - (playerWidth >> 1);
        r.y = y - (playerWidth >> 1);
        canvas.fillRect(r, 0x232323);
    }
        
    /* INTERFACE IPhysicsObj */
    public function getX():Number {return x;}
    public function getY():Number {return y;}
    public function getVX():Number {return velX;}
    public function getVY():Number {return velY;}
    public function setX(value:Number):void {x = value;}
    public function setY(value:Number):void {y = value;}
    public function setVX(value:Number):void {velX = value;}
    public function setVY(value:Number):void {velY = value;}
        
    public function checkConstraints():void 
    {
        // shooting
        if (shooting) {
            // Primary fire happens every 200 ms
            var now:int = getTimer();
            if (!(now - lastShot < 200)) 
            {
                // Create a vector between the player and the mouse, then normalize that vector (to change its length to 1)
                // after multiplying by the desired bullet speed, we get how fast along each axis we want the bullet to be traveling
                var diffX:Number = Controls.getMouseX() - x;
                var diffY:Number = Controls.getMouseY() - y;
                var len:Number = Math.sqrt(diffX * diffX + diffY * diffY);
                
                var bullet:Bullet = new Bullet(x, y, 1500 * diffX / len, 1500 * diffY / len);
                Controls.getPhysics().add(bullet);
                Controls.getRenderer().add(bullet);
                lastShot = now;
            }
        }
            
        // movement
        if (goLeft)
        {
            if (velX > -500)
                velX -= 40;
                    
        } else if (velX < 0) {
            velX *= 0.8; // slow down side-ways velocity if we're not moving left
        }
      
        if (goRight) {
            if (velX < 500)
                velX += 40;

        } else if (velX > 0) {
            velX *= 0.8;
        }
            
        // Collision detection/handling
        // Loop along each edge of the square until we find a solid pixel
        // if there is one, we find out if there's any adjacent to it (loop perpendicular from that pixel into the box)
        // Once we hit empty space, we move the box to that empty space
        onGround = false;
        for (var bottomX:int = int(x - (playerWidth >> 1)); bottomX <= int(x + (playerWidth >> 1)); bottomX++) 
        {
            if (Controls.getTerrain().isPixelSolid(bottomX, int(y + (playerHeight >> 1) + 1)) && (velY > 0))
            {
                onGround = true;
                for (var yCheck:int = int(y + playerHeight / 4); yCheck < int(y + (playerHeight >> 1)); yCheck++) 
                {
                    if (Controls.getTerrain().isPixelSolid(bottomX, yCheck))
                    {
                        y = yCheck - (playerHeight >> 1);
                        break;
                    }
                }
                if (velY > 0)
                {
                    velY *= -0.5; // bounce up
                }
            }
        }
            
            
        topBlocked = false;
        // start with the top edge
        for (var topX:int = int(x - (playerWidth >> 1)); topX <= int(x + (playerWidth >> 1)); topX++) 
        {
            if (Controls.getTerrain().isPixelSolid(topX, int(y - (playerHeight >> 1) - 1))) 
            { // if the pixel is solid
                topBlocked = true;
                if (velY < 0) 
                {
                    velY *= -0.5; 
                }
            } 
        }
            
        var xCheck:int;
        // loop left edge
        if (velX < 0) 
        {
            for (var leftY:int = int(y - (playerHeight >> 1)); leftY <= int(y + (playerHeight >> 1)); leftY++) 
            {
                if (Controls.getTerrain().isPixelSolid(int(x - (playerWidth >> 1)), leftY)) 
                {
                    // next move from the edge to the right, inside the box (stop it at 1/4th the player width)
                    for (xCheck = int(x - playerWidth / 4); xCheck < int(x - (playerWidth >> 1)); xCheck--) 
                    {
                        if (Controls.getTerrain().isPixelSolid(xCheck, leftY)) 
                        {
                            x = xCheck + (playerWidth >> 1); // push the block over 
                            break; 
                        }
                    }
                    if (leftY > y && !topBlocked) 
                    {
                        y -= 1;
                    } else {
                        velX *= -0.4;
                        x += 2;
                    }
                }
            }
        }
            
            
        // do the same for the right edge
        if (velX > 0) 
        {
            for (var rightY:int = int(y - (playerHeight >> 1)); rightY <= int(y + (playerHeight >> 1)); rightY++)
            {
                if (Controls.getTerrain().isPixelSolid(int(x + (playerWidth >> 1)), rightY))
                {
                    for (xCheck =  int(x + playerWidth / 4); xCheck < int(x + (playerWidth >> 1) + 1); xCheck++)
                    {
                        if (Controls.getTerrain().isPixelSolid(xCheck, rightY)) 
                        {
                            x = xCheck - (playerWidth >> 1);
                            break; 
                        }
                    }
                    if (rightY > y && !topBlocked)
                    {
                        y -= 1;
                    } else {
                        velX *= -0.4;
                        x -= 2;
                    }
                }
            }
        }
                
        // Boundary Checks
        if (x < 0 && velX < 0)
        {
            x -= x;
            velX *= -1;
        }
        if (y < 0 && velY < 0) 
        {
            y -= y;
            velY *= -1;
        }
        if (x > Controls.getTerrain().width() && velX > 0) 
        {
            x += Controls.getTerrain().width() - x;
            velX *= -1;
        }
        if (y + (playerHeight >>1) > Controls.getTerrain().height() && velY > 0) 
        {
            y += Controls.getTerrain().height() - y - (playerHeight >> 1);
            velY = 0;
            onGround = true;
        }
    }
}


import flash.display.BitmapData;
class Renderer 
{
        
    private var objects:Vector.<IRenderObj>;
        
    public function Renderer() 
    {
        objects = new Vector.<IRenderObj>();
    }
        
    public function draw(canvas:BitmapData):void
    {
        for (var i:int = 0; i < objects.length; i++)
        {
            var obj:IRenderObj = objects[i];
            obj.draw(canvas);
        }
    }
      
    public function add(obj:IRenderObj):void
    {
        objects[objects.length] = obj;
    }
        
    public function  remove(obj:IRenderObj):void 
    {
        var idx:int = objects.indexOf(obj);
        if (idx < 0) return;
            
        objects.splice(idx, 1);
    }
}



import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.geom.Point;
import flash.geom.Rectangle;
class Terrain 
{
        
    private var img:BitmapData;
    public var destructionRes:int;
        
    private var pixels:Vector.<uint>;
        
    public function Terrain(bmp:Bitmap, dr:int) 
    {
        img = new BitmapData(bmp.width, bmp.height, true, 0x00000000);
        img.draw(bmp);
        pixels = img.getVector(img.rect);
        bmp.bitmapData.dispose();
        bmp = null;
        destructionRes = dr;
    }
        
    public function width():int
    {
        return img.width;
    }
        
    public function height():int
    {
        return img.height;
    }
        
    public function draw(c:BitmapData):void
    {
        c.draw(img);
    }
        
    public function update():void
    {
        img.lock();
        img.setVector(img.rect, pixels);
        img.unlock();
    }
        
    // Determine if a pixel is solid based on whether or not it's transparent
    public function isPixelSolid(x:int, y:int):Boolean
    {
        if (x > 0 && x < img.width && y > 0 && y < img.height)
        {
            return (pixels[x + y * img.width] >> 24) != 0x0;
        }
        return false;
    }
        
    // Make a pixel non-solid
    public function removePixel(x:int, y:int):void
    {
        if (x > 0 && x < img.width && y > 0 && y < img.height)
            pixels[x + y * img.width] = 0x00000000;
    }
        
    // Get a pixel's color
    public function getColor(x:int, y:int):uint
    {
        if (x > 0 && x < img.width && y > 0 && y < img.height)
            return pixels[x + y * img.width];
        return 0;
    }
        
    // Find a normal at a position
    public function getNormal(x:int, y:int):Vector.<Number>
    {
        // First find all nearby solid pixels, and create a vector to the average solid pixel from (x,y)
        var avgX:Number = 0;
        var avgY:Number = 0;
        for (var w:int = -3; w <= 3; w++) 
        {
            for (var h:int = -3; h <= 3; h++) 
            {
                if (isPixelSolid(x + w, y + h)) 
                {
                    avgX -= w;
                    avgY -= h;
                }
            }
        }
        var len:Number = Math.sqrt(avgX * avgX + avgY * avgY); // get the distance from (x,y)
        return new <Number>[ avgX / len, avgY / len ]; // normalize the vector by dividing by that distance
    }
}