弹力细胞 (BounceCell)

一个由JavaScript写的网页小游戏

作为大一菜鸟,这是我第一次比较正式的写文章 [害臊]

游戏玩法

通过鼠标或触屏控制屏幕底部的滑动弹板将发射的小球反弹出去撞击方块,清空所有可撞击方块后游戏获胜,若场上无小球、小球已打空且场上还剩可撞击方块则游戏失败,若是无尽模式,所有小球消失或方块低于红线则游戏失败。




运行思路

分别创建小球( Bullet )、方块( Block )、背景( Block )、反弹板等实体( Entity ),通过对其赋予相应规则、进行相应逻辑判断即可实现游戏运行。

游戏规则

  1. 小球数量有限。

  2. 普通小球碰到分裂触发方块后会在当前位置新增一定数量的小球。

  3. 方块被撞击一定次数后会被破坏。

  4. 拖动或鼠标滑动可移动反弹板。

  5. 反弹板反弹规则为越靠近反弹板顶部的中部,小球反弹越接近正上方,反之朝向两侧。

  6. 爆发效果中反弹板可以在被拖动的状态持续快速发球。

游戏组成

Entity类

由于所有实体都有很多共有属性,所以我们创建了一个父类 Entity 来简化代码。

// 实体基础类
const Entity = function (x, y, w, h) {this.type = "Entity"; // 实体类标识this.x = x; // 中心x坐标this.y = y; // 中心y坐标this.w = w; // 宽this.h = h === undefined ? w : h; // 高this.w2 = this.w / 2;this.h2 = this.h / 2;this.noBounce = false; // 不触发反弹事件this.color = "#000"; // 颜色this.color2 = "#fff"; // 渐变背色this.opacity = 1.0; // 透明度this.txt = ""; // 显示文本this.fontSize = vm(4); // 显示文本的文字大小this.txtColor = "#fff"; // 显示文本的文字颜色this.score = 0; // 价值分数// 获取矩形信息(获取中心点以及宽高)this.rect = () => rectD(this.x, this.y, this.w, this.h);// 获取矩形对角信息(获取左上角坐标以及右下角坐标)this.vector = () => rectF(this.x, this.y, this.w, this.h);// 移动实体坐标this.move = (x, y) => {if (x !== undefined) this.x = x;if (y !== undefined) this.y = y;}// 显示条件this.visible = () => true;
}

PS. 在后续数值计算中大量用到了实体宽高的一半作为参数,所以设置了 w2 h2 来简化代码,优化运行。

Block类

然后我们就创建作为游戏主体的方块类,方块类既可以用来做背景装饰,也可以用来当做游戏内容,实现这一点我们只需要设置其不触发反弹事件即可,其他实体也可以同理实现。

// 方块类
const Block = function (x, y, w, h, hth, nb, color) {Entity.call(this, ...arguments); // 继承实体类this.type = "Block";this.color = color; // 方块颜色this.health = hth; // 方块可受击次数this.newBullet = nb; // 被动子弹分裂数this.effectBoom = false; // 爆发效果// 受击事件this.hit = (index, bullet) => {...}
}

PS. 方块类我们不做主动更新状态,采用被动更新(既是被撞击后才更新状态),因为在运行逻辑中,方块更多时间处于挂起状态。

方块的分类

  1. 装饰方块 newBadblock()
  • 表现见上图中背景,以及白色条纹,装饰方块有各自的绘图接口,同时位于第一绘图层。
// 装饰方块绘图接口
block.update = (block) => {// 可通过对draw进行操作绘图...
}
  1. 永驻方块
  • 是无法被摧毁,但是有反弹事件的方块,表现如反弹板 Launcher 、边界。
  1. 可摧毁方块
  • 是游戏中作为摧毁目标的方块,表现如上图中的灰色方块。
  1. 分裂触发方块
  • 是游戏中被碰撞后出发小球分裂的方块,表现如上图中的绿色方块。
  1. 爆发效果方块
  • 是游戏中被碰撞后给予反弹板被拖动能快速大量发球的效果,表现如红色方块。

Bullet类

作为玩家主要攻击工具,其必须具有可动性,反弹能力,以及在飞出边界后被回收的特性,其实现是本游戏的 难点

