青瓷引擎之纯JavaScript打造HTML5游戏第二弹——《跳跃的方块》Part 3
继上一次介绍了《神奇的六边形》的完整游戏开发流程后(可点击这里查看),这次将为大家介绍另外一款魔性游戏《跳跃的方块》的完整开发流程。
(点击图片可进入游戏体验)
因内容太多,为方便大家阅读,所以分多次来讲解。
若要一次性查看所有文档,也可点击这里。
接上回(《跳跃的方块》Part 2)
(三)控制展示游戏世界
经过前面的准备,虚拟游戏世界已经构建完成,开始着手将虚拟世界呈现出来。
添加图形资源
导入所有的图形资源到Assets/atlas/main@atlas下,并重新打包图集。
创建功能按钮和界面
在game下添加引导界面、暂停按钮(UIImage)、分数显示(UIText)、暂停界面(Node)、暂停界面下的恢复按钮(UIImage)和一个半透明层(UIImage)
-
暂停按钮
暂停按钮的范围为相对于父节点左上角(20, 20, 70, 70),并且需要能接受事件。 -
分数显示
分数显示区域为相对于父节点右上角(width - 90, 20, width - 110, 70)的范围,并设置文本向右对齐。 -
暂停界面
暂停界面有一个屏蔽操作用的半透明遮罩和一个恢复按钮组成。遮罩需要铺满全屏,恢复按钮以父节点的中心为锚点,向下偏移40。 遮罩和恢复按钮都需要接受事件。 -
引导界面 引导界面提示玩家应该如何操作,以屏幕中心为描点,进行布局。
一点关于布局的理解
显示的内容可以看做一个矩形区域,Pivot控制节点原点在矩形区域中的位置,Anchors和偏移值(left,right,top, bottom, anchoredX, anchoredY, width, height)则控制矩形四个顶点的位置。
所以设置时,先确定希望节点原点的位置,设置好Pivot后,根据希望的矩形设置四个顶点的位置。
假设:父节点的高度、宽度分别为h、w。那么当四个边可以根据公式表达分别为:
- x1 = a1 * w + b1
- x2 = a2 * w + b2
- y1 = c1 * h + d1
- y2 = c2 * h + d2
就可以通过如下设置达到希望的效果:
- anchor中 minX = a1, maxX = a2。当a1 === a2时,设置width = -b2 - b1;否则设置left = b1,right = -b2。
- anchor中 minY = c1, maxY = c2。当c1 === c2时,设置height = -d2 - d1; 否者设置top = d1,right = -d2。
创建游戏世界的基础坐标系
在前面章节中创建的game节点,是一个铺满屏幕的节点,可以理解为对应屏幕,且Pivot为(0,0),那么坐标系为从左上到右下的一个坐标系,这个坐标系和虚拟世界的不同,需要转换下。 在game节点下创建一个节点origin,把origin作为显示虚拟世界的原点。
- 先调整x轴的坐标系。 调整origin的width为0,MinAnchors中的minX,maxX调整为0.5。这样origin就位于父节点的水平中心上。
- 在调整y轴方向。 原来的y轴正方向为从上至下,而虚拟世界的却是从下至上,所以对节点进行垂直翻转(scaleY = -1)来达到预期效果。
- 调整y轴原点位置。 垂直翻转后,原点位置位于屏幕上边缘,通过设置AnchoredY=960将节点移动到屏幕下边缘。 最终该节点的布局参数如下:
映射规则
- 原点所在的y轴即为游戏的deadLine。
- 方块在屏幕中的位置为:y - deadLine。
- 关卡在屏幕中的位置为:-deadLine。
创建关卡父节点
在虚拟世界的创建过程中,分析了关卡的特性,在显示时只需要显示屏幕中的关卡,甚至连创建也不需要,并且关卡是一个连着一个,有点类似于单列表格的形式。于是这里选择使用官方插件中的TableView来实现关卡效果。 使用TableView时,需要为所有的关卡创建一个父节点,和创建方块类似,我们创建一个levels的Node节点,作为所有关卡的父节点。 布局参数如下:
创建方块
在origin节点下创建一个UIImage节点:brick。设置它相对于父节点的上边缘水平中心为锚点,以自己的中心为中心,旋转45度。 最终布局参数如下:
创建坐标系、关卡父节点、方块的具体操作如下:
创建关卡数据适配器
使用TableView时,还需要一个数据适配器(TableViewAdapter)来提供关卡数据。 先引入插件ExtraUI(com.qici.extraUI),建立一个脚本LevelAdapter。内容如下:
1 var LevelAdapter = qc.defineBehaviour('qc.engine.LevelAdapter', com.qici.extraUI.TableViewAdapter, function() { 2 var self = this; 3 4 // 载入配置和游戏世界 5 self.config = JumpingBrick.gameConfig; 6 self.world = JumpingBrick.gameWorld; 7 }, { 8 }); 9 10 LevelAdapter.prototype.awake = function() { 11 }; 12 13 /** 14 * 获取表格大小,x、y同时只能有一个为Infinity 15 * @return {{x: number|Infinity, y: number| Infinity}} 16 */ 17 LevelAdapter.prototype.getTableSize = function() { 18 // 关卡为无限的 19 return { x: 1, y: Infinity}; 20 }; 21 22 /** 23 * 根据在Table中的点返回对应的单元格 24 * @param {number} x - x轴坐标 25 * @param {number} y - y轴坐标 26 * @return {{x: number, y: number}}} 返回点所在的单元格信息 27 */ 28 LevelAdapter.prototype.findCellWithPos = function(x, y) { 29 // 第一个格子为第一屏960的高度,第二个格子为第一关 30 return { 31 x: 0, 32 y: y < 960 ? 0 : (1 + Math.floor((y - 960) / this.config.levelInterval)) 33 }; 34 }; 35 36 /** 37 * 获取节点的显示位置 38 */ 39 LevelAdapter.prototype.getCellRect = function(col, row) { 40 if (row === 0) 41 return new qc.Rectangle(0, 0, 100, 960); 42 else 43 return new qc.Rectangle(0, 960 + (row - 1) * this.config.levelInterval, 100, this.config.levelInterval); 44 }; 45 46 /** 47 * 节点处于不可见时,回收节点, 48 * @param {qc.Node} cell - 节点 49 * @param {number} col - 所在列 50 * @param {number} row - 所在行 51 */ 52 LevelAdapter.prototype.revokeCell = function(cell, col, row) { 53 // 关卡不可见时,删除已经生成的关卡数据 54 this.world.deleteLevelInfo(row - 1); 55 }; 56 57 /** 58 * 节点处于可见时,创建节点, 59 * @param {qc.Node} cell - 节点 60 * @param {number} col - 所在列 61 * @param {number} row - 所在行 62 */ 63 LevelAdapter.prototype.createCell = function(cell, col, row) { 64 // 创建关卡时,设置关卡信息 65 var self = this, 66 levelInfo = self.world.getLevelInfo(row - 1); 67 cell.levelShow.setLevelInfo(levelInfo); 68 };
创建关卡的单元格处理脚本
创建脚本LevelShow,用来控制关卡预制的显示方式。 内容如下:
1 var LevelShow = qc.defineBehaviour('qc.engine.LevelShow', qc.Behaviour, function() { 2 // 将脚本对象关联到节点上 3 this.gameObject.levelShow = this; 4 }, { 5 leftLevel : qc.Serializer.NODE, // 关卡左边阻挡 6 rightLevel : qc.Serializer.NODE, // 关卡右边阻挡 7 block : qc.Serializer.NODE // 阻挡块的父节点 8 }); 9 10 LevelShow.prototype.onDestory = function() { 11 // 释放关联 12 this.gameObject.levelShow = null; 13 }; 14 15 LevelShow.prototype.update = function() { 16 var self = this, 17 width = JumpingBrick.gameConfig.getGameWidth(); 18 // 如果是电脑浏览器打开,游戏显示的宽度可能会变化,所以需要根据屏幕宽度的变化,动态调整关卡阻挡的范围。 19 // 防止左右两边出现空白区域 20 if (width !== self.recordWidth) { 21 var diff = (width - self.recordWidth) / 2; 22 self.recordWidth = width; 23 24 if (diff + self.leftLevel.width > 0) { 25 self.leftLevel.x -= diff; 26 self.leftLevel.width += diff; 27 } 28 29 if (diff + self.rightLevel.width > 0) { 30 self.rightLevel.width += diff; 31 } 32 } 33 }; 34 35 LevelShow.prototype.setLevelInfo = function(levelInfo) { 36 var self = this, 37 width = JumpingBrick.gameConfig.getGameWidth(); 38 var blockChildren = self.block.children; 39 var blockLen = blockChildren.length; 40 41 self.recordWidth = width; 42 if (!levelInfo) { 43 self.leftLevel.visible = self.rightLevel.visible = false; 44 while (blockLen--) { 45 blockChildren[blockLen].visible = false; 46 } 47 return; 48 } 49 var passArea = levelInfo.passArea, 50 color = new qc.Color(levelInfo.color); 51 52 self.leftLevel.visible = self.rightLevel.visible = true; 53 // 设置左边阻挡 54 self.leftLevel.x = -0.5 * width; 55 self.leftLevel.y = passArea.y; 56 self.leftLevel.width = passArea.x - self.leftLevel.x; 57 self.leftLevel.height = passArea.height; 58 self.leftLevel.colorTint = color; 59 60 // 设置右边阻挡 61 self.rightLevel.x = passArea.x + passArea.width; 62 self.rightLevel.y = passArea.y; 63 self.rightLevel.width = 0.5 * width - self.rightLevel.x; 64 self.rightLevel.height = passArea.height; 65 self.rightLevel.colorTint = color; 66 67 // 确保块够用 68 while (blockLen < levelInfo.block.length) { 69 blockLen++; 70 self.game.add.clone(self.leftLevel, self.block); 71 } 72 73 blockChildren = self.block.children; 74 blockLen = blockChildren.length; 75 var idx = -1; 76 while (++idx < blockLen) { 77 var blockInfo = levelInfo.block[idx]; 78 if (!blockInfo) { 79 blockChildren[idx].visible = false; 80 } 81 else { 82 blockChildren[idx].colorTint = color; 83 blockChildren[idx].visible = true; 84 blockChildren[idx].x = blockInfo.x; 85 blockChildren[idx].y = blockInfo.y; 86 blockChildren[idx].width = blockInfo.width; 87 blockChildren[idx].height = blockInfo.height; 88 } 89 } 90 };