Bouncy2.js

April 28, 2016

This is the long awaited sequel to the classic game Bouncy.js.

Improvements include now rendering all animation on the canvas, as well as refined physics and collision detection. Mobile friendly with accelerometers as the primary user input.

The demo may take a few seconds to load. Control with arrow keys on a keyboard or accelerometers on a mobile device.

Live Demo (opens in new tab)

// Bouncy 2 : the House of Bounce, by Vlad Turda
var Bouncy = function( ID_bouncy ) {
    
    // Bouncy : get PlayArea element : private
    var PlayArea = document.getElementById( ID_bouncy );

    // Bouncy : game objects : private
    var PlayerOne = new this.Ball();
    var VP = new this.ViewPort();
    var Level = new this.Map();
    
    // Bouncy : game properties : private
    var animation_frame;
    var wake_lock;
    
    var config = { 
        map_name: 'map', 
        fps: 60, 
        gravity: { x: 0, y: 1 },
        sweep_degrees: 5,
        haptics: false 
    };
    
    //+ Bouncy : initialization : method, public
    this.init = function( init_config, init_ball ) {
        // Initialize config object
        config.map_name = init_config.map_name || config.map_name;
        config.fps = init_config.fps || config.fps;
        config.gravity = init_config.gravity || config.gravity;
        config.sweep_degrees = init_config.sweep_degrees || config.sweep_degrees;
        config.haptics = init_config.haptics || config.haptics;
        
        // Set ViewPort dimensions
        VP.setDimensions( PlayArea.clientWidth, PlayArea.clientHeight );
        
        // Add ViewPort canvas to PlayArea DIV
        PlayArea.appendChild( VP.getCanvas() );

        // Initialize the PlayerOne
        PlayerOne.pos = { x: 20, y: 20 };
        PlayerOne.vel = { x: 0, y: 0, rot: 0 };
        PlayerOne.acc = { x: 0, y: 0, jump: init_ball.jump_acc };
        PlayerOne.diameter = init_ball.diameter;
        PlayerOne.bounce = init_ball.bounce;
        PlayerOne.friction = init_ball.friction;
        PlayerOne.setImage( 'img/ball.png' );

        // Set some event handlers
        document.addEventListener( 'keydown', keyHandle );
        document.addEventListener( 'keyup', keyHandle );
        document.addEventListener( "fullscreenchange", fullScreenChange );
        document.addEventListener( "webkitfullscreenchange", fullScreenChange );
        document.addEventListener( "mozfullscreenchange", fullScreenChange );

        // Start animation on mouse click
        PlayArea.addEventListener( 'mousedown', requestStart );

        // Mobile event handlers
        if( /Mobi/.test( navigator.userAgent ) ) {
            config.haptics = true;
            window.addEventListener( 'devicemotion', deviceMotionHandle );
            PlayArea.addEventListener( 'touchstart', requestFullScreen );
            PlayArea.addEventListener( 'touchstart', requestWakeLock );
            PlayArea.addEventListener( 'touchstart', requestStart );
        }
    };
    
    var mapLoaded = function( MAP_DEF ) {
        PlayerOne.pos.x = MAP_DEF.spawn.x;
        PlayerOne.pos.y = MAP_DEF.spawn.y;
        
        startAnimation();
    };
    //+ Bouncy : start animation method : private
    var startAnimation = function() {
        // Get animation frame if it doesn't already exist
        if( !animation_frame ) {
            animation_frame = requestAnimationFrame( gameLoop );
        }
    };
    
    //+ Bouncy : game loop method : private
    var gameLoop = function() {
        // Resolve physics and check collision
        PlayerOne.resolvePhysics( { gravity: config.gravity } );
        PlayerOne.checkCollisionLayer( Level.layers[ 'collision' ], { sweep_degrees: config.sweep_degrees } );
        // Request animation layer for each animated Level layer
        //VP.clearBuffer();
        Object.keys(Level.layers).forEach( function( layer ) {
            if( Level.layers[ layer ].parameters.animate ) { 
                requestAnimationFrame( function() { Level.layers[ layer ].animate(); } ); 
            }
        } );
        
        // Call haptics function if enabled and there was a collision
        if( config.haptics && PlayerOne.cc.incident.total > 0 ) {
            haptics( PlayerOne.cc.incident.coincidence.magnitude ); }
        // Draw ViewPort
        drawViewPort();
        // Call for next animation frame
        requestAnimationFrame( gameLoop );
    };

    //+ Bouncy : calculate ViewPort offset method : private
    var calculateViewportOffset = function() {
        // Check left edge
        if( ( PlayerOne.pos.x - VP.offset.x < 100 ) && ( PlayerOne.pos.x > 100 ) ) {
            VP.offset.x = parseInt( PlayerOne.pos.x - 100 ); }
        
        // Check right edge
        if( ( PlayerOne.pos.x + PlayerOne.diameter - VP.offset.x > PlayArea.clientWidth - 100 )
                && ( PlayerOne.pos.x + PlayerOne.diameter < Level.layers[ 'collision' ].canvas.width - 100 ) ) {
            VP.offset.x = parseInt( PlayerOne.pos.x + PlayerOne.diameter - ( PlayArea.clientWidth - 100 ) ); }
        
        // Check top edge
        if( ( PlayerOne.pos.y - VP.offset.y < 100 ) && ( PlayerOne.pos.y > 100 ) ) {
            VP.offset.y = parseInt( PlayerOne.pos.y - 100 ); }
        
        // Check bottom edge
        if( ( PlayerOne.pos.y + PlayerOne.diameter - VP.offset.y > PlayArea.clientHeight - 100 )
                && ( PlayerOne.pos.y + PlayerOne.diameter < Level.layers[ 'collision' ].canvas.height - 100 ) ) {
            VP.offset.y = parseInt( ( PlayerOne.pos.y + PlayerOne.diameter ) - ( PlayArea.clientHeight - 100 ) ); }
    };
    
    //+ Bouncy : draw to buffer and update ViewPort method : private
    var drawViewPort = function() {
        calculateViewportOffset();
        
        //VP.clearBuffer();
        Object.keys(Level.layers).forEach( function( layer ) {            
            if(Level.layers[ layer ].parameters.drawable) { 
                VP.bufferImage( 
                    (( Level.layers[ layer ].parameters.animate ) ? Level.layers[ layer ].animate_buffer : Level.layers[ layer ].canvas),
                    VP.offset.x * Level.layers[ layer ].parameters.parallax_ratio, 
                    VP.offset.y * Level.layers[ layer ].parameters.parallax_ratio,
                    VP.getDimensions().x,
                    VP.getDimensions().y,
                    0,
                    0,
                    VP.getDimensions().x,
                    VP.getDimensions().y
                ); 

                if( layer === 'midground' ) {
                    VP.bufferImage( PlayerOne.getCanvas(), PlayerOne.pos.x, PlayerOne.pos.y );
                }
            }
        });
        
        VP.update();
    };
    
    //+ Bouncy : haptics method : private
    var haptics = function( vib_strength ) { 
        var vibrate = navigator.vibrate || navigator.mozVibrate;
        if( vib_strength > 5 && typeof vibrate === 'function' ) {
            vibrate( parseInt( vib_strength * 1.5 ) ); 
        }
    };
    
    
    //+ Bouncy : request full screen method : private 
    var requestFullScreen = function() {
        // Remove this touch listener
        PlayArea.removeEventListener( 'touchstart', requestFullScreen );
        // Request full screen from ViewPort
        VP.requestFullScreen();
        // Set landscape orientation lock if possible
        if( screen.orientation ) {
            screen.orientation.lock( 'landscape-primary' );
            window.screen.mozlockOrientation( 'landscape-primary' );
        }
    };
    
    //+ Bouncy : request NoSleep "wake lock" method : private
    var requestWakeLock = function() {
        // Remove this listener
        PlayArea.removeEventListener( 'touchstart', requestWakeLock );
        // Request wake lock from NoSleep.js
        wake_lock = new NoSleep();
        wake_lock.enable();
    };
    
    //+ Bouncy : start game method : private
    var requestStart = function() {
        // Remove this listener
        PlayArea.removeEventListener( 'mousedown', requestStart );
        PlayArea.removeEventListener( 'touchstart', requestStart );
        // Load map with startAnimation as a callback
        Level.loadMap( config.map_name, mapLoaded );
    };
    
    //+ Bouncy : "fullScreenChange" event handler method : private
    var fullScreenChange = function() {
        // Reset the ViewPort dimensions
        VP.setDimensions( PlayArea.clientWidth, PlayArea.clientHeight );
    };
    
    //+ Bouncy : mobile motion event handler method : private
    var deviceMotionHandle = function( e ) {
        // Calculate gravity vector
        var device_gravity = {
            x: ( e.acceleration.x - e.accelerationIncludingGravity.x ),
            y: ( e.acceleration.y - e.accelerationIncludingGravity.y ) };
        
        // Calculate gravity magnitude
        var device_gravity_magnitude = calcMagnitude( device_gravity.x, device_gravity.y );
        
        // Calculate gravity unit vector
        var device_gravity_unit_vector = {
            x: device_gravity.x / device_gravity_magnitude,
            y: device_gravity.y / device_gravity_magnitude };
        
        // Set global gravity vector
        config.gravity.y = -device_gravity_unit_vector.x * 1;
        config.gravity.x = -device_gravity_unit_vector.y * 1;
        
        // Set PlayerOne's acceleration
        PlayerOne.acc.x = ( Math.abs( PlayerOne.acc.x ) < Math.abs( e.acceleration.y ) ) ? e.acceleration.y * 0.5 : 0;
        PlayerOne.acc.y = ( Math.abs( PlayerOne.acc.y ) < Math.abs( e.acceleration.x ) ) ? e.acceleration.x * 0.5 : 0;
    };

    //+ Bouncy : key handler for desktop method : private
    var keyHandle = function( e ) {
        switch( e.type ) {
            case 'keydown':
                if( e.keyCode === 37 ) { PlayerOne.acc.x = -1; }
                if( e.keyCode === 39 ) { PlayerOne.acc.x = 1; }
                if( e.keyCode === 38 ) { PlayerOne.vel.y -= PlayerOne.acc.jump; }
                break;
            case 'keyup':
                if( e.keyCode === 37 ) { PlayerOne.acc.x = 0; }
                if( e.keyCode === 39 ) { PlayerOne.acc.x = 0; }
                break;
        }
        if( e.keyCode === 33 ) { PlayerOne.diameter += 5; }
        if( e.keyCode === 34 ) { PlayerOne.diameter -= 5; }
    };
    
    //+ Bouncy : console data log method : private
    var dataOut = function() {
        console.clear();
        console.log( 'PlayerOne Pos X: ' + PlayerOne.pos.x );
        console.log( 'PlayerOne Pos Y: ' + PlayerOne.pos.y );
        console.log( 'PlayerOne Vel X: ' + PlayerOne.vel.x );
        console.log( 'PlayerOne Vel Y: ' + PlayerOne.vel.y );
        console.log( 'Velocity Angle : ' + calcAngle( PlayerOne.vel.x, PlayerOne.vel.y ) );
    };
};

