前言

植物大战僵尸这款经典游戏相信大家都玩过,最近我用原生JS+ES6语法,并通过canvas绘制的方式实现了这个游戏的一些基本功能,在这里我会介绍一下实现这个游戏的心路历程。

可能又人会问,实现这样的一个小游戏难吗?其实单看每一个实现的游戏模块来说,这个游戏的实现难度并不大,难点主要在于将所有游戏中的模块如何合理的融合在一起,在实现这个小游戏的过程中,我也踩过不少坑,也重写过一部分游戏逻辑。写这篇文章的目的也是为了记录和总结自己最近在开发中遇到的一些问题和解决问题的思路。

很多程序猿可能会说,学习编程不过是为了赚钱而已,也有人是为了提高自身的技术?这个有道理的,为什么么要提高自身的技术呢,其实,最终目的是为了解决问题,解决用户的问题。

既然我们是使用编程解决问题的人,那么,学习它的目的就是为了解决问题,也就是说只要能达到解决问题的深度就可以了。当然问题的大小不同,对语言掌握的程度就有不同的要求。因此,在学习编程的过程中切不可脱离了实际,单纯的为了学习编程语言而学习。

当然,这里可能扯的有点远了,但是主要想说明一下自己写这个小游戏的初衷,也是为了提醒自己,在这个浮躁的时代,不忘初心,方得始终

先上张截图:

试玩链接:植物大战僵尸

游戏引擎篇

在开发这个游戏的时候,我选择基于ES6的class的方式抽象了游戏相关函数,关于这个小游戏的核心引擎,主要的相关属性如下:

