微信小游戏 demo 飞机大战 代码分析(一)(main.js)

微信小游戏 demo 飞机大战 代码分析(二)(databus.js)

微信小游戏 demo 飞机大战 代码分析(三)(spirit.js, animation.js)

微信小游戏 demo 飞机大战 代码分析(四)(enemy.js, bullet.js, index.js)

本博客将使用逐行代码分析的方式讲解该demo,本文适用于对其他高级语言熟悉,对js还未深入了解的同学,博主会尽可能将所有遇到的不明白的部分标注清楚,若有不正确或不清楚的地方,欢迎在评论中指正

本文的代码均由微信小游戏自动生成的demo飞机大战中获取

文件目录

game.js

  • 首先让我们来看一下作为入口的game.js,可以看到在这里只进行了main类的初始化,因此下一步我们应该查看一下main类中的函数

  • 代码

    import Player     from './player/index'
    import Enemy from './npc/enemy'
    import BackGround from './runtime/background'
    import GameInfo from './runtime/gameinfo'
    import Music from './runtime/music'
    import DataBus from './databus' let ctx = canvas.getContext('2d')
    let databus = new DataBus() /**
    * 游戏主函数
    */
    export default class Main {
    constructor() {
    // 维护当前requestAnimationFrame的id
    this.aniId = 0
    //重新生成新的界面
    this.restart()
    } //界面生成函数
    restart() {
    databus.reset() canvas.removeEventListener(
    'touchstart',
    this.touchHandler
    ) this.bg = new BackGround(ctx)
    this.player = new Player(ctx)
    this.gameinfo = new GameInfo()
    this.music = new Music() this.bindLoop = this.loop.bind(this)
    this.hasEventBind = false // 清除上一局的动画
    window.cancelAnimationFrame(this.aniId); this.aniId = window.requestAnimationFrame(
    this.bindLoop,
    canvas
    )
    } /**
    * 随着帧数变化的敌机生成逻辑
    * 帧数取模定义成生成的频率
    */
    enemyGenerate() {
    if ( databus.frame % 30 === 0 ) {
    let enemy = databus.pool.getItemByClass('enemy', Enemy)
    enemy.init(6)
    databus.enemys.push(enemy)
    }
    } // 全局碰撞检测
    collisionDetection() {
    let that = this databus.bullets.forEach((bullet) => {
    for ( let i = 0, il = databus.enemys.length; i < il;i++ ) {
    let enemy = databus.enemys[i] if ( !enemy.isPlaying && enemy.isCollideWith(bullet) ) {
    enemy.playAnimation()
    that.music.playExplosion() bullet.visible = false
    databus.score += 1 break
    }
    }
    }) for ( let i = 0, il = databus.enemys.length; i < il;i++ ) {
    let enemy = databus.enemys[i] if ( this.player.isCollideWith(enemy) ) {
    databus.gameOver = true break
    }
    }
    } // 游戏结束后的触摸事件处理逻辑
    touchEventHandler(e) {
    e.preventDefault() let x = e.touches[0].clientX
    let y = e.touches[0].clientY let area = this.gameinfo.btnArea if ( x >= area.startX
    && x <= area.endX
    && y >= area.startY
    && y <= area.endY )
    this.restart()
    } /**
    * canvas重绘函数
    * 每一帧重新绘制所有的需要展示的元素
    */
    render() {
    ctx.clearRect(0, 0, canvas.width, canvas.height) this.bg.render(ctx) databus.bullets
    .concat(databus.enemys)
    .forEach((item) => {
    item.drawToCanvas(ctx)
    }) this.player.drawToCanvas(ctx) databus.animations.forEach((ani) => {
    if ( ani.isPlaying ) {
    ani.aniRender(ctx)
    }
    }) this.gameinfo.renderGameScore(ctx, databus.score) // 游戏结束停止帧循环
    if ( databus.gameOver ) {
    this.gameinfo.renderGameOver(ctx, databus.score) if ( !this.hasEventBind ) {
    this.hasEventBind = true
    this.touchHandler = this.touchEventHandler.bind(this)
    canvas.addEventListener('touchstart', this.touchHandler)
    }
    }
    } // 游戏逻辑更新主函数
    update() {
    if ( databus.gameOver )
    return; this.bg.update() databus.bullets
    .concat(databus.enemys)
    .forEach((item) => {
    item.update()
    }) this.enemyGenerate() this.collisionDetection() if ( databus.frame % 20 === 0 ) {
    this.player.shoot()
    this.music.playShoot()
    }
    } // 实现游戏帧循环
    loop() {
    databus.frame++ this.update()
    this.render() this.aniId = window.requestAnimationFrame(
    this.bindLoop,
    canvas
    )
    }
    }