//+ Bouncy : prototype : Ball object
Bouncy.prototype.Ball = function() {
    // Every Ball object refers to its own functions
    var Ball = this;
    
    // Ball : properties : public
    this.pos = { x: 0, y: 0 };
    this.vel = { x: 0, y: 0, rot: 0 };
    this.acc = { x: 0, y: 0, jump: 0 };
    this.diameter;
    this.bounce;
    this.friction;
    this.cc = {
        next: { x: this.pos.x, y: this.pos.y},
        incident: { x: 0, y: 0, angle: 0, total: 0 },
        reflection: { x: 0, y: 0 },
        surface_normal: { x: 0, y: 0 }
    };

    var image = new Image();
    var canvas = document.createElement( 'canvas' );
    var ctx;

    this.setImage = function( image_path ) {
        canvas.width = this.diameter;
        canvas.height = this.diameter;
        image.src = image_path;
        image.onload = function() {
            ctx = canvas.getContext( '2d' );
            ctx.drawImage( this, 0, 0, canvas.width, canvas.height );
        };
    };
    
    this.rotate = function() {
        if( Math.abs(this.vel.rot) > 0.012 ) {
            ctx.translate( this.diameter / 2, this.diameter / 2 );
            ctx.rotate( this.vel.rot );
            ctx.translate( -this.diameter / 2, -this.diameter / 2 );
            ctx.drawImage( image, 0, 0, this.diameter, this.diameter );
        }
    };
    
    this.drawTo = function( target_ctx, x_offset, y_offset ) {
        target_ctx.drawImage( canvas, ( this.pos.x - x_offset ), ( this.pos.y - y_offset ), this.diameter, this.diameter );
    };
    
    this.getCanvas = function() { return canvas; };
    
    this.getCanvasCTX = function() { return ctx; };
    
    this.getImageData = function() {
        return ctx.getImageData( 0, 0, this.diameter, this.diameter );
    };
};

