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

2024-06-15 04:52

本文主要是介绍[真机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消息的时候再校验。

这篇关于[真机Bug]解决服务器强制定位玩家位置后NPC加载超时的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1062479

相关文章

解决systemctl reload nginx重启Nginx服务报错:Job for nginx.service invalid问题

《解决systemctlreloadnginx重启Nginx服务报错:Jobfornginx.serviceinvalid问题》文章描述了通过`systemctlstatusnginx.se... 目录systemctl reload nginx重启Nginx服务报错:Job for nginx.javas

Mysql DATETIME 毫秒坑的解决

《MysqlDATETIME毫秒坑的解决》本文主要介绍了MysqlDATETIME毫秒坑的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着... 今天写代码突发一个诡异的 bug,代码逻辑大概如下。1. 新增退款单记录boolean save = s

SpringBoot项目启动后自动加载系统配置的多种实现方式

《SpringBoot项目启动后自动加载系统配置的多种实现方式》:本文主要介绍SpringBoot项目启动后自动加载系统配置的多种实现方式,并通过代码示例讲解的非常详细,对大家的学习或工作有一定的... 目录1. 使用 CommandLineRunner实现方式:2. 使用 ApplicationRunne

vue解决子组件样式覆盖问题scoped deep

《vue解决子组件样式覆盖问题scopeddeep》文章主要介绍了在Vue项目中处理全局样式和局部样式的方法,包括使用scoped属性和深度选择器(/deep/)来覆盖子组件的样式,作者建议所有组件... 目录前言scoped分析deep分析使用总结所有组件必须加scoped父组件覆盖子组件使用deep前言

解决Cron定时任务中Pytest脚本无法发送邮件的问题

《解决Cron定时任务中Pytest脚本无法发送邮件的问题》文章探讨解决在Cron定时任务中运行Pytest脚本时邮件发送失败的问题,先优化环境变量,再检查Pytest邮件配置,接着配置文件确保SMT... 目录引言1. 环境变量优化:确保Cron任务可以正确执行解决方案:1.1. 创建一个脚本1.2. 修

Python项目打包部署到服务器的实现

《Python项目打包部署到服务器的实现》本文主要介绍了PyCharm和Ubuntu服务器部署Python项目,包括打包、上传、安装和设置自启动服务的步骤,具有一定的参考价值,感兴趣的可以了解一下... 目录一、准备工作二、项目打包三、部署到服务器四、设置服务自启动一、准备工作开发环境:本文以PyChar

Mysql8.0修改配置文件my.ini的坑及解决

《Mysql8.0修改配置文件my.ini的坑及解决》使用记事本直接编辑my.ini文件保存后,可能会导致MySQL无法启动,因为MySQL会以ANSI编码读取该文件,解决方法是使用Notepad++... 目录Myhttp://www.chinasem.cnsql8.0修改配置文件my.ini的坑出现的问题

SpringBoot项目删除Bean或者不加载Bean的问题解决

《SpringBoot项目删除Bean或者不加载Bean的问题解决》文章介绍了在SpringBoot项目中如何使用@ComponentScan注解和自定义过滤器实现不加载某些Bean的方法,本文通过实... 使用@ComponentScan注解中的@ComponentScan.Filter标记不加载。@C

MySQL8.0找不到my.ini如何解决

《MySQL8.0找不到my.ini如何解决》在配置MySQL主从复制时,发现找不到my.ini配置文件,通过检查路径和打开隐藏文件夹,最终在C:ProgramDataMySQLMySQLSer... 目录问题描述解决方法总结问题描述今天在配置mysql主从复制的时候发现,找不到my.ini这个配置文件。

springboot 加载本地jar到maven的实现方法

《springboot加载本地jar到maven的实现方法》如何在SpringBoot项目中加载本地jar到Maven本地仓库,使用Maven的install-file目标来实现,本文结合实例代码给... 在Spring Boothttp://www.chinasem.cn项目中,如果你想要加载一个本地的ja