--- front: https://nie.res.netease.com/r/pic/20210728/5507b669-4c6f-4958-b5d0-b8556ab4cfb5.png hard: 进阶 time: 10分钟 --- # ModAPI代码进阶优化 > 本文档介绍了一些高级的ModAPI代码优化技巧,帮助开发者们编写更高效的代码。 > 我们将结合一些开发情景,逐一讲解优化流程 ## 使用实体Attr实现Molang同步 ### 背景说明 在开发过程中,假设我们需要给玩家添加创造飞行或鞘翅动画效果。但是翻找wiki却发现原版并没有提供相关的Molang,所以我们需要自定义以下两个Molang变量: ``` query.mod.ysm_is_create_flying # 是否正在创造飞行 query.mod.ysm_is_elytra_flying # 是否正在鞘翅飞行 ``` ### 实现方案对比 **不推荐的方案:** 通过tick函数持续获取玩家状态并与客户端通信来设置Molang值 这种方式会导致严重的性能开销,十分的不友好 **推荐的方案:** 结合以下两个关键功能实现: 1. [OnPlayerActionServerEvent](https://mc.163.com/dev/mcmanual/mc-dev/mcdocs/1-ModAPI-beta/事件/玩家.html#onplayeractionserverevent) - 用于监听玩家动作状态 2. [实体自定义属性](https://mc.163.com/dev/mcmanual/mc-dev/mcdocs/1-ModAPI-beta/接口/实体/索引.html?catalog=1#自定义属性) - 实现Molang值的自动同步 ### 工作原理 众所周知,当使用服务端的`SetAttr`接口设置属性值后,系统会自动将这些值同步到客户端 在实体Attr文档中,我们可以找到一个关键接口`RegisterUpdateFunc`,它用于注册属性值变化时的回调函数 这个机制就是实现实体Molang值自动同步的核心 ### Attr同步优点 - 高性能、低功耗的实现方式 - 使用游戏原生发包的Attr通信,比直接通信接口更快 - 自动处理可见渲染范围内的同步,无需手动监听`AddPlayerCreatedClientEvent`事件 - 适合在tick内进行多次操作,统一更新数据 ### 代码实现 ##### 服务端代码 ```python # 服务端代码实现 ServerComp = serverApi.GetEngineCompFactory() class PlayerActionServerSystem(ServerSystem): def __init__(self, namespace, systemName): ServerSystem.__init__(self, namespace, systemName) def OnPlayerActionServerEvent(self, args): # 玩家动作事件,当玩家开始/停止某些动作时触发该事件 playerId = args["playerId"] actionType = args["actionType"] modAttr = ServerComp.CreateModAttr(playerId) # 使用鞘翅飞行/创造飞行的动作枚举值为(15,16,34,35) if actionType == 34: # 开始创造飞行 modAttr.SetAttr("playerIsCreateFlying", 1) elif actionType == 35: # 停止创造飞行 modAttr.SetAttr("playerIsCreateFlying", 0) elif actionType == 15: # 开始鞘翅飞行 modAttr.SetAttr("playerIsElytraFlying", 1) elif actionType == 16: # 停止鞘翅飞行 modAttr.SetAttr("playerIsElytraFlying", 0) ``` ##### 客户端代码 ```python # 客户端代码实现 ClientComp = clientApi.GetEngineCompFactory() levelId = clientApi.GetLevelId() playerId = clientApi.GetLocalPlayerId() # 注册自定义Molang变量 ClientComp.CreateQueryVariable(levelId).Register("query.mod.ysm_is_create_flying", 0) ClientComp.CreateQueryVariable(levelId).Register("query.mod.ysm_is_elytra_flying", 0) # key为需要监听的attr名称,value为需要设置的对应Molang变量名称 queryDict = { "playerIsFlying": "query.mod.ysm_is_flying", "playerIsElytraFlying": "query.mod.ysm_is_elytra_flying" } # 根据queryDict自动配置监听,简化代码 def CreateAttrCallBack(bindQuery): def _eventFuckCallBack(args): ClientComp.CreateQueryVariable(args["entityId"]).Set(bindQuery, args["newValue"]) return _eventFuckCallBack class PlayerActionClientSystem(ClientSystem): def __init__(self, namespace, systemName): ClientSystem.__init__(self, namespace, systemName) # 注册本地玩家的属性值变化回调函数 for attr, query in queryDict.items(): ClientComp.CreateModAttr(playerId).RegisterUpdateFunc(attr, CreateAttrCallBack(query)) def OnAddPlayerAOIClient(self, args): """玩家加入游戏或进入视野时触发,注册属性值变化回调""" pId = args["playerId"] for attr, query in queryDict.items(): ClientComp.CreateModAttr(pId).RegisterUpdateFunc(attr, CreateAttrCallBack(query)) ``` ### 工作流程说明 1. 客户端为本地玩家和其他玩家注册属性值变化的回调函数 2. 服务端通过`OnPlayerActionServerEvent`事件监测玩家的飞行状态 3. 使用`SetAttr`设置属性值,自动同步到客户端 4. 客户端的回调函数被触发,更新相应的Molang值 #### 其他性能优化教程,敬请期待...