一点基础知识

  • 帧:游戏中的帧和动画中的帧,视频中的帧概念类似,即游戏过程中物体和动画效果变化的一个周期。
  • 精灵:是游戏中的一个基本概念,指的是在游戏中的一个基本物体或动画或贴图,如NPC或者敌人,在本例中有子弹,敌机和玩家
  • 回调函数:在特定事件发生后,由事件方进行调用的函数
  • 画布:顾名思义就是使用了画东西的地方,其实就是用于渲染相关内容的位置

main.js

main 即为游戏的主函数,我们来逐个分析一下其内容

  • export default 为 ES6,即js的一个版本中的语言,在js中,任何类或对象使用export既可以在其他文件中通过import进行调用使用,使用 import {类或对象名} from 文件路径,但若使用export default则可以省略 { }, 但一份文件中仅仅可以存在一个export default

初始化生成对象

  1. 在main函数前其调用生成了一个2d画布,名称为ctx

  2. 生成了一个数据总线对象databus,数据总线的内容将在下次博客中解释

main 类

contructor()

contructor 用于创建main 对象,其中调用了restart函数,因此我们跳转到restart函数中进行查看

restart()

该函数用于重新生成一个界面

  • 首先重置数据总线对象的内容

  • 监听触碰事件

  • 初始化背景对象,玩家对象,游戏信息对象和音乐对象

    this.bg       = new BackGround(ctx)
    this.player = new Player(ctx)
    this.gameinfo = new GameInfo()
    this.music = new Music()
  • 绑定事件循环,初始化状态,并开始运行

    this.bindLoop     = this.loop.bind(this)
    this.hasEventBind = false // 清除上一局的动画
    window.cancelAnimationFrame(this.aniId); this.aniId = window.requestAnimationFrame(
    this.bindLoop,
    canvas
    )
  • js语法中,可以将某个对象的方法单独拿出来作为一个方法使用,但是在使用过程中,避免不了出现未知该函数所指向的对象的情况

    • 例如在该代码中,若写作this.bindLoop = this.loop 那么该函数所属的类就丢失了,那么该函数一些执行也就无法进行
    • 为了避免这样的情况,js使用bind函数,将所需的类绑定到该函数上,这样就有效地解决了这个问题
  • window.requestAnimationFrame()

    • 该函数使用了两个参数,第一个是回调函数,第二个是画布
    • 画布的功能即用来工作的区域
    • 而回调函数的作用是在浏览器在该帧渲染完毕之后,调用的函数,根据博主的资料查询,回调函数执行次数通常是每秒60次,但在大多数遵循W3C建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。
    • 在该例子中,restart中的该函数仅仅是使用初始化的main对象更新loop函数,并将其作为刷新内容
    • 但由于main对象中的逻辑会产生变更,因此在之后的loop函数也对其进行了请求,并绑定了参数。使用新缠身过的main对象和新产生的canvas在浏览器中进行渲染

enemyGenerate()

该函数用于生成敌人飞机

  • 在databus中有一个frame参数,相当于每次刷新(更新)的计数器,
  • 使用该函数时,若刷新次数为30的整数倍时,就会申请一个新的敌机对象并初始化,其中init的参数为该敌机的速度,生成后加入databus对象的存储数组中

collisionDetection()

