事件Event详解


简介

事件模型很多编程语言中都有广泛的应用,在饥荒中也一样。许多component在执行一些核心函数时都会顺带触发事件,比如combat在攻击到目标是就会触发onhitother事件。这时候如果监听该事件,就能在完成攻击的同时附加一些特殊效果,比如附带闪电伤害之类的。

一个事件模型有三个组成部分:被监听对象source(也称为事件源),事件event和监听对象listener。
首先,由监听对象注册监听回调函数(Callback),当事件源触发了事件后,监听对象会收到事件源的信息,然后决定如何对事件源进行处理,简要流程如下图所示。

事件触发流程

在饥荒中,触发事件的函数是PushEvent(event,data)
event是一个字符串类型,指明事件的名字。如果监听对象提前设置监听了这个事件,那么被监听对象触发这个事件时,监听对象就会执行提前设定好的处理函数。
data则是一张表,可以自由选择发生事件时的数据,记录在其中,然后传递给监听对象,也可以直接传递一个对象过去。

设置监听事件的函数是ListenForEvent(event,fn,source)。
event同上。fn是事件处理函数,固定有两个参数,第一个是被监听对象的引用,第二个是传递过来的数据表。source是被监听对象,此参数可以为空。如果为空,则系统会默认source是监听对象自身。也就是自己监听自己。这在饥荒中非常常见。需要特别注意的是,可以通过重复使用ListenForEvent设置多个监听器,即使其参数完全一致也可以。

有时候我们希望监听器只使用若干次,之后不再监听。那么,可以使用RemoveEventCallback(event,fn)来移除监听器。需要注意的是,这里的fn必须首先由ListenForEvent注册过,如果fn未注册,则会崩溃报错。这个函数同样可以重复使用,可以看作是ListenForEvent的逆函数。

以上的几个函数都是EntityScript类的成员函数。所以实际调用时,是这样写的:对象引用名:PushEvent(event,data) 或者对象引用名:ListenForEvent(event,fn, source) 或者 对象引用名:RemoveEventCallback(event,fn)

上面所提到的ListenForEvent是手动设置监听器,但其实还有另外一种特殊的,格式化了的监听器,EventHandler,参数只有event名和fn,没有source(因为已经默认了source就是监听对象自身了)。这个监听器通常是在StateGraph中被使用,用于转换prefab的State,这里不多提,等到以后讲StateGraph时再说。

应用场景

在知道了基本原理之后,我们更关心的还是应用Event的场景。
根据上面的事件触发流程图看到,我们可以通过设置事件触发点(PushEvent),为一个对象设置向外连接的通道。其它的任何对象,都可以通过设置一个监听器(ListenForEvent)来与触发事件的对象建立连接,获得触发事件的对象以及事件数据。这里的对象,虽然限定了只有prefab才能建立连接,但实际上所有的component和大部分widget之类的对象,构造函数就要求传递它所附着的prefab对象,构造函数里也会将传递过来的prefab对象,用一个成员变量接收(一般写作self.inst=inst)。所以要在component或widget中设定监听器也是非常方便的。

下面简单介绍一些常用的场景。

动作事件

游戏里内置了大量的事件触发点(即执行了PushEvent函数),这些触发点通常都写在某个组件的某个函数里。而这些函数通常会在人物执行某个动作(比如攻击,收获,砍树挖矿等)时执行,从而触发事件。这一类事件,我称之为动作事件,它们通常是写在组件的某个执行函数里,而这个执行函数又和动作绑定在一起,这就造成了做动作必定会触发相应事件。
动作事件在饥荒里存在的数量最多,应用最广泛,和prefab自身的关系也最为密切。通常来说,动作事件的事件源都是prefab自身。

典型的动作事件(事件源都是自身)举例:

| 事件| 描述 |data的字段|
| ------------- |-------------||-------------|
| onhitother | 在攻击命中到其它生物时触发 |target:攻击目标的引用,damage:造成的伤害,stimuli:外部刺激,这个参数主要是晨星用的,redirected 重定向目标|
| picksomething| 收获干草、树枝、 、花等等 | object:要收获的目标(也就是种着的草,小树苗或浆果从) loot:收获到的东西 |

需要注意的是,有时候一个动作可能会对应多个事件,也可能没有触发事件。也就是说,动作触发事件不是必须的,这得看组件里的相关函数是怎么写的。如果要对某些不触发事件的动作进行特殊处理,建议看我的另外一篇专门讲动作的文章。

状态事件

状态事件是指prefab有某些状态变化的时候触发的事件,比如,饥饿度变化的时候就会触发hungerdelta事件,这个事件触发得非常频繁,以至于我们可以利用它来做实时监测人物的全部属性。
状态事件有多种用途,像上面说的hungerdelta由于其触发频繁,甚至可以当成一种监测手段,这里说一个比较重要的用途:指示UI变化。比如说,饥饿度指示器可以监听人物的hungerdelta事件,每次监听到该事件后就对指示器的动画进行调整。虽然实际上饥饿度指示器用的是别的方法来达成目的,但这个方法仍然有效。

典型的状态事件

| 事件| 描述 |data|
| ------------- |-------------||-------------|
| hungerdelta| 在饥饿度有变化时触发|oldpercent:变化前的饥饿度百分比, newpercent:新的饥饿度百分比, overtime:几乎用不到的参数,实际意义不明|
| itemget| container组件的触发事件之一。当背包、箱子获得获得物品时,会触发该事件。相应的容器UI会监听到此事件,让相应的格子里显示出东西或者数字发生变化。 | slot:指出是哪个格子获得物品,item:获得什么物品, src_pos:意义不明|

世界变化事件

这类事件也算是状态事件的一种,只是限定了事件源为World。由于它们比较特殊,用得比较广泛,就单独拿出来说。
用ListenForEvent设置监听器来监听世界状态,主要是用于单机版。
在联机版里,对TheWorld这个特殊的对象,官方提供了一个新的更好用的监听器:WatchWorldState。这个监听器的用法和ListenForEvent很相似,但监听的不是事件,而是TheWorld.state的某个状态量(比如cycle-过了多少天,isday-是不是白天),只要这个状态量一发生变化,就会立刻通知监听器执行预先设置的处理函数,传递的数据也不是data,而是这个状态量变化后的值。

可以利用世界变化做很多花样,不过这里限于时间和精力的关系,不能多写,简单地说一下如何找到我们想要的事件。

对单机版,查找事件比较麻烦。但对联机版,可以查看worldstate组件,它的data表下的所有元素都是可以监听的。也可以从这里找到一些世界变化事件的监听器,根据它们监听的事件来对应单机中的相应事件。

应用示例

利用游戏已有的事件

第一步:确定触发场景,然后寻找适合场景的触发事件(PushEvent),确定事件的传入数据。使用Notepad++搜索整个script文件夹,找到具体的事件名。这种查找效率比较低,不过,事件大多是在component中触发的,可以先从一个prefab找到它的component,进而找到事件。
第二步:设置事件监听器,编写代码。

下面来看几个实例

攻击附带冰冻效果

前面说过,在打到攻击目标时,攻击者会触发onhitother事件,可以利用这个来做到。
这是一个典型的监听自身的监听器,不需要设置ListenForEvent的第三个参数。

将以下代码写入人物的fn或master_init中,打开游戏进行测试。

inst:ListenForEvent("onhitother",function(inst,data)
    if data and data.target then
        local target = data.target
        if target.components and target.components.freezable then --只有有freezable组件的prefab才会被冰冻
            local coldness = 12 --(冰冻强度,每个可冰冻的prefab都有冰冻抗性,只有积累的强度超过抗性了才会被冰冻)
            local freezetime = 10--(冰冻时间)
            target.components.freezable:AddColdness(coldness, freezetime)
        end

    end
end)

更进一步,只有下按键后才会触发冰霜攻击的效果,而且第一次攻击结束后就失去冰霜攻击,除非重新按下按键。