class Game {constructor () {...state: 0,                                                     // 游戏状态值,初始默认为 0state_LOADING: 0,                                             // 准备阶段state_START: 1,                                               // 游戏开始state_RUNNING: 2,                                             // 游戏运行state_STOP: 3,                                                // 游戏暂停state_PLANTWON: 4,                                            // 游戏结束,玩家胜利state_ZOMBIEWON: 5,                                           // 游戏结束,僵尸胜利canvas: document.getElementById("canvas"),                    // canvas元素context: document.getElementById("canvas").getContext("2d"),  // canvas画布timer: null,                                                  // 轮询定时器fps: window._main.fps,                                        // 动画帧数}init () { // 初始化函数let g = this...// 设置轮询定时器g.timer = setInterval(function () {// 根据游戏状态,在canvas中绘制不同游戏场景}, 1000/g.fps)...}
}
复制代码

其实核心逻辑很简单,就是定义一个游戏引擎主函数,生成一个定时器以每秒60帧的频率不停在canvas画布上绘制游戏场景相关元素,然后在定时器函数中根据当前游戏状态(游戏准备游戏开始游戏运行游戏暂停游戏结束)来绘制对应游戏场景。

loading游戏状态:游戏引擎绘制了页面载入图片,并添加了一个开始游戏按钮 start 游戏状态:游戏开始读条倒计时,提醒用户游戏即将开始 running游戏状态:绘制游戏运行时所需所有游戏场景素材 stop游戏状态:游戏进入暂停阶段,游戏中如生成阳关、僵尸的定时器都将清除,角色动画处于静止状态 gameover游戏状态:分为玩家获得胜利以及僵尸获得胜利两种情况,并分别绘制不同游戏结算画面

游戏场景篇

在这里我将游戏中所有可控制的元素都归于游戏场景中,并且将这些元素都抽象为类,方便管理,这里包括:植物类僵尸类阳光计分板类植物卡片类动画类,子弹类

游戏场景中最核心的两个类为植物类僵尸类,不过在这两个核心类中都会用到动画类,这里我先介绍一下。

动画类(Animation)

动画类的作用是将每一个角色的不同动画序列保存起来,每隔一定时间切换当前显示的图片对象,达到播放动画的效果。

class Animation{constructor (role, action, fps) {let a = {type: role.type,                                   // 动画类型(植物、僵尸等等)section: role.section,                             // 植物或者僵尸类别action: action,                                    // 根据传入动作生成不同动画对象数组images: [],                                        // 当前引入动画图片对象数组img: null,                                         // 当前显示动画图片imgIdx: 0,                                         // 当前角色图片序列号count: 0,                                          // 计数器,控制动画运行fps: fps,                                          // 角色动画运行速度系数,值越小,速度越快}Object.assign(this, a)}
}
复制代码

这里用到的images,就是通过new Image()的方式生成并添加到images中组成的动画序列:

其中typesection用于判断当前需要加载植物或僵尸、哪一个动作所对应动画序列,countfps用于控制当前动画的播放速度,而img用于表示当前所展示的图片对象,即images[imgIdx],其关系类似于以下代码:

// 在全局定时器中每1/60秒计算一次
// 获取动画序列长度
let animateLen = images.length
// 计数器自增
count++
// 设置当前显示动画序列号
imgIdx = Math.floor(count / fps)
// 当一整套动画完成后重置动画计数器
imgIdx === animateLen - 1 ? count = 0 : count = count
// 设置当前显示动画图片
img = images[imgIdx]
复制代码

======================== 2017.12.4更新start ============================

角色类(Role)

class Role{constructor (obj) {let r = {id: Math.random().toFixed(6) * Math.pow(10, 6),      // 随机生成 id 值,用于设置当前角色 ID,区分不同角色type: obj.type,                                      // 角色类型(植物或僵尸)section: obj.section,                                // 角色类别(豌豆射手、双发射手...)x: obj.x,                                            // x轴坐标y: obj.y,                                            // y轴坐标w: 0,                                                // 角色图片宽度h: 0,                                                // 角色图片高度row: obj.row,                                        // 角色初始化行坐标col: obj.col,                                        // 角色初始化列坐标isAnimeLenMax: false,                                // 是否处于动画最后一帧,用于判断动画是否执行完一轮isDel: false,                                        // 判断是否死亡并移除当前角色isHurt: false,                                       // 判断是否受伤}Object.assign(this, r)}
}
复制代码

这里的角色类主要用于抽象植物类和僵尸类的公共属性,基本属性包括typesectionxywhrowcol,其中rowcol属性用于控制角色在草坪上绘制的横纵坐标(即x轴和y轴方向位于第几个方格),section属性用于区分当前角色到底是哪一种,如豌豆射手、双发射手、加特林射手、普通僵尸。

植物类(Plant)

class Plant{constructor (obj) {let p = {life: 3,                                             // 角色血量idle: null,                                          // 站立动画对象attack: null,                                        // 攻击动画对象bullets: [],                                         // 子弹对象数组state: 1,                                            // 保存当前状态值,默认为1      state_IDLE: 1,                                       // 站立不动状态state_ATTACK: 2,                                     // 攻击状态}Object.assign(this, p)}// 绘制方法draw(cxt) {// 根据当前植物的状态,分别绘制正常状态动画,以及受伤时的半透明状态动画let self = thiscxt.drawImage(self[stateName].img, self.x, self.y)}// 更新当前植物状态update() {// 通过动画计数器计算出当前植物显示动画序列的图片}// 判断当前植物是否可进入攻击状态canAttack() {// 通过轮询僵尸对象数组,判断处于当前植物同行的僵尸,且进入草坪内时,即开始攻击僵尸// 目前仅有三种射手类植物可使用子弹攻击,樱桃炸弹属于范围伤害类植物(判断范围为其周围八个格子内)// 攻击成功时,减少对应僵尸血量,并在僵尸血量到达特殊值时,切换其动画(如濒死状态,死亡状态),在血量为 0 时,从僵尸对象数组中移除当前僵尸}// 射击方法shoot() {// 当前植物攻击时, bullets 数组添加子弹对象let self = thisself.bullets[self.bullets.length] = Bullet.new(self)}
}
复制代码

植物类的私有属性包括idelattackbulletsstate,其中idelattack为动画对象,相信看过上面关于动画类介绍的小伙伴应该能理解其作用,bullets即用于保存当前植物的所有子弹对象(同动画类子弹类也有属性、方法的配置,这里就不详细叙述了)。

关于植物的状态控制属性,如isHurt属性会在植物受伤时,切换为true,并由此给动画添加一个透明度,模拟受伤效果;isDel属性会在植物血量降为0时,将植物从植物对象数组中移除,即不再绘制当前植物;state属性用于植物在两种形态中进行切换,即普通形态、攻击形态,当前状态值为哪种形态,即播放对应形态动画,对应关系如下:

