64DSC - Fox vs Bunny - Devlog01

Posted by KeithK on April 20, 2014, 8:27 a.m.

Oops, a little late getting around to this project. I spent too much time pushing my last game Neon to mobile devices.

Neon on iOS

Neon on Android

This game has a suggested theme of restricted colour palette. I voted for noir, which is a limited colour palette. But as the theme is in the area of style I chose to go with a simple mechanic that I know I can pull off, and spend more time on the style of the game. So far I have spent maybe a day making and another day or two mulling it over. Sorry I'm not so good at keeping up with devlogs. The idea is this: A simple sidescrolling runner, through parallaxing woods in the moonlight. You play as a bunny being chased and captured by a fox. Or as the fox chasing and capturing the bunny. Simple obstacles to jump, and a sprint button and stamina bar to manage. So far you can only play as the bunny.

Check it out over on gameJolt:

Fox vs Bunny

 

var FB = {};

 

//CLASSES

(function(){

FB.Sprite = function() {

 

//VISIBILITY

this.visible = true;

this.alpha = 1;

 

this.scrollable = true;

 

//WHERE AND HOW BIG IN GAME WORLD

this.x = 0;

this.y = 0;

this.w = 128;

this.h = 128;

 

//WHERE AND HOW BIG ON SPRITE SHEET

this.sourceX = 0;

this.sourceY = 0;

this.sourceW = 128;

this.sourceH = 128;

 

   // COLLISION BUFFERS

   this.bT = 0,

   this.bR = 0,

   this.bB = 0,

   this.bL = 0,

//    this.b = 0,

 

//COLLISION GETTERS

this.centreX = function() { return this.x + (this.w / 2); }

   this.centreY =function() { return this.y + (this.h / 2); }

   //this.rotationCentreX = function() { return this.x + this.rX; }

   //this.rotationCentreY =function() { return this.y + this.rY; }

   this.halfWidth = function() { return (this.w - this.bL - this.bR) / 2; }

   this.halfHeight = function() { return (this.h - this.bT - this.bB) / 2; }

   this.left =function() { return this.x + this.bL; }

   this.right = function() { return this.x + this.w - this.bR; }

   this.top = function() { return this.y + this.bT; }

   this.bottom =function() { return this.y + this.h - this.bB; }

 

//DRAW THE SPRITE

this.render = function(draw,img,cam) {

if(this.visible) {

if(this.scrollable) {

draw.save();

if(this.alpha !== 1) draw.globalAlpha = this.alpha;

draw.drawImage(img,this.sourceX,this.sourceY,

this.sourceW,this.sourceH,

this.x,this.y,this.w,this.h);

draw.restore();

}

else {

draw.save();

if(this.alpha !== 1) draw.globalAlpha = this.alpha;

draw.drawImage(img,this.sourceX,this.sourceY,

this.sourceW,this.sourceH,

Math.floor(cam.x + this.x),

                                            Math.floor(cam.y + this.y),

                                            this.w,this.h);

draw.restore();

 

}

}

}

}

 

FB.createBunny = function() {

var B = new FB.Sprite();

B.sourceX = 0;

B.sourceY = 0;

B.sourceW = 128;

B.sourceH = 96;

B.x = 0;

B.y = 256;

B.w = B.sourceW;

B.h = B.sourceH;

 

B.bR = 20;

 

//PHYSICS PROPERTIES

B.vx = 0;

B.vy = 0;

   B.accelX = 0;

   B.accelY= 0;

   B.speedLimit= 3.5;

//    B.friction= 0.96;

   B.bounce= -0.7;

   B.gravity= 0.2;

   

   B.isGrounded = false;

   B.jumpForce = -50;

//    B.JUMPING = false;

 

//WHEN JUMP IS PRESSED

B.JUMP = false;

//WHEN JUMPED

B.JUMPED = true;

//HOW FAST WE MOVE

B.speed = 4;

//WHEN BOOST IS PRESSED

B.BOOST = false;

//HOW MUCH TO BOOST

B.gain = 2;

//MAX BOOST WE CAN HAVE

B.maxBoost = 5;

//HOW MUCH BOOST WE HAVE LEFT

B.boost = 5;

//HOW FAST BOOST DEPRECIATES

B.tire = 0.02;

//HOW FAST BOOST REACCUMULATES

B.regain = 0.01;

 

B.IDLE = 0;

B.RUNNING = 1;

B.SPRINTING = 2;

B.CRASHED = 3;

B.CAUGHT = 4;

B.state = 0;

 

B.applyPhysics = function() {

       this.vx += this.accelX;

       this.vy += this.accelY;

//        this.vx *= this.friction;

       this.vy += this.gravity;

   }

   

   B.jump = function() {

   if(this.state === this.CRASHED) {

   this.state = this.RUNNING;

   }

       this.vy += this.jumpForce;

       this.vy += 5;

       this.boost -= 0.4;

       this.isGrounded = false;

   }

   

   B.limitSpeed = function() {

       if(this.vx > this.speedLimit)

           this.vx = this.speedLimit;

       if(this.vx < -this.speedLimit)

           this.vx = -this.speedLimit;

       if(this.vy > this.speedLimit * 3)

           this.vy = this.speedLimit * 3;

       if(this.vy < -this.speedLimit)

           this.vy = -this.speedLimit;

   }

 

B.move = function() {

//AUTO RUN

switch(this.state) {

//WHEN WE'RE RUNNING

case this.RUNNING:

//IF WE HIT SPRINT

if(this.BOOST) {

//IF WE HAVE ANY LEFT

if(this.boost > 0) {

// AND WE'RE ON THE GROUND

if(this.isGrounded) {

//THEN WE GO NORMAL SPEED PLUS A LITTLE EXTRA

this.accelX = this.speed + this.gain;

//AND OUR RESERVES DEPLETE

this.boost -= this.tire;

}

//IF WE'RE NOT ON THE GROUND WE GO NORMAL SPEED

else this.accelX = this.speed;

}

//IF WE DON'T HAVE ANY BOOST LEFT WE GO NORMAL SPEED

else this.accelX = this.speed;

}

//IF WE'RE NOT HITTING BOOST

else {

//WE GO NORMAL SPEED

this.accelX = this.speed;

//AND REGAIN STRENGTH

this.boost += this.regain;

//BUT ONLY SO MUCH!

if(this.boost > this.maxBoost) this.boost = this.maxBoost;

}

break;

 

case this.CRASHED:

this.accelX = 0;

//if(Math.abs(this.accelX) < 0.09) this.accelX = 0;

break;

}

//WE ALWAYS NEED TO APPLY PHYSICS

this.applyPhysics();

//AND MOVE THE CHARACTER

this.x += this.vx;

this.y += this.vy;

//AND MAKE SURE THEY DON'T MOVE TOO FAST

this.limitSpeed();

//THIS SHOULD BE REPLACED, BUT PUT THEM ON A GROUND LEVEL

this.limit();

}

 

B.limit = function() {

if(this.y + this.h > 512) {

this.y = 512 - this.h;

this.vy = 0;

//this.isGrounded = true;

}

}

 

 

return B;

}

 

FB.createFox = function() {

var F = FB.createBunny();

F.sourceX = 0;

F.sourceY = 382;

F.sourceW = 128;

F.sourceH = 64;

F.x = -500;

//f.y = 

F.speed = 4.3;

 

//DISTANCE AT WHICH FOX STARTS SPRINTING

F.S = 550;

//DISTANCE TO WHICH FOX SPRINTS IF POSSIBLE

F.T = 450;

 

 

F.updateAI = function(bunnyX) {

//console.log(bunnyX-this.x,this.T,this.BOOST);

if(!this.BOOST) {

if(bunnyX - this.x > this.S) this.BOOST = true;

if(bunnyX - this.x + 100 < this.S) {

this.S = bunnyX - this.x + 100;

this.T = bunnyX - this.x - 50;

}

} else {

if(bunnyX - this.x < this.T) this.BOOST = false;

}

 

 

}

 

return F;

}

 

FB.createTree = function() {

var T = new FB.Sprite();

T.sourceX = 318;

T.sourceY = 0;

T.sourceW = 192;

T.sourceH = 512;

T.x = 100;

T.y = 0;

T.w = T.sourceW;

T.h = T.sourceH;

 

T.update = function(bunnyX) {

if(bunnyX - this.x > 540 + this.w) this.x = bunnyX + 700;

}

return T;

}

 

FB.createStone = function() {

var S = new FB.Sprite();

S.sourceX = 128;

S.sourceY = 0;

S.sourceW = 64;

S.sourceH = 64;

S.w = S.sourceW;

S.h = S.sourceH;

S.x = 960;

S.y = 512 - (S.h);

S.bR = 20;

 

S.TRIPPED = false;

 

S.update = function(bunnyX) {

if(bunnyX - this.x > 540 + this.w) this.reset(bunnyX);

}

 

S.reset = function(bunnyX) {

this.x = bunnyX + 700;

this.TRIPPED = false;

}

return S;

}

 

//BOOST BAR

FB.createStaminaBar = function(X) {

var B = {};

B.front = new FB.Sprite()

B.front.sourceX = 0;

B.front.sourceY = 448;

B.front.sourceW = 318;

B.front.sourceH = 32;

B.front.w = B.front.sourceW;

B.front.h = B.front.sourceH;

B.front.x = X;

B.front.y = 10;

B.front.scrollable = false;

 

B.back = new FB.Sprite()

B.back.sourceX = 0;

B.back.sourceY = 480;

B.back.sourceW = 318;

B.back.sourceH = 32;

B.back.w = B.back.sourceW;

B.back.h = B.back.sourceH;

B.back.x = X;

B.back.y = 10;

B.back.scrollable = false;

 

B.render = function(draw,img,cam) {

this.back.render(draw,img,cam);

this.front.render(draw,img,cam);

}

 

B.update = function(val) {

this.front.sourceW = val * this.back.w;

this.front.w = val * this.back.w;

}

return B;

}

 

//BUTTONS

FB.createJumpButton = function() {

var J = new FB.Sprite();

J.scrollable = false;

J.sourceX = 128;

J.sourceY = 384;

J.sourceW = 92;

J.sourceH = 64;

J.w = J.sourceW * 2;

J.h = J.sourceH * 2;

J.x = 10;

J.y = window.innerHeight - J.h - 10;

 

return J;

}

 

FB.createSprintButton = function() {

var S = new FB.Sprite();

S.scrollable = false;

S.sourceX = 222;

S.sourceY = 384;

S.sourceW = 92;

S.sourceH = 64;

S.w = S.sourceW * 2;

S.h = S.sourceH * 2;

S.x = window.innerWidth - S.w - 10;

S.y = window.innerHeight - S.h - 10;

 

return S;

}

 

FB.createBackDrop = function() {

var B = new FB.Sprite();

//B.scrollable = false;

B.sourceX = 194;

B.sourceY = 2;

B.sourceW = 60;

B.sourceH = 60;

B.w = window.innerWidth;

B.h = window.innerHeight;

B.x = 0;

B.y = 0;

 

return B;

}

 

FB.createPlayButton = function() {

var P = new FB.Sprite();

P.sourceX = 222;

P.sourceY = 320;

P.sourceW = 92;

P.sourceH = 64;

P.w = P.sourceW * 2;

P.h = P.sourceH * 2;

P.x = 600;

P.y = 500;

 

return P;

}

})();

 

 

 

