Skip to content

Instantly share code, notes, and snippets.

@killwing
Last active December 22, 2015 16:29
Show Gist options
  • Save killwing/6499826 to your computer and use it in GitHub Desktop.
Save killwing/6499826 to your computer and use it in GitHub Desktop.
a HTML5 canvas based simplified copycat of the shooting game in Wechat 5.0
<!doctype html>
<html>
<head>
<title>striker</title>
</head>
<body align="center" style="margin:0;">
<canvas id="canvas" width="1500" height="900"></canvas>
<script>
// class inherit
function inherits(ctor, superCtor) {
ctor.super_ = superCtor;
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
};
// collision checking
function pixelCollide(object1, object2) {
var xMin = Math.max(object1.x, object2.x);
var yMin = Math.max(object1.y, object2.y);
var xMax = Math.min(object1.x + object1.width, object2.x + object2.width);
var yMax = Math.min(object1.y + object1.height, object2.y + object2.height);
for (var x = xMin; x < xMax; ++x) {
for (var y = yMin; y < yMax; ++y) {
var pixel1 = ((x - object1.x) + (y - object1.y) * object1.width) * 4 + 3;
var pixel2 = ((x - object2.x) + (y - object2.y) * object2.width) * 4 + 3;
if (object1.data[pixel1] !== 0 && object2.data[pixel2] !== 0) {
return true;
}
}
}
return false;
}
function boundingBoxCollide(object1, object2) {
var top1 = object1.y;
var left1 = object1.x;
var bottom1 = object1.y + object1.height;
var right1 = object1.x + object1.width;
var top2 = object2.y;
var left2 = object2.x;
var bottom2 = object2.y + object2.height;
var right2 = object2.x + object2.width;
if (bottom1 < top2 || top1 > bottom2 || right1 < left2 || left1 > right2) {
return false;
}
return true;
};
function checkCollide(object1, object2, accurate) {
if (boundingBoxCollide(object1, object2)) {
if (accurate) {
return pixelCollide(object1, object2);
}
return true;
}
return false;
}
// for sprites image
function Sprite(img, area) {
this.x = 0;
this.y = 0;
this.area = area;
this.width = area.w;
this.height = area.h;
this.img = img;
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
context.drawImage(img, area.x, area.y, area.w, area.h, 0, 0, area.w, area.h);
this.data = context.getImageData(0, 0, area.w, area.h).data;
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
};
Sprite.prototype.draw = function(pos, center) {
this.x = pos.x;
this.y = pos.y;
center = center || true;
if (center) {
context.drawImage(this.img, this.area.x, this.area.y, this.area.w, this.area.h,
pos.x - this.area.w/2, pos.y - this.area.h/2, this.area.w, this.area.h);
} else {
context.drawImage(this.img, this.area.x, this.area.y, this.area.w, this.area.h,
pos.x, pos.y, this.area.w, this.area.h);
}
};
// for single image
function Img(src) {
var self = this;
var img = new Image;
img.src = src;
img.addEventListener('load', function() {
Sprite.call(self, img, {x: 0, y: 0, w: img.width, h: img.height});
}, false);
};
inherits(Img, Sprite);
// vector
function Vector(x, y) {
this.set(x, y);
};
Vector.random2D = function() {
var r = Math.random() * 2 * Math.PI;
return new Vector(Math.cos(r), Math.sin(r));
};
Vector.add = function(v1, v2) {
return new Vector(v1.x + v2.x, v1.y + v2.y);
};
Vector.sub = function(v1, v2) {
return new Vector(v1.x - v2.x, v1.y - v2.y);
};
Vector.prototype.set = function(x, y) {
if (typeof x === 'object' && typeof y === 'undefined') {
this.x = x.x;
this.y = x.y;
} else {
this.x = x;
this.y = y;
}
};
Vector.prototype.debug = function() {
console.debug('x: ', this.x, 'y: ', this.y);
};
Vector.prototype.add = function(v) {
this.x += v.x;
this.y += v.y;
};
Vector.prototype.sub = function(v) {
this.x -= v.x;
this.y -= v.y;
};
Vector.prototype.mult = function(n) {
this.x *= n;
this.y *= n;
};
Vector.prototype.div = function(n) {
this.x /= n;
this.y /= n;
};
Vector.prototype.mag = function(v) {
return Math.sqrt(this.x * this.x + this.y * this.y);
};
Vector.prototype.normalize = function(v) {
var m = this.mag();
if (m != 0) {
this.div(m);
}
};
Vector.prototype.limit = function(max) {
if (this.mag() > max) {
this.normalize();
this.mult(max);
}
};
// moving object
function Mover(img, l, d, s) {
this.img = img
this.l = l;
this.d = d;
this.s = s;
};
Mover.prototype.update = function() {
this.doUpdate && this.doUpdate();
var dir = Vector.sub(this.d, this.l);
dir.limit(this.s);
this.l.add(dir);
};
Mover.prototype.draw = function() {
this.img.draw(this.l);
};
function Movers(create, n, interval) {
var self = this;
this.movers = [];
this.create = create;
if (typeof n !== 'undefined' && typeof interval !== 'undefined') {
var timer = setInterval(function() {
self.movers.push(create());
if (--n == 0) {
clearInterval(timer);
}
}, interval);
}
};
Movers.prototype.update = function() {
this.doUpdate && this.doUpdate();
this.movers.forEach(function(m) {
m.update();
});
};
Movers.prototype.draw = function() {
this.movers.forEach(function(m) {
m.draw();
});
};
// player fighter
function Player(spt, sptAlt, sptEx, speed) {
Mover.call(this, spt, new Vector(options.startLoc), mouse, speed);
this.imgAlt = sptAlt;
this.alt = false;
this.imgEx = sptEx;
this.hit = false;
this.exFrame = 0;
};
inherits(Player, Mover);
Player.prototype.draw = function() {
if (!this.hit) {
if (this.alt) {
this.img.draw(this.l);
} else {
this.imgAlt.draw(this.l);
}
this.alt = !this.alt;
} else {
if (this.exFrame == this.imgEx.length*5) {
game.state = 'over';
return;
}
this.imgEx[Math.floor(this.exFrame/5)].draw(this.l);
this.exFrame++;
}
};
// player's bullet
function Bullet(spt, playerLoc, speed) {
this.topend = -50;
Mover.call(this, spt, new Vector(playerLoc), new Vector(playerLoc.x, this.topend), speed);
this.playerLoc = playerLoc;
};
inherits(Bullet, Mover);
Bullet.prototype.reset = function() {
this.l.set(this.playerLoc);
this.d.set(this.playerLoc.x, this.topend);
};
function Bullets(create, freq) {
Movers.call(this, create);
this.freq = freq;
this.counter = 0;
};
inherits(Bullets, Movers);
Bullets.prototype.doUpdate = function() {
if (this.counter++ == this.freq) {
for (var i = 0; i < this.movers.length; ++i) {
if (this.movers[i].l.y <= 0) { // find invalid bullet
break;
}
};
if (i == this.movers.length) { // not found
this.movers.push(this.create());
} else {
this.movers[i].reset(); // reuse
}
this.counter = 0;
}
};
Bullets.prototype.hitFighters = function(fighters) { // collide with fighters
for (var i = 0; i < this.movers.length; ++i) {
for (var j = 0; j < fighters.length; ++j) {
if (fighters[j].collide(this.movers[i])) {
fighters[j].hit = true;
this.movers[i].reset();
game.score += 1000;
}
}
}
};
// fighters
function Fighter(spt, sptEx, speed) {
var pos = Math.random() * theCanvas.width;
Mover.call(this, spt, new Vector(pos, 0), new Vector(pos, theCanvas.height), speed);
this.imgEx = sptEx;
this.exFrame = 0;
this.hit = false;
};
inherits(Fighter, Mover);
Fighter.prototype.reset = function() {
var pos = Math.random() * theCanvas.width;
this.l.set(pos, 0);
this.d.set(pos, theCanvas.height);
this.hit = false;
this.exFrame = 0;
};
Fighter.prototype.doUpdate = function() {
if (this.l.y == theCanvas.height) { // re-enter from top
this.reset();
}
//var dir = Vector.sub(player.l, this.l);
//dir.limit(5);
//this.l.add(dir);
};
Fighter.prototype.draw = function() {
if (!this.hit) {
this.img.draw(this.l);
} else {
if (this.exFrame == this.imgEx.length*5) {
this.reset();
return;
}
this.imgEx[Math.floor(this.exFrame/5)].draw(this.l);
this.exFrame++;
}
};
Fighter.prototype.collide = function(obj, accurate) {
if (!this.hit) {
return checkCollide({
x: Math.round(this.l.x - this.img.width/2),
y: Math.round(this.l.y - this.img.height/2),
width: this.img.width,
height: this.img.height,
data: this.img.data,
}, {
x: Math.round(obj.l.x - obj.img.width/2),
y: Math.round(obj.l.y - obj.img.height/2),
width: obj.img.width,
height: obj.img.height,
data: obj.img.data,
}, accurate);
} else {
return false;
}
};
function Fighters(create, n, interval) {
Movers.call(this, create, n, interval);
};
inherits(Fighters, Movers);
Fighters.prototype.hitPlayer = function(player) { // collide with player
for (var i = 0; i < this.movers.length; ++i) {
if (this.movers[i].collide(player, true)) {
player.hit = true;
break;
}
};
};
// stars in background
function Star(spt, speed) {
var pos = Math.random() * theCanvas.width;
Mover.call(this, spt, new Vector(pos, 0), new Vector(pos, theCanvas.height), speed);
};
inherits(Star, Mover);
Star.prototype.reset = function() {
var pos = Math.random() * theCanvas.width;
this.l.set(pos, 0);
this.d.set(pos, theCanvas.height);
};
Star.prototype.doUpdate = function() {
if (this.l.y == theCanvas.height) { // re-enter from top
this.reset();
}
};
function Stars(create, n, interval) {
Movers.call(this, create, n, interval);
};
inherits(Stars, Movers);
function Resources() {
var self = this;
var sprites = {
player: { x: 3, y: 102, w: 96, h: 122 },
player_alt: { x: 168, y: 363, w: 96, h: 122 },
player_ex1: { x: 168, y: 238, w: 96, h: 122 },
player_ex2: { x: 333, y: 628, w: 96, h: 122 },
player_ex3: { x: 333, y: 382, w: 96, h: 122 },
player_ex4: { x: 432, y: 628, w: 96, h: 122 },
fighter: { x: 540, y: 617, w: 48, h: 33 },
fighter_ex1: { x: 273, y: 357, w: 48, h: 33 },
fighter_ex2: { x: 879, y: 708, w: 48, h: 33 },
fighter_ex3: { x: 267, y: 297, w: 56, h: 48 },
fighter_ex4: { x: 932, y: 700, w: 56, h: 48 },
bullet: { x: 1004, y: 987, w: 9, h: 20 },
star: { x: 99, y: 11, w: 80, h: 80 },
};
var img = new Image;
img.src = 'striker.png';
img.addEventListener('load', function() {
var sptPlayer = new Sprite(img, sprites.player);
var sptPlayerAlt = new Sprite(img, sprites.player_alt);
var sptPlayerEx = [new Sprite(img, sprites.player_ex1), new Sprite(img, sprites.player_ex2), new Sprite(img, sprites.player_ex3), new Sprite(img, sprites.player_ex4)];
var sptFighter = new Sprite(img, sprites.fighter);
var sptFighterEx = [new Sprite(img, sprites.fighter_ex1), new Sprite(img, sprites.fighter_ex2), new Sprite(img, sprites.fighter_ex3), new Sprite(img, sprites.fighter_ex4)];
var sptBullet = new Sprite(img, sprites.bullet);
var sptStar = new Sprite(img, sprites.star);
self.playerCreator = function(speed) {
return function() {
return new Player(sptPlayer, sptPlayerAlt, sptPlayerEx, speed);
};
};
self.bulletCreator = function(playerLoc, speed) {
return function() {
return new Bullet(sptBullet, playerLoc, speed);
};
};
self.fighterCreator = function(minSpeed, maxSpeed) {
return function() {
var speed = Math.random() * (maxSpeed - minSpeed) + minSpeed;
return new Fighter(sptFighter, sptFighterEx, speed);
};
};
self.starCreator = function(speed) {
return function() {
return new Star(sptStar, speed);
};
};
game.state = 'start';
});
};
var theCanvas = document.getElementById('canvas');
var context = theCanvas.getContext('2d');
var clientRect = theCanvas.getBoundingClientRect();
var game = {
state: '', // start, running, over
score: 0,
};
var options = {
fps: 60,
bgColor: 'rgb(189, 198, 197)',
startLoc: new Vector(theCanvas.width/2, theCanvas.height - 100),
playerSpeed: 30,
bulletSpeed: 20,
bulletFreq: 10, // higher is slower
fighterMinSpeed: 2,
fighterMaxSpeed: 4,
fighterNum: 10,
fighterFreq: 1000, // 1s for show interval
starSpeed: 0.5,
starNum: 5,
starFreq: 5000, // 5s
};
var mouse = new Vector(options.startLoc);
theCanvas.addEventListener('mousemove', function(e) {
mouse.set(e.clientX - clientRect.left, e.clientY - clientRect.top);
});
var res = new Resources;
var player;
var bullets;
var fighters;
var stars;
var message;
var textWidth;
(function gameLoop() {
setTimeout(gameLoop, 1000/options.fps);
context.fillStyle = options.bgColor;
context.fillRect(0, 0, theCanvas.width, theCanvas.height);
context.fillStyle = '#000';
switch(game.state) {
case 'start':
player = res.playerCreator(options.playerSpeed)();
bullets = new Bullets(res.bulletCreator(player.l, options.bulletSpeed), options.bulletFreq);
fighters = new Fighters(res.fighterCreator(options.fighterMinSpeed, options.fighterMaxSpeed), options.fighterNum, options.fighterFreq);
stars = new Stars(res.starCreator(options.starSpeed), options.starNum, options.starFreq);
context.font = '50px Arial';
message = 'GAME OVER';
textWidth = context.measureText(message).width;
game.state = 'running';
break;
case 'running':
stars.update();
stars.draw();
context.fillText(game.score, 10, 50);
player.update();
player.draw();
if (!player.hit) {
bullets.update();
bullets.draw();
}
fighters.update();
fighters.draw();
fighters.hitPlayer(player);
bullets.hitFighters(fighters.movers);
break;
case 'over':
stars.update();
stars.draw();
context.fillText(game.score, 10, 50);
context.fillText(message, (theCanvas.width/2) - (textWidth/2), theCanvas.height/2);
break;
}
})();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment