# 了解事件驱动

接下来,我们以TutorialMod演示模组的服务端系统的定义部分来了解什么是事件驱动Event-driven)系统。我们一起来查看演示模组中的tutorialServerSystem.py文件。

# -*- coding: utf-8 -*-

# 获取引擎服务端API的模块
import mod.server.extraServerApi as serverApi
# 获取引擎服务端System的基类,System都要继承于ServerSystem来调用相关函数
ServerSystem = serverApi.GetServerSystemCls()
# 获取组件工厂,用来创建组件
compFactory = serverApi.GetEngineCompFactory()

# 在modMain中注册的Server System类
class TutorialServerSystem(ServerSystem):

    # ServerSystem的初始化函数
    def __init__(self, namespace, systemName):
        # 首先调用父类的初始化函数
        super(TutorialServerSystem, self).__init__(namespace, systemName)
        print "===== TutorialServerSystem init ====="
        # 初始时调用监听函数监听事件
        self.ListenEvent()

    # 监听函数,用于定义和监听函数。函数名称除了强调的其他都是自取的,这个函数也是。
    def ListenEvent(self):
        # 在自定义的ServerSystem中监听引擎的事件ServerChatEvent,回调函数为OnServerChat
        self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ServerChatEvent", self, self.OnServerChat)
        # 监听引擎的事件ServerBlockUseEvent, 回调函数为 OnServerBlockUseEvent
        self.ListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ServerBlockUseEvent", self, self.OnServerBlockUseEvent)

    # 反监听函数,用于反监听事件,在代码中有创建注册就对应了销毁反注册是一个好的编程习惯,不要依赖引擎来做这些事。
    def UnListenEvent(self):
        self.UnListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ServerChatEvent", self, self.OnServerChat)
        self.UnListenForEvent(serverApi.GetEngineNamespace(), serverApi.GetEngineSystemName(), "ServerBlockUseEvent", self, self.OnServerBlockUseEvent)

    # 监听ServerBlockUseEvent的回调函数
    def OnServerBlockUseEvent(self, args):
        # 这里的sdkteam_test:block1替换成你自己的自定义方块的命名空间与方块名
        if args["blockName"] == "sdkteam_test:block1":
            # 调用给予物品的接口,与OnServerChat中相似
            playerId = args["playerId"]
            comp = compFactory.CreateItem(playerId)
            # 这里填钻石剑的物品名
            comp.SpawnItemToPlayerInv({"itemName": "minecraft:diamond_sword", "count": 1, 'auxValue': 0}, args["playerId"])

    # 监听ServerChatEvent的回调函数
    def OnServerChat(self, args):
        print "==== OnServerChat ==== ", args
        # 生成掉落物品
        # 当我们输入的信息等于右边这个值时,创建相应的物品
        # 创建Component,用来完成特定的功能,这里是为了创建Item物品
        playerId = args["playerId"]
        comp = compFactory.CreateItem(playerId)
        if args["message"] == "钻石剑":                      
            # 调用SpawnItemToPlayerInv接口生成物品到玩家背包,参数参考《MODSDK文档》
            comp.SpawnItemToPlayerInv({"itemName":"minecraft:diamond_sword", "count":1, 'auxValue': 0}, playerId)
        elif args["message"] == "钻石镐":
            comp.SpawnItemToPlayerInv({"itemName":"minecraft:diamond_pickaxe", "count":1, 'auxValue': 0}, playerId)
        elif args["message"] == "钻石头盔":
            comp.SpawnItemToPlayerInv({"itemName":"minecraft:diamond_helmet", "count":1, 'auxValue': 0}, playerId)
        elif args["message"] == "钻石胸甲":
            comp.SpawnItemToPlayerInv({"itemName":"minecraft:diamond_chestplate", "count":1, 'auxValue': 0}, playerId)
        elif args["message"] == "钻石护腿":
            comp.SpawnItemToPlayerInv({"itemName":"minecraft:diamond_leggings", "count":1, 'auxValue': 0}, playerId)
        elif args["message"] == "钻石靴子":
            comp.SpawnItemToPlayerInv({"itemName":"minecraft:diamond_boots", "count":1, 'auxValue': 0}, playerId)
        else:
            print "==== Sorry man ===="

    # 函数名为Destroy才会被调用,在这个System被引擎回收的时候会调这个函数来销毁一些内容
    def Destroy(self):
        print "===== TutorialServerSystem Destroy ====="
        # 调用上面的反监听函数来销毁
        self.UnListenEvent()

