游戏

飞机大战甲壳虫

我们这周主要讲解的是飞机大战甲壳虫这个游戏,通过上、下、左、右或者W、A、S、D 等键控制自身飞机的方向,通过SPACE空格发射子弹击毁敌对目标,每击毁一个敌对目标得一分。为了让大家清楚这个游戏的制作过程,所以写了这个总结。大家和我一起来看看吧!!

HTML页面制作

通过css设置了页面背景字体颜色canvas画布的位置,隐藏了game-over游戏结束 的 ID

simple_game

创建一个Canvas对象

相信大家都知道,可以通过JS创建画布,也可以直接在HTML页面上创建画布,然后再通过document.getELementById()来获取,我们这里用JS创建画布:

//创建画布
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
//设置画布的宽高
canvas.width = 512;
canvas.height = 480;
document.body.appendChild(canvas);

载入图片

加载图片首先我们要创建对象

var resouceCache = {}; //存放所有的图片对象,键是路径,值是img对象
var readyCallback = [];//存放需要回调的函数

判断传过来的参数是否是数组

function load(urlOrArray){
        //如果是数组,直接循环,如果只是个url直接执行_load方法
        if(urlOrArray instanceof Array){
            urlOrArray.forEach(function(url,index,arr){
                _load(url);
            })
        }else{
            _load(urlOrArray);
        }
    }

判断对象里面的所有值是不是都是对象,如果都是则返回true

function isReady(){
    var flag = true;
    for(var i in resouceCache){
        if(!resouceCache[i]){
            flag = false;
        }
    }
    return flag;
}

为了让大家能更好的理解,请看下面的例子:

var imgs = ['images/1.jpg','images/2.jpg','images/3.jpg','images/4.jpg'];
var imgCache = {}; //存放所有的图片对象

function load(img){
    //判断img是否是数组,如果是数组,直接循环,如果不是则直接执行_load方法
    if(img instanceof Array){
        img.forEach(function(url){
            _load(url);
        })
    }else{
        _load(img)
    }
}
function _load(url){
    var img = new Image();
    img.onload = function(){
        imgCache[url] = img;
        if(isReady()){
            alert('图片全部加载完成');
        }
    }
    imgCache[url] = false;
    img.src = url;
}

function isReady(){
    var flag = true;
    for(var i in imgCache){
        if(!imgCache[i]){
            flag = false;
        }
    }
    return flag;
}

load(imgs);

看了这段代码相信大家都清楚了,上诉代码和我们在游戏中所写的代码差不多,很好理解,图片全部加载完成。

那么,接下来我们继续讲游戏中剩下的代码吧;

判断传递的参数是否是url

function _load(url){
    //如果传的参数是url我们就直接返回url
    if(resouceCache[url]){
        return resouceCache[url];
    }else{
        //就new一个Image对象
        var img = new Image();
        img.onload = function(){
            resouceCache[url] = img;
            if(isReady()){
                //forEach:循环数组
                readyCallback.forEach(function(func){
                    func();
                });
            }
        };
        resouceCache[url] = false;
        img.src = url;
    }
}

将函数加入到回调数组中

function onReady(func){
    readyCallback.push(func);
}

构造函数

this指向函数本身(如果实在敌机中使用那就是指向敌机,在自身飞机中使用就是指向自身飞机....)

function Sprite(url, pos, size, speed, frames, dir, once){
        this.url = url; //图片地址
        this.pos = pos; //对象在图片上的偏移量 []
        this.size = size;//对象的大小【】
        this.speed = speed;//速度
        this.frames = frames;//动画执行的数组【】
        this.dir = dir || 'horizontal';//horizontal 平行 dir 方向
        this.once = once || false;
        this._index = 0;
        this.done = false;
    }

载入背景图片

//原型共享
Sprite.prototype = {
    constructor:Sprite,
    update:function(dt){
        this._index += this.speed * dt;
    }
 };
window.Sprite = Sprite;

命名空间:方便外面使用,我们会用到两张图片(背景、飞机甲壳虫)

 window.resources = {
        load: load,
        get: get,
        onReady: onReady
    }
//加载所有图像
resources.load([
    'img/sprites.png',
    'img/terrain.png'
]);

下面为大家举一个简单的命名空间的例子,希望能帮助大家理解命名空间

(function(){
    var name = "命名空间示例";
    var arr = [];

    function load(){
        _load();
    }
    function _load(){
        alert(name)
    }
    //命名空间
    window.zz = {
        load:load
    }
})();

zz.load();

设置一个背景图片变量(初始化变量)