// 子弹类
const Bullet = function (x, y, r, deg, speed) {Entity.call(this, x, y, r, r); // 继承实体类this.type = "Bullet";this.r = r;this.color = cfg.c.bt; // 子弹颜色this.deg = deg; // 方向角度this.speed = speed; // 初始速度this.addSpeed = 1; // 速度倍率this.st = dt(); // 位移发生时间this.lx = x; // 上一次中心Xthis.ly = y; // 上一次中心Ythis.ia = false; // 为分裂子弹标识// 反弹实现(计算反弹角度)var bounceImpl = (block, isC, isR) => {...}// 不同反弹对象的不同处理const event = [...],// 反弹实现(检测碰撞)call = () => {...}// 反弹事件this.bounce = () => {...}// 主动状态更新this.update = () => {...}
}

PS. 反弹事件 bounce() 在这儿并没有直接进行反弹处理,首先遍历所有可触发碰撞实体,判断当前小球是否与之相碰,然后再进行反弹处理,反弹处理的实现在 bounceImpl() 中。

一个完整的游戏单次迭代 animloop() 包含游戏逻辑一次加上绘图一次,游戏逻辑运行的作用是为绘图提供参数于内容,在每次游戏逻辑运行中,会遍历所有小球实体,并调用 update() 主动触发小球状态更新达到给予小球可动性与反弹能力,同时判断小球是否飞出边界做回收处理。

小球实现的难点

难点在于如何检测小球与其他实体是否相撞 call(),且计算小球反弹角度 bounceImpl()在实现这个目的的时候,我总共实现了两种方案

  • 方案一:检测两个实体是否相邻或者相交,使用了 Frontier.rectWith()
    具体过程为:遍历所有实体,分别于当前小球做检测,若发生相邻或者相交,则进入计算反弹角度的逻辑,反之进入下一次迭代。它只能提供两个实体是否发生了碰撞。
    .
    这种方案存在特别严重的漏洞,若小球移速过快,完成一次游戏单次迭代后,容易越过中途的方块,所以弃用这个方案。

  • 方案二:投影检测,获取小球移动方向上第一个相交或相邻的方块,达到检测目的。它能提供两个实体是否发生了碰撞和碰撞的边是哪个。同时规避了移速过快时越过方块的漏洞。

实现反弹调度的前提是获取小球碰撞的边是哪个,从而用数学的方式实现对称反弹,公式如 deg * -1 + 180

由于是小球去检测是否与其他实体发生碰撞,若其他实体在这个过程中移动将容易判断失误,现在还有小球莫名其妙检测碰撞失败的情况,(希望大佬能提出宝贵意见)

概率实现

关于概率,我想如何较为均衡的实现概率发生的判断,单纯的对计算机生成的随机数进行大小比较产生的结果的概率是很不可靠的,我们采用相对均衡的取模后对比。

暂停游戏实现

暂停游戏就是暂时停止游戏的进度,我们只需要跳过游戏逻辑运行即可,但同时我们还要考虑游戏内时间的变化,我将当前时间戳作为游戏的一个进展参数,这不利于我们实现暂停,所以将其改成每次完成完整的游戏单次迭代都会给游戏时间增加时间戳使用其均衡。

当暂停游戏后,跳过了游戏逻辑运行,时间刻度自然就不会变化,这能保证进展的正常。

关于Buff的实现

我们对相应目标添加对应开关与逻辑,在原有的游戏时间上增加时长判断即可。

游戏分数的统计

对每个实体我们添加了各自的价值分数,在完成指定事件后会根据价值分数对游戏分数进行影响,然后再通知玩家。

运行流程

先按照默认参数创建游戏,然后创建面板,在玩家自定义参数点击开始游戏后,再次按照当前参数创建游戏,丢弃之前创建的所有游戏状态。

之前创建游戏并不是浪费资源,而是可以将创建出来的界面作为面板背景。

创建游戏:清空所有实体,创建所需实体。

Game.bullets = []; // 清空所有小球
Game.blocks = []; // 清空所有方块
newRoadblock(); // 创建新的方块
newLauncher(); // 创建新反弹板
changePaused(false); // 强制更改暂停状态
toggleVisibility(document.querySelector("#op")); // 切换面板显示
toggleVisibility(document.querySelector("#m")); // 切换面板显示

完整的游戏单次迭代:先更新反弹板位置,然后迭代所有小球,若小球发生碰撞,则触发碰撞事件,反之进入下一次迭代,完成迭代后对所有实体进行绘制,然后判断当前游戏是否结束。

绘制的顺序:杂项 > 方块 > 反弹板 > 小球
无论输赢如何都直接弹出最开始的面板,问就是

游戏逻辑

游戏代码大致可分为三部分,第一部分是一些全局参数和公共方法的声明,第二部分是游戏核心逻辑实现,第三部分是游戏流程的控制。

