[真机Bug]解决服务器强制定位玩家位置后NPC加载超时

问题复现

image.png
图1 玩家在指定任务交付地点发现NPC未出现
image.png
这是一个剧情演出的任务,在播放TV结束后,角色会被强制位置到一个指定地点。而NPC的加载来源于 【场景分块加载】和【玩家的视野区(本质玩家位置)】.

  • 场景分块加载是资源优化的一种手段。对于大型地图(类似河阳城主城),由于玩家途经的位置有限。没有必要把所有GameObject放在一个scene当中。而是将一个scene按绝对位置切分为8块、16块甚至更多。保证渲染质量。
  • 视野区是一种加载物体优化的一种手段。可以对照视锥体剔除的概念理解,对于系统判断在离玩家坐标比较远的位置的物体可以不加载,如果已加载。可以让其失活进入对象池。最大程度减少hierarchy列表中可见物体的数量,从而保证渲染质量。

问题排查

首先对于这个问题,我优先想到的是检查场景分块加载的逻辑。因为最终定的位置离初始任务位置比较远,这部分的场景是后续分块加载的。那么有一种可能性是场景在TV快要结束的时候才被加载,人物加载是后于场景加载的。那么人物加载可能会延期。
于是我在场景分块加载的管理器中打了日志,标识场景id 和时间。发现,其实在播TV的前3s场景已经完成加载了,而TV总共有10s,在剩余7s的时间之内应该正常加载完成npc吧。让我们来看看加载NPC的细节。

using ViewObjIdMap = std::map<int32,bool> // obj id - 是否可见

bool Obj_Player::Tick_ViewNpc()
{
    Packet::GC_CREATE_NPC_PAK pak;

    for(auto it = m_viewNPCIdMap.begin() ; it !=m_viewNPCIdMap.end(); it++)
        {
            if(IsInSight(it->first))
            {
                //如果在视野内,插入到创建map里
                m_viewCurNpcIdMap.insert(std::make_pair(it->first,true));
               //消息包插入待创建npc数据
                pak.m_PacketData.add_npcdata();
                curCount++;
                //一批最大创建数量(10)
                if(curCount >= VIEW_NPC_MAX_COUNT)
                {
                    braek;
                } 
            } 
        }

    if(curCount > 10)
    {
        sendPacket(pak);//发GC_CREATE_NPC_PAK创建的包
            for(auto it = m_viewCurNPCIdMap.begin() ; it !=m_viewCurNPCIdMap.end(); it++)
                {
                    //设置每个NPC位置,发GC_MOVE的包,状态同步
                }
    }
    
}

上面是服务器Tick里的部分逻辑,可以看出npc的创建依赖于

  1. m_viewCurNpcIdMap 是否插入了有效id
  2. m_viewCurNpcIdMap的位置,是在第几批创建,我们可以推算,一次创建10,一次tick是1000ms,那么7s正常是可以创建70 个npc实例,那么对于需求来说是完全足够的。这还是挤在单一场景中。
  3. 创建了,但是同步位置出错,导致不可见(已在客户端排除)

所以问题范围缩小到判断m_viewCurNpcIdMap的内容。

于是我在所以insert m_viewCurNpcIdMap的位置断了点。逐帧调试,在字典添加的时候打印一下日志
image.png
发现在14:25:22 ~14:25:29 这里有点奇怪,首先是没有插入别的有效id,然后耽搁了非常多时
发现扫描的id是不变的。
这说明什么呢。说明玩家的位置要么是没有变化,要么是以前已经到达的位置( ps:已经达到过的位置npc已经被标记,不会再次进入map.由于篇幅限制,这里不再展示扫描部分的代码。重点在于理解)

于是我又打印了玩家的位置,发现其在这个时间段,玩家在朝着起点位置移动,移动了一段时间后,才被强制定位到目标位置。
那么就可以解释的通了,正是因为玩家没有第一时间定位,导致玩家的服务器位置没有及时更新,那么依赖于玩家服务器位置的视野扫描在这一时间段扫描的还是旧位置,那么目标NPC便不会在这段时间被纳入字典,也不会创建了。

问题分析

那么为什么会在执行强制位置的时候往回走呢?初步推测这多半跟自动寻路有关系。
先看看强制位置部分的逻辑是怎么做的

void Obj_Char::ForceSetScene(ScenePos val,bool bMoveToPos ,int32 type)
{
    SetScenePos(val);
    if(IsSceneValid())
    {
        //设置新位置,准备发包给客户端
        Packet::GC_FORCE_STEPOS_PAK pak;
        pak.m_packetData.set_serverid(GetId());
        pak.m_packetData.set_posX(m_ScenePos.m_fX);
        pak.m_packetData.set_posY(m_ScenePos.m_fY);
        pak.m_packetData.set_posZ(m_ScenePos.m_fZ);
        //状态同步
        GetrScene().BroadCast_InSight_Inclue(pak,GetId());
    }
    if(IsPlayer())
    {
        Obj_Player& rPlayer = dynamic_cast<Obj_Player&>(*this);
        //玩家强制设置位置后,手动改变消息包的版本号,
        //防止接受到的位置之前的CG_MOVE消息包,导致位置异常。
        rPlayer.IncMovePakVersion();
    }
        
}

然鹅好像没有发现什么跟寻路有关的东西,于是我又换了一种思路,从获取玩家位置的引用地方查,终于。。
找到了这么一个地方。

if(rRealEndPos!=GetScenePos())
{
    bool bMove1 = IsMoveing();
    StopMove(true,false);
    m_TargetPos = rRealEndPosl;
    m_fStopRange = 0.01f;
    m_PathCont.CleanUp();
    m_PahtCont.PushBack(PathNode(GetScenePos(),rRealEndPos))
        //---
}

发现如果m_PathCont缓存不为空,并且当前位置没有校验成功,会朝目标位置移动。所以如果在ForceSetScene的时候m_PathCont 不为空,而这一帧的时候客户端的实际位置又和服务器位置不一样(正常,因为服务器这边强改了位置,要通知客户端改本地的locationPos嘛,符合位置校验没有成功的情况,而m_PathCont 数据又没有清空,此时玩家的【服务器位置】会朝原始位置修正。而为什么修正一段时间后,又成功的定位到了目标位置,可以推测这时刻客户端正确收到了GC_FORCE_STEPOS_PAK的消息,修改了自身的位置,此时服务器校验通过,【服务器位置】变化中止]),这也就解释了为什么实际NPC是延迟加载出来 而不是完全加载不出。
image.png
这里方便阅读做一个补充。
(【客户端位置】就是unity工程中玩家的自身坐标它既依赖于服务器位置消息包的数据而变化(寻路、状态同步),又可以通过自身改变从而影响【服务器位置】,比如客户端主动的行为(按键等等))上文说的位置全是【服务器位置】

问题解决

在ForceSetScene 类似的服务器主动大范围修改玩家位置的地方之前,将m_PathCont 位置缓存清空即可。这样保证了下一帧服务器不会触发位置修正。而在客户端正确收到位置消息包从而改变【客户端位置】后,正常向服务器发送CG_MOVE消息的时候再校验。

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值