Bouncy.prototype.Ball.prototype.checkCollisionLayer = function( collision_layer, parameters ) {
    // Initialize this's collision data
    this.cc.next = { x: this.pos.x, y: this.pos.y };
    this.cc.incident = { x: 0, y: 0, angle: 0, total: 0 };
    this.cc.reflection = { x: 0, y: 0 };
    this.cc.surface_normal = { x: 0, y: 0 };

    // Calc variables
    var check_angle;
    var relative_coordinate;
    var check = { x: 0, y: 0 };
    var vf_i = 0;

    // Check velocity vector for collision, one unit vector at a time
    while( vf_i <= Math.ceil( this.vel.magnitude ) && this.cc.incident.total === 0 ) {
        // First advance the the sweep coordinates by 1 velocity unit
        if( vf_i > 0 ) {
            this.cc.next.x += this.vel.unit_vector.x;
            this.cc.next.y += this.vel.unit_vector.y; }

        // Sweep collision edge normal to velocity vector
        for( var degrees = -90; degrees <= 90; degrees += parameters.sweep_degrees ) {
            // Get relative relative_coordinate coordinates
            check_angle = this.vel.angle + degrees;
            relative_coordinate = getCircleCoordAtAngle( check_angle, this.diameter / 2, 1, 1 );
            // Get absolute coordinates
            check = {
                x: Math.round( this.cc.next.x + ( this.diameter / 2 ) + relative_coordinate.x ),
                y: Math.round( this.cc.next.y + ( this.diameter / 2 ) + relative_coordinate.y ) };
            // Check absolute coordinate in collision array for 255
            if( collision_layer.array[check.y][check.x] === 255 ) {
                this.cc.incident.angle += check_angle;
                this.cc.incident.total++; }
        } // End of sweep

        // Reflect velocity vector if collisions were detected
        if( this.cc.incident.total > 0 ) {
            // Calculate indcident reflection vector
            this.cc.incident.angle = this.cc.incident.angle / this.cc.incident.total;
            this.cc.incident.surface_normal = getCircleCoordAtAngle( this.cc.incident.angle, this.diameter / 2, 1, 1 );
            this.cc.incident.reflection = calcReflection( this.vel.unit_vector, this.cc.incident.surface_normal );

            // Calculate reflected velocity vector
            this.vel.x = this.cc.incident.reflection.x * this.vel.magnitude;
            this.vel.y = this.cc.incident.reflection.y * this.vel.magnitude;
            this.cc.incident.surface_normal.magnitude = calcMagnitude( this.cc.incident.surface_normal.x, this.cc.incident.surface_normal.y );
            this.vel.x -= ( ( 1 - this.bounce ) * this.vel.x ) * Math.abs( this.cc.incident.surface_normal.x / this.cc.incident.surface_normal.magnitude );
            this.vel.y -= ( ( 1 - this.bounce ) * this.vel.y ) * Math.abs( this.cc.incident.surface_normal.y / this.cc.incident.surface_normal.magnitude );

            // Subtract 1 velocity unit back from the current coordinates
            this.cc.next.x -= this.vel.unit_vector.x;
            this.cc.next.y -= this.vel.unit_vector.y;

            // Add back left over velocity as reflected vector
            if( vf_i < this.vel.magnitude ) {
                this.vel.magnitude = calcMagnitude( this.vel.x, this.vel.y );
                this.cc.next.x += ( this.cc.incident.reflection.x * ( this.vel.magnitude - vf_i ) );
                this.cc.next.y += ( this.cc.incident.reflection.y * ( this.vel.magnitude - vf_i ) );
            }

            // Calculate reflection to surface coincidence vector and magnitude
            this.cc.incident.coincidence = {
                    x: ( this.cc.incident.surface_normal.x / this.cc.incident.surface_normal.magnitude * this.vel.x ),
                    y: ( this.cc.incident.surface_normal.y / this.cc.incident.surface_normal.magnitude * this.vel.y ) };
            this.cc.incident.coincidence.magnitude = calcMagnitude( this.cc.incident.coincidence.x, this.cc.incident.coincidence.y );
        }

        vf_i++; // Increment velocity factor itterator\
    } // End of velocity check, while

    // Set final positions
    this.pos.x = Math.round( this.cc.next.x );
    this.pos.y = Math.round( this.cc.next.y );
};

