首先我们来讨论下游戏开发中的几个坐标系,为了方便解释,我截取了灯塔AOI DEMO当NPC数目为0时候的样子(代码地址觉得有帮助的童鞋记得给我代码点个星_)
先对这张图简单说明下:
- 蓝色的坐标轴表示是灯塔AOI坐标系,绿色的坐标轴表示的是游戏坐标系,向左为X轴正方向,向上为Y轴正方向(这个坐标是我自己后面画上去的)
- 深蓝色的点表示灯塔AOI坐标,左下的表示(0,0),右上表示(1,1)
- 深绿色的点表示游戏坐标,左下表示(0,0),右上表示(1,1)
- 每个灰色的小格子代表一个游戏坐标(边长为15像素)
- 每个黄灰的大格子代表一个灯塔AOI坐标(边长为11个灰色小格子)
- 瓦片地图大小为宽:100个灰色小格子 高:70个灰色小格子
- 深蓝色的方块为玩家,大红色方框为玩家在游戏坐标系中的视野(视野半径为10个灰色小格子),兰色方框表示玩家在灯塔坐标系中的视野
像素
这个是客户端图形实际显示要用到的坐标(和游戏逻辑无关),每个图片长宽各多少像素,描点在什么位置,但是在游戏中直接使用像素作为计量单位(坐标点)十分不方便,比如物体的移动,况且移动也不会一个像素一个像素移动,所以我们做了一层抽象(格子),游戏相关的贴图(地图/人物/道具)必须是格子的整数倍,这样游戏中所有的物体才能被正常显示,而不会产生偏差。
格子/瓦片(游戏坐标系)
什么是格子?格子是游戏逻辑使用的最小单位,代表游戏坐标系的点,它的单位为像素(DEMO中15*15像素为一格),游戏中所有设定都必须是格子的整数倍,所以不会出现有物体在两个格子之间,导致坐标不确定的情况,物体的移动的步长单位也变成多少格,而不是像素了,即如果使用像素作为坐标点,从(0,0)移动到(0,1),则移动了1个像素,现在用格子作为坐标点,从(0,0)移动到(0,1),则移动了15个像素。
什么是瓦片?一张大的世界地图或者背景图可以由几种地形来表示,每种地形对应一张小的的图片,我们称这些小的地形图片为瓦片。把这些瓦片拼接在一起,一个完整的地图就组合出来了,这就是瓦片地图。
在DEMO中,为了简化逻辑,我将瓦片设置为一倍格子大小,玩家和NPC的大小也设置为一倍格子大小。
灯塔AOI坐标系
为什么需要灯塔AOI?假设我们想知道某点周围10格内有哪些对象,在没有灯塔AOI的情况下,我们需要遍历所有的对象计算其是否在范围内,随着地图内的对象越来越多,查找的效率也会越来越差,所以我们需要一种方法来过滤那些明显不需要参与计算的对象,所以我们将地图分割成一个个区域,在其中心放置一个假想的"灯塔",每个"灯塔"都会保存区域内的对象,这样当我们需要知道某点周围10格内有哪些对象时,我们只需要计算出范围内有哪些"灯塔",然后获取这些"灯塔"保存的对象列表,针对这些对象进行计算就能节省大量计算。为了方便表示和管理这些"灯塔",我们为其分配了新坐标(左下为(0,0)),这个新的坐标系即灯塔AOI坐标系(这个坐标系是用来做碰撞检测的)
灯塔视野和玩家视野
"灯塔"的视野越小,在碰撞检测时能过滤的无效对象就越多,但是整张地图"灯塔"的数目也就越多,消耗的内存就越大,而且对象进出灯塔的计算量就越多
"灯塔"的视野越大,在碰撞检测时能过滤的无效对象就越少,碰撞检测的计算量就越大,想象下只有一个"灯塔"的情况,即退回了没有灯塔AOI系统的情况
由于玩家的视野一般是固定的(屏幕显示区域大小一般固定),所以灯塔视野大小一般是玩家视野的1/2或1/3比较合适
灯塔坐标的计算
假设要计算游戏中某点所处的灯塔坐标(详细逻辑见代码注释):
### 将游戏坐标系和灯塔AOI坐标系对齐,然后除以灯塔边长(两倍视野内包含的格子数 + 自身坐标格子)
static_cast<int>(std::floor(static_cast<float>(游戏X坐标 - 游戏地图左下角原点X坐标) / (2 * "灯塔"视野 + 1))
static_cast<int>(std::floor(static_cast<float>(游戏Y坐标 - 游戏地图左下角原点Y坐标) / (2 * "灯塔"视野 + 1))
灯塔AOI逻辑
对象进入(角色登入、生成怪物):
- 根据对象坐标计算对象所属灯塔,将对象添加到灯塔的对象列表,如果灯塔上绑定了观察者,则通知观察者有对象进入
- 找出对象视野范围的灯塔,将自身绑定为其观察者,绑定灯塔会将自身现有对象列表发送给对象
对象离开(角色登出、怪物被杀死):
- 根据对象坐标计算对象所属灯塔,将对象从灯塔的对象列表中移除,如果灯塔上绑定了观察者,则通知观察者有对象离开
- 找出对象视野范围的灯塔,解除其观察者绑定,解除绑定灯塔会将自身现有对象列表发送给对象
对象移动
- 如果对象所属灯塔没变,则不做任何操作
- 如果对象所属灯塔改变,则对旧灯塔执行对象离开逻辑,对新灯塔执行对象进入逻辑,但是要注意的是对视野的处理,前后视野交集内的灯塔不需要执行解绑和绑定操作