在内部有以下全局接口供使用:

  1. [ cfg ] 参数配置,存放游戏所有可自定义参数。
  2. [ MouseEvent ] 为控制接口,提供鼠标、触控接口。
  3. [ Frontier ] 为边缘碰撞,提供点与形状的相交相邻检测。
  4. [ Game ] 为游戏的环境,提供游戏内容与游戏实现。
  5. [ draw ] 为canvas(画布)的绘图环境,提供绘图相关功能。

由于游戏设计采用状态保存的逻辑,每运行完一次完整的游戏单次迭代都会更新所有需要更新的实体的状态,然后再绘图,所以我们很方便的动态调整游戏流程与内容,比如突然的结束游戏、清空所有实体等,这有很大一部分原因是我对游戏实体进行了分类存贮,对一部分进行清空或其他处理即可达到想要的效果。

// 游戏实体分类存贮
const game = {// 所有障碍方块blocks: [],// 所有小球bullets: [],// 杂项common: [],// 无尽模式标识infinity: false,// 无尽模式下落时间戳infinity_dt: dt(),// 游戏暂停标识paused: false,// 游戏分数score: 0
};

在存贮方块的时候采用从头插入,在存贮小球以及杂项的时候采用尾部插入,因为小球于方块检测碰撞时,有限考虑最下方的方块可以减少计算量。

cfg

由于每个实体的状态都是确定的,既可以实现当某一个参数被修改后很快就可以看到效果,我们将参数单独提取出来,命名为 cfg ,玩家可以对其自定义。

我有一个想法,在后续更新中推出一个无限模式,我想这个 cfg 会对我有很大的帮助。

MouseEvent

对控制游戏的监听是游戏操作性中很重要的一点,准确性由浏览器绝对,我们可以决定对事件划分的灵活性,在处理不同的事件的时候,要确保事件能够被快速找到获快速替换修改,我不打算使用切片编程,在结构上使用字典来存储,不同的事件划分不同地方存贮。

// 按下 移动 抬起 点击
const TODO = { down: {}, move: {}, up: {}, click: {} };// 更新鼠标坐标
MouseEvent.move.Mouse = (x, y, isDown) => {...
}
// 使平台可以自主发射小球
MouseEvent.up.Launcher = (x, y, dx, dy) => {...
}

在使用时可以直接将对应事件赋值既可以完成绑定。

为了兼容手机上能够正常的控制游戏,对原本的 MouseEvent 进行了封装,参数上的复制,对于是那个手指进行的控制并未进行分类,因为个人觉得一个手指足够控制游戏了。