Bouncy.prototype.Ball.prototype.resolvePhysics = function( state ) {
    // Add acceleration and gravity to velocity
    this.vel.y += this.acc.y + state.gravity.y;
    this.vel.x += this.acc.x + state.gravity.x;
    // Calculate velocity magnitude and angle
    this.vel.magnitude = calcMagnitude( this.vel.x, this.vel.y );
    this.vel.angle = calcAngle( this.vel.x, this.vel.y );
    // Calculate the velocity unit vector
    this.vel.unit_vector = {
        x: ( ( this.vel.magnitude !== 0 ) ? this.vel.x / this.vel.magnitude : 0 ),
        y: ( ( this.vel.magnitude !== 0 ) ? this.vel.y / this.vel.magnitude : 0 ) };
    // Calculate rotational velocity
    this.vel.rot = 0;
    if( calcMagnitude(this.vel.x, this.vel.y) >= 1 ) {
        this.vel.rot = 2 * ((this.vel.x*state.gravity.y)-(this.vel.y*state.gravity.x)) / this.diameter;
    }
    // Call this to redraw its identity canvas
    this.rotate();
};
    
//+ Bouncy : prototype : ViewPort object
Bouncy.prototype.ViewPort = function() {
    var canvas = document.createElement( 'canvas' );
    var buffer = document.createElement( 'canvas' );
    canvas.style.width = '100%';
    canvas.style.height = '100%';
    
    var ctx = canvas.getContext( '2d' );
    var buffer_ctx = buffer.getContext( '2d' );

    this.offset = { x: 0, y: 0 };
    
    this.setDimensions = function( d_x, d_y ) {
        canvas.width = d_x;
        canvas.height = d_y;
        buffer.width = d_x;
        buffer.height = d_y;
    };
    this.getDimensions = function() {
        return { x: canvas.width, y: canvas.height };
    };
    this.clear = function() {
        ctx.clearRect( 0, 0, canvas.width, canvas.height );
    };
    this.clearBuffer = function() {
        buffer_ctx.clearRect( 0, 0, buffer.width, buffer.height );
    };
    this.whiteBuffer = function() {
        buffer_ctx.fillStyle = 'white';
        buffer_ctx.fillRect( 0, 0, buffer.width, buffer.height );
    };
    this.bufferImageData = function( image_data, x, y ) {
        buffer_ctx.putImageData( image_data, x - this.offset.x, y - this.offset.y );
    };
    this.bufferImage = function( image, x, y ) {
        buffer_ctx.drawImage( image, x - this.offset.x, y - this.offset.y );
    };
    this.update = function() {
        this.clear();
        ctx.drawImage( buffer, 0, 0 );
    };
    this.requestFullScreen = function() {
        if( canvas.requestFullscreen ) { canvas.requestFullscreen(); }
        else if( canvas.msRequestFullscreen ) { canvas.msRequestFullscreen(); }
        else if( canvas.mozRequestFullScreen ) { canvas.mozRequestFullScreen(); }
        else if( canvas.webkitRequestFullscreen ) { canvas.webkitRequestFullscreen(); }
    };
    
    this.getCanvas = function() { return canvas; };
};