//背景图片
var terrainPattern;

游戏的初始化

function init(){
    terrainPattern = ctx.createPattern(resources.get('img/terrain.png'),'repeat');

    main();
}

在画布canvas上加载背景图片

function render(){
    //加载背景图片
    ctx.fillStyle = terrainPattern;
    ctx.fillRect(0, 0, canvas.width, canvas.height);

}

回调执行init方法

resources.onReady(init);

效果显示如下:

simple_game

自身飞机图片的加载

Sprite.prototype = {
    constructor:Sprite,
    update:function(dt){
        this._index += this.speed * dt;
    },
    render:function(ctx){
        var frame;
        //计算本身飞机旋转
        if(this.speed > 0){
            var max = this.frames.length;
            var idx = Math.floor(this._index);
            frame = this.frames[idx % max];
            if(this.once && idx >= max){
                this.done = true;
                return;
            }
        }else{
            frame = 0;
        }
        var x = this.pos[0];//x轴
        var y = this.pos[1];//y轴
        if(this.dir == 'vertical'){

        }else{
            x += frame * this.size[0];
        }
        ctx.drawImage(resources.get(this.url),
                    x, y,
                    this.size[0], this.size[1],
                    0, 0,
                    this.size[0], this.size[1]);
    }
};

自身飞机出现的位置

var player = {//自身飞机
    pos: [0,0],//出现的坐标
    sprite:new Sprite('img/sprites.png',[0,0],[39,39],16,[0,1])

};

判断自身飞机是否超出边界

if(player.pos[0]>canvas.width){
    player.pos[0]=canvas.width-39;
}
//左
if(player.pos[0]<0){
    player.pos[0]=0;
}
//上
if(player.pos[1]<0){
    player.pos[1]=0;
}
//下
if(player.pos[1]>canvas.height){
    player.pos[1] = canvas.height-39;
}

自身飞机通过SPACE空格键发射子弹

设置子弹变量

var bullets = []; //子弹
var lastFire = Date.now(); //第二次按下的子弹?

子弹的方向、位置、图片,具体代码如下:

if(input.isDown('SPACE') && (Date.now() - lastFire) > 100){
    bullets.push({
        dir: 'right',
        pos: [player.pos[0] + player.sprite.size[0]/2,player.pos[1] + player.sprite.size[1]/2],
        sprite: new Sprite('img/sprites.png',[0,39],[18,8])

    });
    bullets.push({
        dir: 'top',
        pos: [player.pos[0] + player.sprite.size[0]/2,player.pos[1] + player.sprite.size[1]/2],
        sprite: new Sprite('img/sprites.png', [0, 50], [9,5])

    });
    bullets.push({
        dir: 'bottom',
        pos: [player.pos[0] + player.sprite.size[0]/2,player.pos[1] + player.sprite.size[1]/2],
        sprite: new Sprite('img/sprites.png', [0, 60], [9, 5])
    });
    lastFire = Date.now();
}

子弹发射,具体代码如下:

var i = 0;
//子弹发射的方向
for(i=0;i<bullets.length;i++){
    if(bullets[i].dir == 'right'){
        bullets[i].pos[0] += bulletSpeed * dt;
    }else if(bullets[i].dir == 'top'){
        bullets[i].pos[1] -= bulletSpeed * dt;
    }else if(bullets[i].dir == 'bottom'){
        bullets[i].pos[1] += bulletSpeed * dt;
    }
    bullets[i].sprite.update(dt);

判断子弹是否超出边界

    if(bullets[i].pos[0] + bullets[i].sprite.size[0] > canvas.width){
        bullets.splice(i,1);
        i --;
    }
}

自身飞机的移动

上面我们已经讲了,飞机通过通过上、下、左、右或者W、A、S、D 等键控制自身飞机的方向

首先我们要获得按键的值

var pressKey = {};
function setKey(e,flag){
    var code = e.keyCode;
    var key;

    switch (code){
        case 32:
            key = 'SPACE';
            break;
        case 37:
            key = 'LEFT';
            break;
        case 38:
            key = 'UP';
            break;
        case 39:
            key = 'RIGHT';
            break;
        case 40:
            key = 'DOWN';
            break;
        default:
            key = String.fromCharCode(code);
    }
    pressKey[key] = flag;
}

键盘点击的动作

document.addEventListener('keydown',function(e){
    setKey(e,true);
},false);
document.addEventListener('keyup',function(e){
    setKey(e,false);
},false);
window.addEventListener('blur',function(e){
    pressKey = {};
},false);

