05. 技能解析:触发技

在上回中,我们创造了第一个技能"激姿",就是一个改造版的英姿,它的作用是摸牌阶段摸牌时候让摸牌数+4。

像这种在游戏特定时机被触发的技能,就称为触发技,它们通常都是形如"当xxx时,你可以xxx"的技能。

下面来介绍触发技。首先介绍触发技的几个基本函数,再说明触发技的执行流程,最后说明怎么创建触发技。这篇文章大多为比较无聊的概念解析,也没有实操,建议自己对着已经写好的触发技实操。

触发技的基本函数

触发技中涉及这些函数:

  • can_trigger :技能能否被触发?
  • on_trigger :技能是如何执行的?
  • on_cost :技能的执行消耗是什么?
  • on_use :技能正式发动后,执行什么代码?

所有这些函数的函数原型全都是一样的:

---@param self TriggerSkill
---@param event Event
---@param target ServerPlayer
---@param player ServerPlayer
---@param data any
function(self, event, target, player, data)
end

这个函数原型还是稍微有些难以理解,得结合触发技的具体执行流程来看。

触发技的执行流程

第一步 触发一个时机

触发技若是想要被发动,那么肯定就先要有时机被触发了。而用来触发事件的函数就是如下这位:

---@param event Event
---@param target ServerPlayer
---@param data any
function GameLogic:trigger(event, target, data) end

直接调查这个函数的代码就能知道触发技执行的所有细节了。但这个函数并没有那么好懂,故在此进行说明。

首先,从这个函数可以看出,某一个触发时机一共有三要素:

  • event :具体是哪个触发时机。
  • target :这个触发时机涉及的玩家,这名玩家在后面会称为"时机的承担者"。
  • data :可以是任何值,视具体时机而定。

首先,event是这个时机具体是什么,比如"受到伤害后"( fk.Damaged);target则是时机的承担者,比如"受到伤害后"这个时机,承担者就是此次伤害的目标;data就完完全全是根据时机而定了。

想要知道某个时机具体对应着哪个target和data,最直接的办法就是直接从源码中找到trigger函数调用的点了,这样一下子就知道这个时机的相关数据了。不过呢,文档后面也是会一一列出的,毕竟有些时机的data还是多少复杂了点。

第一步(续) 假设出一个例子情景

在开始接下来的解说之前,还是想象一下有这么一桌军五吧:

     郭嘉      司马懿

*关羽 -------杀------>  郭嘉 -1

        周瑜(一号位)

如图所示,关羽杀郭嘉(二号位),郭嘉掉血,此时执行到了伤害流程的"受到伤害后"时机。

假设当前回合的角色是关羽。

假设郭嘉拥有在这个时机可以发动的技能"遗计",其代码如下:

local easy_yiji = fk.CreateTriggerSkill{
  name = "easy_yiji",
  events = {fk.Damaged},
  on_use = function(self, event, target, player, data)
    player:drawCards(2)
  end,
}

为了简化说明,这是是一段简化版的遗计代码。其作用是受到伤害后,可以摸两张牌。

前面说到一个触发技得有4种函数,而这里却只有个 on_use
啊。这是因为其他三个函数此处可以取默认值,所以实际写Lua的时候省略掉了。为了便于说明,现在将这4个函数补全(包括默认情况):

local easy_yiji = fk.CreateTriggerSkill{
  name = "easy_yiji",
  events = {fk.Damaged},
  can_trigger = function(self, event, target, player, data)
    return target == player and target:hasSkill(self.name)
  end,
  on_trigger = function(self, event, target, player, data)
    return self:doCost(event, target, player, data)
  end,
  on_cost = function(self, event, target, player, data)
    return player.room:askForSkillInvoke(player, self.name)
  end,
  on_use = function(self, event, target, player, data)
    player:drawCards(2)
  end,
}

这里假设出来的情景是"受到伤害后"时机,写成代码就是

logic:trigger(fk.Damaged, guojia, data)

这里不关心data。第二个参数guojia表示受到伤害后的那个郭嘉。注意场上有两个郭嘉,这是为了后面详细解释而安排的。

第二步 遍历场上玩家

现在的时机是fk.Damaged,刚好遗计的时机也是fk.Damaged,所以遗计就能在这个时机发动了。隔壁司马懿也有个反馈能在这个时机发动。

所以现在能够在该时机发动的技能有:遗计、反馈。

假设反馈的代码和上文的遗计一模一样,只是技能名不同罢了。

确定了可能可以发动的技能后,Fk就会从当前回合角色开始,对所有角色进行遍历。每一趟遍历的步骤如下:

  1. 把当前正在遍历到的玩家称为player。
  2. 执行 can_trigger(self, event, target, player, data)
  3. 如果第二步的执行返回了true,就执行 on_trigger