 state === state_IDLE => // 播放植物普通形态动画 idlestate === state_ATTACK => // 播放植物攻击形态动画 attack
复制代码

攻击形态的切换,这里就涉及需要循环当前植物对象与所有的僵尸对象所组成的数组,判断是否有僵尸处于当前植物对象的射程内(即处于同一行草坪,且进行屏幕显示范围)。

这里主要介绍了植物类的相关属性,其方法包括初始化植物对象植物绘制植物射击更新植物状态检测植物是否可攻击僵尸...

僵尸类(Zombie)

// 僵尸类
class Zombie{constructor (obj) {let z = {life: 10,                                            // 角色血量idle: null,                                          // 站立动画对象run: null,                                           // 奔跑动画对象attack: null,                                        // 攻击动画对象dying: null,                                         // 濒临死亡动画对象die: null,                                           // 死亡动画对象state: 1,                                            // 保存当前状态值,默认为1state_IDLE: 1,                                       // 站立不动状态state_RUN: 2,                                        // 奔跑状态state_ATTACK: 3,                                     // 攻击状态state_DYING: 4,                                      // 濒临死亡状态state_DIE: 5,                                        // 死亡状态canMove: true,                                       // 判断当前角色是否可移动attackPlantID: 0,                                    // 当前攻击植物对象 IDspeed: 3,                                            // 移动速度}Object.assign(this, z)}// 绘制方法draw() {// 根据当前僵尸的状态,分别绘制正常状态动画,以及受伤时的半透明状态动画let self = thiscxt.drawImage(self[stateName].img, self.x, self.y)}// 更新当前僵尸状态update() {// 动画计数器计算出当前植物显示动画序列的图片}// 判断当前僵尸是否可进入攻击状态canAttack() {// 通过轮询植物对象数组,判断处于当前僵尸同行的植物,且进入其攻击范围内时,即开始攻击植物// 攻击成功时,当前僵尸 canMove 属性将为 false ,记录其 attackPlantID ,即所攻击植物 id 值,并减少对应植物血量;// 在植物血量为 0 时,切换其动画(进入死亡状态),并从植物对象数组中移除该植物,同时// 将所有攻击该植物的僵尸的状态切换为移动状态, canMove 属性值改为 true}
}
复制代码

这里可以看到僵尸类的很多属性与植物类类似,就不过多叙述了,由于目前只开发了一种僵尸,所以section属性是固定值。

关于僵尸的动画对象可能会比植物复杂一点,包含idlerunattackdyingdie五种形态的动画序列,其中dyingdie对应僵尸较低血量和血量为0时所播放的动画。

在僵尸的控制属性上,与植物同理,这里僵尸的五种动画对象也对应五种状态值,并随状态值的切换而切换。

这里主要介绍了僵尸类的相关属性,其方法包括初始化僵尸对象僵尸绘制僵尸攻击更新僵尸状态检测僵尸是否可攻击植物...

======================== 2017.12.4更新end ============================

游戏主函数篇

在游戏主函数中,将会把之前所有用到的游戏相关类,进行实例化,并保存在Main类中,在这里调用start游戏启动函数,将会开启游戏引擎,开始绘制游戏场景,所以游戏启动函数会在页面加载完成后立即调用。

class Main {constructor () {let m = {allSunVal: 200,                           // 阳光总数量loading: null,                            // loading 动画对象sunnum: null,                             // 阳光实例对象cards: [],                                // 实例化植物卡片对象数组cards_info: {                             // 初始化参数x: 0,y: 0,position: [{name: 'peashooter', row: 1, sun_val: 100},{name: 'repeater', row: 2, sun_val: 150},{name: 'gatlingpea', row: 3, sun_val: 200},]},plants: [],                               // 实例化植物对象数组zombies: [],                              // 实例化僵尸对象数组plants_info: {                            // 初始化参数type: 'plant',                          // 角色类型x: 250,                                 // 初始 x 轴坐标,递增量 80y: 92,                                  // 初始 y 轴坐标,递增量 100len: 0,position: []                            // section:植物类别,row:横行坐标(最小值为 5),col:竖列坐标(最大值为 9)},zombies_info: {                           // 初始化参数type: 'zombie',                         // 角色类型x: 250,                                 // x轴坐标y: 15,                                  // y轴坐标position: []                            // section:僵尸类别,row:横行坐标(最小值为 9),col:竖列坐标(最大值为 13)},zombies_idx: 0,                            // 随机生成僵尸 idxzombies_row: 0,                            // 随机生成僵尸的行坐标zombies_iMax: 30,                          // 随机生成僵尸数量上限sunTimer: null,                            // 全局定时器,用于控制全局定时生成阳光zombieTimer: null,                         // 全局定时器,用于控制全局定时生成僵尸game: null,                                // 游戏引擎对象fps: 60,                                   // 动画帧数}Object.assign(this, m)}// 游戏启动函数start() {// 实例化游戏场景篇中的所有类}
}
window._main = new Main()
window._main.start()
复制代码

这里就简单介绍下plantszombies对象数组;当游戏运行时,所以种植的植物以及生成的僵尸都会配合其相关初始化参数plants_infozombies_info进行实例化再分别保存在plantszombies对象数组中。

后记

较以往的经验来看,关于游戏中相关的方法逻辑作者就不详细介绍了,这个分享主要是为了提供给对小游戏感兴趣,但是却不知如何下手的小伙伴一个思路和经验。

如果有小伙伴对游戏相关代码有任何疑问,或想了解相关小游戏的实现逻辑,需要游戏相关素材,都可以通过以下方式联系作者。

博客:www.yangyunhe.me github地址:github.com/yangyunhe36… QQ:314786482 邮箱:yangyunhe369@qq.com

转载于:https://juejin.im/post/5a16431f6fb9a0451f309975

如何实现H5小游戏—植物大战僵尸相关推荐

