• Html飞机大战(九): 使徒来袭 (设计敌机)


    好家伙,本篇介绍敌机

     

    好了,按照惯例我们来理一下思路:

     

    我们有一个敌机类,第一步当然是实例一个敌机对象,

    然后我们把这个敌机放入我们的敌机群(敌机数组)

    然后是熟悉的移动和绘制

     

    那我们回顾一下子弹的生成逻辑

    变量: 子弹  bullet  弹夹(用来装子弹的东西)bulletList[] 

    方法:装填子弹  绘制子弹 移动子弹

    子弹发射的物理逻辑是很简单的:

    生产第一个子弹,推入弹夹中,绘制弹夹(即绘制弹夹中的所有子弹),

    生产第二个子弹,同样推入弹夹,移动第一颗子弹(应该说是改变第一颗子弹的y坐标),绘制弹夹中的所有子弹 

    。。。。。。

    生产第n个子弹,推入弹夹中,改变第n-1颗子弹的Y坐标,绘制弹夹中的所有子弹

     

    有没有感觉到两者逻辑的相似之处

    (像啊,太像了)

     

     

    子弹和敌机的处理,本质上是用的是同一套逻辑

     

    那么,开始干活:

     

    1.配置项

    这里我们会用到两种类型的配置项E1和E2

    (因为我们有两种类型的敌人,大敌机和小敌机,其中e1为小敌机(血少),e2为大敌机(血厚))

    先设置一个数组存放图片资源

    复制代码
    //e1用于存放小敌机的图片素材
            const e1 = {
                live: [],
                death: [],
            }
            e1.live[0] = new Image();
            e1.live[0].src = "img/enemy1.jpg"
            e1.death[0] = new Image();
            e1.death[0].src = "img/enemy1_boom1.jpg"
            e1.death[1] = new Image();
            e1.death[1].src = "img/enemy1_boom2.jpg"
            e1.death[2] = new Image();
            e1.death[2].src = "img/enemy1_boom3.jpg"
    
            //e2用于存放小敌机的图片素材
            const e2 = {
                live: [],
                death: [],
            }
            e2.live[0] = new Image();
            e2.live[0].src = "img/enemy2.jpg"
            e2.death[0] = new Image();
            e2.death[0].src = "img/enemy2_boom1.jpg"
    复制代码

     

     

     

      

     

     

     

     (图片素材来自网络)

     

     

     

     2.敌机配置项

    复制代码
    //小敌机
            const E1 = {
                type: 1,
                width: 57,
                height: 51,
                life: 1, //少点血,一下打死
                score: 1,
                frame: e1,
                minSpeed: 20,
                maxSpeed: 10,
            }
            //大敌机
            const E2 = {
                type: 2,
                width: 69,
                height: 95,
                life: 2,
                frame: e2,
                minSpeed: 50,
                maxSpeed: 20,
            }
    复制代码
    minSpeed: 50,
    maxSpeed: 20,
    值得说明一下,这两个玩意是为了弄敌机的随机速度(更刺激一点,但实际上好像没什么感觉)
    关于如何弄到一个”随机速度“,接着往下看


    3.敌机类

    复制代码
    class Enemy {
    
                constructor(config) {
                    //敌机类型
                    this.type = config.type;
                    //敌机宽,高
                    this.width = config.width;
                    this.height = config.height;
                    //敌机的初始化位置
                    this.x = Math.floor(Math.random() * (480 - config.width));
                    //这里我们让飞机从头部开始渲染,所以Y轴坐标自然是飞机高度的负值
                    this.y = -config.height;
                    //敌机生命
                    this.life = config.life;
                    //敌机分数
                    this.score = config.score;
                    //敌机图片库
                    this.frame = config.frame;
                    //此刻展示的图片
                    this.img = null;
                    //活着的证明
                    this.live = true;
                    // this.minSpeed = config.minSoeed;
                    // this.maxSpeed = config.speed;
                    //随机去生成一个速度
                    this.speed = Math.floor(Math.random() * (config.minSpeed - config.maxSpeed + 1)) + config.maxSpeed;
                    //最后渲染的时间
                    this.lastTime = new Date().getTime();
    
                }
                //移动敌机
                move() {
                    const currentTime = new Date().getTime();
                    //
                    
                    if (currentTime - this.lastTime >= this.speed) {
                        // console.log("此处为this.frame"+this.frame.live[0]);
                        this.img = this.frame.live[0];
                        this.y++;
                        //时间修正
                        this.lastTime = currentTime;
                    }
                }
    
                //渲染敌机方法
                paint(context) {
                    // console.log("此处为this.img"+this.img);
                    if(this.img !=null){
                        context.drawImage(this.img, this.x, this.y);  
                    }
                    
                }
            }
    复制代码

     

     

    3.1.随机速度

    先浅浅的说明一下

    随机数方法 Math.random

     

    这玩意会在[0,1)也就是在0到1之间取一个值

    然后问题来了,这是一个半开半闭区间,也就是说它会取到0但是不会取到1

    this.speed = Math.floor(Math.random() * (config.minSpeed - config.maxSpeed + 1)) + config.maxSpeed;

    在这里我们要取的是一个10到20之间的速度由于我们向下取整

    Math.floor(Math.random() * (config.minSpeed - config.maxSpeed )) + config.maxSpeed;

    必然只能取得10-19之间的数


    于是我们在(config.minSpeed - config.maxSpeed )中加一

    变成(Math.random() * (config.minSpeed - config.maxSpeed +1))


    (聪明的你一定能很快想明白,而愚蠢的我想了很久才想明白)


    3.2.敌机的移动方法

    复制代码
    move() {
                    const currentTime = new Date().getTime();
                    //
                    
                    if (currentTime - this.lastTime >= this.speed) {
                        // console.log("此处为this.frame"+this.frame.live[0]);
                        this.img = this.frame.live[0];
                        this.y++;
                        //时间修正
                        this.lastTime = currentTime;
                    }
                }
    复制代码

    移动同样的用时间判定的方式去控制速率

    现在和过去的时间差大于速度,更新地址


    3.3.渲染方法

    复制代码
    paint(context) {
                    // console.log("此处为this.img"+this.img);
                    if(this.img !=null){
                        context.drawImage(this.img, this.x, this.y);  
                    }
                    
                }
    复制代码

    嗯,非常好理解了,多加的一个if是为了防止出现空img导致报错


    4.全局函数(生产敌机)

    复制代码
    //以下三项均为全局变量
            const enemies = [];
            //敌机产生的速率
            const ENEMY_CREATE_INTERVAL = 2000;
            let ENEMY_LASTTIME = new Date().getTime();
    
            //全局函数 用于生产敌机
            function createComponent() {
                const currentTime = new Date().getTime();
                const forenemyTime = new Date().getTime();
    
                //一手经典判断
                if (currentTime - ENEMY_LASTTIME >= ENEMY_CREATE_INTERVAL) {
                    //当时间满足 实例化一架敌机 放入敌机数组中
                    // 小飞机 70% 中飞机30%
                    //用随机数去弄概率
                    //[0,99]
                    //Math.random()=>[0,1)*100
                    //EnemyTypeRandom产生的随机数用于判断产生不同的飞机
                    let EnemyTypeRandom = Math.floor(Math.random() * 100);
                    if (EnemyTypeRandom > 70) {
                        enemies.push(new Enemy(E1));
                    } else if (EnemyTypeRandom < 30) {
                        enemies.push(new Enemy(E2));
                    }
                    console.log(enemies);
                    //更新时间
                    ENEMY_LASTTIME = currentTime;
                }
            }
    复制代码
    
    

    这里同样的,我们用随机数去控制出现大/小敌机的概率

    (E1,E2分别是大小敌机的配置项)

    
    
    复制代码
    let EnemyTypeRandom = Math.floor(Math.random() * 100);
                    if (EnemyTypeRandom > 70) {
                //产小敌机 enemies.push(new Enemy(E1)); }
    else if (EnemyTypeRandom < 30) {
                //产大敌机 enemies.push(new Enemy(E2)); }
    复制代码
    
    

    你细品,这个控制得还是非常巧妙的

     

    5.全局函数渲染


    到这里就非常简单了

    这里也揭开了前面的谜底

    因为敌机生成和子弹生成的逻辑太过相似

    所以我们把他们放到同一个全局函数是一个非常明智的选择

    复制代码
    //全局函数 来移动所有的子弹/敌人组件
            function judgeComponent() {
                console.log("judge被触发");
                for (let i = 0; i < hero.bulletList.length; i++) {
                    hero.bulletList[i].move();
                }
                for(let i=1;i){
                    enemies[i].move();
                }
            }
            //全局函数 来绘制所有的子弹/敌人组件
            function paintComponent() {
                for (let i = 0; i < hero.bulletList.length; i++) {
                    hero.bulletList[i].paint(context);
                }
                for(let i=1;i){
                    enemies[i].paint(context);
                }
            }
    复制代码
    
    

     

    6.方法调用

    
    
    复制代码
    case RUNNING:
                            sky.judge();
                            sky.paint(context);
                            //加载主角
    
                            hero.paint(context);
                            hero.shoot();
                            createComponent();
                            //子弹发射
                            judgeComponent();
                            paintComponent();
                            deleteComponent();
                            // context.drawImage(hero_frame.live[0], 0, 0);
                            break;
    复制代码
    
    

     

    
    
     
    ok,来看看效果吧:

     

     

     

    确实是非常地nice啊

     

  • 相关阅读:
    C++数据类型总结,看这一篇就够了
    STM32——OLED菜单
    小满Vue3第四十六章(Proxy跨域)
    JavaScript学习小结
    leetcode 54. 螺旋矩阵
    C++简单实现AVL树
    【力扣算法简单五十题】07.二进制求和
    案例-JS控制二级联动
    手撕各种排序
    如何准确获取地点位置的经纬度?(高德)
  • 原文地址:https://www.cnblogs.com/FatTiger4399/p/16644619.html