全局碰撞检测

  • 首先对于每个子弹,判断子弹是否与敌机相撞,若相撞则隐藏敌机和子弹

    • 该处需要解释一下的是,将子弹和敌机隐藏的是直接代表子弹和敌机已经销毁
    • 但此处并未在逻辑中将对象销毁,而是在绘图中判断其visible是否为true,若为true则才会画入画布中
    • 而统一更新回收入pool
  • 对每一架敌机,判断是否与用户相撞,若相撞,则在databus中设置游戏结束

touchEventHandler(e)

游戏结束后判断是否重新开始的函数

  • 获取触摸的坐标
  • 在gameinfo中获取重新开始上下左右xy坐标
  • 比对触摸位置是否在按钮内部,若在则调用restart函数重新启动函数

render()

渲染函数,用于渲染场景,用于每次修改内容后重新渲染场景内容(每一帧调用)

  • 清除画布的所有内容
  • 调用背景类的渲染函数,在ctx上渲染出一个背景
  • concat函数为js函数,用于连接连个数组
  • 连接databus中的bullets和enemys数组,并且将这个合成数组中的每一项画到画布上,画到画布上的操作是以利用函数drawToCanvas,而该函数实现于Spirite类中,
  • spirit即精灵,是游戏设计中的一个概念,相当于游戏中一个最基本的物体或者一个概念,该demo中的spirit实现方式将在后续博客中写上
  • 将player画到画布上,同样的,player也继承于Spirit类
  • 将所有动画类的未播放的内容进行播放,在该demo中,Animation类继承Spirit,而所有物体均继承于Animation类,因此都具有该能力,不过由于所有物体都均仅有一帧图像,因此无需进行播放,
  • 在databus类中有一个专门存放动画的数组,任何继承于Animation类的对象都会在初始化构造时被放入该数组当中
  • 调用gameinfo的函数更新图像左上角的分数内容
  • 判断,若游戏结束
    • 若未绑定事件,将touchHandler事件添加绑定,
    • 将事件加入监听中
    • (该段代码博主并未非常理解,欢迎在评论中指正或指导)

update()

游戏逻辑更新主函数

  • 若游戏已经结束,不执行该代码,直接放回结束
  • 更新背景参数
  • 对所有bullets和enemys对象进行更新
  • 调用enemyGenerate() 生成敌人(根据前面描述,需要判断是否满足刚好经过30帧)
  • 进行全局碰撞检测,并进行处理
  • 判断是否经过20帧,每经过20帧,调用player生成一个新的bullet(子弹),并且调用射击音乐

loop()

实现游戏帧循环

  • 每次循环将帧计数器加一
  • 更新逻辑
  • 渲染逻辑更新后的场景
  • 使用window.requestAnimationFrame进行调用,为下一帧界面渲染做准备