  1. 原生JS实现的h5小游戏-植物大战僵尸

    代码地址如下: http://www.demodashi.com/demo/12755.html 项目介绍 本项目是利用原生js实现的h5小游戏-植物大战僵尸,主要结合了一下自己对于h5小游戏的理解, ...

  2. python小游戏“植物大战僵尸”

    python讨论qq群:996113038 导语: 这几天一直写爬虫,感觉写累了.本来准备写一个画画的程序的,但是想来想去没有想到合适的程序.后来想到好久没有给大家推送过游戏了.上次推送游戏还是两个星 ...

  3. 著名游戏植物大战僵尸,从0到1亿美元 ---- PopCap创始人自述

    PopCap是哪家公司我就不介绍了,如果没有玩过他们的游戏,可以说你没有玩儿过PC游戏 8 ) 比较震惊的是他所说的最后一句话:"我们始终有一个信念,那就是一定要做出顶尖游戏,那样才能赚到大 ...

  4. 【CE入门教程】使用Cheat Engine(CE)修改游戏“植物大战僵尸”之僵尸篇

    目录 1.寻找僵尸位置基址 2.实现"秒杀"僵尸(修改僵尸血量) 上一期教程中,我们学习了修改植物大战僵尸的单卡片无CD.全卡片无CD.豌豆射手射速修改以及实现豌豆射手发射&quo ...