将以下代码写入人物的fn或master_init中,打开游戏进行测试。

local freezeAttack = function(inst,data)
    if data and data.target then
        local target = data.target
        if target.components and target.components.freezable then --只有有freezable组件的prefab才会被冰冻
            local coldness = 12 --(冰冻强度,每个可冰冻的prefab都有冰冻抗性,只有积累的强度超过抗性了才会被冰冻)
            local freezetime = 10--(冰冻时间)
            target.components.freezable:AddColdness(coldness, freezetime)
        end
        inst:RemoveEventCallback("onhitother",freezeAttack)--执行过一次之后,就移除监听器。
    end
end

TheInput:AddKeyDownHandler(KEY_R,function()
    inst:ListenForEvent("onhitother",freezeAttack)--每次按下R,都会设置一个监听器。
end)

人物下雨时攻击力加倍

这是一个监听其它对象,但是改变自身状态的事件。

将以下代码写入人物的fn或master_init中,打开游戏进行测试。

单机版-稍后再写。

联机版

local function OnIsRaining(inst, israining)
    if israining then --如果在下雨
        inst.components.combat.damagemultiplier =2
    else
        inst.components.combat.damagemultiplier =1
    end
end
inst:WatchWorldState("israining", OnIsRaining)

自定义事件

一般来说,自定义事件常用于自定义UI。
比如说,你设置了一个新组件,这个组件的主要属性是一个新的变量:mana。然后你添加了一个widget用于指示这个Mana的剩余量。现在的问题是,怎样在每次mana有变化的时候,把mana的数据传递给widget。一种做法是启动它的自动更新功能,然后在OnUpdate函数里接收数据并修改widget中的显示数据,这就是饥饿度指示器所用的方法。但像mana这种数据,实际上变化得并不像饥饿度那么频繁,所以可以采用事件监听的方式来节约系统资源。为mana组件设置一个控制mana数据变化的DoDelta函数,要修改mana只能通过这个函数来完成。这样,在这个函数中添加一个manadelta事件触发点,就可以在每次mana发生变化的时候,通知UI更新mana的数据了。

示例如下:
此处只做简单示范了如何传递值,没有调整UI的显示。稍后会在我的网盘内上传相应的实例以供参考。

component:mana

Mana = class(function(self,inst)
    self.inst=inst
    self.maxMana = 100--最大值
    self.current = self.maxMana
end)
function Mana:DoDelta(delta)
    local newMana = self.current+delta  
    local newPercent = newMana/self.maxMana
    self.current = newMana 
    self.inst:PushEvent(manadelta,{current = newMana,npercent = newPercent})
end
return Mana

widget:manaIndicator

local Widget = require "widgets/widget"


ManaIndicator = Class(Widget, function(self, owner)
    Widget._ctor(self, "ManaIndicator")
    self.owner = owner
    
    self.mana = 100
    self.percent = 1
    
    self.owner:ListenForEvent("manadelta",function(inst,data)
        self:onManaDelta(inst,data)
    end)
end)

function ManaIndicator:onManaDelta(inst,data)
    self.mana = data.current 
    self.percent = npercent
end
return ManaIndicator 
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • https://nodejs.org/api/documentation.html 工具模块 Assert 测试 ...
    KeKeMars阅读 6,221评论 0 6
  • 1.JQuery 基础 改变web开发人员创造搞交互性界面的方式。设计者无需花费时间纠缠JS复杂的高级特性。 1....
    LaBaby_阅读 1,285评论 0 2
  • 1.JQuery 基础 改变web开发人员创造搞交互性界面的方式。设计者无需花费时间纠缠JS复杂的高级特性。 1....
    LaBaby_阅读 1,101评论 0 1
  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,621评论 2 17
  • 心里痒痒想要写点东西,下载了简书。 每每看到谁又辞职了,专职写文字了,就痒痒地羡慕,但是想不明白,只是写文字怎么能...
    沧海一粟15阅读 279评论 2 3