事已至此,触发技函数中的参数也基本明朗了:

  • self :这个技能本身。
  • event :当前的触发时机。
  • target :时机的承担者。
  • player :当前被遍历到的玩家。
  • datalogic:trigger 函数中传入的那个额外的data参数。

下面进行针对前面那桌军五,模拟一下这么个遍历流程。

可能可以发动的技能: 遗计,反馈
当前回合角色:关羽
当前时机:受到伤害后
时机的承担者(target):郭嘉 - 二号位
当前的data:没人在意data

对 关羽 进行遍历,令 player 为 关羽
  -> 遗计的can_trigger:失败,target ~= player
  -> 反馈的can_trigger:失败,target ~= player

对 周瑜 进行遍历,令 player 为 周瑜
  -> 遗计的can_trigger:失败,target ~= player
  -> 反馈的can_trigger:失败,target ~= player

对 郭嘉二号位 进行遍历,令 player 为 郭嘉二号位
  -> 遗计的can_trigger:通过,target == player and player:hasSkill(self.name)
    -> 遗计的 on_trigger 开始执行
    -> 执行 TriggerSkill:doCost
  -> 反馈的can_trigger:失败,target == player,但是player:hasSkill(反馈)为false,郭嘉不会反馈

对 司马懿 进行遍历,令 player 为 司马懿
  -> 遗计的can_trigger:失败,target ~= player
  -> 反馈的can_trigger:失败,target ~= player,虽然player拥有技能反馈

对 郭嘉四号位 进行遍历,令 player 为 郭嘉四号位
  -> 遗计的can_trigger:失败,target ~= player
  -> 反馈的can_trigger:失败,target ~= player

遍历结束了,本次触发时机也随之结束了。

从上面的实机演练中我们差不多能明白 can_triggeron_trigger
的执行流程。

在实际的执行中,其实是先都执行 can_trigger,然后将所有通过的技能暂存在表中,玩家可以从这里面选出自己想要先发动的技能,然后再去执行那个技能的on_trigger

第三步 询问消耗执行,以及正式发动技能

on_coston_use,则是在on_trigger中调用doCost函数时候调用的。doCost的内容如下:

-- do cost and skill effect.
-- DO NOT modify this function
function TriggerSkill:doCost(event, target, player, data)
  local ret = self:cost(event, target, player, data)
  if ret then
    return player.room:useSkill(player, self, function()
      return self:use(event, target, player, data)
    end)
  end
end

在这段代码中,首先执行一下cost函数(也就是这里聊的on_use),如果返回true,那么调用useSkill函数正式发动技能。useSkill函数先播放技能发动的特效、增加技能发动次数,再去调用传入的第三个函数(这里就是on_use了)。

这也就是说,on_cost函数掌握的是技能是否确实要发动,用户得在这里做出自己的选择。如果用户作出了肯定的答复,那么on_cost就返回true,这之后技能发动次数的历史记录便加一,然后开始真正执行技能的效果。

创建触发技的办法

要创建一个触发技,我们使用 fk.CreateTriggerSkill
函数。该函数接收一个表作为参数,表中各种键值的含义如下:

  • name :技能名。别和其他技能重名了。
  • frequency :技能的发动频率,可能是锁定技。
  • anim_type :技能的动画类型。上一篇好像已经聊过了。
  • mute :技能是否静默。

静默的技能不会播放配音、动画、发log,如果你想播放配音,就得自己手动做这些工作。
有些需要根据情况手动播放相应配音的技能,比如自书、英魂等,就得先设为静默,然后自己去技能发动的环节添加这些跟播放特效有关的代码。

上面这4项其实是对所有技能都通用的。下面是一些触发技专用的:

  • global :是否是全局技能。全局技能必定会参与到遍历中。
  • events :一个数组,保存着可能可以触发这个技能的所有时机。
  • can_trigger :触发该技能的条件。
  • on_trigger :技能触发的内容。这个函数一般是自定义如何去询问发动、发动几次的。总之自定义的话,记得在里面调用doCost进行实际的询问和生效就行了。
  • on_cost :技能生效前要对玩家进行询问的内容,或者说是"消耗"。
  • on_use :技能生效环节。

除非万不得已,不要把技能的global设为true!global技能在任何情况下都会被纳入游戏的处理范围,随着global的增多,遍历的技能也会变多,这会使游戏的性能下降!

有些时候我们不希望增加技能发动的次数,只想执行一些代码而已,比如说清理掉某些不可见标记等等。为了实现这个效果,触发技中还有一种称为"refresh"的行为(相对于发动技能的"use"),创建触发技的时候可以用这些来指定:

  • refresh_events :可能触发refresh的所有时机。
  • can_refresh :类似 can_trigger ,只不过是针对refresh的。注意这个函数没有默认值。
  • on_refresh :类似 on_trigger ,但是是针对refresh。

refresh和实际发动技能也差不多,一样的遍历,判断can_refresh,执行on_refresh。在实际trigger中,是先执行refresh,再执行use

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

推荐阅读更多精彩内容