上面我们已经知道,这样写不方便我们调用,所以我们要写一个命名空间

window.input = {
    isDown: function(key){
        return pressKey[key.toUpperCase()];
    }
}

上诉中我们获得了上、下、左、右键的值,那么接下来我们就该让飞机通过上、下、左、右或者W、A、S、D动起来了

 if(input.isDown('A') || input.isDown('LEFT')){
    player.pos[0] -= playerSpeed * dt;
}
if(input.isDown('W') || input.isDown('UP')){
    player.pos[1] -= playerSpeed * dt;
}
if(input.isDown('D') || input.isDown('RIGHT')){
    player.pos[0] += playerSpeed * dt;
}
if(input.isDown('S') || input.isDown('DOWN')){
    player.pos[1] += playerSpeed * dt;
}

敌机图片的加载

敌机出现的位置、数量.....

var singleCount = 0;
var doubleCount = 0;

设置敌机

var enemies = []; //敌机

敌机出现的位置

singleCount ++;
if(singleCount % 2 == 0){
    doubleCount ++;
    if(doubleCount % 5 == 0){
        //敌机
        enemies.push({
            pos: [canvas.width,Math.random() * (canvas.height - 39)],
            sprite:new Sprite('img/sprites.png',[0, 78], [79, 39], 6, [0,1,2,3,2,1])
        });
        doubleCount = 0;
    }
}

出现敌机

for(i=0;i<enemies.length;i++){
    enemies[i].pos[0] -= enemySpeed * dt;
    enemies[i].sprite.update(dt);

判断敌机是否超出边界

    if(enemies[i].pos[0] + enemies[i].sprite.size[0] < 0){
        enemies.splice(i,1);
        i --;
    }
}

碰撞图片的加载

for(var i=0;i<enemies.length;i++){
    //敌机的位置大小
    var pos = enemies[i].pos;
    var size = enemies[i].sprite.size;
    for(var j=0;j<bullets.length;j++){
        //子弹的位置大小
        var pos2 = bullets[j].pos;
        var size2 = bullets[j].sprite.size;
        //子弹、敌机碰撞
        if(boxCollides(pos, size, pos2, size2)){
            //敌机减一个
            enemies.splice(i,1);
            i --;
            //分数
            scroses += 1;
            scrose.innerHTML = scroses;
            //敌机爆炸时的图片
            explosions.push({
                pos: [pos[0], pos[1]],
                sprite: new Sprite('img/sprites.png',
                    [0, 117],
                    [39, 39],
                    16,
                    [0,1,2,3,4,5,6,7,8,9,10,11,12],
                    null,
                    true)
            });
            //子弹减一个
            bullets.splice(j,1);
            j --;
        }
    }
}

爆炸图片

for(i=0;i<explosions.length;i++){
    explosions[i].sprite.update(dt);

判断爆炸是否超出边界

    if(explosions[i].done){
        explosions.splice(i,1);
        i --;
    }
}

}

检测碰撞效果

自身飞机和敌机碰撞在一起,游戏结束

首先我们要知道自身飞机和敌机的位置、大小,那么我们可以这样写:

function collides(x, y, r, b, x2, y2, r2, b2){
    return !(r < x2 || r2 < x || b < y2 || b2 < y);
}

写好了方法之后我们就开始调用:

function boxCollides(pos, size, pos2, size2){
    return collides(
        //pos[0]:x,pos[1]:y
        // pos[0] + size[0]:x+width
        //pos[1] + size[1]:y+height
        pos[0], pos[1],        
        pos[0] + size[0], pos[1] + size[1],
        pos2[0], pos2[1],
        pos2[0] + size2[0], pos2[1] + size2[1]);
}

最后判断一下,碰撞结束游戏

if(boxCollides(pos, size, player.pos, player.sprite.size)){
        gameOver();
    }

游戏主循环

//游戏主循环
var lastTime = Date.now();
function main(){
    var now = Date.now();
    //时间差
    var dt = (now - lastTime) / 1000;

    lastTime = now;
    //回调函数
    requestAnimFrame(main);
}

重新开始游戏

var isGameOver = false;

代码如下:

function reset() {
    document.getElementById('game-over').style.display = 'none';
    document.getElementById('game-over-overlay').style.display = 'none';
    isGameOver = false;
    gameTime = 0;

    scroses = 0;
    scrose.innerHTML = scroses;
    enemies = [];
    bullets = [];

    player.pos = [50, canvas.height / 2];
}
posted @ 2014-11-29 11:49  胡珍  阅读(144)  评论(0编辑  收藏  举报