//--------GAME--------//

 

(function(){

//MULTI PLATFORM CLEANUP

var requestAnimFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||

window.webkitRequestAnimationFrame;

window.requestAnimationFrame = requestAnimFrame;

 

//CANVAS ELEMENT AND ID

    var canvas = document.createElement("canvas");

    canvas.setAttribute("id","canvas");

 

//SET CANVAS TO SIZE OF WINDOW

    canvas.width  = window.innerWidth ;

    canvas.height = window.innerHeight;

    

    //MAKE IT SO!

    document.body.appendChild(canvas);

    

    //WHAT WE DRAW ON

    var draw = canvas.getContext("2d");

    

    //THE WORLD WE IN

    var gameWorld = {

    x: 0,

    y: 0

    };

    

    //THE SCREEN YOU SEE

    var cam = {

   x: 0,

   y: 0,

   width: canvas.width,

   height: canvas.height,

   

   rightInnerBoundary: function() {

   return this.x + (this.width) - (this.width / 4);

   },

   leftInnerBoundary: function() {

       return this.x + (this.width / 4);

   },

   topInnerBoundary: function() {

       return this.y + (this.height / 2) - (this.height / 4);

   },

   bottomInnerBoundary: function() {

       return this.y + (this.height / 2) + (this.height / 4);

   }

};

 

 

//  GAME STATES

var LOADING = 1;

var MENU = 2;

var PLAY = 3;

var GAMEOVER = 4;

var gameState = LOADING;

 

var GameOver = false;

 

//  OBJECT ARRAYS

var assetsLoaded = 0;

var assetsToLoad = [];

 

//  KEY CONTROLS for testing convenience

var UP = 38;

var DOWN = 40;

var LEFT = 37;

var RIGHT = 39;

var SPACE = 32;

 

//  TOUCH/MOUSE CONTROLS

var mouseX = 0;

var mouseY = 0;

 

//----CHARACTER CONTROLS

//WHEN JUMP IS PRESSED

//var JUMP = false;

//WHEN JUMPED

//var JUMPED = true;

//HOW FAST WE MOVE

//var speed = 2;

//WHEN BOOST IS PRESSED

//var BOOST = false;

//HOW MUCH TO BOOST

//var gain = 2;

//MAX BOOST WE CAN HAVE

//var maxBoost = 5;

//HOW MUCH BOOST WE HAVE LEFT

//var boost = 5;

//HOW FAST BOOST DEPRECIATES

//var tire = 0.02;

//HOW FAST BOOST REACCUMULATES

//var regain = 0.01;

 

//TOUCH EVENT LISTENER

window.addEventListener("keydown",keyPressed,false);

window.addEventListener("keyup",keyReleased,false);

window.addEventListener("touchstart",touchStart,false);

window.addEventListener("touchend",touchEnd,false);

window.addEventListener("mousedown",mouseDown,false);

window.addEventListener("mouseup",mouseUp,false);

 

function keyPressed(event) {

switch(event.keyCode) {

case SPACE:bunny.JUMP = true;break;

case RIGHT:bunny.BOOST = true;break;

}

}

function keyReleased(event) {

switch(event.keyCode) {

case SPACE:bunny.JUMP = false;break;

case RIGHT:bunny.BOOST = false;break;

}

}

function touchStart(event) {

//UPDATE TOUCH POSITIONS

for(var i=0; i<event.targetTouches.length; i++) {

touchX = event.targetTouches[i].pageX - canvas.offsetLeft;

touchY = event.targetTouches[i].pageY - canvas.offsetTop;

switch(gameState) {

 

case MENU:

if(touchX > playButton.x && touchX < playButton.x + playButton.w &&

touchY > playButton.y && touchY < playButton.y + playButton.h) {

initGame();

}

break;

 

case PLAY:

if(touchX > jumpButton.x && touchX < jumpButton.x + jumpButton.w &&

touchY > jumpButton.y && touchY < jumpButton.y + jumpButton.h)

bunny.JUMP = true;

if(touchX > sprintButton.x && touchX < sprintButton.x + sprintButton.w &&

touchY > sprintButton.y && touchY < sprintButton.y + sprintButton.h)

bunny.BOOST = true;

break;

}

}

}

function touchEnd(event) {

if(gameState === PLAY) {

bunny.JUMP = false;

bunny.BOOST = false;

}

}

function mouseDown(event) {

//UPDATE TOUCH POSITIONS

touchX = event.pageX - canvas.offsetLeft;

touchY = event.pageY - canvas.offsetTop;

switch(gameState) {

 

case MENU:

if(touchX > playButton.x && touchX < playButton.x + playButton.w &&

touchY > playButton.y && touchY < playButton.y + playButton.h) {

initGame();

}

break;

 

case PLAY:

if(touchX > jumpButton.x && touchX < jumpButton.x + jumpButton.w &&

touchY > jumpButton.y && touchY < jumpButton.y + jumpButton.h)

bunny.JUMP = true;

if(touchX > sprintButton.x && touchX < sprintButton.x + sprintButton.w &&

touchY > sprintButton.y && touchY < sprintButton.y + sprintButton.h)

bunny.BOOST = true;

break;

 

}

}

function mouseUp(event) {

if(gameState === PLAY) {

bunny.JUMP = false;

bunny.BOOST = false;

}

}

 

 

 

 

//LOAD SPRITESHEET

var img = new Image();

img.onload = loadHandler;

img.src = "images/FoxVsBunny.png";

assetsToLoad.push(img);

 

//----LOAD GAME OBJECTS

 

//BACK DROP

var backDrop = FB.createBackDrop();

 

//BUNNY CHARACTER

var bunny = FB.createBunny();

 

//FOX

var fox = FB.createFox();

 

//TREE QUEUE

var tree = FB.createTree();

var tree2 = FB.createTree();

tree2.x += 540;

 

//STONE QUEUE

var stone = FB.createStone();

 

var boostBar = FB.createStaminaBar(canvas.width/2);

 

//BUTTONS

var jumpButton = FB.createJumpButton();

var sprintButton = FB.createSprintButton();

 

var playButton = FB.createPlayButton();

 

//LOAD SOUND HERE

 

//WHEN EVERYTHING IS LOADED

function loadHandler() {

   assetsLoaded++;

   if(assetsLoaded === assetsToLoad.length) {

       console.log(assetsLoaded +" assets loaded");

       //image.removeEventListener("load", loadHandler, false);

       gameState = MENU;

   }

}

 

//MAIN ANIMATION LOOP

var tick = setInterval(update,1000/60);

 

function update() {

   switch(gameState) {

       case LOADING:

           console.log("loading...");

           break;

       case PLAY:

           play();

           break;

   }

   render();

}

 

function initGame() {

GameOver = false;

bunny = FB.createBunny();

bunny.state = bunny.RUNNING;

fox = FB.createFox();

fox.state = fox.RUNNING;

//TREE QUEUE

tree = FB.createTree();

tree2 = FB.createTree();

tree2.x += 540;

 

//STONE QUEUE

stone = FB.createStone();

 

gameState = PLAY;

}

 

//WHERE WE PLAY GAMES

function play() {

 

//SCROLL CAMERA

//    if(bunny.x < cam.leftInnerBoundary())

//        cam.x = Math.floor(bunny.x - (cam.width / 4));

//    if(bunny.y < cam.topInnerBoundary())

//        cam.y = Math.floor(bunny.y - (cam.height / 4));

   //if(bunny.right() > cam.rightInnerBoundary())

       cam.x = bunny.x - (cam.width / 2);

//    if(bunny.y + bunny.h > cam.bottomInnerBoundary())

//        cam.y = Math.floor(bunny.y + bunny.h - (cam.height / 4 * 3));

 

 

fox.isGrounded = false;

if(fox.y + fox.h > 510) fox.isGrounded = true;

 

bunny.isGrounded = false;

if(bunny.y + bunny.h > 510) bunny.isGrounded = true;

 

//MOVE BUNNY OBJECT

bunny.move();

 

//console.log(stone.x, fox.x, fox.JUMP);

if(stone.x > fox.x && stone.x - fox.x < 200 && fox.isGrounded) fox.jump();

 

 

//MOVE THE FOX

fox.move();

fox.updateAI(bunny.x);

 

//SEND DATA TO BOOST BAR

boostBar.update(bunny.boost/bunny.maxBoost);

 

//COLLISION WITH STONES

switch(FB.blockRectangle(bunny,stone,false)) {

case "bottom":

bunny.isGrounded = true;

break;

 

case "right":

if(!stone.TRIPPED) {

bunny.state = bunny.CRASHED;

bunny.x -= 5;

stone.TRIPPED = true;

}

break;

}

//BUNNY HOP

if(bunny.isGrounded) bunny.JUMPING = false;

 

if(bunny.JUMP && bunny.isGrounded) {

bunny.JUMP = false;

bunny.JUMPING = true;

bunny.jump();

}

 

//END CONDITIONS

if(bunny.right() - fox.right() < 32)

GameOver = true;

 

if(GameOver) gameState = MENU;

 

//UPDATE TREES

tree.update(bunny.x);

tree2.update(bunny.x);

 

//UPDATE STONES

stone.update(bunny.x);

 

 

}

 

//WHERE WE DRAW THINGS

function render() {

//CLEAR THE SCREEN

   draw.clearRect(0,0,canvas.width,canvas.height);

   

   //BACK DROP

backDrop.render(draw,img);

 

switch(gameState) {

 

case MENU:

playButton.render(draw,img);

break;

 

case PLAY:

//MOVE TO THE camS POSITION

draw.save();

draw.translate(-cam.x,-cam.y);

 

tree.render(draw,img);

tree2.render(draw,img);

bunny.render(draw,img);

fox.render(draw,img);

stone.render(draw,img);

 

boostBar.render(draw,img,cam);

jumpButton.render(draw,img,cam);

sprintButton.render(draw,img,cam);

 

 

draw.restore();

break;

}

 

 

}

 

})();

 

