• 游戏


    飞机大战甲壳虫

    我们这周主要讲解的是飞机大战甲壳虫这个游戏,通过上、下、左、右或者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];
    }
  • 相关阅读:
    命令式语言和声明式语言对比——JavaScript实现快速排序为例
    merge sort 的javascript实现
    快速排序算法的简短描述
    Hadoop 2.x 版本的单机模式安装
    数据分析招聘网招聘信息分析报告
    使用PROC TRANSPOSE过程步对数据集进行转置时如何保持日期变量的时间顺序
    饼图微创意
    我的微博关键字
    QQ群成员发言次数统计(词云制作)
    在SAS数据步中执行过程步的简单示例
  • 原文地址:https://www.cnblogs.com/huzhen/p/4130511.html
Copyright © 2020-2023  润新知