HTML5 2D平台游戏开发#6地图绘制
此前已经完成了一部分角色的动作,现在还缺少可以交互的地图让游戏看起来能玩。不过在开始之前应当考虑清楚使用什么类型的地图,就2D平台游戏来说,一般有两种类型的地图,Tile-based和Art-based,即基于瓦片风格和美术风格两种。Tile-based的典型代表是《Super Mario》(超级马里奥),Art-based记不太清楚了,能够回想起来的是去年出的一款叫做《Owlboy》(猫头鹰男孩)的游戏。
![]() |
![]() |
| Super Mario | Owlboy |
由于Art-based的实现需要一款较为专业的可视化游戏开发工具,因此我选择较为传统的Tile-based。
- 瓦片尺寸的选择
通常可选的瓦片尺寸有16X16、32X32、64X64、128X128等,都是2的N次方。原因之一当然是比较好计算,另外一个原因是计算机对2的倍数计算速度有比较明显的优化。这里采用32X32的尺寸。
![]() |
| 游戏中暂时用到的瓦片集合 |
- 将瓦片集合制作成地图
Tiled Map Editor是一款非常强大的地图编辑软件,体积不大而且还是开源的,是地图编辑的首选。
|
| 编辑中的地图 |
可以添加多个图层来表现游戏地图的层次,这里使用两个图层,背景层和地面层。经过考虑,我暂时不打算使用对象层,故而上面的勾去掉了。虽然添加这个层可以很方便地得到碰撞数据,但需要对每个碰撞的部分建立包围盒,当地图很大并且很多的时候就十分耗费时间去添加和维护,后期会在添加角色出生点或者寻路时才使用它。
- 导出地图数据
将地图数据导出为JSON格式,得到类似下面的数据:
layers:[
[3, 3, 3, 8, 9, 3, 3, 3, 3, 3, 3, 3...],//背景层
[-1, -1, -1, -1, -1, -1,-1, -1, 10...]//地面层
]
其中的数字代表瓦片在瓦片集合中的索引,不过需要注意索引是从1开始的,而一般我们计算时都喜欢从0开始,可以在代码中将其减去1或者用数组的map方法处理一下并覆盖原始数据。同时建立一个列表来标注瓦片的类型:
MAPCONFIG.FIELDTYPE = {
'solid': [0, 1, 2, 13, 14, 15, 26, 27, 28, 30, 31, 32, 33, 34, 35],
'null': [3, 5, 6, 7, 8, 9, 11, 12, 16, 17, 18, 19, 22, 23]
};
根据瓦片的数字来判断它是否需要建立碰撞包围盒,这样就省去了上述建立对象层的步骤。
- 在画布上绘制地图
上面已经得到了地图数据,可以开始进行地图的绘制了。新建一个地图管理函数MapManager():
class MapManager {
constructor(level, ctx, assets) {
this.spriteSheet = assets.image;
//416,96 所采用图片资源的宽高
this.dimensions = {w: assets.w, h: assets.h};
this.level = level;
this.layerLength = this.level.layers.length;
this.ctx = ctx;
}
//prototype ...
}
接下来需要一个获取瓦片代表数字的方法getTile(),这个方法返回像3,5,-1这样的数字,即瓦片在图片上的索引。
//layerIndex 图层索引
//col 瓦片所在列的编号
//row 瓦片所在行的编号
getTile(layerIndex, col, row) {
//this.level.cols为整个地图的列数
return this.level.layers[layerIndex][row * this.level.cols + col];
}
_drawLayer()方法绘制一个层:
let startCol = 0,//起始列
endCol = 40,//结束列
startRow = 0,//开始行
endRow = 20,//结束行
tileSize = MAPCONFIG.TILESIZE;// for (let r = startRow; r < endRow; r++) {
for (let c = startCol; c < endCol; c++) {
let tile = this.getTile(layerIndex, c, r),
x = (c - startCol) * tileSize, //瓦片的x坐标
y = (r - startRow) * tileSize; //瓦片的y坐标
if (tile !== -1) {//-1代表空瓦片不绘制
this.ctx.drawImage(
this.spriteSheet,
tile * tileSize % this.dimensions.w, //瓦片精灵图上的x坐标
Math.floor(tile * tileSize / this.dimensions.w) * tileSize, //瓦片精灵图上的y坐标
tileSize, tileSize,
Math.round(x), Math.round(y),
tileSize, tileSize
);
}
}
}
_drawMap()方法绘制多个层:
_drawMap() {
this.level.layers.forEach((layer, index) => this._drawLayer(index));
}
最后使用render()方法进行绘制:
render() {
this._drawMap();
}
最终得到下面的地图:

这类绘制地图的方法在很多游戏中也通用,我曾经在《使用HTML5制作简单的RPG游戏》里也使用了同样的地图数据格式,不过那时是依靠框架完成的,里面具体发生了什么自己并不清楚,现在正好回顾和总结一下。
- 处理地图与角色的碰撞
现在地图还只是单纯的视角效果,并不能与角色产生交互,需要将各个瓦片进行标注,以表明它到底是不能通过的固体还是可以通过的空瓦片。在此之前,需要先了解一个概念,即axis-aligned bounding box,翻译过来就是轴对齐边界盒子,简称AABB。通过为瓦片与角色建立AABB,可以很方便地检测它们之间是否碰撞,这一概念在之前写过的一篇文章《Chrome自带恐龙小游戏的源码研究(七)》中也有涉及到。至于更为复杂的碰撞检测,比如SAT,现在还没有深入研究,后续会逐步加入游戏中。
AABB的实现代码:
class AABB {
/**
* 碰撞盒子
* @param x {number} 盒子x坐标
* @param y {number} 盒子y坐标
* @param w {number} 盒子宽度
* @param h {number} 盒子高度
*/
constructor(x,y,w,h) {
this.pos = new Vector(x,y);
this.size = new Vector(w,h);
this.center = new Vector(this.pos.x + w / 2,this.pos.y + h / 2);
this.halfSize = new Vector(this.size.x / 2,this.size.y / 2);
}
}
其中又涉及到向量的概念(累
HTML5 2D平台游戏开发#6地图绘制的更多相关文章
- HTML5 2D平台游戏开发#4状态机
在实现了<HTML5 2D平台游戏开发——角色动作篇之冲刺>之后,我发现随着角色动作的增加,代码中的逻辑判断越来越多,铺天盖地的if() else()语句实在让我捉襟见肘: 这还仅仅是角色 ...
- HTML5 2D平台游戏开发#11斜坡物理
在游戏中会经常遇到斜坡地形,比如众所周知的魂斗罗,角色可以在坡上移动和跳跃: 斜坡在2D游戏中很常见,处理起来也较为棘手.最初我打算用分离轴定律来实现,在建立了一个物理模型之后: 发现上坡时没什么问题 ...
- HTML5 2D平台游戏开发#8指令技
一般在动作游戏中,玩家可以通过对输入设备输入一系列的指令让角色完成某个或多个特定的动作.以格斗游戏<拳皇>为例,键入↓↘→↘↓↙← + A or C可以触发IORI的必杀技八稚女: 通过一 ...
- HTML5 2D平台游戏开发#7Camera
在庞大的游戏世界中,玩家不能一览地图全貌,而是只能看到其中一部分,并一步步探索,这时就要用到一种技术来显示局部的地图,游戏术语称为摄像机(Camera).下面两张图中的白色矩形框表示了Camera的作 ...
- HTML5 2D平台游戏开发#9蓄力技
在很多动作游戏中,玩家操控的角色可以施放出比普通攻击更强力的蓄力技,一般操作为按住攻击键一段时间然后松开,具体效果像下面这张图: 要实现这个操作首先要记录下按键被按住的时间,初始是0: this.sa ...
- HTML5 2D平台游戏开发#5攻击
目前为止,角色除了基本的移动外还什么都不能做,于是我打算先实现角色的攻击动画.角色的普通攻击一共可以分为三个阶段: 一段斩 二段斩 三段斩 移动攻击 跳跃攻击 触发方式为角色站立时按下J(攻击)键,角 ...
- HTML5 2D平台游戏开发#1
在Web领域通常会用到一组sprite来展示动画,这类动画从开始到结束往往不会有用户参与,即用户很少会用控制器(例如鼠标.键盘.手柄.操作杆等输入设备)进行操作.但在游戏领域,sprite动画与控制器 ...
- HTML5 2D平台游戏开发#10Wall Jump
这个术语不知道怎么翻译比较贴切,但并不妨碍对字面意思的理解,大概就是飞檐走壁.比如: 这是游戏<忍者龙剑传>中的场景,玩家可以通过操纵角色在墙面上移动并跳跃. 首先需要实现角色抓墙这一动作 ...
- HTML5 2D平台游戏开发#2跳跃与二段跳
在上一篇<Canvas制作时间与行为可控的sprite动画>中已经实现了角色的左右移动,本篇继续实现角色的一系列动作之一:跳跃.先来看看最终效果: 要实现跳跃,必须模拟垂直方向的速度和重力 ...
随机推荐
- Codeforces Gym 101194G Pandaria (2016 ACM-ICPC EC-Final G题, 并查集 + 线段树合并)
题目链接 2016 ACM-ICPC EC-Final Problem G 题意 给定一个无向图.每个点有一种颜色. 现在给定$q$个询问,每次询问$x$和$w$,求所有能通过边权值不超过$w$的 ...
- 【bzoj1598】【 [Usaco2008 Mar]牛跑步】启发式搜索思路+spfa
(上不了p站我要死了,侵权度娘背锅) 最近复习搜索,先从启发式搜索来吧. 感觉启发式搜索这玩意挺玄学的,先从其思想入手,做一道经典的K短路. Description BESSIE准备用从牛棚跑到池塘的 ...
- 八. 输入输出(IO)操作8.文件的压缩处理
Java.util.zip 包中提供了可对文件的压缩和解压缩进行处理的类,它们继承自字节流类OutputSteam 和 InputStream.其中 GZIPOutputStream 和 ZipOut ...
- 在cnBlogs上使用MarsEdit发blog
工欲善其事,必先利其器.既然决定了要经常使用blog,就要给自己一个好环境! 1.Mac下优秀的发博客工具--MarsEdit 网上有许多有用的文章教你如何使用它. 比如 http://fduo.or ...
- android RelativeLayout控件或view实现叠加效果
在开发android程序的时候,我们经常会遇到让控件或是view实现叠加的效果,一般这种情况,很多人会使用Framelayout来处理,可是有一个问题Framelayout布局在布局上会有很多限制,不 ...
- QQ协议
http://www.cnblogs.com/sufei/archive/2012/12/13/2816737.html http://www.360doc.com/content/12/0822/1 ...
- tiny4412 串口驱动分析三 --- log打印的几个阶段之内核自解压
作者:彭东林 邮箱:pengdonglin137@163.com 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 ...
- ArcMAP定义投影坐标
WGS84形式的高斯克吕格投影在ArcGIS系统中是不存在的,需要自己去定义.下面为MARK的定义过程.
- ServicePointManager.ServerCertificateValidationCallback 冲突的解决
ServicePointManager是用于创建. 维护和删除的实例的静态类ServicePoint类. 当应用程序请求对 Internet 资源统一资源标识符 (URI) 的连接通过ServiceP ...
- SimpleDateFormat关于时间类的一些常用处理
项目中经常会出现对时间类的一些处理,记录一下: 实例一:/** * 获取当前时间是星期几? * * @param args */ public static void main(String[] ar ...