(function(){

 

FB.blockRectangle = function(r1, r2, bounce, side) {

   

   if(typeof bounce === "undefined")

       bounce = false;

   if(typeof side === "undefined")

       side = "all";

   

   var collisionSide = "";

   

   // DISTANCE VECTOR

   var vx = r1.centreX() - r2.centreX();

   var vy = r1.centreY() - r2.centreY();

   

   // COMBINED HALF WIDTHS/HEIGHTS

   var halfWidths = r1.halfWidth() + r2.halfWidth();

   var halfHeights = r1.halfHeight() + r2.halfHeight();

   

   // CHECK FIRST AXIS

   if(Math.abs(vx) < halfWidths) {

       // CHECK SECOND AXIS

       if(Math.abs(vy) < halfHeights) {

           

           // AMOUNT OF OVERLAP 

           var overlapX = halfWidths - Math.abs(vx);

           var overlapY = halfHeights - Math.abs(vy);

           

           if(overlapX >= overlapY){

               if(vy > 0) {

                   collisionSide = "top";

                   if(side === collisionSide || side === "all")

                       r1.y = r1.y + overlapY;

               }

               else {

                   collisionSide = "bottom";

                   if(side === collisionSide || side === "all")

                       r1.y = r1.y - overlapY;

               }

               //  BOUNCE

               if(bounce) r1.vy *= -1;

               else r1.vy = 0;

           }

           else {

               if(vx >= 0) {

                   collisionSide = "left";

                   if(side === collisionSide || side === "all")

                       r1.x = r1.x + overlapX;

               }

               else {

                   collisionSide = "right";

                   if(side === collisionSide || side === "all")

                       r1.x = r1.x - overlapX;

               }

               //  BOUNCE

               if(bounce) r1.vx *= -1;

               else r1.vx = 0;

           }            

       }

       else collisionSide = "none";

   }

   else collisionSide = "none";

   return collisionSide;

}

 

})();

 

Comments

Pirate-rob 11 years, 4 months ago

All the code!

KeithK 11 years, 4 months ago

I could post the sprite sheet too…

Pirate-rob 11 years, 4 months ago

It's up to you, but I think you should hide the code on your blog. Most of us have our own code to deal with and don't feel like reading and understanding someone else's code. Sprite sheet would be cool though ^^

Powerful Kyurem 11 years, 4 months ago

I broke your game. I lowered the boost bar to zero (possibly past 0), and the game froze. It didn't crash or anything. Everything simply froze.