寻路算法
广度优先算法
从地图上任意一点S到其他所有可达点的最短路径,考虑上下左右四个所有方向行走的情况
- openQueue存放即将搜索的结点,closeQueue存放已经搜索完后的结点
- 设定搜索起点S,放入openQueue中;
- 判断openQueue是否为空,若为空,搜索结束;若不为空,拿出openQueue中的第一个节点A;
- 遍历A的上下左右四个相邻节点N1-N4。对每个节点Ni,如果Ni 不在openQueue或closeQueue中,那么令Ni 的父节点为A,将N放入openQueue中;如果Ni 已经在openQueue或closeQueue中,跳过不处理;
- 将A放入closeQueue中,重复步骤2
Dijkstra算法
在地图内的每个区块移动消耗不同时,Dijkstra算法可以非常方便的找出从地图上某个起始区块到其他所有可达区块的最短路径。
- 设定起始区块S,初始化区块S和总移动消耗cost=0, 记作(S,0), 放入openQueue (优先队列: 总移动消耗cost越低, 优先级越高);
- 判断openQueue是否为空,如果是空,算法结束;否则,从openQueue中拿出优先级最高的区块A;
- 遍历A的上下左右四个相邻区块N1-N4,对每个区块Ni,如果Ni已经在closeQueue中,忽略该区块;如果Ni不可达,忽略该区块;否则:
- 如果Ni 不在openQueue中,那么将(Ni,costi) 放入openQueue中,其中costi (S到Ni 的移动总消耗)= S到A的移动总消耗 + Ni 本身的移动消耗,令Ni 的父节点为A;
- 如果Ni 已经在openQueue中,取出(Ni,costi),仍然用前面的公式计算 costi2 ,如果此时的costi2 小于costi,用(N,costi2)替换openList中的(N,costi2),令N的父节点为A;如果costi2大于或等于costi ,不做处理。
- 重复步骤2直至算法结束。
希望角色更倾向于经过某些区块时(比如经过这些区块可以获得增益效果、道具等等)或者倾向于躲避某些区块时(比如经过这些区块会丢失生命值,或者这些区块上的敌人非常危险),可以通过调整这些区块的移动消耗来影响移动路径的产生从而影响角色的移动行为。
A*算法
通过计算一个值来判断探索的方向。对于节点N,计算公式:F(N)=G(N)+H(N) , G(N) = Dijkstra cost 迪杰斯特拉算法, H(N) = Manhattan distance 曼哈顿
- 选择起始节点S和目标节点E,将(S,0)(节点,节点F(N)值)放入openQueue (优先队列: 节点F(N)值越小,优先级越高);
- 判断openQueue是否为空,若为空,则搜索失败,目标节点不可达;否则,取出openQueue中优先级最高的节点A;
- 遍历A的上下左右四个相邻接点N1-N4,对每个节点Ni,如果Ni已经在closeQueue中,忽略;否则:
- 如果Ni不在openQueue中,令G(Ni)= 从S到当前节点A的总移动消耗 + 从节点A到相邻节点Ni的移动消耗【从起点 S 移动到指定点A的移动代价】; H(Ni) = Ni 到A的曼哈顿距离【从指定点A移动到下一个结点Ni 的估算成本】; 令F(Ni) = G(Ni) + H(Ni),令Ni的父节点为P,将(Ni,F(Ni)) 放入openQueue;
- 如果Ni已经在openQueue中,用前面的公式计算当前 G(Ni2),如果 G(Ni2) 小于 G(Ni),那么用 G(Ni2) 替换 G(Ni),重新计算F(Ni),用新的(N,F(Ni))替换openQueue中旧的(N,F(Ni)),令N的父节点为P;如果 G(Ni2) 不小于 G(Ni),不作处理。
- 将节点A放入closeQueue中。判断节点A是不是目标节点E,如果是,搜索成功,获取节点A的父节点,并递归这一过程(继续获得父节点的父节点),直至找到初始节点S,从而获得从A到S的一条路径;否则,重复步骤2;
优化:在情况允许的前提下,在生成地图或者加载地图时,记录地图上的特征区域。
- 不可到达区域
- 导航点,所谓导航点,就是地图上两个区域间移动的必经之路
A*退化情况
- 当H(N) = 0, A*会退化成Dijkstra算法,如果代价都是相同的话就会退化成BFS
- 当G(N) = 0,A*会退化成Manhattan算法
还可以怎么优化?
- H(Ni) = Ni 到A的移动消耗 + Ni到目标节点E的曼哈顿距离
- 用更快的优先队列
洗牌算法
全局洗牌法
- 首先生成一个数组,大小为54,初始化为1~54
- 按照索引1到54,逐步对每一张索引牌进行洗牌,首先 srand((int)time(0))(以当前的时间作为种子来生成随机数,这样更加的随机),再生成一个余数 value = rand %54,那么我们的索引牌就和这个余数牌进行交换处理。
- 等索引到54结束后,一副牌就洗好了
局部洗牌法(random_shuffle)
- 首先我们生成一个大小为54的数组,数组排列为1~54
- 索引牌从1开始,到54结束。这一次索引牌只和剩下还没有洗的牌进行交换, value = index + rand() %(54 - index)
- 等到所有的索引牌都洗好之后,一副牌就弄好了
Inside_Out算法
- 首先生成一个数组,大小为54,初始化为1~54
- i在1~54,在[0, i]之间随机一个下标j,然后用位置j的元素替换掉位置i的数字
游戏中用什么容器管理怪物的数据结构
list存放怪物:经常涉及插入删除, 链表好维护和管理需要频繁产生消除的对象
如何找到扇形攻击内的敌人
- 判断该目标点与技能释放者的距离是否小于扇形的半径;
- 目标点与技能释放者面对的方向向量(forward)夹角是否小于扇形角度的二分之一;
如何找到圆形攻击内的敌人:判断敌人和SkillPosition之间的距离是否小于半径。
游戏排行榜的数据结构应该怎么设计
- 桶排序:假设分数区间是1-5000。设计成5000个桶,然后把每个用户按照自己的分数分别放入对应的分数桶。要查询实时排行榜topN只需要把分数高的前面几个桶合起来展示就可以了。
- 实现:redis的sorted set有序列表来排序。用zadd、zrange以及zrank轻松实现实时的排名。redis的sorted set是用skip list(跳表)算法实现的。时间复杂度为O(log(N))。skip list是一个基于链表然后额外创建父链表,从而使得链表的查找效率得到提高。
- 红黑树:添加有序。
- 前几名可以用堆排序。
3D模型动画
渐变(Morph)
直接指定动画每一帧的顶点位置,其动画关键中存储的是Mesh所有顶点在关键帧对应时刻的位置。
关节动画
- 原理:通过一个父子层次结构将这些分散的Mesh组织在一起,父Mesh带动其下子Mesh的运动,各Mesh中的顶点坐标定义在自己的坐标系中,这样各个Mesh是作为一个整体参与运动的。
- 实现:通过子到父,一级级的变换累加(如果是矩阵操作是累乘)得到该Mesh在整个动画模型所在的坐标空间中的变换,从而确定每个Mesh在世界坐标系中的位置和方向,然后以Mesh为单位渲染即可
- 缺点: 各部分Mesh中的顶点是固定在其Mesh坐标系中的,这样在两个Mesh结合处就可能产生裂缝。
骨骼蒙皮 (SkinnedMesh)
- 原理:在骨骼控制下,通过顶点混合动态计算蒙皮网格的顶点,而骨骼的运动相对于其父骨骼,并由动画关键帧数据驱动。
- 骨骼和关节动画类似,决定了模型整体在世界坐标系中的位置和朝向。
- 骨骼的层次结构:关节只是描述骨骼的位置,即骨骼自己的坐标空间原点在其父空间中的位置,绕关节旋转是指骨骼坐标空间(包括所有子空间)自身的旋转。一个4X4矩阵就可以表达一个骨骼,因为4X4矩阵中含有的平移分量决定了关节的位置,旋转和缩放分量决定了骨骼空间的旋转和缩放。
- 蒙皮:指将Mesh中的顶点绑定在骨骼之上,而且每个顶点可以被多个骨骼所控制,这样在关节处的顶点由于同时受到父子骨骼的拉扯而改变位置就消除了裂缝。
- Skin数据:决定顶点如何绑定到骨骼上:包括顶点受哪些骨骼影响以及这些骨骼影响该顶点时的权重(weight)。
原理:先设置根骨骼的位置和朝向,然后根据骨骼层次结构中父子骨骼之间的变换关系计算出各个骨骼的位置和朝向,然后根据骨骼对Mesh中顶点的绑定计算出顶点在世界坐标系中的坐标,从而对顶点进行渲染。
游戏引擎一般的模块?
- 渲染引擎: 按照不同的面把材质贴图赋予模型,这相当于为骨骼蒙上皮肤,最后再通过渲染引擎把模型、动画、光影、特效等所有效果实时计算出来并展示在屏幕上
- 物理引擎: 模拟物理世界,开发出类似物理世界的效果。
- 碰撞检测: 探测游戏中各物体的物理边缘。当两个3D物体撞在一起的时候,这种技术可以防止它们相互穿过
- 脚本引擎: 提供脚本接口,让开发者,通过脚本设计游戏,使游戏的开发更加灵活
- 网络引擎:数据交换的模块,在开发多人在线游戏时使用
- 场景管理
- 音频UI: 提供音频特效,以及游戏的UI部分,让游戏与用户进行交互
地板、瓷砖反射怎么做的
通过镜面反射
人物材质用到了哪些
皮肤的纹理,用凹凸或用法线来控制漫射光表现毛囊和文理结构
眼睛的玻璃的材质
毛发,衣服纹理
打怪掉装备,100种掉落物,每个掉落物有自己的概率,加起来是1,怎么随机掉一个装备出来?
- 构造一个容量为100(或其他)的数组,将其中10个元素填充为类型1(武器),20个元素填充为类型2(饰品)...构造完毕之后,在1到100之间取随机数rand,取到的array[rand]对应的值,即为随机到的类型。这种方法优点是实现简单,构造完成之后生成随机类型的时间复杂度就是O(1),缺点是精度不够高,占用空间大,尤其是在类型很多的时候。
- 离散算法,通过概率分布构造几个点,[10, 30, 60, 100]。在生成1~100的随机数,看它落在哪个区间,比如50在[30,60]之间,就是类型3。在查找时,可以采用线性查找,或效率更高的二分查找,时间复杂度O(logN)。
在圆形里随机选一个点
- 在圆形的外切正方形内随机选取一点,然后判断此点是否在圆内(通过计算此点到圆心的距离)。如果在圆内,则此点即为所求;如果不在,则重新选取直到找到为止。
- 从[0, 2*pi)中随机选一个角度,对应于圆中的一条半径,然后在此半径上选一个点。但半径上的点不能均匀选取,选取的概率应该和距圆心的长度成正比,这样才能保证随机点在圆内是均匀分布的。
贪心算法 v.s 动态规划
贪心:自顶向下。把求解的问题分成若干个子问题,对每一子问题求解,得到子问题的局部最优解。 把子问题的解局部最优解合成原来解问题的一个解。
动态规划:自底向上。通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。
如何做子弹碰撞检测
- Collider:高速移动的物体,碰撞器很有可能检测不到
- 射线检测:Physics.Raycast(ray, out hit) // camera的屏幕坐标可以转换成射线
3C是什么
- Character: 表示一个或多个游戏角色,玩家可以扮演该角色、也可以在游戏中可以观察该角色的行为。
- Camera: 表示一个或多个摄像机,以此来辅助玩家观察游戏世界,增加玩家的体验感和沉浸感。
- Control: 表示用一个或多个控制设备,设置特定的交互规则来使玩家控制对应的角色。
参考资料