五邑隐侠, 本名关健昌, 10 年游戏生涯, 现隐居五邑. 本系列文章以 TypeScript 为介绍语言.
这一篇介绍瓦片地图, 在开发模拟经营类游戏, SLG 类游戏, RPG 游戏, 都会使用到瓦片地图. 瓦片地图地面是通过一个个地砖拼起来的, 又分为 45 度角和 90 度角两种. 45 度角俗称 2.5D, 每个格子都是菱形, 而 90 度角每个格子都是正方形.
瓦片地图一般包括以下图层(不一定同时存在, 例如一般 RPG 游戏没有背景和自由装饰层):
1. 背景层(大图拼接的背景)
2. 地形层(瓦片格子拼接的地形)
3. 建筑层(按瓦片格子摆放的建筑, 地面物品, 角色)
4. 自由装饰层(云朵, 烟雾)
除此以外, 还需要提供隐藏的层用于编辑数据, 控制游戏逻辑, 例如阻挡, 摆放区域等, 称为数据层. 其中需要瓦片布局的有 3 种图层: 地形层, 建筑层, 数据层.
先来说说背景层 BgLayer, 背景层在美术设计里是张完整的大图, 为了避免连续内存太大导致加载失败, 把大图打碎成等大小的小图拼接. 一般出于性能考虑, 小图长宽选择为 1024,512,256,128.
设置背景需要提供背景的小图数组, 列数, 每张小图的宽高
public setPieces(fileArr: Array<string>, colCount: number, pieceW: number, pieceH: number, loadAtOnce: boolean = true): void {}
如果是在 H5 平台, 要考虑资源的逐步加载. 所以在 H5 平台该方法最好只是做占位, 提供方法进行资源加载
public loadPiece(file: string) {}
除了加载外, 由于玩家在移动地图时只看到背景的一部分, 不在视窗内的应该裁剪掉, 所以还要提供方法对背景进行裁剪
public setViewPort(x: number, y: number, w: number, h: number): void {}
完全不在视窗的小图, 通过 removeFromParent 从渲染列表移除.
接下来说说基于瓦片布局的图层. 基于瓦片的图片我们会摆放一些物品, 例如地形层的地形块, 建筑层的建筑, 地面物品, 角色. 所以在介绍瓦片布局的图层前, 先介绍下地图里的物品. 一般包括:
1. 地形块
2. 建筑, 地面物品
3. 角色
4. 编辑数据
先设计一个基类 MapItem, 定义物品的基本属性: 格子位置, 宽高占格子数, 是否可穿越, 可否编辑
- export class MapItem extends cc.Component {
- protected mGridX: number = 0;
- protected mGridY: number = 0;
- protected mRow: number = 1;
- protected mCol: number = 1;
- protected mCanPass: boolean = true;
- private mEditable: boolean = true;
- private mIsLock: boolean = false;
- }
地形块主要是显示一张一格大小的图片, 一般会先把这些地形图片合成一张大图, 按照格子大小等分. 设计一个 TileSet 类定义这张合图的结构
- export class TileSet {
- private mId: number = -1;
- private resPath: string = null;
- private tex: cc.Texture2D = null;
- private gridW: number = 0;
- private gridH: number = 0;
- private row: number = 0;
- private col: number = 0;
- }
每个地形块 Tile 引用 TileSet 里的资源
- export class Tile extends MapItem {
- private mSpr: cc.Sprite = null;
- private mId: number = 0;
- private mTileSetId: number = 0;
- private mIdx: number = 0;
- }
当然, 如果希望地面有帧动画, 也可以让 Tile 保存一个帧系列, 按顺序切换 mSpr 的 spriteFrame 属性.
建筑要考虑建筑, 地面物品的方向和操作
- export class MapBuilding extends MapItem {
- protected mId: number = 0;
- protected mType: number = 0;
- protected mResType: number = 0; // 0 image, 1 spine
- protected mResPath: string = null;
- protected mDir: number = 0;
- protected mReverse: boolean = false;
- }
为了区分摆放和旋转点, 我会给 MapBuilding 添加两个子节点, 因为在 45 角地图摆放建筑时, 一般以右下角格子做摆放参考点, 但是旋转是以左上角格子为参考.
需要注意, 在 H5 平台, 建筑的图片资源应该是动态加载的, 在地图移动时逐渐加载显示. 所以要提供接口对资源进行加载.
public loadRes(): void {}
还有点击范围, 图片是矩形的, 点击的时候可能是点到了图片的空白区域, 这时后玩家看到的是点击了后面的建筑, 如果没有处理透明区域剔除, 实际判断却是点击了前面的建筑. 对于图片资源的建筑在 H5 平台, 可以通过使用该图片相同位置, 截取点击区的一个像素, 模拟画在屏幕该地方, 如果 getImageData 获取的 data[3]不是 0, 说明点击到了该建筑, 否则是点击了空白区.
角色都是 spine 动画, 是可以运动的物体, 需要路径, 速度相关的属性
- export class MapActor extends cc.Component {
- protected mActorId: number = 0;
- protected mResPath: string = null;
- protected mRoadGrids: Array<cc.Vec2> = []; // 路径
- protected mCurRoadIdx: number = 0;
- protected mSpeed: number = 0; // 设置的行走速度, 跟 x 方向速度绝对值一样
- protected mSpeedX: number = 0; // 实时行走 X 速度
- protected mSpeedY: number = 0; // 实时行走 Y 速度
- protected mTestActor: boolean = false;
- protected mIsKeepRoad: boolean = false;
- protected mIsPauseWalk: boolean = false;
- }
编辑数据是不可见的, 只有基本的位置信息和相应数据
- export class MapDataItem extends MapItem {
- private data: any = null;
- }
瓦片布局的图层有一个基类, 负责物品 MapItem 的添加, 删除, 查询等操作
- /**
- * 基于地砖的图层基类
- * 定义基于地砖图层基本数据和接口
- */
- export abstract class TileBaseLayer extends cc.Component implements GestureListener {
- protected mRow: number = 0;
- protected mCol: number = 0;
- protected mGridW: number = 0;
- protected mGridH: number = 0;
- protected mMapItems: Array<MapItem> = [];
- protected mOriX: number = 0;
- protected mOriY: number = 0;
- }
对于 90 度角的地图, 没有太多复杂的地方需要处理. 点击点到格子的映射是简单除以宽, 高. 遮挡是从左往右, 从上到下设定 zorder.
对于 45 度角的地图, 点击点到格子的映射需要用到解析几何
每个点击点, 按照格子宽高比的斜率经过该点作直线, 到经过点 (oriX,oriY) 平行于 x 轴的直线都有交点, 而且交点的距离等于格子的宽度. 所以, 交点相对于点 (oriX,oriY) 的距离 (x - oriX) 可以判断该点属于哪行或哪列. 对于行, 交点可以通过(oriY - touchY)* (gridW / gridH) - (touchX - oriX). 对于列, 交点可以通过(touchX - oriX) + (oriY - touchY)* (gridW / gridH).(平行, 三角形中同样大小的角的对边比邻边比例一样)
角色起始在格子中心点, 行走的时候, 按照格子的宽高比, 把速度分解为 x,y 两个方向, 这样角色就能沿着格子走下去. 每次走到格子中心时才做站立, 继续行走的处理.
遮挡方面, 45 度角依然是从上到下遮挡
要注意的是, 建筑占多格, 而设置遮挡时只能设置 zorder, 所以采用的是中心位置方式, 把建筑中心格子作为 zorder 的参照. 角色在地图中行走的时候, 按上图的规则修改角色的 zorder, 建筑的 zorder 一直不变.
自由装饰层没有摆放限制, 可以放置云朵, 雾等动画, 增加层次感.
瓦片地图先说到这里, 下一篇我们将介绍 A * 寻路算法.
来源: https://www.cnblogs.com/niudanshui/p/10429880.html