--- front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg hard: 进阶 time: 30分钟 --- # 挑战:自定义传送点 在本节中,我们一起来制作一个传送点模组。我们知道,在我的世界中,地形可以近似认为是无限广阔的,所以,我们在探索的过程中可能经常迷失方向,找不到回到据点的路。因此,我们可以自定义一个传送点模组。通过保存一个传送点来做到在迷路时快速回到传送点所指定的位置。 下面,我们一起使用模组SDK来制作一个传送点模组。 ## 创建项目 我们在我的世界开发工作台中创建一个“传送点模组”的组件,然后使用编辑器“资源管理”窗格中“创建”按钮来快速创建一个Python模组模块,将其命名为`TeleportPointMod`。 ![](./images/11.7_tp_point_server_create.png) 由于我们知道,只有服务端才能获取和控制每个玩家的位置,所以我们只需创建一个服务端系统,将其命名为`TeleportPointServerSystem`。对于每个玩家来说,客户端一般只负责屏幕的渲染和本地的一些计算,是无法直接改变玩家处于整个世界中的位置的,所以我们无需使服务端与客户端交互。因此我们无需创建客户端系统。 ![](./images/11.7_tp_point_server_created.png) 创建结束后,我们可以在“资源管理”窗格中看到如图的文件排布。我们将整个行为包文件夹在Python的IDE中打开,以便之后的代码编写。此时的Python文件内容如下。 `modMain.py`: ```python # -*- coding: utf-8 -*- from common.mod import Mod @Mod.Binding(name="TeleportPointMod", version="0.0.1") class TeleportPointMod(object): def __init__(self): pass @Mod.InitServer() def TeleportPointModServerInit(self): pass @Mod.DestroyServer() def TeleportPointModServerDestroy(self): pass @Mod.InitClient() def TeleportPointModClientInit(self): pass @Mod.DestroyClient() def TeleportPointModClientDestroy(self): pass ``` `TeleportPointServerSystem.py` ```python # -*- coding: utf-8 -*- import server.extraServerApi as serverApi ServerSystem = serverApi.GetServerSystemCls() class TeleportPointServerSystem(ServerSystem): def __init__(self, namespace, systemName): ServerSystem.__init__(self, namespace, systemName) # ScriptTickServerEvent的回调函数,会在引擎tick的时候调用,1秒30帧(被调用30次) def OnTickServer(self): """ Driven by event, One tick way """ pass # 这个Update函数是基类的方法,同样会在引擎tick的时候被调用,1秒30帧(被调用30次) def Update(self): """ Driven by system manager, Two tick way """ pass def Destroy(self): pass ``` ## 注册服务端系统 我们知道,单纯创建了服务端系统文件是无法在主模组文件中同时将其注册的。我们手动将我们的服务端系统注册。我们在`TeleportPointModServerInit`函数中输入注册系统的方法: ```python @Mod.InitServer() def TeleportPointModServerInit(self): serverApi.RegisterSystem('TeleportPointMod', 'TeleportPointServerSystem', 'Script_TeleportPointMod.TeleportPointServerSystem.TeleportPointServerSystem') ``` 其中`Script_TeleportPointMod.TeleportPointServerSystem.TeleportPointServerSystem`对应着`Script_TeleportPointMod`文件夹中`TeleportPointServerSystem.py`文件的`TeleportPointServerSystem`类。这样,我们便成功注册了系统。 ## 制作传送点模组的主体 传送点模组的主体部分应该都位于服务单系统中,我们只需要考虑实现的逻辑即可。 我们可以允许玩家使用多种方式来触发传送。最简单的一种便是使用聊天栏触发。我们不妨就采取这种方式来制作模组。我们可以设置一些关键词,用于传送点的设置、传送和移除,比如`tppoint set`、`tppoint apply`、`tppoint remove`。而每个玩家为其使用一个变量来保存其设置的传送点坐标的数据。为了方便保存,我们可以使用一个字典来做到这一点。字典中的键为玩家的ID,而值为玩家保存的传送点坐标。当玩家执行传送时,我们主需要检索该玩家的ID,获取字典保存的值,然后将玩家的坐标设定为这个值即可。 我们依次来实现这个逻辑。 ### 监听玩家的聊天栏消息 我们通过查阅文档可知,服务端的`ServerChatEvent`事件可以做到响应玩家发送聊天栏信息,于是我们为其设置事件监听。 ```python def __init__(self, namespace, systemName): ServerSystem.__init__(self, namespace, systemName) self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ServerChatEvent", self, self.on_chat) ``` 同时加入一个`on_chat`方法,用于充当回调函数。 ```python def on_chat(self, event): pass ``` ### 记录传说点 下面我们按照我们刚才的设想开始写入基本逻辑。我们读取事件数据中的`playerId`和`message`。它们分别是发送消息的玩家ID和所发送的消息。我们使用字符串的`startswith`方法来判定消息是否以`tppoint set`开头。如果消息以`tppoint set`开头,那么就通过玩家ID创建一个`pos`引擎组件。`pos`引擎组件具备`GetFootPos`方法、`GetPos`方法和`SetPos`方法。我们可以使用`GetFootPos`方法来获取玩家脚步坐标并存入一个字典,我们不妨起名为`player_tp_cache`。`player_tp_cache`字典变量由于是需要在整个类中实现的,所以我们将其放在类的初始化方法里。 ```python class TeleportPointServerSystem(ServerSystem): def __init__(self, namespace, systemName): ServerSystem.__init__(self, namespace, systemName) self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ServerChatEvent", self, self.on_chat) # 用来暂时缓存玩家的传送点信息,在每次重新开启一次游戏后会被重置 self.player_tp_cache = {} def on_chat(self, event): # 获取玩家ID player_id = event['playerId'] # 获取聊天消息 message = event['message'] if message.startswith('tppoint set'): pos_comp = serverApi.GetEngineCompFactory().CreatePos(player_id) teleport_pos = pos_comp.GetFootPos() # 保存玩家的当前坐标至字典缓存中 self.player_tp_cache[player_id] = teleport_pos print '已设置玩家传送点' ``` ### 删除传送点 依据同样的原理,我们使用字典的`pop`方法来移除一个玩家的键值对。我们加入删除传送点的逻辑。 ```python def on_chat(self, event): # 获取玩家ID player_id = event['playerId'] # 获取聊天消息 message = event['message'] if message.startswith('tppoint set'): pos_comp = serverApi.GetEngineCompFactory().CreatePos(player_id) teleport_pos = pos_comp.GetFootPos() # 保存玩家的当前坐标至字典缓存中 self.player_tp_cache[player_id] = teleport_pos print '已设置玩家传送点' if message.startswith('tppoint remove'): # 判断该玩家是否存有数据 if player_id in self.player_tp_cache: # 弹出字典中的玩家数据 self.player_tp_cache.pop(player_id) print '已移除玩家传送点' else: print '该玩家没有设置传送点' ``` ### 传送至传送点 最后,我们使用`pos`引擎组件的`SetPos`方法来实现传送逻辑。 ```python def on_chat(self, event): # 获取玩家ID player_id = event['playerId'] # 获取聊天消息 message = event['message'] if message.startswith('tppoint set'): pos_comp = serverApi.GetEngineCompFactory().CreatePos(player_id) teleport_pos = pos_comp.GetFootPos() # 保存玩家的当前坐标至字典缓存中 self.player_tp_cache[player_id] = teleport_pos print '已设置玩家传送点' if message.startswith('tppoint remove'): # 判断该玩家是否存有数据 if player_id in self.player_tp_cache: # 弹出字典中的玩家数据 self.player_tp_cache.pop(player_id) print '已移除玩家传送点' else: print '该玩家没有设置传送点' if message.startswith('tppoint apply'): pos_comp = serverApi.GetEngineCompFactory().CreatePos(player_id) # 判断该玩家是否存有数据 if player_id in self.player_tp_cache: teleport_pos = self.player_tp_cache[player_id] # 传送玩家 pos_comp.SetPos(teleport_pos) print '已应用玩家传送点' else: print '该玩家没有设置传送点' ``` 这样,我们便完成了传送逻辑的编写。额外地,我们还可以通过`msg`组件来为玩家显示消息提示,展示设置、传送或移除成功的提示。我们可以修改如下: ```python def on_chat(self, event): # 获取玩家ID player_id = event['playerId'] # 获取聊天消息 message = event['message'] if message.startswith('tppoint set'): pos_comp = serverApi.GetEngineCompFactory().CreatePos(player_id) teleport_pos = pos_comp.GetFootPos() # 保存玩家的当前坐标至字典缓存中 self.player_tp_cache[player_id] = teleport_pos msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) msg_comp.NotifyOneMessage(player_id, "已设置传送点") print '已设置玩家传送点' if message.startswith('tppoint remove'): # 判断该玩家是否存有数据 if player_id in self.player_tp_cache: # 弹出字典中的玩家数据 self.player_tp_cache.pop(player_id) msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) msg_comp.NotifyOneMessage(player_id, "已移除传送点") print '已移除玩家传送点' else: msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) msg_comp.NotifyOneMessage(player_id, "请先设置传送点") print '该玩家没有设置传送点' if message.startswith('tppoint apply'): pos_comp = serverApi.GetEngineCompFactory().CreatePos(player_id) # 判断该玩家是否存有数据 if player_id in self.player_tp_cache: teleport_pos = self.player_tp_cache[player_id] # 传送玩家 pos_comp.SetPos(teleport_pos) msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) msg_comp.NotifyOneMessage(player_id, "已传送至传送点") print '已应用玩家传送点' else: msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) msg_comp.NotifyOneMessage(player_id, "请先设置传送点") print '该玩家没有设置传送点' ``` 我们可以进入游戏查看传送和消息提示情况。 ![](./images/11.7_in-game.png) 我们可以看到,传送和消息都和我们所设置的一样正常运作! 我们最后放出完整的代码供大家参考,`modMain.py`如下: ```python # -*- coding: utf-8 -*- from mod.common.mod import Mod import mod.server.extraServerApi as serverApi @Mod.Binding(name="TeleportPointMod", version="0.0.1") class TeleportPointMod(object): def __init__(self): pass @Mod.InitServer() def TeleportPointModServerInit(self): serverApi.RegisterSystem('TeleportPointMod', 'TeleportPointServerSystem', 'Script_TeleportPointMod.TeleportPointServerSystem.TeleportPointServerSystem') @Mod.DestroyServer() def TeleportPointModServerDestroy(self): pass @Mod.InitClient() def TeleportPointModClientInit(self): pass @Mod.DestroyClient() def TeleportPointModClientDestroy(self): pass ``` `TeleportPointServerSystem.py`如下: ```python # -*- coding: utf-8 -*- import mod.server.extraServerApi as serverApi from mod.server.system.serverSystem import ServerSystem ServerSystem = serverApi.GetServerSystemCls() class TeleportPointServerSystem(ServerSystem): def __init__(self, namespace, systemName): ServerSystem.__init__(self, namespace, systemName) self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ServerChatEvent", self, self.on_chat) # 用来暂时缓存玩家的传送点信息,在每次重新开启一次游戏后会被重置 self.player_tp_cache = {} def on_chat(self, event): # 获取玩家ID player_id = event['playerId'] # 获取聊天消息 message = event['message'] if message.startswith('tppoint set'): pos_comp = serverApi.GetEngineCompFactory().CreatePos(player_id) teleport_pos = pos_comp.GetFootPos() # 保存玩家的当前坐标至字典缓存中 self.player_tp_cache[player_id] = teleport_pos msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) msg_comp.NotifyOneMessage(player_id, "已设置传送点") print '已设置玩家传送点' if message.startswith('tppoint remove'): # 判断该玩家是否存有数据 if player_id in self.player_tp_cache: # 弹出字典中的玩家数据 self.player_tp_cache.pop(player_id) msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) msg_comp.NotifyOneMessage(player_id, "已移除传送点") print '已移除玩家传送点' else: msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) msg_comp.NotifyOneMessage(player_id, "请先设置传送点") print '该玩家没有设置传送点' if message.startswith('tppoint apply'): pos_comp = serverApi.GetEngineCompFactory().CreatePos(player_id) # 判断该玩家是否存有数据 if player_id in self.player_tp_cache: teleport_pos = self.player_tp_cache[player_id] # 传送玩家 pos_comp.SetPos(teleport_pos) msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) msg_comp.NotifyOneMessage(player_id, "已传送至传送点") print '已应用玩家传送点' else: msg_comp = serverApi.GetEngineCompFactory().CreateMsg(player_id) msg_comp.NotifyOneMessage(player_id, "请先设置传送点") print '该玩家没有设置传送点' def Destroy(self): pass ```