//+ Bouncy : prototype : Map object
Bouncy.prototype.Map = function( ) {
    // Each Map object may refer to its own Map functions
    var Map = this;
    
    // These are the available layers
    this.layers = [];
    this.layers_loaded = 0;
    
    //+ Map : load map method : method, public
    this.loadMap = function( map_name, callback ) {
        var map_parameters = document.createElement('script');
        map_parameters.onload = function() { loadLayers( BOUNCY_MAP_DEF, callback ); };
        map_parameters.src = 'maps/' + map_name + '/def_map.js';
        document.getElementsByTagName( 'head' )[0].appendChild(map_parameters);
    };
    
    var loadLayers = function( MAP_DEF, callback ) {
        MAP_DEF.layers.forEach( function( layer ) {
            // Create new layer object
            Map.layers[ layer.name ] = new Map.Layer( layer );
            Map.layers[ layer.name ].vp_dimensions = MAP_DEF.vp_dimensions;
            
            switch( layer.type ) {
                case 'collision':
                    Map.layers[ layer.name ].image.onload = function() {
                        Map.layers[ layer.name ].ctx = Map.layers[ layer.name ].canvas.getContext( '2d' );
                        Map.layers[ layer.name ].ctx.drawImage( this, 0, 0 );
                        
                        Map.layers[ layer.name ].imageData = Map.layers[ layer.name ].ctx.getImageData( 0, 0, this.width, this.height );
                        Map.layers[ layer.name ].array = [ ];
                        
                        for( var i_y = 0; i_y < this.height; i_y++ ) {
                            Map.layers[ layer.name ].array[i_y] = [];
                            for( var i_x = 0; i_x < this.width; i_x++ ) {
                                Map.layers[ layer.name ].array[ i_y ][ i_x ] = Map.layers[ layer.name ].imageData.data[ ( ( i_y * this.width + i_x ) * 4 ) + 3 ]; 
                            } 
                        }
                        
                        Map.layers_loaded++;
                        if( Map.layers_loaded === MAP_DEF.layers.length ) { callback( MAP_DEF ); }
                    };
                    // Set each layer's image source
                    Map.layers[ layer.name ].image.src = 'maps/' + MAP_DEF.name + '/' + layer.name + '.png';
                    
                    break;
                    
                case 'raster':
                    Map.layers[ layer.name ].image.onload = function() {
                        Map.layers[ layer.name ].ctx = Map.layers[ layer.name ].canvas.getContext( '2d' );
                        Map.layers[ layer.name ].ctx.drawImage( this, 0, 0 );
                        // If this is the collision layer, generate imageData array
                        
                        Map.layers_loaded++;
                        if( Map.layers_loaded === MAP_DEF.layers.length ) { callback( MAP_DEF ); }
                    };
                    // Set each layer's image source
                    Map.layers[ layer.name ].image.src = 'maps/' + MAP_DEF.name + '/' + layer.name + '.png';
                    
                    break;
                    
                case 'generate':
                    Map.layers[ layer.name ].ctx = Map.layers[ layer.name ].canvas.getContext('2d');
                    Map.layers[ layer.name ].animate_buffer.width = Map.layers[ layer.name ].parameters.dimensions.x;
                    Map.layers[ layer.name ].animate_buffer.height = Map.layers[ layer.name ].parameters.dimensions.y;
                    Map.layers[ layer.name ].animate_buffer_ctx = Map.layers[ layer.name ].animate_buffer.getContext('2d');
                    Map.layers[ layer.name ].generate( );
                    Map.layers_loaded++;
                    if( Map.layers_loaded === MAP_DEF.layers.length ) { callback( MAP_DEF ); }
                    break;
            }
            
            if( layer.animate ) { 
                Map.layers[ layer.name ].animate();
            }
        } );
    };
};

