博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
青瓷引擎之纯JavaScript打造HTML5游戏第二弹——《跳跃的方块》Part 2
阅读量:5265 次
发布时间:2019-06-14

本文共 15759 字,大约阅读时间需要 52 分钟。

继上一次介绍了《神奇的六边形》的完整游戏开发流程后(),这次将为大家介绍另外一款魔性游戏《跳跃的方块》的完整开发流程。

          (点击图片可进入游戏体验)

因内容太多,为方便大家阅读,所以分多次来讲解。

若要一次性查看所有文档,也可

 

接上回(

三. 游戏世界

为了能更快的体验到游戏的主体玩法,调整游戏数值,这里我们先来搭建游戏世界。

建立基础世界

在《跳跃的方块》中,下一关的信息尤为关键。如果能提前获知阻挡点或者通道位置,会为当前的操作提供一定的指导。为了保证所有玩家获取的信息基本一致,屏幕中显示的关卡数量需要严格的控制。

所以这里我们将屏幕的高度通过UIRoot映射为一个固定值:960,添加一个锁定屏幕旋转方向的脚本,并创建游戏的根节点game,设置game节点铺满屏幕。

操作如下所示:

分步构建世界

  • 游戏配置
  • 构建世界逻辑
  • 控制展示游戏世界

 

(一)游戏配置

设置可调整参数

这个游戏中,一些参数会严重影响用户体验,需要进行不停的尝试,以找到最合适的设置。所以,这里将这些参数提取出来,群策群力,快速迭代出最终版本。

分析游戏内容后,将游戏数据分为两类:

1. 关卡数据 如何生成关卡、如何生成阻挡。把这些数据配置到一个Excel文件JumpingBrick.xls中,并拷贝到Assets/excel目录下。内容如下: 

2. 物理信息 游戏使用的物理碰撞比较简单,而且移动的方块自身有旋转45度,不太适合直接使用引擎的物理插件。故而这里直接设置方块上升的速度,下落的加速度等物理信息,由游戏脚本自己处理。

新建一个脚本GameConfig.js,内容如下:

1 /*  2  *  游戏配置  3  */  4 var GameConfig = qc.defineBehaviour('qc.JumpingBrick.GameConfig', qc.Behaviour, function() {  5     var self = this;  6   7     // 设置到全局中  8     JumpingBrick.gameConfig = self;  9  10     // 等级配置 11     self.levelConfigFile = null; 12  13     // 游戏使用的重力 14     self.gravity = -1600; 15  16     // 点击后左右移动的速度 17     self.horVelocity = 100; 18  19     // 点击后上升的速度 20     self.verVelocity = 750; 21  22     // 点击后上升速度的持续时间 23     self.verVelocityKeepTime = 0.001; 24  25     // 锁定状态下竖直速度 26     self.verLockVelocity = -200; 27  28     // 块位置超过屏幕多少后,屏幕上升 29     self.raiseLimit = 0.5; 30  31     // 层阻挡高度 32     self.levelHeight = 67; 33  34     // 层间距 35     self.levelInterval = 640; 36  37     // 普通阻挡的边长 38     self.blockSide = 45; 39  40     // 方块的边长 41     self.brickSide = 36; 42  43     // 计算碰撞的最大时间间隔 44     self.preCalcDelta = 0.1; 45  46     // 关卡颜色变化步进 47     self.levelColorStride = 5; 48  49     // 关卡颜色的循环数组 50     self.levelColor = [0x81a3fc, 0xeb7b49, 0xea3430, 0xf5b316, 0x8b5636, 0x985eb5]; 51  52     // 保存配置的等级信息 53     self._levelConfig = null; 54  55     self.runInEditor = true; 56 }, { 57     levelConfigFile: qc.Serializer.EXCELASSET, 58     gravity : qc.Serializer.NUMBER, 59     horVelocity : qc.Serializer.NUMBER, 60     verVelocity : qc.Serializer.NUMBER, 61     verVelocityKeepTime : qc.Serializer.NUMBER, 62     raiseLimit : qc.Serializer.NUMBER, 63     levelHeight : qc.Serializer.NUMBER, 64     levelInterval : qc.Serializer.NUMBER, 65     blockSide : qc.Serializer.NUMBER, 66     preCalcDelta : qc.Serializer.NUMBER, 67     levelColorStride : qc.Serializer.NUMBER, 68     levelColor : qc.Serializer.NUMBERS 69 }); 70  71 GameConfig.prototype.getGameWidth = function() { 72     return this.gameObject.width; 73 }; 74  75 GameConfig.prototype.awake = function() { 76     var self = this; 77  78     // 将配置表转化下,读取出等级配置 79     var rows = self.levelConfigFile.sheets.config.rows; 80     var config = []; 81     var idx = -1, len = rows.length; 82     while (++idx < len) { 83         var row = rows[idx]; 84         // 为了方便配置,block部分使用的是javascript的数据定义语法 85         // 通过eval转化为javascript数据结构 86         row.block = eval(row.block); 87         config.push(row); 88     } 89  90     self._levelConfig = config; 91  92     // 计算出方块旋转后中心到顶点的距离 93     self.brickRadius = self.brickSide * Math.sin(Math.PI / 4); 94 }; 95  96 /* 97  *  获取关卡配置 98  */ 99 GameConfig.prototype.getLevelConfig = function(level) {100     var self = this;101     var len = self._levelConfig.length;102     while (len--) {103         var row = self._levelConfig[len];104         if (row.start > level || (row.end > 0 && row.end < level)) {105             continue;106         }107         return row;108     }109     return null;110 };
View Code

 

(二)构建世界逻辑

《跳跃的方块》是一个无尽的虚拟世界,世界的高度不限,宽度根据显示的宽度也不尽相同。为了方便处理显示,我们设定一个x轴从左至右,y轴从下至上的坐标系,x轴原点位于屏幕中间。如下图所示:

基础设定

  1. 方块的坐标为方块中心点的坐标。
  2. 方块的初始位置为(0, 480)。
  3. 关卡的下边界的y轴坐标值为960。保证第一个屏幕内,看不到关卡;而当方块跳动后,关卡出现。
  4. 关卡只需要生成可通行范围的矩形区域,阻挡区域根据屏幕宽度和可通行区域计算得到。
  5. 阻挡块需要生成实际占据的矩形区域。

创建虚拟世界

创建虚拟世界的管理脚本:GameWorld.js。代码内容如下:

1 var GameWorld = qc.defineBehaviour('qc.JumpingBrick.GameWorld', qc.Behaviour, function() { 2     var self = this; 3  4     // 设置到全局中 5     JumpingBrick.gameWorld = self; 6  7     // 创建结束监听 8     self.onGameOver = new qc.Signal(); 9 10     // 分数更新的事件11     self.onScoreChanged = new qc.Signal();12 13     self.levelInfo = [];14 15     self.runInEditor = true;16 }, {17 18 });19 20 GameWorld.prototype.awake = function() {21     var self = this;22     // 初始化状态23     this.resetWorld();24 };
View Code

游戏涉及到的数据

在虚拟世界中,方块有自己的位置、水平和竖直方向上的速度、受到的重力加速度、点击后上升速度保持的时间等信息。每次游戏开始时,需要重置这些数据。 现在大家玩游戏的时间很零碎,很难一直关注在游戏上,所以当游戏暂停时,我们需要保存当前的游戏数据。这样,玩家可以再找合适的时间来继续游戏。

先将重置、保存数据、恢复数据实现如下:

1 /** 2  * 设置分数 3  */ 4 GameWorld.prototype.setScore = function(score, force) { 5     if (force || score > this.score) { 6         this.score = score; 7         this.onScoreChanged.dispatch(score);     8     } 9 };10 11 /**12  * 重置世界13  */14 GameWorld.prototype.resetWorld = function() {15     var self = this;16 17     // 方块在虚拟世界坐标的位置18     self.x = 0;19     self.y = 480;20 21     // 方块在虚拟世界的速度值22     self.horV = 0;23     self.verV = 0;24 25     // 当前受到的重力26     self.gravity = JumpingBrick.gameConfig.gravity;27 28     // 维持上升速度的剩余时间29     self.verKeepTime = 0;30 31     // 死亡线的y轴坐标值32     self.deadline = 0;33 34     // 已经生成的关卡35     self.levelInfo = [];36 37     // 是否游戏结束38     self.gameOver = false;39 40     // 当前的分数41     self.setScore(0, true);42 };43 44 /**45  * 获取要保存的游戏数据46  */47 GameWorld.prototype.saveGameState = function() {48     var self = this;49     var saveData = {50         deadline : self.deadline,51         x : self.x,52         y : self.y,53         horV : self.horV,54         verV : self.verV,55         gravity : self.gravity,56         verKeepTime : self.verKeepTime,57         levelInfo : self.levelInfo,58         gameOver : self.gameOver,59         score : self.score60     };61     return saveData;62 };63 64 /**65  * 恢复游戏66  */67 GameWorld.prototype.restoreGameState = function(data) {68     if (!data) {69         return false;70     }71     var self = this;72     self.deadline = data.deadline;73     self.x = data.x;74     self.y = data.y;75     self.horV = data.horV;76     self.verV = data.verV;77     self.gravity = data.gravity;78     self.verKeepTime = data.verKeepTime;79     self.levelInfo = data.levelInfo;80     self.gameOver = data.gameOver;81     self.setScore(data.score, true);82     return true;83 };
View Code

动态创建关卡数据

世界坐标已经确定,现在开始着手创建关卡信息。 因为游戏限制了每屏能显示的关卡数,方块只会和本关和下关的阻挡间产生碰撞,所以游戏中不用在一开始就创建很多的关卡。而且游戏中方块不能下落出屏幕,已经通过的,并且不在屏幕的内的关卡,也可以删除,不予保留。

所以,我们根据需求创建关卡信息,创建完成后保存起来,保证一局游戏中,关卡信息是固定的。 代码如下:

1 /** 2  * 获取指定y轴值对应的关卡 3  */ 4 GameWorld.prototype.transToLevel = function(y) { 5     // 关卡从0开始,-1表示第一屏的960区域 6     return y < 960 ? -1 : Math.floor((y - 960) / JumpingBrick.gameConfig.levelInterval); 7 }; 8  9 /**10  * 获取指定关卡开始的y轴坐标11  */12 GameWorld.prototype.getLevelStart = function(level) {13     return level < 0 ? 0 : (960 + level * JumpingBrick.gameConfig.levelInterval);14 };15 16 /**17  * 删除关卡数据18  */19 GameWorld.prototype.deleteLevelInfo = function(level) {20     var self = this;21 22     delete self.levelInfo[level];23 };24 25 26 /**27  * 获取关卡信息28  */29 GameWorld.prototype.getLevelInfo = function(level) {30     if (level < 0) 31         return null;32 33     var self = this;34     var levelInfo = self.levelInfo[level];35 36     if (!levelInfo) {37         // 不存在则生成38         levelInfo = self.levelInfo[level] = self.buildLevelInfo(level);39     }40     return levelInfo;41 };42 43 /**44  * 生成关卡45  */46 GameWorld.prototype.buildLevelInfo = function(level) {47     var self = this,48         gameConfig = JumpingBrick.gameConfig,49         blockSide = gameConfig.blockSide,50         levelHeight = gameConfig.levelHeight;51 52     var levelInfo = {53         color: gameConfig.levelColor[Math.floor(level / gameConfig.levelColorStride) % gameConfig.levelColor.length],54         startY: self.getLevelStart(level),55         passArea: null,56         block: []57     };58 59     // 获取关卡的配置60     var cfg = JumpingBrick.gameConfig.getLevelConfig(level);61 62     // 根据配置的通行区域生成关卡的通行区域63     var startX = self.game.math.random(cfg.passScopeMin, cfg.passScopeMax - cfg.passWidth);64     levelInfo.passArea = new qc.Rectangle(65         startX, 66         0, 67         cfg.passWidth,68         levelHeight);69 70     // 生成阻挡块71     var idx = -1, len = cfg.block.length;72     while (++idx < len) {73         var blockCfg = cfg.block[idx];74         // 阻挡块x坐标的生成范围是可通行区域的左侧x + minX 到 右侧x + maxX75         var blockX = startX + 76             self.game.math.random(blockCfg.minx, cfg.passWidth + blockCfg.maxx - blockSide);77         // 阻挡块y坐标的生成范围是关卡上边界y + minY 到上边界y + maxY78         var blockY = JumpingBrick.gameConfig.levelHeight + 79             self.game.math.random(blockCfg.miny, blockCfg.maxy - blockSide);80 81         levelInfo.block.push(new qc.Rectangle(82             blockX,83             blockY,84             blockSide,85             blockSide));86     }87     return levelInfo;88 };
View Code

分数计算

根据设定,当方块完全通过关卡的通行区域后,就加上一分,没有其他的加分途径,于是,可以将分数计算简化为计算当前完全通过的最高关卡。代码如下:

1 /** 2  * 更新分数 3  */ 4 GameWorld.prototype.calcScore = function() { 5     var self = this; 6  7     // 当前方块所在关卡 8     var currLevel = self.transToLevel(self.y); 9     // 当前关卡的起点10     var levelStart = self.getLevelStart(currLevel);11 12     // 当方块完全脱离关卡通行区域后计分13     var overLevel = self.y - levelStart - JumpingBrick.gameConfig.levelHeight - JumpingBrick.gameConfig.brickRadius;14     var currScore = overLevel >= 0 ? currLevel + 1  : 0;15     self.setScore(currScore);16 };
View Code

物理表现

方块在移动过程中,会被给予向左或者向右跳的指令。下达指令后,方块被赋予一个向上的速度,和一个水平方向的速度,向上的速度会保持一段时间后才受重力影响。 理清这些效果后,可以用下面这段代码来处理:

1 /** 2  * 控制方块跳跃 3  * @param {number} direction - 跳跃的方向 < 0 时向左跳,否则向右跳 4  */ 5 GameWorld.prototype.brickJump = function(direction) { 6     var self = this; 7     // 如果重力加速度为0,表示方块正在靠边滑动,只响应往另一边跳跃的操作 8     if (self.gravity === 0 && direction * self.x >= 0) { 9         return;10     }11     // 恢复重力影响12     self.gravity = JumpingBrick.gameConfig.gravity;13     self.verV = JumpingBrick.gameConfig.verVelocity;14     self.horV = (direction < 0 ? -1 : 1) * JumpingBrick.gameConfig.horVelocity;15     self.verKeepTime = JumpingBrick.gameConfig.verVelocityKeepTime;16 };17 18 /**19  * 移动方块20  * @param {number} delta - 经过的时间21  */22 GameWorld.prototype.moveBrick = function(delta) {23     var self = this;24 25     // 首先处理水平方向上的移动26     self.x += self.horV * delta;27 28     // 再处理垂直方向上得移动29     if (self.verKeepTime > delta) {30         // 速度保持时间大于经历的时间31         self.y += self.verV * delta;32         self.verKeepTime -= delta;33     }34     else if (self.verKeepTime > 0) {35         // 有一段时间在做匀速运动,一段时间受重力加速度影响36         self.y += self.verV * delta + 0.5 * self.gravity * Math.pow(delta - self.verKeepTime, 2);37         self.verV += self.gravity * (delta - self.verKeepTime);38         self.verKeepTime = 0;39     }40     else {41         // 完全受重力加速度影响42         self.y += self.verV * delta + 0.5 * self.gravity * Math.pow(delta, 2);43         self.verV += self.gravity * delta;44     }45 };
View Code

碰撞检测

这样方块就开始运动了,需要让它和屏幕边缘、关卡通道、阻挡碰撞,产生不同的效果。

  1. 当方块与关卡阻挡碰撞后,结束游戏。
  2. 当方块与屏幕下边缘碰撞后,结束游戏。
  3. 当方块与屏幕左右边缘碰撞后,将不受重力加速度影响,沿屏幕边缘做向下的匀速运动,直到游戏结束,或者接收到一个向另一边边缘跳跃的指令后恢复正常。

旋转45°后的方块与矩形的碰撞:

  1. 当方块的包围矩形和矩形不相交时,不碰撞。
  2. 当方块的包围矩形和矩形相交时。如下图分为两种情况处理。

代码实现如下:

1 /**  2  * 掉出屏幕外结束  3  */  4 GameWorld.GAMEOVER_DEADLINE = 1;  5 /**  6  * 碰撞结束  7  */  8 GameWorld.GAMEOVER_BLOCK = 2;  9  10 /** 11  * 块与一个矩形阻挡的碰撞检测 12  */ 13 GameWorld.prototype.checkRectCollide = function(x, y, width, height) { 14     var self = this, 15         brickRadius = JumpingBrick.gameConfig.brickRadius; 16  17     var    upDis = self.y - y - height; // 距离上边距离 18     if (upDis >= brickRadius)  19         return false; 20  21     var downDis = y- self.y; // 距离下边距离 22     if (downDis >= brickRadius) 23         return false; 24  25     var leftDis = x - self.x; // 距离左边距离 26     if (leftDis >= brickRadius) 27         return false; 28  29     var rightDis = self.x - x - width; // 记录右边距离 30     if (rightDis >= brickRadius) 31         return false; 32  33     // 当块中点的y轴值,在阻挡的范围内时,中点距离左右边的边距小于brickRadius时相交 34     if (downDis < 0 && upDis < 0) { 35         return leftDis < brickRadius && rightDis < brickRadius; 36     } 37  38     // 当块的中点在阻挡范围上时 39     if (upDis > 0) { 40         return leftDis < brickRadius - upDis && rightDis < brickRadius - upDis; 41     } 42     // 当块的中点在阻挡范围下时 43     if (downDis > 0) { 44         return leftDis < brickRadius - downDis && rightDis < brickRadius - downDis; 45     } 46     return false; 47 }; 48  49 /** 50  * 碰撞检测 51  */ 52 GameWorld.prototype.checkCollide = function() { 53     var self = this; 54  55     // game节点铺满了屏幕,那么节点的宽即为屏幕的宽 56     var width = this.gameObject.width; 57     var brickRadius = JumpingBrick.gameConfig.brickRadius; 58     var leftEdge = -0.5 * width; 59     var rightEdge = 0.5 * width; 60  61     // 下边缘碰撞判定,方块中心的位置距离下边缘的距离小于方块的中心到顶点的距离 62     if (this.deadline - self.y > brickRadius) { 63         return GameWorld.GAMEOVER_DEADLINE; 64     } 65  66     // 左边缘判定,方块中心的位置距离左边缘的距离小于方块的中心到顶点的距离 67     if (self.x - leftEdge < brickRadius) { 68         self.x = leftEdge + brickRadius; 69         self.horV = 0; 70         self.verV = JumpingBrick.gameConfig.verLockVelocity; 71         self.gravity = 0; 72     } 73     // 右边缘判定,方块中心的位置距离右边缘的距离小于方块的中心到顶点的距离 74     if (rightEdge - self.x < brickRadius) { 75         self.x = rightEdge - brickRadius; 76         self.horV = 0; 77         self.verV = JumpingBrick.gameConfig.verLockVelocity; 78         self.gravity = 0; 79     } 80  81     // 方块在世界中,只会与当前关卡的阻挡和下一关的阻挡进行碰撞 82     var currLevel = self.transToLevel(self.y); 83     for (var idx = currLevel, end = currLevel + 2; idx < end; idx++) { 84         var level = self.getLevelInfo(idx); 85         if (!level)  86             continue; 87  88         var passArea = level.passArea; 89         // 检测通道左侧和右侧阻挡 90         if (self.checkRectCollide( 91                 leftEdge,  92                 passArea.y + level.startY,  93                 passArea.x - leftEdge,  94                 passArea.height) || 95             self.checkRectCollide( 96                 passArea.x + passArea.width,  97                 passArea.y + level.startY,  98                 rightEdge - passArea.x - passArea.width, 99                 passArea.height)) {100             return GameWorld.GAMEOVER_BLOCK;101         }102 103         // 检测本关的阻挡块104         var block = level.block;105         var len = block.length;106         while (len--) {107             var rect = block[len];108             if (self.checkRectCollide(rect.x, rect.y + level.startY, rect.width, rect.height)) {109                 return GameWorld.GAMEOVER_BLOCK;110             }111         }112     }113 114     return 0;115 };
View Code

添加时间处理

到此,游戏世界的基本逻辑差不多快完成了。现在加入时间控制。

1 /** 2  * 游戏结束的处理 3  */ 4 GameWorld.prototype.doGameOver = function(type) { 5     var self = this; 6     self.gameOver = true; 7     self.onGameOver.dispatch(type); 8 }; 9 10 /**11  * 更新逻辑处理12  * @param {number} delta - 上一次计算到现在经历的时间,单位:秒13  */14 GameWorld.prototype.updateLogic = function(delta) {15     var self = this,16         screenHeight = self.gameObject.height;17     if (self.gameOver) {18         return;19     }20     // 将经历的时间分隔为一小段一小段进行处理,防止穿越21     var calcDetla = 0;22     while (delta > 0) {23         calcDetla = Math.min(delta, JumpingBrick.gameConfig.preCalcDelta);24         delta -= calcDetla;25         // 更新方块位置26         self.moveBrick(calcDetla);27         // 检测碰撞28         var ret = self.checkCollide();29         if (ret !== 0) {30             // 如果碰撞关卡阻挡或者碰撞死亡线则判定死亡31             self.doGameOver(ret);32             return;33         }34     }35 36     // 更新DeadLine37     self.deadline = Math.max(self.y - screenHeight * JumpingBrick.gameConfig.raiseLimit, self.deadline);38 39     // 结算分数40     self.calcScore();41 };
View Code

 

 

经过前面的准备,虚拟游戏世界已经构建完成,下次将讲解如何着手将虚拟世界呈现出来。敬请期待!

 

其他相关链接

 

转载于:https://www.cnblogs.com/qici/p/5069304.html

你可能感兴趣的文章
ArcGIS Engine 中的绘制与编辑
查看>>
Oracle--通配符、Escape转义字符、模糊查询语句
查看>>
c# 文件笔记
查看>>
第一页 - 工具的使用(webstorm)
查看>>
Linux 进程资源用量监控和按用户设置进程限制
查看>>
IE浏览器整页截屏程序(二)
查看>>
D3.js 之 d3-shap 简介(转)
查看>>
制作满天星空
查看>>
类和结构
查看>>
CSS3选择器(二)之属性选择器
查看>>
adidas crazylight 2018 performance analysis review
查看>>
typeset shell 用法
查看>>
python 之 循环语句
查看>>
心得25--JDK新特性9-泛型1-加深介绍
查看>>
[转]ceph网络通信模块_以monitor模块为例
查看>>
HDOJ 1754 I Hate It(线段树基本操作)
查看>>
latex tree
查看>>
安装NVIDIA驱动时禁用自带nouveau驱动
查看>>
HDU-1255 覆盖的面积 (扫描线)
查看>>
css3学习01
查看>>