Bouncy.js

Platforming game implementing simple phyics and pixel collision over bitmap file.

var Bouncy = function (div_play_area, div_ball, div_level, div_output, canvas_ball) {
    var level_array = [];
    var background_offset = 0;
    var collision_degree_increment = 3;
    var ball_image = new Image();
    var fps;
    var gravity;
    var ball_diameter;
    var ball_bounce;
    var ball_friction;
    var jump_velocity;
    var PlayArea = document.getElementById(div_play_area);
    var BallDiv = document.createElement('div');
    var CanvasBall = document.createElement('canvas');
    var Level = document.createElement('div');
    var Output = document.getElementById(div_output);

    Ball.appendChild(CanvasBall);
    PlayArea.appendChild(BallDiv);
    PlayArea.appendChild(Level);

    var ctx;

    var Ball = {
        pos: {x: 0, y: 0},
        vel: {x: 0, y: 0, rot: 0},
        acc: {x: 0, y: 0}
    };

    var Ymin;
    var Ymax;

    this.init = function (map_name, init_fps, init_gravity, init_ball_diameter, init_ball_bounce, init_ball_friction, init_jump_velocity) {
        //Setup instance
        fps = init_fps;
        gravity = init_gravity;
        ball_diameter = init_ball_diameter;
        ball_bounce = init_ball_bounce;
        ball_friction = init_ball_friction;
        jump_velocity = init_jump_velocity;

        //Setup Canvas
        ball_image.src = './img/ball.png';
        ball_image.onload = function () {
            CanvasBall.setAttribute('width', ball_image.width);
            CanvasBall.setAttribute('height', ball_image.height);
            CanvasBall.style.width = CanvasBall.style.height = ball_diameter + 'px';
            ctx = CanvasBall.getContext('2d');
            ctx.drawImage(ball_image, 0, 0);
        }

        Ymin = PlayArea.clientHeight;
        Ymax = PlayArea.clientHeight;

        //Set ball
        Ball.pos = {x: 50, y: 20};
        Ball.vel = {x: 0, y: 0, rot: 0};
        Ball.acc = {x: 0, y: 0};

        //Set Physics
        Ball.acc.y = gravity;

        document.onkeydown = keyHandle;
        document.onkeyup = keyHandle;

        //Get level_array
        Level.style.backgroundImage = 'url("./maps/' + map_name + '/background.png")';
        getMap(map_name, gameLoop);
    };

    var gameLoop = function () {
        resolvePhysics();
        checkCollision();
        setPosition(Ball, Math.round(Ball.pos.x), Math.round(Ball.pos.y));
        rotateBall();
        //ballStats();
    };

    var getMap = function (mapfile, callback) {
        var ajaxRequest = new XMLHttpRequest();
        var response;
        ajaxRequest.onreadystatechange = function () {
            if (ajaxRequest.readyState == 4) {
                level_array = JSON.parse(ajaxRequest.responseText);
                setInterval(callback, Math.round(1000 / fps));
            }
        }
        ajaxRequest.open("GET", "/load/" + mapfile, true);
        ajaxRequest.send(null);
    }

    var rotateBall = function () {
        ball_circum = ball_diameter * Math.PI;

        Ball.vel.rot = Math.round(Ball.vel.x) / ball_circum * 360;

        ctx.translate(ball_image.height / 2, ball_image.width / 2);
        ctx.rotate(Ball.vel.rot * Math.PI / 180);
        ctx.translate( - ball_image.height / 2, - ball_image.width / 2);
                ctx.drawImage(ball_image, 0, 0);
    };

    this.drawMap = function () {
        for (iy = 0; iy < 10; iy++) {
            for (ix = 0; ix < 100; ix++) {
                if (level_array[iy].charAt(ix) == '1') {
                    drawPixel(ix, iy);
                }
            }
        }
    };

    var ballStats = function () {
        Output.innerHTML = 'Ball Pos X: ' + Ball.pos.x + 
                           ' Ball Pos Y: ' + Ball.pos.y + 
                           ' MinY: ' + Ymin + ' ';

        Output.innerHTML += 'Ball Vel X: ' + Ball.vel.x + 
                            ' Ball Vel Y: ' + Ball.vel.y + ' ';

        Output.innerHTML += 'Ball Nxt X: ' + (Ball.pos.x + Ball.vel.x) + 
                            ' Ball Nxt Y: ' + (Ball.pos.y - Ball.vel.y) + ' ';

        Output.innerHTML += 'angle : ' + calcAngle(Ball.vel.x, Ball.vel.y) + ' ';
    };

    //Collision Math Functions-------------------------
    var checkCollision = function () {
        var velocityMagnitude = calcMagnitude(Ball.vel.x, Ball.vel.y);
        var velocityUnit = new Object();

        if (velocityMagnitude != 0) {
            velocityUnit.x = Ball.vel.x / velocityMagnitude;
            velocityUnit.y = Ball.vel.y / velocityMagnitude;
        } else {
            velocityUnit.x = 0;
            velocityUnit.y = 0;
        }

        var surfNormal = new Object();
        surfNormal.x = 0;
        surfNormal.y = 0;

        var checkX = 0;
        var checkY = 0;

        var colReflection;

        var finalCoord = new Object();
        finalCoord.x = Ball.pos.x;
        finalCoord.y = Ball.pos.y;
        finalCoord.colX = 0;
        finalCoord.colY = 0;
        finalCoord.numberOfCollisions = 0;
        finalCoord.xcol = false;
        finalCoord.ycol = false;

        var velAngle = calcAngle(Ball.vel.x, Ball.vel.y);

        for (i = 0; i <= Math.round(velocityMagnitude); i++) {
            for (degrees = -90; degrees <= 90; degrees += collision_degree_increment) {
                var checkAngle = velAngle + degrees;
                if (checkAngle < 0) {
                    checkAngle += 360;
                } else if (checkAngle >= 360) {
                    checkAngle -= 360;
                }

                var circleX = Math.round(getCircleCoordAtAngle(checkAngle, ball_diameter / 2, 1, 1).x);
                var circleY = Math.round(getCircleCoordAtAngle(checkAngle, ball_diameter / 2, 1, 1).y);

                checkX = Math.round(Ball.pos.x + (ball_diameter / 2) + (velocityUnit.x * i) + circleX);
                checkY = Math.round(Ball.pos.y + (ball_diameter / 2) - (velocityUnit.y * i) - circleY);

                if (level_array[checkY][checkX] == '1' || checkX < 1 || checkX == level_array[0].length || checkY == Ymax || checkY <= 1) {
                    finalCoord.colX += checkX;
                    finalCoord.colY += checkY;
                    finalCoord.numberOfCollisions++;
                    finalCoord.ycol = true;
                } else if (degrees == 90 && level_array[Math.round(Ball.pos.y + ball_diameter) + 1][Math.round(Ball.pos.x + ball_diameter / 2)] != '1') {
                    Ymin = Ymax;
                }
            }

            if (finalCoord.ycol == true) {
                Ymin = Ball.pos.y;
                finalCoord.colX = Math.round(finalCoord.colX / finalCoord.numberOfCollisions);
                finalCoord.colY = Math.round(finalCoord.colY / finalCoord.numberOfCollisions);
                surfNormal.x = Math.round(Ball.pos.x + Ball.clientWidth / 2) - finalCoord.colX;
                surfNormal.y = finalCoord.colY - Math.round(Ball.pos.y + Ball.clientWidth / 2);
                Ball.vel.x = (colReflection.x * (velocityMagnitude)); 
                Ball.vel.y = (colReflection.y * (velocityMagnitude)); 
                var xMod = ( (1-ball_bounce) * Ball.vel.x) * Math.abs(surfNormal.x / calcMagnitude(surfNormal.x, surfNormal.y) ); 
                var yMod = ( (1-ball_bounce) * Ball.vel.y) * Math.abs(surfNormal.y / calcMagnitude(surfNormal.x, surfNormal.y) ); 
                Ball.vel.x -= xMod; 
                Ball.vel.y -= yMod; break; 
            } else { 
                finalCoord.x = Math.round(Ball.pos.x + (velocityUnit.x * i)); 
                finalCoord.y = Math.round(Ball.pos.y - (velocityUnit.y * i)); 
            } 
        } 

        //Move the viewport across the map 
        if(finalCoord.x - background_offset > PlayArea.clientWidth - 100 && Ball.vel.x > 0 && finalCoord.x + 100 < level_array[0].length)	{
                background_offset = finalCoord.x - (PlayArea.clientWidth - 100);
                Level.style.backgroundPosition = -1 * background_offset + "px";
            }

            if (finalCoord.x - background_offset < 100 && Ball.vel.x < 0 && finalCoord.x - 100 > 0) {
                background_offset = finalCoord.x - 100;
                Level.style.backgroundPosition = -1 * background_offset + "px";
            }

            //Set final positions
            Ball.pos.x = finalCoord.x;
            Ball.pos.y = finalCoord.y;
        }

        var resolvePhysics = function () {
            Ball.vel.y += Ball.acc.y;
            Ball.vel.x += (Math.abs(Ball.vel.x) < 15) ? Ball.acc.x : 0;

            if (Ball.pos.y == Ymin) {
                Ball.vel.y = (Math.abs(Ball.vel.y - Ball.acc.y) <= 1) ? 0 : Ball.vel.y;
                Ball.vel.x = (Math.abs(Ball.vel.x) <= 1) ? 0 : ball_friction * Ball.vel.x;
            }
        };

        var setPosition = function (Element, posX, posY) {
            Element.style.left = posX - background_offset + "px";
            Element.style.top = posY + "px";
        };

        var keyHandle = function (e) {
            switch (e.type) {
                case 'keydown':
                    if (e.keyCode == 37) {
                        Ball.acc.x = -2;
                    }
                    if (e.keyCode == 39) {
                        Ball.acc.x = 2;
                    }
                    if (e.keyCode == 38) {
                        Ball.vel.y -= jump_velocity;
                    }
                    break;

                case 'keyup':
                    if (e.keyCode == 37) {
                        Ball.acc.x = 0;
                    }
                    if (e.keyCode == 39) {
                        Ball.acc.x = 0;
                    }
                    break;
            };

            if (e.keyCode == 33) {
                ball_diameter += 10;
                CanvasBall.style.width = ball_diameter + 'px';
                CanvasBall.style.height = ball_diameter + 'px';
            }

            if (e.keyCode == 34) {
                ball_diameter -= 10;
                CanvasBall.style.width = ball_diameter + 'px';
                CanvasBall.style.height = ball_diameter + 'px';
            }
        }

        var resizeRefresh = function () {
            Ball.pos.x = boxX;
            Ball.pos.y = boxY;
        };

        return this;
    };
};