这个游戏大家肯定都玩过,今天分享一个用canvas写的一个小demo。
效果图:
一、首先要做的肯定是图片预加载
var imgArr = {
"bg":"img/background.png",
"herofly":"img/herofly.png",
"bullet1":"img/bullet1.png",
"bullet2":"img/bullet2.png",
"enemy1":"img/enemy1.png",
"enemy2":"img/enemy2.png",
"enemy3":"img/enemy3.png",
"prop":"img/prop.png"
}
var imgLength = 0;
for(var i in imgArr){
imgLength++;
}
var num = 0;
var loadArr = {};
for(let i in imgArr){
var img = new Image();
img.src = imgArr[i];
img.onload = function(){
loadArr[i] = this;
num++;
if(num>=imgLength){
main(loadArr);
}
}
}
二、分析游戏中都几个对象
对象一:主机;
对象二:敌机;
对象三:奖励;
每个对象都有move移动,draw绘画,isClear是否清除的方法
以子弹为例:其余对象的构造函数类似
// 子弹构造函数
function Bullet(x,y,power,obj,speed){
this.x = x;
this.y = y;
this.power = power;
this.obj = obj;
this.w = obj.width;
this.h = obj.height;
this.speed = speed;
}
Bullet.prototype.draw = function(){
context.drawImage(this.obj,0,0,this.w,this.h,this.x,this.y,this.w,this.h);
// 子弹与敌机的碰撞检测
for(var j = 0 ; j < enemy.length;j++){
if((this.xenemy[j].x) && (this.yenemy[j].y)){
enemy[j].blood -= 25;
if(enemy[j].i == 5){
music2.src = "audio/enemy1_down.mp3";
}else if(enemy[j].i == 6){
music2.src = "audio/enemy2_down.mp3";
}else{
music2.src = "audio/enemy3_down.mp3";
}
this.y = -10;
}
}
}
Bullet.prototype.move = function(){
this.y -= this.speed;
}
Bullet.prototype.isClear = function(){
if(this.y<0){
return true;
}else{
return false;
}
}
三、定义几个数组存放实例化的子弹,敌机,奖励
方便后面做碰撞判断
// 存放子弹的容器
var bullet = [];
var num = 0;
var bgY = 0;
// 存放敌机的容器
var enemy = [];
var enemyBlood = 100;
// 存放奖励的容器
var award = [];
// 子弹的颗数
var bulletNum = loadArr.bullet1;
var bulletX = 30;
// 敌机与主机碰撞的开关
var isCarld = true;
四、动画函数
创建一个act函数做动画,函数里实例化子弹,敌机,以及做各种碰撞检测,这里注意:碰撞检测不要全部在act方法里判断,可以在构造函数的draw方法里判断,优化性能,使游戏画面流畅。
- 背景图滚动
bgY--;
context.drawImage(loadArr.bg,0,0+bgY,canvas.width,canvas.height);
context.drawImage(loadArr.bg,0,canvas.height+bgY,canvas.width,canvas.height);
if(Math.abs(bgY) >= canvas.height){
bgY = 0;
}
- 绘画主机和实例化敌机,奖励,Rand()是一个封装好取随机数的函数,使用随机数出现的概率来决定不同敌机和不同奖励出现的概率。
// 画主机
newHero.draw();
newHero.move();
// 实例化敌机和奖励
if(num%50 == 0){
var randNum = Rand(0,34);
// console.log(randNum);
if(randNum>=0 && randNum<20){
// 随机位置
var randX = Rand(0,canvas.width-loadArr.enemy1.width/5);
// 敌机的血量
var blood = enemyBlood -20;
var newEnemy = new Enemy(randX,-loadArr.enemy1.height,blood,20,loadArr.enemy1,1.5,5);
enemy.push(newEnemy);
}else if(randNum >= 20 && randNum <28){
var randX = Rand(0,canvas.width-loadArr.enemy2.width/6);
var blood = enemyBlood -50;
var newEnemy = new Enemy(randX,-loadArr.enemy2.height,blood,35,loadArr.enemy2,1,6);
enemy.push(newEnemy);
}else if(randNum >= 28 && randNum <30){
var blood = enemyBlood;
var randX = Rand(0,canvas.width-loadArr.enemy3.width/10);
var newEnemy = new Enemy(randX,-loadArr.enemy3.height,blood,50,loadArr.enemy3,0.5,10);
enemy.push(newEnemy);
// console.log(newEnemy.x,newEnemy.y);
}else if(randNum >= 30 && randNum <31){
// 奖励1
var randX = Rand(0,canvas.width-loadArr.prop.width/3);
var newRaward = new Award(randX,-loadArr.prop.height,loadArr.prop,1,0);
console.log(newRaward);
award.push(newRaward);
}else if(randNum >= 31 && randNum <33){
// 奖励2
var randX = Rand(0,canvas.width-loadArr.prop.width/3);
var newRaward = new Award(randX,-loadArr.prop.height,loadArr.prop,1,1);
console.log(newRaward);
award.push(newRaward);
}else{
// 奖励3
var randX = Rand(0,canvas.width-loadArr.prop.width/3);
var newRaward = new Award(randX,-loadArr.prop.height,loadArr.prop,1,2);
console.log(newRaward);
award.push(newRaward);
}
}
- 敌机与主机的碰撞检测
这里使用的事普通碰撞,大家有兴趣可以换成像素碰撞。
遍历敌机enemy数组,使用isCarld控制只碰撞一次,主机血量减少。血量为0时主机爆炸游戏结束。
for(var i = 0 ; i < enemy.length ; i++){
enemy[i].draw();
enemy[i].move();
// 敌机与主机的碰撞检测
if((newHero.x< enemy[i].x+enemy[i].w && newHero.x+newHero.w>enemy[i].x) && (newHero.y< enemy[i].y+enemy[i].h && newHero.y+newHero.h>enemy[i].y)){
if(isCarld){
isCarld = false;
newHero.blood -= enemy[i].power;
if(newHero.blood < 0){
newHero.blood = 0;
}
bloodNum.innerHTML = "血量:"+ newHero.blood;
bloodPress.style.width = 200*newHero.blood/100 + "px";
}
enemy[i].isF = true;
console.log(newHero.blood);
// music2.src = "audio/enemy2_out.mp3";
}
if(enemy[i].isF && enemy[i].y >= canvas.height){
isCarld = true;
}
if(enemy[i].isClear()){
enemy.splice(i,1);
}
// console.log(enemy.length);
}
像子弹和敌机的碰撞还有主机和奖励的碰撞代码大同小异,就是其中逻辑不同,这里就不一一展示了。
- 游戏结束
使用requestAnimationFrame做动画,使用cancelAnimationFrame(Timer);来停止动画。
var Timer = window.requestAnimationFrame(act);
if(newHero.index >= newHero.i-1){
console.log("dfsf");
music1.src = "audio/game_over.mp3";
music1.loop = "";
music2.remove();
cancelAnimationFrame(Timer);
// 游戏结束
btn.style.display = "block";
context.beginPath();
context.fillStyle = "grey";
context.fillRect(0,150,canvas.width,200);
context.beginPath();
context.textBaseline = "middle";
context.textAlign = "center";
context.font = "40px Arial";
context.fillStyle = "red";
context.fillText("gameover",canvas.width/2,275);
}
游戏结束的页面图: