先说结论,之所以会出现角色自动任务时走不到NPC任务触发范围内的BUG,以及寻路时角色走路抖动很可能是因为float类型的精度丢失造成的!!!!
(抖动也可能是因为角色旋转控制使用了欧拉角导致万象锁,这种情况会抖得很频繁,建议用四元数)
(走不到终点除了精度问题还有可能是寻路算法出现了问题,其中一小段路径有折叠)
1.精度问题
控制玩家寻路一共有两个入口
一个是WorldManager.cs 中的 Update 中,会时刻监视 鼠标左键点击,在第290行调用 aiMgr中的 MoveToPt() 方法,另一个入口是在 AIActMoveTo.cs 的 process()处理函数中。
可见 两种方法 最终都是调用 AiMgr.cs中的 MoveToPt() 方法。
在该方法中,可以使用Astar.Find() 获取 寻路的路径节点列表。
(这个方法中返回的路径列表有问题,所以会导致每次寻路角色都会向下转身,因为前几个节点会比较靠下)
真正控制玩家转身的函数 在 Locomotor.cs 中的 _updateMove()方法中
这个方法本身没什么问题,是targetPos 的 x ,y 坐标 每次前几个值都会有偏差,所以要修复这个,需要修改寻路算法。
在Astar.Find() 中,通过以下代码计算路径节点
由于最后将结果list翻转(一定要注意,计算出的第一个点是终点,最后一个点是起点),所以第一个节点其实解释 for 循环的 最后一个节点。(这里面涉及到一个二叉树的搜索算法,因为我们只需要剔除根节点,不适合从这里修改,直接从返回处修改。)
之所以 前几个节点都会有偏差(小一点),是因为在 该方法中有这样的语句:
在上方的红框内,为了得到 二位世界中的其实坐标,将起始点 坐标 * Scale / Size 后取int,这个过程会省略掉小数(直接舍弃小数点后的数),在下面的红框里又需要反算出对应的世界坐标系坐标,此时由于 startX 和 startY 是已经舍弃小数部分的数,所以反算不出真正的世界坐标(有偏差,并且偏小 )。具体小了 x_ * Scale / Size - (int)( x_ * Scale / Size )这么多。
现在仅以 startX 为例,
设这个补偿值 为 comX ,由图可知,虽然在该方法中,(startX *Scale ) / Size,其中Size是碰撞格子大小,这是为了在该方法中计算碰撞(位于第几个格子中)。但是在真正输出寻路 路径前,都会 再 *Size(再次转换为 二位地图中的坐标。)所以在真正输出前,需要加上 comX * Size 这么多的补偿。
补偿测试计算:
在这里犯了一个错误,comX 是float 类型, Size 是int类型,两者相乘后再取int会造成精度损失,如下图。这是因为两者相乘 返回一个 float类型的结果,也就是 11(在float中是10.9999999 或者11.0000001),此时对结果强制转换成int,有时候会变成 10 ,有时候是11。(int 转换,直接忽略小数点后面的数字),最后导致结果有时候会偏差 1。
这也是为什么寻路点的前几个路径值会偏小!!!
对于startY有如下代码,计算角色在第几行碰撞格子中 _row 为总行数,而 用
算出的 Y 所在行数是从世界原点开始的行数(起点在地图的左下角),所以需要用 _row – startY 得到从上往下数,该点所在的行数, 而X 则不用。
但是这里有一个问题,举个例子
在寻路的Y点输出是这样的,
其中的 end_tile.Y 就是_row – startY 得到的从上往下所在的行数。 但是 按照上面的方法计算出 comY 是对于 startY的补偿,也就是说,是对于世界坐标的补偿,无法对end_tile.Y进行补偿。需要按照等式 (_row - end_tile.Y) * Size + comY = worldPos.y * Scale = node.Y;
进行补偿。
综上,按照上面的方法,可以仅对起点和终点进行补偿(其他的点不用补偿)。
2.路径折叠
首先写个小脚本通过将寻路系统画出来的方式,我发现寻路之后模型并不会走完路径的所有节点,很多时候只是走到索引值为5的倍数的点。
比如这一条寻路路径:
此时角色停止的点为这个点为:
路径上全部点为:
所有的路径节点列表在这里。现在一共有16个点,但是为什么停在了索引为10的点而不是索引为15的点呢?
在真正执行寻路的函数中,发现了下列语句:
按照该方法,第一个点剔除后,还有15个点。For循环中,步长为5,此时也就是索引值为0、5、10的点才会被执行(I == 15时超出循环,所以索引为 15 的点没有走。)
再来仔细看一下这段代码。
可以知道,如果寻路路径点数大于10的时候,5个点为一步这样走。点数小于等于10时,一点一点的走。现在有16个点,剔除起始点后是15个点。按照5个点为一步行走。
但是走完了之后呢?剩下的5个点呢?不走了吗?这里是不是少一个循环?怎么看这里都少一个循环啊!还有,pop()删除的是起始点?明明是把终点删了好吧!!!!!
应该每次循环,判断的点都少一点,最后小于10个了,再循环一次
接下来按照循环的方法修改这个方法。
经过试验,不可以用循环,因为如果循环的话,会在上一段寻路刚开始走,就走下一段,程序会崩溃。。。。
现在尝试下一个方法,将该段代码修改为下列代码。(把_movePoints 和剩下的不走的点拼接起来)
此法可行。