  5. 【CE入门教程】使用Cheat Engine(CE)修改游戏“植物大战僵尸”之植物篇

    目录 1.单卡片无CD 1.1 思路一 1.2 思路二 2.全卡片无CD 3.豌豆射手射速修改(修改植物射速) 4.实现豌豆射手发射"玉米加农炮"(思路) 上一期教程中,我们学习了 ...

  6. 【CE入门教程】使用Cheat Engine(CE)修改游戏“植物大战僵尸”之其他篇

    目录 1.跳关(任意选择关卡) 2.修改金币值 3.实现自动收集阳光 上一期教程中,我们学习了寻找植物大战僵尸僵尸距离基址.实现"秒杀"僵尸的方法.PS:上篇链接:[CE入门教程] ...

  7. 以休闲游戏“植物大战僵尸”为例,制作无限阳光修改器

    制作游戏修改器,应该有很多人对这类内容感兴趣吧. 发现这个游戏纯属巧合,一日我在百度搜索"植物"这个词时,搜索框下方就列出一大堆"植物大战僵尸"的相关内容,于时 ...

  8. 【CE入门教程】使用Cheat Engine(CE)修改游戏“植物大战僵尸”之阳光篇

    目录 1.阳光数量(找出阳光基址) 2.向日葵生产阳光速率(基址) 3."阳光雨"(无限掉落阳光基址) 1.阳光数量(找出阳光基址) 首先,游戏刚开始时,我们可以看到此时阳光数量为 ...

  9. 植物大战僵尸anroid版

    anroid植物大战僵尸简易版小游戏 文章目录 一.游戏背景 二.代码内容解析 1.项目展示 总结 # 前言 提示: 小游戏植物大战僵尸,这个是随便做的,不是大项目,所以做的没那么认真 提示:以下是本 ...

最新文章

  1. 行列式介绍及Eigen/OpenCV/C++的三种实现
  2. 数据竞赛Tricks集锦
  3. c# 使用线程方式实现消息订阅
  4. 设置CentOS 6.6系统默认的语言为中文
  5. linux下vim编辑器的基本使用
  6. 【深度学习】如何更好的Fit一个深度神经网络框架下的模型
  7. python适合零基础学习吗-零基础,经济学专业,适合自学Python吗?
  8. 咖友:打个雷啊 | 今日头条VS腾讯新闻:让马化腾也坐不住的新闻资讯行业未来之争...
  9. mysql保存特殊表情_让 MySQL 支持 emoji 表情等特殊字符存储
  10. powerdesigner中如何在自动生成建表SQL时添加模式名schema
  11. IOS之学习笔记六(可变形参)
  12. 深度学习笔记之win7下TensorFlow的安装
  13. C++工作笔记-结构体与类的进一步探究(在C++中的结构体,非C语言结构体)
  14. python人脸识别门禁系统毕设_人脸识别门禁系统 毕业设计 可移植树莓派
  15. 在线正则表达式可视化测试工具
  16. JavaScript:对象转换为字符串、字符串转换为对象
  17. netstat命令详解Linux,Linux netstat命令详解
  18. 多功能智慧路灯杆商业模式解决方案
  19. Java实现家谱家族管理系统,图形化家谱家族树,单机应用程序
  20. 名企笔试真题精选 (六)

热门文章

  1. 全局对象和全局静态、局部静态析构顺序
  2. 用最少数量的箭引爆气球(Java)
  3. hadoop官网介绍及如何下载hadoop(2.4)各个版本与查看hadoop API介绍
  4. 谷歌浏览器:您的连接不是私密连接
  5. 无法进入netsarang官网
  6. 工作中常用到的Linux命令操作
  7. flv视频流页面播放
  8. 区块链是怎么保证可信的?附:一张图看懂区块链
  9. 【详细】手把手实现Grafana自定义主题
  10. apache karaf quick start