微信小游戏 demo 飞机大战 代码分析 (一)(game.js, main.js)的更多相关文章

  1. 微信小游戏 demo 飞机大战 代码分析(四)(enemy.js, bullet.js, index.js)

    微信小游戏 demo 飞机大战 代码分析(四)(enemy.js, bullet.js, index.js) 微信小游戏 demo 飞机大战 代码分析(一)(main.js) 微信小游戏 demo 飞 ...

  2. 微信小游戏 demo 飞机大战 代码分析 (三)(spirit.js, animation.js)

    微信小游戏 demo 飞机大战 代码分析(三)(spirit.js, animation.js) 微信小游戏 demo 飞机大战 代码分析(一)(main.js) 微信小游戏 demo 飞机大战 代码 ...

  3. 微信小游戏 demo 飞机大战 代码分析 (二)(databus.js)

    微信小游戏 demo 飞机大战 代码分析(二)(databus.js) 微信小游戏 demo 飞机大战 代码分析(一)(main.js) 微信小游戏 demo 飞机大战 代码分析(三)(spirit. ...

  4. Python小游戏之 - 飞机大战美女 !

    用Python写的"飞机大战美女"小游戏 源代码如下: # coding=utf-8 import os import random import pygame # 用一个常量来存 ...

  5. Python小游戏之 - 飞机大战 !

    用Python写的"飞机大战"小游戏 源代码如下: # coding=utf-8 import random import os import pygame # 用一个常量来存储屏 ...

  6. 微信demo小游戏:飞机大战从无到有

    微信demo游戏飞机大战从无到有 现在创建新项目会默认给飞机大战的demo,这里给大家从基础开始讲解游戏的从无到有是怎么实现的. 具体实现步骤: 创建背景图->背景图运动起来->创建飞机并 ...

  7. 【转】利用 three.js 开发微信小游戏的尝试

    前言 这是一次利用 three.js 开发微信小游戏的尝试,并不能算作是教程,只能算是一篇笔记吧. 微信 WeChat 6.6.1 开始引入了微信小游戏,初期上线了一批质量相当不错的小游戏.我在查阅各 ...

  8. 微信小游戏的本地缓存和清除的使用 (text.js image.js file-util.js)

    参考: 微信小游戏,文件系统 UpdateManager-小游戏 一.Egret提供的本地缓存工具类( 备注:新版本进行了修改,并增加了sound.js等) 在微信小游戏项目中,Egret提供了fil ...

  9. cocos creator开发微信小游戏记录

    先用cocoscreator实现游戏逻辑 在cocoscreator项目里可以调用微信小游戏api 在cocos里面判断小游戏的运行环境 if (cc.sys.platform === cc.sys. ...

随机推荐

  1. BZOJ4552(二分+线段树)

    要点 序列是n个不同的数,则新学到的一种策略就是二分这个位置的答案,然后可以上下调. 神奇地只关注大于还是小于mid并赋值0.1,这样m个操作的排序就能用线段树维护了! #include <cs ...

  2. js——本地存储

    1. cookie 容量小:4k,在同源的http请求时携带传输,占用带宽,有日期限制 <!DOCTYPE html> <html lang="en"> & ...

  3. 转 MYSQL_GTID详解

    http://blog.itpub.net/27067062/viewspace-2141906/   一.GTID概述  GTID是MYSQL5.6新增的特性,GTID(Global Transac ...

  4. VMware ESXi5忘记登录密码解决办法

    很久没有登录ESXi5了,今天登录发现忘记密码了: 网上搜索到的方法都是使用linux其他版本的镜像的恢复模式来重置密码(尝试过了,ESXI自己的镜像没有恢复模式).于是使用现有的Ubuntu镜像来操 ...

  5. 数据库(DBUtils)

    DBUtils和连接池 今日内容介绍 u DBUtils u 连接池 第1章 DBUtils 如果只使用JDBC进行开发,我们会发现冗余代码过多,为了简化JDBC开发,本案例我们讲采用apache c ...

  6. jQuery学习笔记(三)

    jQuery中的事件 页面加载 原生DOM中的事件具有页面加载的内容onload事件,在jQuery中同样提供了对应的内容ready()函数. ready与onload之间的区别: onload re ...

  7. 常用模块random,time,os,sys,序列化模块

    一丶random模块 取随机数的模块 #导入random模块 import random #取随机小数: r = random.random() #取大于零且小于一之间的小数 print(r) #0. ...

  8. vue $set用法

    需求,想给下面的数据添加一个hoby属性 {{data.hoby}}-->让这里的视图改变 data:{ name: "简书", age: '3', info: { cont ...

  9. testNG测试基础一

    1.TestNG概念 TestNG:Testing Next Generation 下一代测试技术,是一套根据JUnit和Nunit思想构建的利用注释来强化测试功能的测试框架,可用来做单元测试,也可用 ...

  10. UVA Stacks of Flapjacks 栈排序

    题意:给一个整数序列,输出每次反转的位置,输出0代表排序完成.给一个序列1 2 3 4 5,这5就是栈底,1是顶,底到顶的位置是从1~5,每次反转是指从左数第i个位置,将其及其左边所有的数字都反转,假 ...