Bouncy.prototype.Map.prototype.Layer = function( parameters ) {
    this.parameters = parameters;
    
    this.image = document.createElement( 'img' );
    
    this.canvas = document.createElement( 'canvas' );
    this.canvas.width = this.parameters.dimensions.x;
    this.canvas.height = this.parameters.dimensions.y;
    
    this.animate_buffer = document.createElement( 'canvas' );
    this.animate_buffer.width = this.parameters.dimensions.x;
    this.animate_buffer.height = this.parameters.dimensions.y;
    this.animate_float = { x: 0, y: 0 };
};

Bouncy.prototype.Map.prototype.Layer.prototype.generate = function( ) {
    
    switch( this.parameters.objects.type ) {
        case 'clouds':
            for( var g_i = 0; g_i < this.parameters.objects.count; g_i++ ) {
                var random_start = { 
                    x: (Math.random() * (this.parameters.dimensions.x)), 
                    y: (Math.random() * (this.parameters.dimensions.y - this.parameters.objects.dimensions.y)) };
                
                var delta = { 
                    x: (Math.random()*this.parameters.objects.dimensions.variation.x) + this.parameters.objects.dimensions.x, 
                    y: (Math.random()*this.parameters.objects.dimensions.variation.y) + this.parameters.objects.dimensions.y };
                
                //var red = parseInt(Math.random()*255);
                //var green = parseInt(Math.random()*255);
                //var blue = parseInt(Math.random()*255);
                
                this.ctx.fillStyle = this.parameters.objects.color;
                
                if( random_start.x + delta.x < this.parameters.dimensions.x ) {
                    this.ctx.fillRect( random_start.x, random_start.y, delta.x, delta.y );       
                }
                else {
                    this.ctx.fillRect( random_start.x, random_start.y, this.parameters.dimensions.x-random_start.x, delta.y );
                    this.ctx.fillRect( 0, random_start.y, (random_start.x+delta.x)-this.parameters.dimensions.x, delta.y );
                }
            }    
        break;
    }
};