# 事件驱动

我们可以看到,为了定义我们的系统的类,我们从额外APIExtra API,服务端为mod.server.extraServerApi)下的GetServerSystemCls方法得到了继承自系统基类mod.common.system.baseSystem的服务端系统类mod.server.system.serverSystem的引用。我们的TutorialServerSystem类的定义将继承自该服务端系统类。

接着,在TutorialServerSystem类的__init__方法中,我们可以看到在调用完毕超类的初始化方法后紧接着便调用了自定义方法ListenEvent。这是用来注册各种事件监听的方法。

ListenEvent方法中,我们通过调用系统(self)的ListenForEvent方法来注册一个事件监听。前两个参数分别是该事件监听要监听的系统的命名空间和系统的名称。如果我们要监听引擎默认给出的模组API原生事件,我们需要使用额外API中的GetEngineNamespaceGetEngineSystemName方法获取引擎系统Engine System)的命名空间和名称。引擎系统也是一个系统,是基岩引擎中模组API原生的系统。如果要监听自己的或者其他模组的自定义系统广播的事件,则需要正确填写对应的命名空间和名称,使两个参数和modMain.py中注册系统时提供的命名空间和系统名相一致。

事件Event)是由基岩引擎发出或用户自定义的代码发出的,在游戏发生某种特定的逻辑时触发的一种标识。对一个事件进行监听Listen)指的是将一条回调函数传入一个事件的响应代码中,在该事件被触发时同时执行该回调函数。这条回调函数往往被称为事件的响应Response)。而在事件触发时将这一消息发送给指定的客户端或服务端的行为,根据发送的对象和接收端的数目不同分别被称作广播Broadcast)或通知Notify)。

ListenEvent方法的第三个参数便是事件的标识符,告诉引擎我将“哪个”事件的监听设置到了该系统上。第四个参数是该监听绑定到的系统的实例。因为我们的事件监听响应(即回调函数)是在该系统的示例中定义的,所以我们填写该系统自身(self)。在第五个参数中写入该监听的响应函数。这个函数会在事件触发时异步触发。

这一系列操作便是一个简单的事件驱动逻辑。通过注册一个事件的监听将一个回调函数作为相应绑定到事件上。当事件被触发并广播或通知到该端时便异步执行该回调函数,从而运行相关的逻辑。

# 组件接口的使用

我们从额外API下的GetEngineCompFactory方法可以获得服务端引擎组件工厂Engine Component Factory)的类mod.server.component.engineCompFactoryServer的引用。引擎组件工厂是用来创建引擎组件Engine Component)的类。引擎组件与常规的ECS系统中的组件不同,他们不仅是用来存储数据的,更是用来执行逻辑的。每个引擎组件下都具有各种各样的方法,每种方法都可以用来执行一个不同的逻辑或完成一个不同的操作。比如引擎组件game(服务端为mod.server.component.gameCompServer的实例)下的KillEntity方法便可以杀死一个指定ID的实体。只需使用如下实例代码便可做到杀死一个实体:

import mod.server.extraServerApi as serverApi
comp = serverApi.GetEngineCompFactory().CreateGame(levelId)
comp.KillEntity(entityId)

上面的comp变量便是mod.server.component.gameCompServer引擎组件的一个实例。

通过引擎组件可以做到各种逻辑,配合事件驱动系统便可以”精准打击“到游戏中各种逻辑发生点,并插入自己的逻辑。这样的运作模式便是模组API如何修改游戏的基本方法!