Python 逻辑层常见错误
本页面向编写 Assets、EntityDef、Base/Cell 脚本和客户端协议的开发者。重点解决“脚本为什么没有执行”“数据为什么没有同步”“Entity 为什么处于错误状态”,不展开 C++ 编译、vcpkg、CI 和操作系统内核问题。
先学会看日志
逻辑层问题建议按以下顺序排查:
- 找到第一条 Python traceback 或 EntityDef ERROR,不要只看最后一条连锁错误;
- 确认错误来自 BaseApp、CellApp、DBMgr、LoginApp、Interfaces 还是 Bots;
- 记录 Entity 类型、Entity ID、Space ID、方法名和当前生命周期状态;
- 对照
.def检查属性、RPC 参数和脚本实现; - 判断错误发生在本地调用、序列化、远程传输还是目标回调;
- 最后才考虑引擎底层问题。
一条有用的业务日志应包含上下文:
ERROR_MSG(
"Avatar::enterSpace failed: entityID=%i, spaceID=%i, state=%s" %
(self.id, spaceID, self.state)
)不要在公共入口使用下面这种写法:
try:
doSomething()
except Exception:
pass它会吞掉 traceback,使最初的类型或生命周期错误在更远的位置变成空引用、协议错误或状态不一致。确实需要补充上下文时,应记录后重新抛出:
try:
self.inventory.addItem(itemData)
except Exception:
ERROR_MSG(
"Avatar::addItem failed: entityID=%i, itemData=%r" %
(self.id, itemData)
)
raiseEntityDef 与脚本加载
启动时报 EntityDef 解析错误
现象
DBMgr、BaseApp 或 CellApp 启动失败,日志包含 .def、entities.xml、types.xml、unknown type、duplicate utype 或 XML parse error。
常见原因
- XML 标签没有闭合、大小写错误或层级错误;
- 属性或 RPC 使用了未定义的数据类型;
- Entity 没有加入
entities.xml,或名称与文件名不一致; - Interface、EntityComponent 或插件定义的路径/名称错误;
- 显式 UType 重复;
- 同一个类型别名被宿主 Assets 或插件重复声明。
验证方式
- 从日志中找到第一个出现的文件名、Entity 名和字段名;
- 临时只保留本次新增的一项定义,确认错误是否随它消失;
- 检查
.def引用的类型是否已在types.xml或插件类型文件中声明; - 检查 Entity 名、
.def文件名和 Python 类名是否完全一致; - 不要只检查最后一个报错,前一个定义失败会导致后续大量 unknown type。
修复方案
修正第一处定义错误后完整重启服务。EntityDef 属于协议与类结构,不应依赖普通脚本热更新生效。若变更涉及客户端可见定义,还需要重新生成客户端 SDK。
详见 Entity 和 EntityComponent。
ModuleNotFoundError 或找不到 Entity Python 类
现象
引擎已经读取 .def,但加载脚本时报模块不存在、类不存在,或 Entity 类型初始化失败。
常见原因
- 脚本没有放在对应组件的搜索路径中;
- Base 脚本放到了 Cell 目录,或反之;
- 文件名、类名和 Entity 名大小写不一致;
- 模块导入过程中先发生了另一个 Python 异常;
- 循环导入导致类尚未完成定义;
- 实际启动的是另一套 Assets。
验证方式
查看 traceback 最底部的原始异常。ModuleNotFoundError 有时只是上层表现,真正原因可能是模块内部导入失败。确认启动输出中的 KBE_RES_PATH,并在日志中打印目标模块的 __file__ 验证实际加载位置。
修复方案
让模块职责与组件目录一致,保持 Entity 名、文件名和类名一致。公共代码放入公共脚本路径,避免 Base 与 Cell 模块互相导入实现类;共享常量和纯函数应下沉到无组件依赖的模块。
修改 .def 后热更新没有生效
现象
新增属性或 RPC 后执行 KBEngine.reloadScript(),现有 Entity 仍没有该成员,客户端协议也没有变化。
原因
普通热更新重载 Python 逻辑,不会重新建立 EntityDef 协议表,也不会重建已经存在的 Entity 实例。
修复方案
以下变化应重启相关服务:
- 属性的新增、删除、类型、Flags 或持久化设置;
- BaseMethods、CellMethods、ClientMethods 的签名;
- EntityComponent、Interface、
types.xml或插件清单; - 进程入口和模块级初始化结构。
客户端可见协议变化还应重新生成 SDK。仅修改普通函数实现时才优先使用热更新。详见 服务端热更新。
属性与数据类型
属性赋值时报 TypeError、ValueError 或 can't set
现象
给 Entity 或 EntityComponent 属性赋值时抛出异常,或者在 RPC/写库阶段出现 addToStream: pyValue(...) is wrong。
常见原因
- Python 值与 EntityDef 类型不一致;
- 整数超出
UINT8、INT32等类型范围; STRING、UNICODE、BLOB与str/bytes混用;VECTOR2/3/4的长度或元素类型错误;- ARRAY 元素类型错误;
- FIXED_DICT 缺少字段、字段名错误或嵌套字段类型不匹配;
- 把
None赋给不接受空值的协议类型。
验证方式
从最外层属性或 RPC 参数开始,记录期望类型和实际 type(value).__name__。复杂结构逐层检查,不要只打印一个很大的 dict:
def validateAvatarInfo(info):
assert isinstance(info, dict), type(info).__name__
assert isinstance(info.get("dbid"), int), type(info.get("dbid")).__name__
assert isinstance(info.get("name"), str), type(info.get("name")).__name__修复方案
在数据进入 Entity 属性或 RPC 边界前完成转换和校验。不要依赖底层把错误值自动转换成默认值;底层异常是协议契约被违反的信号,应修正调用方。
RPC 报参数数量或类型错误,但不知道是哪一个参数
现象
调用 EntityCall 时抛出 TypeError,日志提示方法参数不匹配或序列化失败。
验证方式
按下面的顺序对照:
.def中方法所属区域是 BaseMethods、CellMethods 还是 ClientMethods;- 参数数量和顺序;
- 每个
<Arg>对应的 Python 类型; - 嵌套 ARRAY/FIXED_DICT 的具体字段;
- 当前调用使用的是
entity.base、entity.cell、entity.client还是其他 EntityCall; - 客户端是否仍使用旧 SDK。
建议在业务入口保留方法名、Entity ID 和参数类型:
DEBUG_MSG(
"Avatar::reqUseItem: entityID=%i, itemID=%r(%s), count=%r(%s)" %
(self.id, itemID, type(itemID).__name__, count, type(count).__name__)
)修复方案
以 .def 为唯一协议来源修正调用方。不要在接收方法中通过 *args 猜测参数,也不要为了绕过错误随意放宽协议类型。
修改容器后客户端或数据库没有得到预期结果
现象
修改 ARRAY、FIXED_DICT 或 EntityComponent 内部内容后,本地对象看起来变化了,但客户端同步、持久化或属性回调没有按预期发生。
常见原因
- 修改的是普通 Python 临时副本,而不是 Entity 属性;
- 深层原地修改没有经过项目预期的属性更新路径;
- 属性 Flags 不包含目标组件或客户端;
- 业务修改后没有在预期时机写库;
- 客户端看到的是 DetailLevel、AOI 或初始化同步后的另一份状态。
验证方式
记录修改前后的 Entity 属性值、对象身份、属性回调和写库时机。先用“构造完整新值后重新赋给属性”的方式验证是否属于原地变更通知问题。
修复方案
对需要同步或持久化的复杂属性,优先通过明确的领域方法修改,并在方法末尾形成一次清晰的属性更新。避免让外部代码任意持有并修改深层容器,这也有助于校验权限和维护不变量。
属性在 Base、Cell 或客户端访问不到
现象
同名属性在某个组件存在,在另一个组件访问时报 AttributeError,或客户端始终收不到。
原因
属性可见范围由 EntityDef Flags 决定。Base、Cell、Client、其他客户端和持久化是不同语义,不能因为同一个 Entity 在多个位置存在,就假设所有属性都会自动复制。
验证与修复
检查属性 Flags 和当前代码运行组件。只把真正需要的数据暴露到 Cell 或 Client;敏感服务端状态不要为了方便访问而扩大同步范围。涉及客户端的修改需要重启并重新生成 SDK。
RPC 与 EntityCall
客户端调用 Base/Cell 方法没有反应
现象
客户端发起 RPC 后服务端方法没有执行,也没有业务结果。
常见原因
- 方法未在
.def中声明Exposed; - 客户端调用了错误的 Base/Cell EntityCall;
- Cell 尚未创建或玩家尚未进入 Space;
- 客户端 SDK 过期;
- 服务端方法签名与
.def不一致; - 请求被服务端的调用者、会话或频率校验拒绝;
- 方法内部立即抛出异常,但只检查了客户端日志。
验证方式
在方法入口记录日志。如果入口日志没有出现,检查协议定义、EntityCall 和生命周期;如果入口出现但没有结果,继续查看完整 Python traceback 和业务分支。
修复方案
只对客户端确实需要调用的方法增加 Exposed,并在服务端重新校验权限、目标归属、状态和参数。Exposed 表示网络可达,不表示调用可信。
self.cell、self.base 或 self.client 是 None
现象
调用 self.cell.xxx()、self.base.xxx() 或 self.client.xxx() 时出现空引用或不可用错误。
常见原因
- Cell 还没有创建完成,Base 就调用了
self.cell; - Cell 已销毁或正在迁移,旧逻辑仍继续调用;
- Entity 没有客户端,或客户端已经断线;
- 当前 Entity 类型本身不具备对应部分;
- 异步回调返回时生命周期已经变化。
验证方式
记录 onGetCell、onLoseCell、客户端获得/失去、Entity 销毁和异步请求开始/结束的时间顺序。不要只在调用前打印一次布尔值,因为远程操作期间状态仍可能变化。
修复方案
将操作绑定到明确状态机:
- 只有
onGetCell后才发送依赖 Cell 的请求; onLoseCell后清理等待中的空间操作;- 调用客户端前确认当前存在有效客户端;
- 异步回调中重新检查 Entity 是否仍有效、请求是否仍属于当前状态版本。
EntityCall 保存后调用失效
现象
缓存的远程 EntityCall 一开始可用,稍后调用没有结果或目标已不存在。
原因
EntityCall 指向远程 Entity 的某一部分。Entity 销毁、Cell 销毁、迁移、组件退出或客户端断线后,旧引用可能失效。
修复方案
不要把 EntityCall 当作永久本地对象。长期关系优先保存稳定业务标识,需要调用时重新定位;关键请求增加 request ID、超时、幂等和结果确认。清理 Entity 时同步移除订阅、等待队列和缓存引用。
详见 EntityCall。
服务端调用 ClientMethods,客户端没有收到
常见原因
- Entity 当前没有客户端;
- 客户端尚未创建对应 Entity,或已经离开 View;
- 调用的是错误 Entity 的
client; - SDK 过期或客户端实现类没有对应方法;
- 客户端事件系统暂停了 Out 事件;
- 参数序列化失败;
- 网络已断开但服务端断线状态尚在收敛。
验证方式
依次检查服务端调用前状态、服务端网络日志、客户端 SDK 收包日志和游戏层事件日志。必须区分“服务器没有发送”“SDK 收到但排队”“游戏层没有注册处理”。
修复方案
把重要状态保存在客户端可恢复的 Entity 属性或明确请求/响应流程中,不要假设一次 ClientMethods 调用必然被游戏层立即处理。场景加载期间暂停事件时,还要控制恢复后的事件有效期和消费量。
Entity 生命周期
createCellEntity 后立即使用 Cell 失败
现象
Base 调用 createCellEntity 后下一行立即访问 self.cell,结果为空或 RPC 没有发送。
原因
创建 Cell 是跨进程异步流程。函数调用返回只表示请求已发起,不表示 Cell 已创建完成。
修复方案
把依赖 Cell 的逻辑放到 onGetCell 之后,或在 Base 上维护等待队列:
def enterSpace(self, spaceCell):
self._pendingEnter = True
self.createCellEntity(spaceCell)
def onGetCell(self):
if self._pendingEnter:
self._pendingEnter = False
self.cell.onEnterReady()同时处理创建失败、重复请求、玩家断线和目标 Space 已销毁的情况。
destroyCellEntity 后旧回调改变了新状态
现象
玩家快速切换 Space、重连或连续发起进入请求时,旧的创建/销毁回调覆盖了新的状态,出现进入错误场景、重复创建或空 Cell。
原因
跨组件操作异步完成,回调顺序不一定等同于业务请求顺序。
修复方案
为每次空间切换生成递增的操作版本或 request ID。回调执行时比较当前版本,只允许最新请求推进状态;旧回调只做必要清理,不得覆盖新状态。
Entity 已销毁,Timer、协程或数据库回调仍访问它
现象
onDestroy 后出现属性访问错误、重复保存、重复发奖,或异步回调修改了已无效对象。
常见来源
- 重复 Timer 未删除;
- asyncio Task 未取消;
- 数据库或 Interfaces 回调迟到;
- 全局管理器仍保存 Entity 引用;
- 事件订阅未解除;
- 旧状态的 RPC 回调晚于新状态到达。
修复方案
把异步资源的所有权放在创建它的 Entity 或组件中,并在销毁/离开状态时统一释放:
- 保存 Timer ID 并删除重复 Timer;
- 保存 Task,销毁时取消;
- 回调携带 request ID,并重新检查当前状态;
- 全局集合在
onDestroy/onLoseCell中移除成员; - 清理逻辑保持幂等,可被调用多次而不产生副作用。
EntityComponent 的回调没有触发
现象
组件实现了 onTimer、Cell 事件或生命周期回调,但运行时没有收到。
常见原因
- 组件实例没有真正挂到当前 Entity;
- 回调实现放错 Base/Cell 目录;
- 组件定义、属性名和脚本类映射不一致;
- 订阅发生在旧 Cell,迁移或重建后没有恢复;
- 热更新只改变类代码,但旧实例/订阅状态没有重建;
- 回调名称或签名写错。
验证方式
在组件构造、附加、Cell 获取/丢失、迁移和销毁位置记录 owner ID 与组件实例。确认事件订阅跟随当前 Cell 生命周期,而不是只在首次创建时执行一次。
修复方案
让组件在明确的附加/获得 Cell 阶段建立订阅,在丢失 Cell/销毁阶段解除,并保证迁移后能够重新建立。不要依赖 owner Entity 实现一个无业务意义的同名空方法来“唤醒”组件回调。
Timer 与异步任务
Timer 重复触发或无法删除
现象
同一业务动作执行多次,调用 delTimer 报无效 ID,或热更新后出现多个周期任务。
常见原因
- 每次进入状态都新增 Timer,却没有先删除旧 Timer;
- 一次性 Timer 回调后仍使用旧 ID 删除;
- 保存了错误 Entity/组件创建的 Timer ID;
- 状态切换或销毁时没有清理;
- 热更新重新执行初始化逻辑并重复注册。
修复方案
一个业务职责只保留一个 Timer 所有者和一个 ID。删除后立即清零,回调中先验证 ID 和当前状态:
def startThink(self):
self.stopThink()
self._thinkTimerID = self.addTimer(0.2, 0.2, TIMER_THINK)
def stopThink(self):
if self._thinkTimerID:
self.delTimer(self._thinkTimerID)
self._thinkTimerID = 0
def onTimer(self, timerID, userArg):
if timerID != self._thinkTimerID or userArg != TIMER_THINK:
return
if self.state != STATE_ACTIVE:
self.stopThink()
return
self.think()Timer 不应作为唯一的保存保证或关服状态推进机制。详见 计时器。
asyncio 协程让 BaseApp/CellApp 卡顿
现象
使用 async/await 后仍出现慢 Tick,多个请求同时完成时尤其明显。
原因
协程只在等待期间让出执行权,恢复后的 Python 代码仍运行在组件调度线程。CPU 密集循环、同步 IO、大结果解析和同一 Tick 唤醒大量任务都会阻塞逻辑线程。
修复方案
- 使用真正异步的网络客户端;
- 限制并发任务数和每 Tick 消费量;
- 分批处理大型结果;
- 不在协程中调用同步文件、HTTP 或数据库库;
- Entity 销毁时取消其任务;
- 用慢 Tick 和 Python 回调耗时区分“等待慢”与“执行慢”。
详见 asyncio 支持。
热更新后旧对象仍执行旧逻辑
现象
新创建 Entity 使用新代码,部分旧 Entity、Timer 回调、闭包或缓存对象仍表现为旧行为。
原因
热更新替换模块与类方法,并不会自动重建所有实例状态,也不会替换已经保存的函数对象、闭包、Task 或第三方注册回调。
修复方案
把热更新设计为显式迁移:更新版本字段、重建需要的订阅/策略对象,并清理旧 Timer 和回调。无法安全迁移的结构性修改应重启,不要把热更新当作无状态重新加载。
数据库与持久化
Persistent 属性没有保存
现象
运行时属性已经改变,但重新登录或从数据库创建 Entity 后恢复成旧值。
常见原因
- 属性没有设置
Persistent; - 修改发生在 Cell 临时状态,没有同步到负责保存的数据;
- 没有调用写库接口,或预期的自动保存尚未发生;
- Entity 没有有效数据库 ID;
- 写库回调失败但被忽略;
- 关服或销毁流程早于异步写库完成;
- 读写使用了不同数据库接口。
验证方式
记录 Entity ID、数据库 ID、保存原因、请求时间、回调结果和数据库接口名。直接查询数据库确认“没有写入”还是“读取了另一条记录”。
修复方案
由 Base 维护需要长期保存的权威状态,在明确业务节点调用写库并处理结果。高价值操作需要保存状态机、幂等标识和失败重试,不能只在 onDestroy 或某个 Timer 中碰运气保存。
writeToDB 回调没有执行或执行太晚
常见原因
- 数据库请求失败或排队;
- Entity 在回调前已销毁;
- 回调对象或签名不正确;
- 大量 Entity 同时保存造成积压;
- 回调中再次触发保存,形成保存风暴;
- 业务只检查 BaseApp 日志,没有检查 DBMgr 的第一处错误。
修复方案
为保存请求记录 request ID 与开始时间,限制同一 Entity 的并发写入,合并重复保存,并为关键操作设计超时后的状态处理。异步写库意味着“不阻塞等待数据库”,不意味着“没有队列、不会失败或一定在关服前完成”。
createEntityFromDBID 返回空或重复创建
现象
从数据库加载 Entity 时回调得到空对象,或同一个数据库 ID 在多个流程中被重复创建。
常见原因
- Entity 类型或数据库接口名错误;
- 数据库 ID 不存在或为 0;
- 该数据库 Entity 已经处于激活状态;
- 登录、重连和角色选择并发发起加载;
- 回调到达时账号状态已经变化;
- 把 Runtime Entity ID 与数据库 ID 混用。
修复方案
明确区分 entity.id 与数据库 ID。以账号或角色为粒度建立“未加载、加载中、已激活、卸载中”状态,加载中拒绝重复请求;回调必须校验请求版本和当前会话。
原生数据库回调阻塞逻辑线程
现象
数据库请求本身异步,但回调返回大量数据后 BaseApp/CellApp 出现慢 Tick。
原因
结果回调、反序列化和后续 Python 处理仍在逻辑线程执行。一次返回过多行或在回调中进行复杂聚合会造成长帧。
修复方案
限制查询结果大小,分页或分批消费,把报表和重分析任务放到外部服务。原生命令入口还应校验权限与参数,不允许客户端直接拼接 SQL 或数据库命令。
AOI、Space 与客户端同步
Entity 已进入 Space,但客户端看不到
常见原因
- 玩家 Cell 尚未获得 Witness;
- 两个 Entity 不在同一 Space;
- 距离超出 View 范围;
- 目标 Entity 没有 Client 部分或没有客户端可见属性;
- Entity 尚未进入玩家 View,服务端就调用了其 ClientMethods;
- 位置、坐标轴或 layer 数据错误;
- 客户端没有实现生成的 Entity 类或创建事件处理。
验证方式
同时记录双方 Entity ID、Space ID、position、Witness 状态以及进入/离开 View 回调。客户端侧确认“未收到创建消息”还是“已创建但没有渲染”。
修复方案
先让 Space、Witness 和 View 关系正确,再检查客户端表现。不要用全局广播补偿 AOI 配置错误,否则会显著增加网络包量并泄露本不应可见的状态。
属性在服务端改变,客户端没有更新
常见原因
- 属性 Flags 不包含 Client 或对应可见范围;
- Entity 尚未进入该客户端 View;
- 修改的是 Base 属性,但客户端同步定义在 Cell 可见路径;
- 修改了临时对象或深层容器,没有形成预期属性更新;
- DetailLevel/易变属性策略降低了同步频率或精度;
- 客户端 SDK 过期;
- 客户端收到更新但游戏层事件暂停或覆盖了值。
验证方式
从服务端属性赋值、Witness 打包、客户端 SDK 收包到游戏层 setter/事件逐段记录。不要只根据画面判断网络是否同步。
修复方案
按实际可见性设置 Flags,并控制更新频率。位置、朝向等高频属性应使用易变属性和 DetailLevel 策略;低频关键状态应通过明确属性或 RPC 同步。
View 范围增大后 CellApp 明显变慢
原因
AOI 成本取决于每个 Witness 可见的 Entity 数量和变化频率。扩大 View 半径会同时增加空间关系、进入/离开事件、属性同步和网络包量;密集场景中增长可能远高于半径本身的比例。
修复方案
- 先统计单个 Witness 平均/峰值可见 Entity 数;
- 减少不需要客户端知道的 Entity 和属性;
- 降低高频属性同步精度或频率;
- 按玩法拆分 Space、区域或对象层级;
- 使用真实移动和战斗行为压测 Cell Tick、AOI 与包量。
不要仅通过提高 Tick 预算掩盖可见关系设计问题。
导航与移动
navigate 返回 0,没有开始移动
常见原因
navigatePathPoints在maxSearchDistance内找不到目标导航点;- 起点不在 navmesh;
- 目标与起点不连通;
- layer 错误;
- 目标坐标或 Y 轴来自另一坐标系;
- 当前 Entity 已销毁、没有 Cell 或状态禁止移动。
验证方式
记录起点、目标、layer、maxSearchDistance、maxMoveDistance 和返回的路径点。分别验证“目标能否投影到网格”和“起点到目标是否存在完整路径”。
修复方案
maxSearchDistance 只控制目标附近寻找可用导航点的范围,maxMoveDistance 控制已有路径最多移动多远。增大搜索距离不能修复断裂的 navmesh,也不应把目标投影到与玩家意图无关的远处区域。
导航在转角停顿或实体移动异常
常见原因
- 业务短时间重复发起导航,持续取消旧控制器;
- 旧导航完成回调覆盖了新 AI 状态;
- navmesh 转角、Agent 半径、坡度或间隙不匹配;
- 业务手动调用内部单步推进接口制造首帧移动;
- 服务端导航高度、客户端地形贴合和客户端插值互相覆盖;
maxMoveDistance提前结束移动;- layer 或目标点映射错误。
验证方式
- 暂停 AI 重入,只执行一次导航;
- 记录控制器 ID、创建、取消、完成和回调顺序;
- 输出完整路径点和每个转角;
- 对比服务端位置、客户端收到的位置和最终渲染位置;
- 在导航工具中检查相同 Agent 参数下的连通性。
修复方案
一个 AI 移动状态只拥有一个有效控制器。新请求应明确接管旧请求,并用操作版本忽略过期回调。控制器由 CellApp Tick 统一推进,不应从 Python 逻辑层手动推进首帧。
详见 导航网格。
导航完成后 Y 轴上升或跳变
原因
导航期间服务端位置可能按 navmesh 高度同步,结束后客户端又恢复地形、物理或自身插值高度。如果过早清理用于保持贴地的导航同步状态,两套高度来源会发生切换。
验证方式
分别记录服务端 Y、网络同步 Y、客户端 Entity Y、地形采样 Y 和渲染对象 Y,确认是哪一层首次改变。
修复方案
统一高度权威来源和状态切换时机。不要为了让 isOnNavigate 表面闭合而清理仍承担贴地语义的底层状态;也不要在客户端无条件用地形高度覆盖服务端位置。
移动超速回调频繁触发
常见原因
- 客户端帧卡顿后一次发送过大位移;
- 客户端预测、服务器校正和重发叠加;
- 传送或服务端强制移动没有进入正确状态;
- 速度、单位或 Tick 时间理解不一致;
- 玩家确实发送了非法移动。
修复方案
记录客户端位置、服务端位置、时间差、单包距离、累计距离和当前移动状态。传送、击退、载具等合法例外应由明确状态授权,不要简单提高全局速度阈值。对异常客户端仍应由服务端权威校验和校正。
客户端协议与 SDK
客户端提示 EntityDef MD5 不一致
常见原因
- 修改了客户端可见属性、方法、类型或 UType 后没有生成 SDK;
- 客户端工程仍打包旧生成文件;
- SDK 和服务器使用了不同 Assets 或插件清单;
- 连接到了错误的测试/生产环境;
- 插件 EntityDef 的客户端协议发生变化。
修复方案
使用目标服务器同一套 KBE_RES_PATH、Assets 和插件配置重新生成 SDK,清理客户端构建缓存后重新打包。不要关闭摘要校验或手工修改摘要常量。
只修改未暴露给客户端的 Base/Cell RPC 或内部属性,不应改变客户端摘要。如果确实发生变化,应比较变更前后的生成文件和最小 EntityDef diff,作为引擎回归问题排查。
详见 SDK 自动生成。
客户端 Entity 已生成,但业务类没有回调
常见原因
- 实现类名称与生成基类约定不一致;
- 没有继承正确的生成基类;
- ClientMethods 签名与生成代码不同;
- 游戏层事件没有注册或已取消注册;
- Out 事件处于暂停队列;
- 客户端存在两套 SDK,运行时加载了旧类;
- Entity 已离开 View,回调属于旧对象。
验证方式
按“网络收包 → SDK 消息分发 → Entity 查找 → 生成基类方法 → 游戏层事件”逐层加日志。确认 Entity ID 与实例生命周期一致,避免旧对象和新对象同名造成误判。
启动时最常见的逻辑层问题
服务端一直显示 finding dbmgr
这通常不是当前 BaseApp/CellApp 的逻辑错误,而是 DBMgr 没有完成初始化。逻辑开发者优先检查 DBMgr 日志中更早出现的:
- EntityDef/XML 解析错误;
- Entity Python 模块导入异常;
types.xml或插件类型冲突;- 数据库账号、数据库名或连接失败;
- 数据模型同步失败。
先修复 DBMgr 的第一条错误,等待日志会随之消失。若 DBMgr 本身正常但组件仍无法发现,再交给部署或引擎维护人员检查 Machine、网卡和进程配置。
修改代码后日志完全没有变化
常见原因
- 修改了错误 Assets 下的同名文件;
- 当前组件没有重载该模块;
- 修改的是 Base 脚本,但观察 CellApp 日志,或反之;
- Logger 过滤了当前日志级别;
- 代码路径根本没有被调用;
- 旧进程没有退出,客户端连接的仍是旧服务。
验证方式
确认启动输出中的资源路径,在模块加载或目标入口打印包含文件路径、组件和版本号的临时调试日志。定位完成后移除高频日志,避免在 Tick、AOI 或 RPC 热路径持续输出。
提交逻辑层问题时需要提供什么
为了让问题可以复现,请提供:
- 引擎 commit 或版本;
- 出错组件名称;
- 完整 Python traceback 和它之前的第一条 ERROR;
- Entity 类型、Entity ID、数据库 ID、Space ID;
- 相关
.def、types.xml、Python 方法和调用方; - Base/Cell/Client 中哪一侧发起、哪一侧应该接收;
- 生命周期顺序,例如创建 Base、创建 Cell、进入 Space、断线、销毁;
- 稳定复现步骤、期望结果和实际结果;
- 若涉及客户端,提供 SDK 类型、生成时间和协议摘要。
提交日志前移除密码、management token、私钥和账号凭据,但保留方法名、参数类型、Entity 上下文和时间顺序。