const trans = fn => {return e => {const target = e.targetTouches[0]; // 暂时支持单指if (target !== undefined) {e.clientX = target.clientX;e.clientY = target.clientY;tx = e.clientX;ty = e.clientY;} else {e.clientX = tx;e.clientY = ty;}fn(e);}
}

Frontier

存放一些点与矩形的相邻相交的检测方法。

  1. 检测点与矩形
  • 只需判断点的横坐标与纵坐标是在矩形上下左右内即可。
  1. 检测矩形与矩形
  • 先判断俩矩形横向上的中心点的距离是否小于或等于俩矩形宽度之和的一半,在判断矩形纵向上的中心点的距离是否小于或等于俩矩形高度之和的一半,若同时满足以上两个条件,则两矩形相交或相邻。

Game

这里主要是游戏核心与实现部分,对其控制可以达到对整个游戏进行控制的效果,主要逻辑是创建所有实体,并定义其规则,已对外开发 G 作为浏览器调试接口,效果与内部访问 Game 一致,同时 G 也链接了 cfg 可访问 G.cfg 达到访问 cfg 的效果。

draw

通过 requestAnimationFrame 对每一帧进行绘制,同时可以达到设备支持的理论最大帧数,将每个实体按照不同规格绘制出,绘制原理详见 Canvas。

draw.push = (...args) => {// 绘制的先后顺序为从左到右,后绘制的内容将覆盖上一次绘制的内容...
}

我们在纯色的基础上添加了渐变效果,使得界面看起来更加丰富。

作者的一些话

咱就是说因为想起小时候电脑前的快乐,才有了这个尝试,我想我会继续写出很多以前玩过的游戏,并丰富它们,你对游戏有什么意见、疑问、想法都可以留言。

本项目遵循BSD开源协议,创作不易感谢理解。

开源地址: https://gitee.com/ExFTLPT/bounce-cell

To be continued…

弹力细胞,一个由JavaScript写的网页小游戏相关推荐

  1. 利用Python编写一个AI脚本自动控制2048网页小游戏

    前言 本文将使用python+selenium自动控制游戏运行.当然采用的是伪随机数进行键盘控制.只作为一个抛砖迎玉的参考,不涉及专业算法. – 一.前期准备(必须有) 1.在安装好的pyCharm中 ...

  2. 利用JavaScript写猜数字小游戏

    要求:  在页面中写一个猜数字的游戏:  要求:   1)生成0~100之间的随机数,让用户猜   2)输入错误需要提示,并让用户重新输入   3)输入正确,提示正确,并询问是否继续游戏 结果如下: ...

  3. javascript编写的网页小游戏,很给力

    这是游戏界面 以下为游戏代码: 1 <html> 2 <HEAD> 3 4 <SCRIPT LANGUAGE="JavaScript"> 5 & ...

  4. html5 网页游戏论文,JavaScript编写的网页小游戏,很给力

    以下为游戏代码: var timerID = null; var INT = 40; var loadFLG = 0; var gameFLG = 0; var missFLG = 0; var ti ...

  5. 226款H5手机端小游戏源码下载 - HTML+JavaScript开发的网页小游戏开源源码大合集,经过亲测可用!

    演示端:http://game.tutou.wang/  (手机访问)  可在线试玩的分享. 链接:https://pan.baidu.com/s/1-R6wEZjLXYm0iowBJLFS8g?pw ...

  6. 用JavaScript写的俄罗斯方块小游戏(很简单,很详细)

    效果 编写外部框架 <!DOCTYPE html> <html><head lang="en"><meta charset="U ...

  7. 你写一个web网页小游戏

    写一个 web 网页小游戏需要以下几个步骤: 选择一种编程语言,常用的有 HTML.CSS.JavaScript 和 Python. 使用编辑器创建一个 HTML 文件,这个文件将是你的网页的基础. ...

  8. 使用Hype物理引擎制作一个网页小游戏(中)

    在上一小节中,相信大家跟着小编的教程都能够顺利完成这款网页小游戏的静态场景制作,在本小节中,小编将带着大家完成网页小游戏的动画录制部分. 第一步:从上一节的游戏预览效果可以看到,游戏过程中,上方的矩形 ...

  9. JavaScript实现贪吃蛇小游戏(网页单机版)

    文章目录 项目地址 项目介绍 游戏开始 游戏暂停 游戏模式 游戏死亡 重新开始 结尾 今天使用 JavaScript 实现了一个网页版的贪吃蛇小游戏. 项目地址 Github: https://git ...

最新文章

  1. c++ 隐式类型转换
  2. 仿WINDWS无限级Ajax菜单树升级1.2版(菜单名支持非法字符)
  3. CSS自学教程--一天搞定CSS(终篇总结)
  4. 约束条件创建定义表(主码,外码的确定和防止空值等)
  5. 关系型数据库,第一!
  6. linux下unix timestamp 与 可视化时间/常规时间进行转换
  7. 微贷网 Java_关于Java集合的小抄
  8. UniWebView3 使用中遇到的坑
  9. 推荐几个我喜爱的英文民谣歌手
  10. 教师对php作品评语通用,期末教师给学生的评语
  11. Windows 下 VS 配置 OpenGL 环境
  12. 论文笔记-Understanding Convolution for Semantic Segmentation
  13. 什么是静态网站生成器
  14. mysql怎么增加内存_MySQL内存不足怎么办
  15. java 找出所有水仙花数,java 水仙花数 所有的
  16. mfc实现c语言的注释,C语言学习:标识符、关键字、注释、表达式和语句
  17. QLineEdit setFocus失效问题
  18. 一碗鸡汤与学习方法——鱼与熊掌都可兼得
  19. [leetcode 面试题 17.17] -- 多次搜索,KMP与字典树
  20. 近红外二区(磷化铟/硫化锌)InP/ZnS量子点波长在650-900 nm,齐岳供应

热门文章

  1. 【Fluent Meshing】04:计算域提取
  2. [内附完整源码和文档] 基于JAVA的合同管理系统
  3. 让一维数组像糖果盒一样简单
  4. 升压芯片很简单(三),FSB628升压芯片大串讲
  5. 【报告分享】2020年中国工业机器视觉产业发展白皮-赛迪(附下载)
  6. 二维码在线生成api
  7. BPSK、2FSK、2ASK
  8. 欧尼酱讲JVM(13)——本地方法栈
  9. 图片文件转二进制数据
  10. mPaaS 客户端证书错误避坑指南