Unity项目,由于人物移动时一般用摇杆或者方向键控制, 需要有八方向方式控制朝向, 所以没有用 NavMesh Agent, 而是自己控制人物方向移动,然后贴合地面。
用了NavMesh.CalculatePath只是用于目标点的寻路, 寻找出路经后自己计算实现移动。
实际项目中发现,方向键控制移动后,到了边界碰撞检测后,人物就停在那里。 就算与障碍物很小一个夹角,也会卡在那里,体验很不好。
打个部分,比如上图,角色在红色点, 方向键往绿色方向移动,结果角色检测到碰撞,就不移动, 一直停在了红点。 希望它能按蓝色箭头方向缓慢移动。 怎么实现呢?
用几何的 什么切线啥的好像太复杂了, 数学学得不怎么好。 后来有同事提示,可以用寻路网格的辅助, 确实是一个好办法。
NavMesh 想要从红点到不可行走的绿点, 会自动寻找一个路径到蓝色点附近。 代码调整移动朝向沿着蓝线走就行了。
实际试下来效果还行,除了某些网格的凹角仍然会卡住。觉得可以交差时,意想不到的问题出现了, 爬坡(楼梯)时,会在明显可以直接穿过去的地方,2个网格交界的地方一根网格线卡住
比如上图,往绿色的方向移动,结果它计算出来的路径却是沿着这根网格线 红色箭头方向慢慢移动。。。。遇到了空气墙一样,囧。
打了很多日志,发现它确实计算出这个路径来了,而且只有2个corner(起点和目标点)。
发现只要路径移动远一点 就能计算正确,如果移动很短,在这种上坡的边缘出错概率就很大(下坡正常)。 这难道是navmesh bug? 不应该,网上也没人提到过这个问题。
仔细看了坐标, 发现角色的y坐标 还有移动后的目标点 y 坐标,和寻路网格的高度不一样。 是不是这个原因造成的呢?
怎么找到自己的点在网格上的投射点呢? 用 NavMesh.RayCast 发现不能满足我的要求, 还是会有问题。
后来发现一个接口,NavMesh.SamplePosition
sourcePosition | The origin of the sample query. |
hit | Holds the properties of the resulting location. |
maxDistance | Sample within this distance from sourcePosition. |
allowedMask | A mask specifying which NavMesh layers are allowed when finding the nearest point. |
boolean - True if a nearest point is found.
Sample the NavMesh closest to the point specified.
参考代码:
m_movement = moveVelocity * deltaTime; //这次要移动的方向和距离
Vector3 cur_position = transform.position;
NavMeshHit hit;
if (NavMesh.SamplePosition(cur_position, out hit, 0.5f, -1))
{
cur_position = hit.position; //校准起始点
}
Vector3 temp = cur_position + m_movement;
if (NavMesh.SamplePosition(temp, out hit, 0.5f, -1))
{
temp = hit.position; //校准目标点
}
bool cannot_move = false;
//用NavMesh计算目标点可否过去,
if (!NavMesh.CalculatePath(cur_position, temp, Layer.NavWalkableMask, path))
{
//m_movement = Vector3.zero;
cannot_move = true;
}
else
{
if (path.corners.Length < 2)
{
if (path.corners.Length == 1) //有时会寻路出一个点的情况,这个还没好好研究
{
m_movement = path.corners[0] - cur_position;
m_movement.y = 0;
}
else
{
//m_movement = Vector3.zero;
cannot_move = true;
}
}
else
{
m_movement = path.corners[1] - cur_position; //调整方向
m_movement.y = 0;
moveTargetDirection = m_movement.normalized;
float dot = Vector3.Dot(moveTargetDirection, moveVelocity.normalized); //新旧方向的夹角余弦值, 可以算出在新方向的速度分量
moveVelocity = moveTargetDirection * moveSpeed * dot; //移动朝向也要跟着调整
m_movement = moveVelocity * deltaTime;
}
}
if (cannot_move)
{
//寻路网格认为不可走了,再用不可行走区域做一下检测,防止很陡的空的卡住
}