Bouncy.prototype.Map.prototype.Layer.prototype.animate = function( ) {
    
    switch( this.parameters.animate.type ) {
        case 'translate':
            this.animate_float.x += this.parameters.animate.speed.x;
            this.animate_float.y += this.parameters.animate.speed.y;
            
            if ( this.animate_float.x >= 1 || this.animate_float.y >= 1 ) {
                this.animate_buffer_ctx.clearRect(0, 0, this.parameters.dimensions.x, this.parameters.dimensions.y);
                this.animate_buffer_ctx.drawImage(
                    this.canvas,
                    this.animate_float.x, 
                    this.animate_float.y,
                    this.parameters.dimensions.x - this.animate_float.x,
                    this.parameters.dimensions.y - this.animate_float.y,
                    0,
                    0, 
                    this.parameters.dimensions.x - this.animate_float.x, 
                    this.parameters.dimensions.y - this.animate_float.y
                );
                this.animate_buffer_ctx.drawImage(
                    this.canvas,
                    0, 
                    0,
                    this.animate_float.x,
                    this.parameters.dimensions.y,
                    this.parameters.dimensions.x - this.animate_float.x,
                    this.animate_float.y, 
                    this.animate_float.x, 
                    this.parameters.dimensions.y
                );
                this.animate_float.x = 0;
                this.animate_float.y = 0;
                
                this.ctx.clearRect(0, 0, this.parameters.dimensions.x, this.parameters.dimensions.y);
                this.ctx.drawImage(this.animate_buffer, 0, 0);
            }
            break;
    }
};
Tags: JavaScript