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 AndroidThis 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;
}
})();
All the code!
I could post the sprite sheet too…
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 ^^
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.