3D沙盒游戏开发日志9——寻路模块优化附带网格建筑系统修改

这篇博客介绍了作者对游戏寻路模块的优化过程,包括用精确坐标和实际碰撞体替换网格坐标和大小,以提高寻路的精确性和效率。作者详细阐述了新旧方案的区别,并展示了如何在MapManager中应用这些改变。此外,还讨论了以碰撞体为目标的寻路策略,解决了寻路终点判断的问题。最后,提到了性能考虑和未来可能的改进方向。
摘要由CSDN通过智能技术生成

日志

之前实现了一个简单的寻路模块PathFinder,但它的所有部分都很粗糙,只是将A*算法转变成为实际,现在我已经
完成了游戏主体的大部分,是时候着手改造它使它变得更精确和高效了。改造它的同时自然也就顺带优化了相关的
MapManager和Building系统

优化策略

一. 用精确坐标+实际碰撞体代替网格坐标+网格大小

旧方案

之前的PathFinder和MapManager中,几乎一切都是使用网格坐标(整数)计算的,使用的是GridPos+模型长宽多少个Grid这样的方案去确定需要检测的网格

  1. 这样的精确程度太低因为我的网格过大(模型过小),很多模型长宽只占两三个格,这时直接取整带来的误差是不可接受的;
  2. 此外,我需要手动的为每个建筑物配置长宽信息,太过繁琐而且也不精确,一旦有调整就需要重新配置

新方案

于是在新的PathFinder中我普遍使用精确坐标配合碰撞体大小计算前后左右四个边界,再根据四个边界的坐标去计算覆盖了哪些网格,这个计算主要应用在计算建筑物覆盖网格和寻路时检测某一点是否可以站立(周围网格是否无建筑)
MapManager中的应用

public void RegisterBuildingLand(Collider collider)
{
    RegisterBuildingLand(collider.bounds.center, collider.bounds.size.x, collider.bounds.size.z);
}
public void RegisterBuildingLand(Vector3 center, float length, float width)
{
    for(int i = ToolMethod.TwoEightRoundToInt(center.x - length / 2); i < ToolMethod.TwoEightRoundToInt(center.x + length / 2); ++i)
    {
        for(int j = ToolMethod.TwoEightRoundToInt(center.z - width / 2); j < ToolMethod.TwoEightRoundToInt(center.z + width / 2); ++j)
        {
            SetCoveredPos(i, j);
            if(debug) Instantiate(visualizedMapQuad, new Vector3(i + 0.5f, 0.01f, j + 0.5f), Quaternion.Euler(90, 0, 0));
            //Debug.Log(String.Format("i:{0},j:{1}", i, j));
        }
    }
}
public bool CanStand(Vector3 pos, Collider collider)
{
    return CanStand(pos, collider.bounds.size.x, collider.bounds.size.z);
}
public bool CanStand(Vector3 pos, float length, float width)
{
    for(int i = ToolMethod.TwoEightRoundToInt(pos.x - length / 2); i < ToolMethod.TwoEightRoundToInt(pos.x + length / 2); ++i)
    {
        for(int j = ToolMethod.TwoEightRoundToInt(pos.z - width / 2); j < ToolMethod.TwoEightRoundToInt(pos.z + width / 2); ++j)
        {
            if(GetGridState(i, j)) return false;
        }
    }
    return true;
}

其中使用的是二舍八入的计算方法(我认为比较合理),使用collider.bounds.center也比transform的position要更精确
在PathFinder中对应的我也加入了coverRadius作为寻路物体的碰撞体size

public static List<Vector3> FindPath(bool[,] grids, Vector3 start, Vector3 end, float coverRadius)
private static bool Walkable(bool[,] grids, int x, int z, int coverRadius)

coverRadius的计算方法

if(coll) collSize = Mathf.Max(coll.bounds.size.x, coll.bounds.size.z) / 2;

二. 目标为碰撞体的寻路

在上一步优化后,我们已经能够很好的处理关于网格和寻路的几乎一切事情,但当处理实际情况时仍然 不理想。试想一下我想要挖一块石头,石头有一个较大的碰撞体,显然我不能以石头的position作为终点,如果用collider.closestPointOnBound计算终点的话也不合理,最近的点未必是可达的,也许需要绕道背面呢,而且上面两种方法的终点以及终点附近的点几乎肯定是不可达的(因为就在建筑物范围内),也就是需要做额外的特判给终点(之前的版本就是这么做的)。
现在看来这样真的是糟透了,而且这种寻路并不少,事实上,游戏中的寻路可以由此分为两种,一种是以点为目标的(生物的游走,人物的走动等),另一种则是以物体为目标的(任何行为开采,捡起,攻击等都是这种),第二种的应用要远多于第一种
于是我就想从PathFinder的底层提供对第二种寻路的支持,即以一个碰撞体(目标物体)为“终点”的寻路,当我确定目标有碰撞体的时候在寻路过程中就可以做一些很酷的事情
我可以简单的以目标的position作为终点,因为我能够通过collider.closestPointOnBound判断我是否已经可以接触到物体(结合之前所得到的寻路物体的radius),可以让物体在以目标position寻路的情况下尽可能的走到最近的网格(至于之后的事情就是调用者来检测了,比如按照武器的攻击距离在达到距离时终止移动StopMove)

private static bool CanReachTarget(PathPoint curr, Collider target, float radius)
{
    Vector3 pos = curr.GetVector();
    return target.ClosestPointOnBounds(pos).PlanerDistance(pos) < Mathf.CeilToInt(radius) + 1;
}

至于新版的代码实际上没有太多修改,正如上面所说主要是修改了终点和何时停下的问题(CanReachTarget)

public static List<Vector3> FindPath(bool[,] grids, Vector3 start, Collider target, float coverRadius)

关于性能

在寻路中频繁的调用closestPointOnBounds是否会产生性能问题,我没有实际测试,但我想有两点理论值得一提

  1. 这个性能上的花费是必须的,如果不这样做,不仅寻路有误差,你甚至不能正确的寻路!(例如前面所说的需要绕到石头背面的情况)
  2. 在之前版本中,会在update中频繁的调用closestPointOnBounds,至少现在对于静止的目标来说只需要在寻路时计算一次就可以了,这肯定是远小于在update中调用的消耗的
  3. 可以尽量避免使用MeshCollider,只有MeshCollider才需要对closestPointOnBounds进行大量的计算

其它改动

如开头所说,现在无需再在BuildingStats中记录width,length等信息,自然也就简化了Constructable的工作,Constructable可以直接使用自身的碰撞体进行所有计算

总结

借着这次改动我顺便完善了寻路模块的Debug,经过测试我对现在的寻路已经很满意,大小物体都能够正确的移动,之后应该不会再进行大的更改在寻路模块上了,总的来说没有辜负我自己写寻路的初心,初心之一就是为了能够修改底层以直接提供对该游戏的支持,而且之前的方案如此差的原因之一是因为我不知道collider.bounds能够获取任意碰撞体的边界信息(之前只知道box和capsule的)
请添加图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值