游戏开发论坛

 找回密码
 立即注册
搜索
查看: 7343|回复: 4

[分享] 基于触发机制的脚本系统

[复制链接]

1万

主题

1万

帖子

3万

积分

论坛元老

Rank: 8Rank: 8

积分
36572
发表于 2015-6-8 10:04:36 | 显示全部楼层 |阅读模式
01300000244525124565891348630.jpg

  文 / rooyeewu  

  1.问题

  绝大多数游戏项目都会开发新手引导,剧情之类的功能。这类功能有两个最大的特点:一是跳跃大,二是特殊化。一个完整的功能(如一段教学指引)需要在多个逻辑系统中跳转,并且具体形为的触发条件千差万别,需要很多特殊处理。这类功能实现对程序结构的挑战巨大,如果没有统一规划,很容易掉入Hard-Code的深渊。


  2.脚本系统

  解决这类问题的一个较好方法是抽象功能形为与触发事件,形成离散的形为(Action)集与事件(Event)集,然后通过脚本的形式组织成一个个完整的功能。为了以最干净的方式实现上述目的,我们只需要抽象两个概念,即上面提到的:行为(Action)与事件(Event)。一个行为只需要配置两个事件表达式:开始事件与结束事件即可明确指示其行为的执行时机。

1.jpg

  开始事件表达式必须存在,否则行为无法开始执行;结束事件表达式可为空,取决于当前行为是否有“自结束”方式。另外,为了实现行为动作的组织,系统需要实现一个非常重要的事件:

  EventActionEnd:ID[ 额外参数列表]

  该事件在指定ID的行为结束后发出,这样就可以利用此事件统一的组织触发脚本,一个触发脚本本质上就是由上述表格中若干具有逻辑相关性的行组成的集合。

  到此,我们就可以完整描述一个具体的行为动作的执行过程:

2.jpg

  3.应用

  上述对Action的实现方式采用常用的“三段式”:Begin(), Update(), End()。这样做的好处是Action的实现者可以实现三种形为方式:

  1) 实现单帧的“瞬时行为”;

  如瞬间给一个玩家加血可实现为一个瞬时行为。

  2) 实现跨帧的“持续行为”;

  如将怪物从A点移动到B点可实现为一个持续行为。

  3) 实现接受用户操作的“交互形为”。

  如剧情中要求用于作出选择可实现为一个交互形为。

  这些形为方式特别适合新手引导或者剧情之类的功能需求。

  另外事件的检查不局限于单一事件,而是事件表达式,这样方便组织逻辑结构。下面抽象一些常用的执行流程形式,再给出运用此脚本系统的表达方式:

  1) 顺序执行

3.jpg

4.jpg

  2) 分支执行

5.jpg

6.jpg

  上述分支很容易扩展成多分支,实现类似switch-case的执行结构。

  3) 循环执行

7.jpg

8.jpg

  4) 逻辑判断

9.jpg

10.jpg

11.jpg

  如何应用到新手引导与剧情

  可以理解为每段新手引导或剧情都是一个脚本,一个脚本就是上述表格的若干行。一个脚本在游戏系统中需要给定一个唯一标识,通过外围游戏逻辑规则按需加载指定脚本处于WAITING-RUNNING状态。这样一段引导逻辑就进入游戏中,但它需要一定的事件去触发。我们可以实现任意有意义的事件在游戏中的任何地方,如打开一个界面时抛出一个事件,关闭一个界面时抛出事件,点击一个按钮时抛出一个事件,抛出事件是随心所欲的,只要有需要就可以。

  典型地,抛出一个事件只需要在任意地方添加一行代码,如:

  noxssSystem.Instance.Trigger(EventClickUI, “uiName”);

  这样事件被异步的发送出去,抛出事件的执行代价可忽略不计。如果玩家在当前游戏进程中没有加载任何脚本,这个事件很快被丢弃;如果有脚本加载,此事件会尝试去触发脚本中行为的执行。这样可以做到新手引导对系统的最小化开销。

  4.小结

  通过统一的触发脚本系统,可以完成任意的逻辑脚本开发,包括新手引导,剧情,甚至客户端AI等;同时减少某些“跳跃大,特殊化”功能需求对程序结构的破坏,避免HardCode的产生;脚本形式统一简单,可以通过excel形式编写,也利于开发可视化编辑器。

via:gad

声明:游资网登载此文仅代表作者观点,不代表本站立场。

23

主题

3388

帖子

6440

积分

论坛元老

Rank: 8Rank: 8

积分
6440
发表于 2015-6-8 21:44:01 | 显示全部楼层
总结起来,这叫事件驱动。

而且我认为根本不需要把事件分成“开始事件”和“结束事件”——C丢来一个事件,脚本系统就按一定的分发机制把这个事件丢给对应的脚本去处理就OK了。脚本在处理过程中用定义好的各种操作接口与C通信。

23

主题

3388

帖子

6440

积分

论坛元老

Rank: 8Rank: 8

积分
6440
发表于 2015-6-8 21:46:10 | 显示全部楼层
至于“顺序”、“循环”、“分支”……这是任何一种基于有限状态机的编程语言都具备的功能,不需要你在设计脚本结构的时候专门去考虑。

23

主题

3388

帖子

6440

积分

论坛元老

Rank: 8Rank: 8

积分
6440
发表于 2015-6-8 21:51:43 | 显示全部楼层
本帖最后由 卡特铁角 于 2015-6-8 21:55 编辑

--投掷烟雾弹(突击手)
------------------------------------------global----------------------------------
local _G = _G
------------------------------------------require---------------------------------
local luacom = require("mapdata.lua.common.luacom")
------------------------------------------module----------------------------------
module("mapdata.lua.skill.skill31")

-----------------------------------------无效值约定-------------------------------
local INVALID_POSITION = -0x7fffffff
local INVALID_TIME = 0
local INVALID_ID = 0

-----------------------------------------施放失败类型-----------------------------
local CAST_FAIL_INTERRUPT = 1                                 --主动打断
local CAST_FAIL_DISTANCE        = 2                                    --距离
local CAST_FAIL_DIR                = 3                                 --方向
local CAST_FAIL_COST                = 4                             --所需开销不足
local CAST_FAIL_SRC_REL        = 5                                 --技能发起人关系匹配
local CAST_FAIL_TAR_NIL        = 6                                 --技能需要一个目标
local CAST_FAIL_AREA                = 7                             --不能在该区域释放此技能
local CAST_FAIL_SILENCE        = 8                                 --被沉默
local CAST_FAIL_CD                = 9                             --冷却
local CAST_FAIL_INVALID        = 10                         --无效技能
local CAST_FAIL_WRONG_WEAPON = 11                  --武器不匹配
local CAST_FAIL_OUT_OF_AMMO = 12                   --弹药耗尽
local CAST_FAIL_OUT_OF_ITEM = 13                   --缺少道具
local CAST_FAIL_OUT_OF_SP = 14                     --SP不足
local CAST_FAIL_OUT_OF_HP = 15                     --HP不足
local CAST_FAIL_OUT_OF_FOOD = 16                   --FOOD不足
local CAST_FAIL_WRONG_ENVIROMENT = 17              --不能在当前环境施放
local CAST_FAIL_WRONG_TARGET = 18                  --不能对该目标施放
local CAST_FAIL_OUT_OF_RANGE = 19                  --超出射程
local CAST_FAIL_ANOTHER_SKILL_CASTING = 20         --正在施放其它技能
local CAST_FAIL_NOT_COOL_DOWN = 21                 --技能还未冷却
local CAST_FAIL_OUT_OF_MONEY = 22                  --金钱不足
local CAST_FAIL_OUT_OF_FUEL = 23                   --燃料不足
local CAST_FAIL_OUT_OF_ENDURE = 24                 --机体耐久不足
local CAST_FAIL_CAN_NOT_MOVE = 25                  --不能在移动中施放
local CAST_FAIL_OUT_OF_ENERGY = 26                 --机体能量不足
local SKILL_WRONG_WEAPONTYPE = 29                  --武器类型不匹配
local CAST_FAIL_NO_SKILL = 30                      --没有(学会)该技能
local CAST_FAIL_CASTER_DEAD = 31                   --施放者已死亡

-------------------------------宏定义---------------------------------------------
local PI = 3.1415
local DOUBLE_PI = 6.283
local ANGLE_TO_RADIAN = 3.1415/180




-------------------------------code-----------------------------------------------

-----------------------------------事件处理函数数组------------------------------------
local eventProcessor = {}

eventProcessor[luacom.SKILL_BULLET_IMPACT_TARGET] = function(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8)
        local skillID = arg1
        local casterID = arg2
        local impact_x = arg4
        local impact_y = arg5
        local impact_z = arg6
        local itemID = arg8
        
        local npcTypeID
        if(itemID == 299) then
                npcTypeID = 1002
        end
        local mapID,roomID = luacom.GetCharacterMap(casterID)
        local npcInstanceID = luacom.CreateNpc(npcTypeID,mapID,roomID,impact_x,impact_y,impact_z)
        if(npcInstanceID) then
                luacom.SetNpcView(npcInstanceID,0,0)
                luacom.SetDelayedEvent(0,1,luacom.NPC_CHARACTER_ENTER_VIEW,npcInstanceID,0,0,0,0,0,0,0)
        end
end

eventProcessor[luacom.SKILL_EXPLODE_AFFECT_CHARACTER] = function(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8)
        
end
-----------------------------------------事件脚本固定结构------------------------------------------------------
function OnLoad()
        local scriptType = luacom.SKILL_SCRIPT
        local scriptID = 31
        local event_tab = {luacom.SKILL_BULLET_IMPACT_TARGET}
        luacom.RegisterEvent(event_tab,scriptType,scriptID,OnEvent)
end

function OnEvent(event,arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8)
        eventProcessor[event](arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8)
end

OnLoad()

这是好多年前的一段技能脚本。
OnEvent()是每个执行脚本的唯一入口
由脚本总入口中的一个事件分发函数统一调用,事件分发函数则是C调用脚本的唯一入口
luacom.RegisterEvent()用来对当前脚本需要响应的事件进行注册
当脚本总入口中的分发函数接收到某个事件,则根据事件注册表调用注册过这个事件的所有执行脚本

2

主题

5

帖子

38

积分

注册会员

Rank: 2

积分
38
发表于 2015-7-20 11:04:15 | 显示全部楼层
抽象功能形为与触发事件

形为 --》行为
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2025-6-14 19:27

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表