Volcano场景任务设计手册

 

一. 相关基础知识

演示程序的任务信息定义表源文件为"demo1\bin\client\data\vdb\csv\missions.csv",请用excel打开此文件.我们看到,里面已经定义了9个任务,分别对应volcano系统目前所支持的9个不同的任务类型.

该表的各个字段含义请查看表头标题,各个任务所对应的类型请见"任务的描述文本"字段内容.


vdb数据表

该csv文件最终将被编译为游戏内使用的vdb数据表文件,vdb在csv文件中的定义格式如下:

    1. 所有以";"字符开始的列及其后所有列都将被认为是注释列;

    2. 整数型字段支持十六进制格式数值,该格式数值需要以"0x"开头,譬如"0xFFF";

    3. 表格第一列必须为整数型,用作提供该表格行的行ID值.该整数值不可重复,必须大于0,ID值之间的间隔不能太大;

    4. 表格任何一列文本(包括第一列)如果以';'开头,则说明从本列开始到行尾均为注释,其中的数据将被忽略.

    5. 整数型字段内容可以设置为自定义函数,目前所支持的自定义函数如下:

1. ITEM_ID (物品数量, 物品类型, 物品ID值[, band])
    物品类型: equ:武器; misc:杂类
    物品ID值: 对应武器装备信息表("equipments.csv")或杂类物品信息表("GameItemMisc.csv")的行ID
    band: 物品是否被绑定
2. ITEM_ID (物品类型, 物品ID值)
    此时"物品数量"参数被固定为1
 

    6.第一行有效数据定义各列的数据类型,为以下值之一(大小写无关):

"INT": 整数型

"ENUM0 xxx": 从0开始的枚举整数型,必须为"ENUM0"关键字加上以空白分隔的许可枚举值列表. 譬如数据类型"ENUM0 male female"定义了一个枚举列数据类型,其中的所有列数据只能为"male"或者"female"之一.实际存储列数据时,每个列数据值将被替换为对应的枚举值索引位置,譬如上面的"male"和"female"将被分别替换为整数值0和1.

"ENUM1 xxx": 从1开始的枚举整数型,必须为"ENUM1"关键字加上以空白分隔的许可枚举值列表. 譬如数据类型"ENUM1 male female"定义了一个枚举列数据类型,其中的所有列数据只能为"male"或者"female"之一.实际存储列数据时,每个列数据值将被替换为对应的枚举值索引位置+1,譬如上面的"male"和"female"将被分别替换为整数值1和2.

"MASK xxx": 从Bit0位开始的组合整数型,必须为"MASK"关键字加上以空白分隔的许可值列表. 譬如数据类型"MASK creature human"定义了一个MASK列数据类型,其中的所有列数据只能为"creature"和/或"human"的组合项.实际存储列数据时,每个列数据值将被替换为对应的MASK,譬如如果某列值为"creature",则该列值实际上为整数值1(第1个bit被置位),为"human",则为整数值2(第2个bit被置位),为"creature human",则为整数值3(第1和第2个bit均被置位).

"BOOL": 逻辑型. 列数据值只能为"1"/"TRUE"(代表真)或者"0"/"FALSE"(代表假)

"FLOAT": 小数型. 列数据值不支持科学计数法.

"TEXT": 文本型

"ITEXT": 忽略英文字母大小写的文本型.此类型的文本数据在存储时去除重复文本及建立索引时将忽略字母大小写.

以上除开BOOL的任何数据类型,如果具有前缀"INDEX"/"UQ_INDEX"(譬如"INDEX INT","UQ_INDEX ENUM1"等),则说明将为该数据列内容建立索引(INDEX)或非重复索引(UQ_INDEX,不允许存在重复的列数据).

以上任何数据类型,如果具有后缀"_NULL"(譬如"INT_NULL","ENUM1_NULL"等),则说明允许该数据列内容为空,此时数值型列将被填入0,逻辑型将被填入假,文本型将被填入空文本,否则不允许数据列内容为空.


定义游戏处理类:

游戏处理类由"属性","数据成员","预定义方法插槽"三部分组成,演示程序的游戏类定义源文件为"demo1\bin\client\data\def\GameClasses.cfg_w",其文件头部说明了游戏处理类的具体定义方法:

; ------------------------------------------------------------ 通用处理类

; 通用处理类声明格式:
; CommonClass
; {
; --------------------------------------------- 基本属性
;
; id = 类ID ; 不可省略,不可重复.
; name = 类名称 ; 不可省略,不可重复.
; [BaseClass = "xxx"] ; 可以指定一个基础类名(必须与本类类型一致),以便在其基础上进行更改.
; [private = TRUE/FALSE] ; 是否不对外公开. 如果本类仅用作其它类的基础类,可以定义此属性. 如果被省略则为假.
;
; --------------------------------------------- 数据成员
;
; ; 如果有数据成员,顺序排列所有数据成员定义信息.
; ; 注意: 如果希望新增数据成员不会影响到已设计好的场景中该类的实例数据,请始终在尾部加入新数据成员,且不要删除已有的数据成员.
; element
; {
; id = 成员ID ; 不可省略,不可重复.
; name = 成员名称 ; 不可省略,不可重复.
; type = 成员类型 ; 不可省略
; ; 可供选择的参数类型:
; ; int: 整数
; ; float: 小数
; ; bool: 逻辑型
; ; text: UTF8文本
; ; IntArray: 整数数组
; ; FloatArray: 小数数组
; ; RegionPath: 区域路径名称文本
; ; LinePath: 线型路径名称文本
; ; ObjectGroup: 对象组
; [private = TRUE/FALSE] ; 定义本成员在设计时是否不对外公开,如果被省略,默认值为假.
; [NotEmpty = TRUE/FALSE] ; 是否不允许成员值为空,如果被省略,则默认为假.针对以下成员类型有效:
; ; text: 不能为空文本
; ; IntArray, FloatArray: 不能为空数组
; ; RegionPath, LinePath: 必须指定有效路径名称
; [value = xxx] ; 提供成员初始值
; }
;
; --------------------------------------------- 预定义方法插槽
; 注: 1. 所有方法插槽均可以省略不设置,此时为默认值空;
; 2. 在方法插槽内置入实际处理方法时,可同时提供该处理方法的各参数初始值,如:
; OnTimer = "MyOnTimer"
; {
; Minute = 10 ; 每隔10分钟触发一次时钟事件
; }
;
; OnStartup = xxx ; 本对象被启动后自动调用,用作初始化本对象. 返回1表示初始化成功,返回0表示初始化失败.
; OnStop = xxx ; 本对象被停止后自动调用,用作清理本对象
; OnTimer = xxx ; 周期性时钟事件(注意处理周期的单位是分钟)
; OnTiming = xxx ; 定时时钟事件
; OnCurrentStageChanged = xxx ; 当前处理阶段被改变
; }

; ------------------------------------------------------------ 场景对象处理类

; 场景对象处理类声明格式:
; SceneObjectClass
; {
; 基本属性,数据成员定义方法,方法插槽设置方法均同CommonClass.
;
; --------------------------------------------- 特有属性
;
; [privilege = TRUE/FALSE] ; 定义是否为特权处理类,如果被省略则为假.
; [OnlyCreature = TRUE/FALSE] ; 是否只有动物对象才能应用本类,如果被省略则为假. 注意:如果下面设置了任意状态管理类,则本属性被强制为真.
;
; --------------------------------------------- 预定义方法插槽
;
; 对象各种状态管理类的实例创建方法插槽(注意: 一旦置入了此类处理方法,则OnlyCreature属性被自动强制为真):
; mgrIdle = xxx ; 空闲状态
; mgrAction = xxx ; 行动状态
; mgrFighting = xxx ; 战斗状态
;
; 其他处理方法插槽:
; OnDead = xxx ; NPC对象已经死亡
; OnFightingDamaged = xxx ; NPC对象在战斗中受伤
; OnFightingBeforeSkillCast = xxx ; NPC对象在战斗中即将施放指定ID技能. 返回1表示允许施放,返回0表示不允许施放.
; OnFightingSkillCast = xxx ; NPC对象在战斗中施放了指定ID技能
; OnEnterFighting = xxx ; NPC对象已经进入战斗状态
; OnLeaveFighting = xxx ; NPC对象已经离开战斗状态
; OnToolUsed = xxx ; 当本对象为工具类对象时被使用
; OnAnswerQuestion = xxx ; 玩家人物回答了本对象所询问的问题
; }

; ------------------------------------------------------------ 任务处理类

; 任务处理类声明格式:
; MissionClass
; {
; 基本属性,数据成员定义方法,方法插槽设置方法均同CommonClass.
;
; --------------------------------------------- 预定义方法插槽
;
; OnAccepted = xxx ; 任务被玩家承接后自动调用,用作初始化本对象. 返回真表示初始化成功,返回假表示初始化失败.
; OnCanceled = xxx ; 任务被玩家放弃后自动调用
; OnCommitted = xxx ; 任务被玩家完成后提交时自动调用. 返回真表示提交成功,返回假表示提交失败.
; OnTimer = xxx ; 周期性时钟事件(注意处理周期的单位是秒)
; OnCurrentStageChanged = xxx ; 当前处理阶段被改变
;
; OnKillSpecTypeNPC = xxx ; 玩家杀死了指定类别的NPC
; OnKillSpecIDNPC = xxx ; 玩家杀死了指定ID场景中指定ID的NPC
; OnUseSpecTypeToolObject = xxx ; 玩家使用了指定类别的工具类场景对象
; OnUseSpecIDToolObject = xxx ; 玩家使用了指定ID场景中指定ID的工具类场景对象
; OnAnswerObjectQuestion = xxx ; 玩家回答了指定ID场景中指定ID的场景对象的问题
; OnMsnItemGetted = xxx ; 玩家背包中指定任务相关物品的数目发生改变,任务完成后提交或者任务本中止时,这些物品将被从玩家背包中扣除.
; OnToolItemAtBagUsed = xxx ; 玩家使用了背包内的指定工具类物品
; OnEquUsed = xxx ; 玩家使用了已经佩戴的指定装备
; OnEnterGameZone = xxx ; 玩家人物进入了所指定游戏区域(注意不支持建筑物室内区域)
; OnLeaveGameZone = xxx ; 玩家人物离开了所指定游戏区域(注意不支持建筑物室内区域)
; }
 

游戏处理类分为三种: "通用处理类"(用作场景全局事件的处理),"场景对象处理类"(用作针对场景内指定对象的处理),"任务处理类"(用作定义游戏任务),每类游戏类都有自己特有的各 种成员集合,具体请见前面列出的源文件中相关说明信息.

游戏处理类的方法用作置入游戏处理类的方法插槽,必须单独定义,演示程序的游戏类方法定义源文件为"demo1\bin\client\data\def\GameClassMethods.cfg_w",其文件头部说明了游戏处理类方法的具体定义方法:

; 类方法声明格式:
; method
; {
; id = 方法ID ; 不可省略,不可重复.
; name = 方法名称 ; 不可省略,不可重复. 以'@'开头的名称表示为系统内定义方法,否则为脚本中定义的方法.
;
; ; 如果有参数,顺序排列所有参数信息. 最多8个.
; ; 注意: 如果希望新增参数不会影响到已设计好的场景中使用了本方法的类实例数据,请始终在尾部加入新参数,且不要删除已有的参数.
; arg
; {
; name = 参数名称 ; 不可省略,不可重复.
; type = 参数类型 ; 不可省略
; ; 可供选择的参数类型:
; ; int: 整数
; ; float: 小数
; ; bool: 逻辑型
; ; text: UTF8文本
; ; IntArray: 整数数组
; ; FloatArray: 小数数组
; ; RegionPath: 区域路径名称文本
; ; LinePath: 线型路径名称文本
; ; ObjectGroup: 对象组
;
; [NotEmpty = TRUE/FALSE] ; 是否不允许参数值为空,如果被省略,则默认为假.针对以下参数类型有效:
; ; text: 不能为空文本
; ; IntArray, FloatArray: 不能为空数组
; ; RegionPath, LinePath: 必须指定有效路径名称
; }
; }
 

游戏处理类方法的参数分为设计时参数运行时参数两部分,设计时参数用作在世界设计器中由设计人员置入对应的初始值,在方法定义文件中定义的均为设计时参数,运行时参数由系统运行时环境 自动提供.

使用游戏处理类的关键点在于为其中的方法插槽置入合适的方法来处理对应的游戏事件.方法分为系统内部定义方法(以'@'字符开头)及用户lua脚本定义方法两种,目前定义有以下系统内部方法:

系统内部方法名称 描述 可置入处理类方法插槽
@mgrStay "始终停留原地"行为状态的处理方法 SceneObjectClass.mgrIdle
@mgrWander "四处游荡"行为状态的处理方法 SceneObjectClass.mgrIdle
@mgrPatrol "按照路径进行巡逻"行为状态的处理方法 SceneObjectClass.mgrIdle
@mgrFight "战斗"行为状态的处理方法 SceneObjectClass.mgrFighting
@OnKillSpecTypeNPC "玩家杀死了指定类别的NPC"事件管理方法 MissionClass.OnKillSpecTypeNPC
@OnKillSpecIDNPC "玩家杀死了指定类别场景中指定ID的NPC"事件管理方法 MissionClass.OnKillSpecIDNPC
@OnMsnItemGetted "玩家背包中指定任务相关物品的数目发生改变"事件管理方法 MissionClass.OnMsnItemGetted
@OnEnterGameZone "玩家人物进入了所指定游戏区域"事件管理方法 MissionClass.OnEnterGameZone
@OnLeaveGameZone "玩家人物离开了所指定的游戏区域"事件管理方法 MissionClass.OnLeaveGameZone
@OnUseSpecTypeToolObject "玩家人物使用了指定类别的工具类场景对象"事件管理方法 MissionClass.OnUseSpecTypeToolObject
@OnUseSpecIDToolObject "玩家人物使用了指定类别场景中指定ID的工具类场景对象"事件管理方法 MissionClass.OnUseSpecIDToolObject

游戏处理类的每个方法插槽都有固定的方法设计时参数表要求,任何想置入该方法插槽的方法,其设计时参数表首部(后面的可以随意)都必须等同所指定的要求(参数名称无匹配要求).

方法插槽名称

描述 所要求的设计时参数表首部(空白表示无) 运行时参数及方法返回值(空白表示无)

CommonClass处理类

OnStartup 本对象被启动后自动调用,用作初始化本对象.   返回值:
  返回1表示初始化成功,返回0表示初始化失败.
OnStop 本对象被停止后自动调用,用作清理本对象.    
OnTimer 周期性时钟事件(注意处理周期的单位是分钟) arg
{
  name = Minute  ; 更新周期时间,单位分钟.
  type = int
  NotEmpty
}
 
OnTiming 定时时钟事件 arg
{
  name = Month  ; 触发时间的月份(0-12),0表示匹配任何月.
  type = int
},
arg
{
  name = Day  ; 触发时间的日期(0-31),0表示匹配任何日期.
  type = int
},
arg
{
  name = Hour  ; 触发时间的小时(0-23)
  type = int
}
 
OnCurrentStageChanged 当前处理阶段被改变   参数1(INT): 当前所处处理阶段

SceneObjectClass处理类

mgrIdle 空闲状态管理器   参数1(INT): 提供当前所启动的子动作索引;
参数2(INT): 如果当前所处阶段已经被改变,则记录具体阶段值,否则为0.

返回值:
  -1: 更新失败,退出本行为状态控制器;
  0: 更新成功,退出本行为状态控制器.
  1: 需要继续更新.

mgrAction 动作状态管理器  
mgrFighting 战斗状态管理器   参数1(INT): 提供当前所启动的子动作索引;
参数2(INT): 如果当前所处阶段已经被改变,则记录具体阶段值,否则为0;
参数3(UINT64): 当前具有最大威胁值的对象ID

返回值:
  -1: 更新失败,退出本行为状态控制器;
  0: 更新成功,退出本行为状态控制器.
  1: 需要继续更新.

OnDead NPC对象已经死亡    
OnFightingDamaged NPC对象在战斗中受伤 arg
{
  name = aryTriggerHealths  ; 记录一系列的生命值百分比阀值,当受到伤害后当前生命值首次下降到任一阀值百分比时,触发事件.
  type = IntArray
  NotEmpty
}
参数1(INT): 具体触发的剩余生命值占全部生命值的百分比阀值
OnFightingBeforeSkillCast NPC对象在战斗中即将施放指定ID技能 arg
{
  name = aryWatchingSkillIDs  ; 记录所有被监视的技能ID(技能信息表"skills.csv"的行ID)
  type = IntArray
  NotEmpty
}
参数1(INT): 该技能的ID值

返回值: 返回1表示允许施放,返回0表示不允许施放.

OnFightingSkillCast NPC对象在战斗中施放了指定ID技能 arg
{
  name = aryWatchingSkillIDs  ; 记录所有被监视的技能ID(技能信息表的行ID)
  type = IntArray
  NotEmpty
}
参数1(INT): 该技能的ID值
OnEnterFighting NPC对象已经进入战斗状态    
OnLeaveFighting NPC对象已经离开战斗状态    
OnToolUsed 当本对象为工具类对象时被使用    
OnAnswerQuestion 玩家人物回答了本对象所询问的问题   参数1(INT): 玩家人物所提供的答案. 0:确认

MissionClass处理类

OnAccepted 任务被玩家承接后自动调用,用作初始化本任务对象.   返回值: 返回1表示初始化成功,返回0表示初始化失败.
OnCanceled 任务被玩家放弃后自动调用    
OnCommitted 任务被玩家完成后提交时自动调用   返回值: 返回1表示提交成功,返回0表示提交失败.
OnTimer 周期性时钟事件(注意处理周期的单位是秒) arg
{
  name = Second  ; 更新周期时间,单位秒.
  type = int
  NotEmpty
}
 
OnCurrentStageChanged 当前处理阶段被改变   参数1(INT): 当前所处处理阶段
OnKillSpecTypeNPC 玩家杀死了指定类别的NPC arg
{
  name = aryNPCTypes  ; 记录所有的NPC场景对象类别(服务器对象类别表"ServerObjectTypes.csv"的行ID)
  type = IntArray
  NotEmpty
}
参数1(INT): 该NPC对象在aryNPCTypes参数数组中的记录索引
OnKillSpecIDNPC 玩家杀死了指定ID场景中指定ID的NPC arg
{
  name = arySpecialNPCIDs  ; 记录所有的特定NPC ID(特定场景对象信息表"SpecialSceneObject.csv"的行ID)
  type = IntArray
  NotEmpty
}
参数1(INT): 该NPC对象在arySpecialNPCIDs参数数组中的记录索引
OnUseSpecTypeToolObject 玩家使用了指定类别的工具类场景对象 arg
{
  name = aryObjectTypes  ; 记录所有的工具类场景对象类别(服务器对象类别表的行ID)
  type = IntArray
  NotEmpty
}
参数1(INT): 该对象在aryObjectTypes参数数组中的记录索引
OnUseSpecIDToolObject 玩家使用了指定ID场景中指定ID的工具类场景对象 arg
{
  name = arySpecialObjectIDs  ; 记录所有的特定工具类场景对象ID(特定场景对象信息表的行ID)
  type = IntArray
  NotEmpty
}
参数1(INT): 该对象在arySpecialObjectIDs参数数组中的记录索引
OnAnswerObjectQuestion 玩家回答了指定ID场景中指定ID的场景对象的问题 arg
{
  name = arySpecialObjectIDs  ; 记录所有的场景对象ID(特定场景对象信息表的行ID)
  type = IntArray
  NotEmpty
}
参数1(INT): 该对象在arySpecialObjectIDs参数数组中的记录索引;
参数2(INT): 玩家人物所提供的答案. 0:确认
OnMsnItemGetted 玩家背包中指定任务相关物品的数目发生改变,任务完成后提交或者任务本中止时,这些物品将被从玩家背包中扣除. arg
{
  name = ItemID1  ; 欲检测的最多第1个杂类物品的物品ID(杂类游戏物品信息表"GameItemMisc"的行ID)
  type = int
  NotEmpty
},
arg
{
  name = ItemID2  ; 欲检测的第2个杂类物品(为0表示无)
  type = int
},
arg
{
  name = ItemID3  ; 欲检测的第3个杂类物品(为0表示无)
  type = int
},
arg
{
  name = ItemID4  ; 欲检测的第4个杂类物品(为0表示无)
  type = int
}
参数1(INT): 待检测物品1在背包中的实际数目
参数2(INT): 待检测物品2在背包中的实际数目
参数3(INT): 待检测物品3在背包中的实际数目
参数4(INT): 待检测物品4在背包中的实际数目
OnToolItemAtBagUsed 玩家使用了背包内的指定工具类物品 arg
{
  name = aryMiscItemIDs  ; 记录所有工具类物品的ID(杂类物品信息表的行ID)
  type = IntArray
  NotEmpty
}
参数1(INT): 该对象在aryMiscItemIDs参数数组中的记录索引;
参数2(BOOL): 如果该物品具有使用准备阶段,提供是否处于该阶段.

返回值:
  当第2个参数值为真时,返回1表示允许使用,返回0表示不允许使用.

OnEquUsed 玩家使用了已经佩戴的指定装备 arg
{
  name = aryEquIDs  ; 记录所有装备物品的ID(装备信息表"equipments.csv"的行ID)
  type = IntArray
  NotEmpty
}
参数1(INT): 该对象在aryEquIDs参数数组中的记录索引;
参数2(BOOL): 如果该物品具有使用准备阶段,提供是否处于该阶段.

返回值:
  当第2个参数值为真时,返回1表示允许使用,返回0表示不允许使用.

OnEnterGameZone 玩家人物进入了所指定游戏区域(注意不支持建筑物室内区域) arg
{
  name = GameZoneID  ; 记录所指定的游戏区域ID(游戏区域信息表"GameZone.csv"的行ID)
  type = int
  NotEmpty
}
 
OnLeaveGameZone 玩家人物离开了所指定游戏区域(注意不支持建筑物室内区域) arg
{
  name = GameZoneID  ; 记录所指定的游戏区域ID(游戏区域信息表"GameZone.csv"的行ID)
  type = int
  NotEmpty
}
 

定义游戏处理类举例说明:

我们剖析一下在"demo1\bin\client\data\def\GameClasses.cfg_w"源文件中定义的名称为"FindDead"(自寻死路任务使用)的任务处理类定义信息:

; 自寻死路任务使用
MissionClass
{
    id = 4008
    name = "FindDead"

    OnAnswerObjectQuestion = "OnFindDeadMissionStart"
    OnCurrentStageChanged = "OnFindDeadStageChanged"
    OnKillSpecIDNPC = "@OnKillSpecIDNPC"
}
 

我们可以看到它里面填写了任务处理类的三个方法插槽内容,其中"OnKillSpecIDNPC"插槽中填写的是系统提供的内置处理类方法,其它两个插槽填写的是用户自定义的方法,这两个方法在"demo1\bin\client\data\def\GameClassMethods.cfg_w"中定义,其信息如下:

method
{
    id = 1006
    name = OnFindDeadMissionStart

    arg
    {
        name = arySpecialObjectIDs
        type = IntArray
        NotEmpty
    }
}

method
{
    id = 1008
    name = OnFindDeadStageChanged
}
 

与前面的表格对比一下,就会发现其首部的参数表是符合要求的.方法的名称即为在lua脚本中提供的lua函数名称.


定义游戏处理类实例

游戏处理类使用时必须定义其实例对象,定义格式如下:

ClassInstance
{
    class = "xxx" ; 指定所基于的处理类名,不可省略,不可重复.
    [name = 处理类实例名称] ; 可省略,不可重复.
    [AutoStartup = TRUE/FALSE] ; 设置创建本对象实例后是否自动将其启动,默认值为真.
    [PlaneSpaces = "xxx"]
        ; 本属性仅当对象实例定义信息处于场景服务器端游戏处理类实例定义文件中时有效,
        ; 提供本对象实例所应用到的所有位面空间ID列表,多个位面空间之间用逗号分隔.为-1
        ; 表示应用到该场景的任何位面空间.

    ; ------------- 设置处理类条件(可以同时定义多个)

    cond
    {
        [id = 整数值] ; 本条件的ID,如果设置,则设置值必须大于0,且必须在本处理实例内唯一. 本属性如果被省略,则默认值为0.

        desc = 整数值 ; 条件描述模板文本ID(游戏用户消息表"GameUserMessages.csv"的行ID),其中可以包括相应的替换符(见下). 本属性不能省略
        {
            ; 本整数型属性可以同时提供多个(最多不超过4个),用作顺序提供置换条件描述文本中对应位置替换符的内容ID值.
            ; 属性数目必须与描述文本中替换符的数目一致.
            ; 属性值可以为以下3种方式之一:
            pm = ID数值 ; 纯粹的一个整数值,用作直接提供对应的ID值;
            pm = "方法插槽名称.参数名称" ; 指定方法的整数型非数组参数值
            pm = "方法插槽名称.参数名称 [数组成员索引]" ; 指定方法的整数型数组参数指定索引位置处的成员值(成员索引值基于0开始). 注明:此处的中括号为实际存在的字符.
        }

        ; 记录本条件所参考到的目标方法条件输出参数(整数型)的索引位置(0-3)及该输出参数满足本条件的目标值. 本属性不能省略
        ; 所参考到的目标方法输出参数值一旦达到目标值即认为满足了本条件.
        ; 属性值可以为以下2种方式之一:
        goal = "方法插槽名称.所参考到的目标方法条件输出参数的索引位置" ; 此时输出参数目标值默认为1
        goal = "方法插槽名称.所参考到的目标方法条件输出参数的索引位置 -> 输出参数目标值"
    }

    ; ------------- 设置类实例的数据成员值

    数据成员名称 = 值

    ; ------------- 设置类方法插槽内的方法参数值

    方法插槽名称
    {
        插槽内方法的参数名称 = 值
    }
}
 

注意: 在游戏任务信息表的"任务的游戏处理类实例定义文本"字段和场景对象的"GameClassInstance"属性中定义对应处理类实例时,最外层的"ClassInstance"可以被省略.


游戏用户消息表中可以使用的替换符:

%g: 游戏物品 参数: 游戏物品ID
%s: 普通文本 参数: 索引到本游戏消息表的行ID
%z: 游戏区域显示名称 参数: 游戏区域ID
%e: 游戏场景显示名称 参数: 游戏场景模板ID
%c: 游戏场景内服务器端对象的类型名称 参数: 服务器端对象类型信息表行ID
%q: 游戏装备名称 参数: 装备信息表的行ID
%i: 游戏杂类物品的名称 参数: 杂类物品信息表的行ID
%o: 游戏特定场景对象的名称 参数: 游戏特定场景对象信息表的行ID
%m: 游戏任务的名称 参数: 游戏任务信息表的行ID
%p: 接收到此消息的玩家对象名称 参数: 无
%d: 普通整数值 参数: 整数值
 


编写对应的lua脚本文件

A. 提供场景在服务器端的lua脚本文件名

    请使用世界设计器打开欲设计场景,点击左侧的"场景"选项夹,右键单击最上面的"场景"项目结点,选择其中的"属性"菜单打开场景的属性表,在最下方的"脚本设置信息"栏目中,将对应的服务器端脚本文件名(相对服务器端程序所处的"script"子目录,譬如演示程序的相关目录即为"demo1\bin\server\script")填写到"ServerScriptFile"属性中即可.譬如"main"场景的服务器端脚本文件名即被设置为"main.lua".

B. 使用lua脚本语言在所指定文件中为所有被使用到的游戏处理类方法提供对应名称的lua函数代码.

所提供的lua运行时库介绍(函数定义采用C格式描述):

A. 可以使用的lua系统库:

    "base", "table", "string", "math", "bit"

B. 引擎定义的通用lua库:

    1. scmgr

        int GetTickCount ();  // 返回当前系统启动后的滴答数

    2. logger

        void LogError (const char* szText);  // 记录错误信息文本szText到服务器log日志

        void LogWarning (const char* szText);  // 记录警告信息文本

        void LogMessage (const char* szText);  // 记录普通信息文本

        void LogDetail (const char* szText);  // 记录细节信息文本

C. 引擎定义的服务器端场景lua库:

    1. gmcls

UINT64 GetSelf ();  // 如果本对象的处理类为场景对象处理类,返回对应的场景对象ID.如果为任务处理类,返回对应的玩家人物对象ID.

 

//------------ 读取/设置任务条件输出参数
// 本对象的处理类必须为任务处理类

// 读取任务条件输出参数值
int GetMissionCondValue (const int nOutArgIndex);
    // nOutArgIndex: 所指定的条件输出参数索引位置

// 设置任务条件输出参数值为nOutArgValue
void SetMissionCondValue (const int nOutArgIndex, const int nOutArgValue);

// 将指定任务条件输出参数值加1
void IncMissionCondValue (const int nOutArgIndex);

 

//------------ 任务状态设置
// 本对象的处理类必须为任务处理类

// 设置任务处于失败状态
void SetMissionFailed ();

 

//------------ 读取/设置当前处理阶段

int GetCurrentStage ();
void SetCurrentStage (const int nCurrentStage);


//------------ 读取方法局部缓存属性值

int GetIntLocalBufValue (const int idLocalUserProp);  // idLocalUserProp: 局部用户属性ID值
UINT64 GetID64LocalBufValue (const int idLocalUserProp);
float GetFloatLocalBufValue (const int idLocalUserProp);
BOOL GetBoolLocalBufValue (const int idLocalUserProp);
const char* GetStrLocalBufValue (const int idLocalUserProp);

 

//------------ 设置方法局部缓存属性值

void SetIntLocalBufValue (const int idLocalUserProp, const int nNewValue);
void SetID64LocalBufValue (const int idLocalUserProp, const UINT64 id64NewValue);
void SetFloatLocalBufValue (const int idLocalUserProp, const float fNewValue);
void SetBoolLocalBufValue (const int idLocalUserProp, const BOOL blNewValue);
void SetStrLocalBufValue (const int idLocalUserProp, const char* szNewValue);

 

//------------ 读取处理类全局缓存属性值

int GetIntGlobalBufValue (const int idGlobalUserProp);  // idGlobalUserProp: 全局用户属性ID值
UINT64 GetID64GlobalBufValue (const int idGlobalUserProp);
float GetFloatGlobalBufValue (const int idGlobalUserProp);
BOOL GetBoolGlobalBufValue (const int idGlobalUserProp);
const char* GetStrGlobalBufValue (const int idGlobalUserProp);

 

//------------ 设置处理类全局缓存属性值

void SetIntGlobalBufValue (const int idGlobalUserProp, const int nNewValue);
void SetID64GlobalBufValue (const int idGlobalUserProp, const UINT64 id64NewValue);
void SetFloatGlobalBufValue (const int idGlobalUserProp, const float fNewValue);
void SetBoolGlobalBufValue (const int idGlobalUserProp, const BOOL blNewValue);
void SetStrGlobalBufValue (const int idGlobalUserProp, const char* szNewValue);

 

//------------ 读取处理类数据成员值

int GetIntElement (const int idClassElement);  // idClassElement: 游戏处理类中定义的数据成员ID值
float GetFloatElement (const int idClassElement);
BOOL GetBoolElement (const int idClassElement);
const char* GetStrElement (const int idClassElement);
const int* GetIntAryElement (const int idClassElement);  // 实际上返回的是lua数组对象,下同.
const float* GetFloatAryElement (const int idClassElement);

 

//------------ 读取处理类方法设计时参数值

int GetIntParam (const int npMethodParamIndex);  // npMethodParamIndex: 在当前处理类方法的参数表内的参数索引位置
float GetFloatParam (const int npMethodParamIndex);
BOOL GetBoolParam (const int npMethodParamIndex);
const char* GetStrParam (const int npMethodParamIndex);
const int* GetIntAryParam (const int npMethodParamIndex);
const float* GetFloatAryParam (const int npMethodParamIndex);
 

    2. stage

// 休眠指定fSleepInterval秒,然后设置当前处理阶段为nGoalStage.
//   nGoalStage: 大于0: 当前处理阶段 += nGoalStage; 小于0: 当前处理阶段 = -nGoalStage; 等于0: 不修改当前处理阶段. 下同
//   fSleepInterval: 所休眠的时间,单位秒.
void sleep (const int nGoalStage, const float fSleepInterval);

// 启动指定NPC的指定动作,动作执行完毕后设置当前处理阶段为nGoalStage.
//    id64NPCObject: 所欲启动其动作的NPC对象ID. 注意: 如果本处理类为场景对象处理类,则该NPC必须与本场景对象位于同一个组中.
//    nSubActionIndex: 欲启动的子动作索引;
//    u64UserActionParam: 启动时所附带的动作参数. 注意: 如果当前动作已经在执行,则本参数被忽略.也就是说,始终只记录首次启动本动作时的用户参数.
//    blActionMustWatching : 如果本处理类为任务处理类,则指定NPC执行此动作时是否必须全程位于执行此任务的玩家人物的视野中,否则NPC将自动退出此动作并失败.
void StartupNPCAction (const int nGoalStage, const UINT64 id64NPCObject, const INT nSubActionIndex, const UINT64 u64UserActionParam = 0, const BOOL blActionMustWatching = true);

// 等待NPC完成所指定的动作,动作执行完毕后设置当前处理阶段为nGoalStage.
void WaitNPCAction (const int nGoalStage, const UINT64 id64NPCObject, const INT nSubActionIndex);

 

    3. scene

// 新建一个临时场景对象并返回其ID
//    id64TemplateServerObject: 模板场景对象ID. 以下对象不允许复制: 直接间接载具/为所处服务器端对象集领导者/始终激活/特权/临时对象本身/位于载具中
//    fMaxIdleDuration: 该场景对象最大允许处于空闲状态的时间,单位秒.
UINT64 NewTemporyObject (const UINT64 id64TemplateServerObject, const FLOAT fMaxIdleDuration);

// 释放所创建的临时场景对象
void ReleaseTemporyObject (const UINT64 id64TemporayServerObject);

// 将NPC对象id64ServerObject的当前具有最大威胁值的敌人设置为id64EnemyServerObject对象.
void SetNPCCurrentEnemy (const UINT64 id64ServerObject, const UINT64 id64EnemyServerObject);

// 与指定玩家人物对象进行交谈
//    id64SpeakerObject: 发言人对象ID,为0表示无.
//    id64PlayerObjectObject: 发言人与之交谈的玩家对象ID,为0表示与所有玩家人物进行交谈;为-1表示与附近所有玩家人物进行交谈.
//    nTopicType: 聊天话题类型: 0:通常; 1:通知(不会显示发言者和发言动作); 2:交易; 3:防务
//    nEmotionType: 聊天时所使用的情绪,从0开始.
//    idMessage: 所聊天的信息ID(用户消息表"GameUserMessages"的行ID)
//    nMessageParam: 聊天的信息所需要指定的参数
void TalkingWithPlayer (const UINT64 id64SpeakerObject, const UINT64 id64PlayerObjectObject, const int nTopicType, const int nEmotionType, const int idMessage, const int nMessageParam);

// 与所有玩家人物对象进行交谈
void TalkingWithAllPlayer (const UINT64 id64SpeakerObject, const int nTopicType, const int nEmotionType, const int idMessage, const int nMessageParam);

// 与附近所有玩家人物对象进行交谈
void TalkingWithNearbyPlayers (const UINT64 id64SpeakerObject, const int nTopicType, const int nEmotionType, const int idMessage, const int nMessageParam);

// 与指定游戏区域内所有玩家人物对象进行交谈
//    idGameZone: 游戏区域ID(游戏区域信息表"GameZone.csv"的行ID)
TalkingAtZone (const UINT64 id64SpeakerObject, const int idGameZone, const int nTopicType, const int nEmotionType, const int idMessage, const int nMessageParam);

// 将id64DestObject场景对象移动到id64SrcObject场景对象所处的位置
void SetObjectPos (const UINT64 id64DestObject, const UINT64 id64SrcObject);

// 恢复id64ServerObject场景对象的可视状态到其设计时所指定的值
void RestoreInitialVisibleState (const UINT64 id64ServerObject);

// 恢复id64ServerObject场景对象的禁止状态到其设计时所指定的值
void RestoreInitialDisabledState (const UINT64 id64ServerObject);

// 设置id64ServerObject场景对象的当前可视状态
void ShowObject (const UINT64 id64ServerObject, const BOOL blShow);

// 设置id64ServerObject场景对象的当前禁止状态
void DisableObject (const UINT64 id64ServerObject, const BOOL blDisable);

// 在id64ServerObject场景对象启动一个延迟动作
//    nDelayAction: 0:无; 1:翻转到初始可视状态的相反状态,然后恢复到初始可视状态.; 2:显示一段指定时间,然后隐藏; 3:隐藏一段指定时间,然后显示.
//    fDelayActionDuration: 延迟动作的持续时间. 单位秒.
void StartupDelayAction (const UINT64 id64ServerObject, const int nDelayAction, const float fDelayActionDuration);

// 返回id64Object1和id64Object2场景对象之间的边缘距离,单位米.
float GetObjectEdgeDistance (const UINT64 id64Object1, const UINT64 id64Object2);

// 显示指定信息到id64PlayerObject玩家人物的客户端
void ShowMessageToPlayer (const UINT64 id64PlayerObject, const int idMessage, const int nMessageParam);

// 返回id64Object场景对象是否已经死亡
BOOL IsObjectDeath (const UINT64 id64Object);

// 返回id64Object场景对象是否处于战斗状态
BOOL IsObjectFighting (const UINT64 id64Object);

// 返回是否可以在id64Object场景对象上启动nSubActionIndex子动作
BOOL IsObjectActionStartable (const UINT64 id64Object, const int nSubActionIndex);
 

    4. npc    注: 本库内的命令只能在场景对象处理类中使用

// 为本场景对象配备指定的装备
//    nWeaponID: 装备信息表"equipments.csv"的行ID
void SetWeapon (int nWeaponID);

// 返回调用StartupNPCAction启动本场景对象的动作时所提供的动作参数
UINT64 GetFirstActionParam ();

// 设置指定技能在本场景对象战斗时的施放权重
//     idSkill: 待设置技能的ID,为技能信息表"skills.csv"的行ID.
//     nSkillCastWeight: 待设置的权重值,从-1到100,-1表示为最优先施放此技能,0表示禁止施放此技能;
//     nCastTimes: 所允许施放次数. 如果大于0,则该技能被施放了所指定次数后其权重值将自动恢复到原默认值,如果小于等于0,则不自动恢复.
void SetFightingSkillCastWeight (const int idSkill, const int nSkillCastWeight, const int nCastTimes);

// 重置指定技能在战斗中的施放权重为默认权重
void ResetFightingSkillCastWeight (const int idSkill);

 

    5. npc_act_stage    注: 本库内的命令只能在场景对象处理类中使用

// 将本场景对象沿着所指定名称的场景路径进行移动,移动完毕后设置当前处理阶段为nGoalStage.
void MoveAlongPath (const int nGoalStage, const char* szMovingPathName);

// 将本场景对象沿着所指定ID的场景路径进行移动,移动完毕后设置当前处理阶段为nGoalStage.
void MoveAlongPath (const int nGoalStage, const int idMovingPath);

// 通知本场景对象对id64SkillTragetObject目标场景对象施放ID为idSkill的法术,施放完毕后设置当前处理阶段为nGoalStage.
void CastSkill (const int nGoalStage, const int idSkill, const UINT64 id64SkillTragetObject);

// 通知本场景对象对id64SkillTragetObject目标场景对象施放索引位置为nAnimActionIndex的动画动作,施放完毕后设置当前处理阶段为nGoalStage.
void CastAnimAction (const int nGoalStage, const int nAnimActionIndex, const UINT64 id64SkillTragetObject);

 

    6. player    注: 本库内的命令只能在任务处理类中使用

// 显示指定信息到本玩家人物对象的客户端
void ShowMessage (const int idMessage, const int nMessageParam);

 

    7. player_stage    注: 本库内的命令只能在任务处理类中使用

// 通知本玩家人物对象进入指定的场景,进入完毕后设置当前处理阶段为nGoalStage.
//    szReqCurrentSceneNameAtServer: 本玩家人物对象当前所处场景的服务器端名称
//    szEnterSceneNameAtServer: 本玩家人物对象欲进入场景的服务器端名称
void EnterScene (const int nGoalStage, const char* szReqCurrentSceneNameAtServer, const char* szEnterSceneNameAtServer)


二. 场景任务样例分析

我们对demo中所提供的"最终决战"场景任务利用前面的知识进行分析:

该任务在missions.csv任务信息表中的定义文本如下(为便于分析,对该文本进行了分行和缩进整理),相关说明被添加到了里面:   

class = FinalFighting  ; 使用所定义的"FinalFighting"游戏处理类

; 定义第1个任务条件. 本条件没有定义描述文本,所以在游戏任务界面中不显示本条件.
cond
{
    id = 1  ; 设置条件ID,用作在外部引用.
    goal = "OnAnswerObjectQuestion.0"  ; 当OnAnswerObjectQuestion方法插槽中的第一个条件输出参数被设置为1时认为本任务条件完成
}

; 定义第2个任务条件
cond
{
    ; 定义本条件描述文本为用户消息表"GameUserMessage.csv"中行ID为31的文本,即"在最终决战中保护%o".
    desc = 31
    {
        ; 由于在描述文本中有"%o"模板符(用作指定游戏特定场景对象的名称),此处需要提供此场景对象在游戏特定场景对象表"SpecialSceneObject.csv"中的行ID.
        ; 此处引用了OnAnswerObjectQuestion方法插槽中提供的第一个数组成员值.
        pm = "OnAnswerObjectQuestion.arySpecialObjectIDs[0]"

    }

    goal = "OnCurrentStageChanged.0"  ; 当OnCurrentStageChanged方法插槽中的第一个条件输出参数被设置为1时认为本任务条件完成
}

; 定义第3个任务条件
cond
{
    ; 定义本条件描述文本为用户消息表"GameUserMessage.csv"中行ID为19的文本.
    desc = 19
    {
        ; 为条件信息文中的模板符提供对应的参数
        pm = "OnKillSpecIDNPC.arySpecialNPCIDs[0]"
    }

    goal = "OnKillSpecIDNPC.0"  ; 当OnKillSpecIDNPC方法插槽中的第一个条件输出参数被设置为1时认为本任务条件完成
}

; 为OnAnswerObjectQuestion方法插槽置入指定的参数
OnAnswerObjectQuestion
{
    ; 根据前面表格中的描述,应该为此参数提供特定场景对象信息表的行ID数组.
    ; 此处的"4"成员ID代表了特定场景对象信息表中指定的"飞扬"场景对象.
    arySpecialObjectIDs = "4"
}

; 为OnKillSpecIDNPC方法插槽置入指定的参数
OnKillSpecIDNPC
{
    ; 根据前面表格中的描述,应该为此参数提供特定场景对象信息表的行ID数组.
    ; 此处的"3"成员ID代表了特定场景对象信息表中指定的"老钻风"场景对象.
    arySpecialNPCIDs="3"
}

"FinalFighting"游戏处理类在"demo1\bin\client\data\def\GameClasses.cfg_w"中的定义信息如下:

; 最终决战任务使用
MissionClass
{
    id = 4009
    name = "FinalFighting"

    OnAnswerObjectQuestion = "OnFinalFightingMissionStart"  ; 使用所定义的方法置入该插槽. 下同.
    OnCanceled = "OnFinalFightingMissionEnd"
    OnCommitted = "OnFinalFightingMissionEnd"
    OnCurrentStageChanged = "OnFinalFightingStageChanged"
    OnKillSpecIDNPC = "@OnKillSpecIDNPC"  ; 将系统内部定义的处理方法置入该插槽
}

相应的游戏处理方法在"demo1\bin\client\data\def\GameClassMethods.cfg_w"中的定义信息如下:

method
{
    id = 1011
    name = OnFinalFightingMissionStart

    arg
    {
        name = arySpecialObjectIDs
        type = IntArray
        NotEmpty
    }
}

method
{
    id = 1012
    name = OnFinalFightingMissionEnd
}

method
{
    id = 1013
    name = OnFinalFightingStageChanged
}

该任务在游戏中的发布界面如下:

现在我们开始详细分析此任务:

1. 玩家如何完成第1个任务条件:

第1个任务条件的目标为:

goal = "OnAnswerObjectQuestion.0"  ; 当OnAnswerObjectQuestion方法插槽中的第一个条件输出参数被设置为1时认为本任务条件完成

看一下类"FinalFighting"中,为"OnAnswerObjectQuestion"插槽填入的方法为"OnFinalFightingMissionStart",由于其没有以'@'开头,所以我们知道这是一个用户自定义Lua方法,我们在"main.lua"文件中找到其源代码如下:

function OnFinalFightingMissionStart (nObjectIndex, nAnswer)

    gmcls.SetMissionCondValue (0, 1) -- 完成所指定的条件值,避免被重复交谈.
    player_stage.EnterScene (1, "main", "FinalFighting") -- 切换进位面空间

end  

可以看到,在该方法中,第一行代码即直接将所处方法插槽的第一个条件输出参数值设置为1,从而完成第1个任务.

结合类实例中的定义文本:

; 为OnAnswerObjectQuestion方法插槽置入指定的参数
OnAnswerObjectQuestion
{
    ; 根据前面表格中的描述,应该为此参数提供特定场景对象信息表的行ID数组.
    ; 此处的"4"成员ID代表了特定场景对象信息表中指定的"飞扬"场景对象.
    arySpecialObjectIDs = "4"
}

即可得出,玩家完成第1个任务的方法为: 与"飞扬"交谈一次并认可其要求(注:在回答"飞扬"的问题时,如果回答为"否",则此答案会被客户端忽略并不会被传递到服务器).

注意: "飞扬"不是可以随时可以与其交谈的,用Excel打开服务器端对象类型信息表"ServerObjectType.csv",查看"飞扬"对象的"AZ","BA","BB"字段值即可知道,只有当玩家人物承接了"最终决战"任务且其ID为1(任务实例定义文本中有指定)的条件尚未被完成时才能与其交谈.

2. 玩家如何完成第2个任务条件:

第2个任务条件的目标为:

goal = "OnCurrentStageChanged.0"  ; 当OnCurrentStageChanged方法插槽中的第一个条件输出参数被设置为1时认为本任务条件完成

    这个就要从第1个任务的lua代码讲起,第1个任务的Lua代码中这一条语句:

player_stage.EnterScene (1, "main", "FinalFighting") -- 切换进位面空间

将会把玩家人物切换到服务器端名称为"FinalFighting"的场景,由于该场景与服务器端名称为"main"的场景具有相同的客户端内容(见场景信息表"scene.csv"),所以在客户端的表现为无缝切换,即位面空间式切换.

该语句执行完毕后,会将类实例的"当前处理阶段"内部属性值+1,由于类实例的当前处理阶段初始值为1,也就是说当场景切换完毕后,当前处理阶段将会被修改为2,从而自动触发相应事件调 用"OnCurrentStageChanged"方法插槽.在类"FinalFighting"中,为该方法插槽填入了"OnFinalFightingStageChanged"方法,该方法在"main.lua"中的代码如下:

function OnFinalFightingStageChanged (nCurrentStage)

    if nCurrentStage == 2 then -- 进入了第2处理阶段(位面空间进入成功)?

        stage.StartupNPCAction (1, 10471, 1) -- 飞扬开始执行子动作1
        scene.TalkingWithPlayer (0, gmcls.GetSelf (), 1, 0, 24, 0) -- 通知最终决战任务开始

    elseif nCurrentStage == 3 then -- 进入了第3处理阶段(飞扬动作执行完毕)

        gmcls.SetMissionCondValue (0, 1) -- 完成所指定的条件值(保护成功)

    end

end

我们可以看到,在当前处理阶段变为2的时候,将会自动启动"飞扬"的子动作1(10471为"飞扬"在场景中的对象ID,在设计场景时指定),"飞扬"的子动作1执行完毕后将会进入处理阶段3.我们再看看"飞扬"的该 子动作做了些什么:

我们用世界设计器打开"main"场景,选中"飞扬"npc,可以看到,其"GameClassInstance"属性值为: "Class = FeiYang",也就是说,在该NPC上,应用的是处理类"FeiYang"的实例,我们看一下该类的定义文本:

SceneObjectClass
{
    id = 3010
    name = "FeiYang"
    BaseClass = "StayingNPC"

    mgrAction = "OnFeiYangAction"
}

发现其"mgrAction"动作状态管理器方法插槽被置入了"OnFeiYangAction"方法,我们查看该方法的lua代码:

-- 用作实现飞扬的动作
function OnFeiYangAction (nSubActionIndex, nCurrentStage)

    if nSubActionIndex == 0 then -- 为子动作0(接收到燕子张三的死讯后执行)?

        if nCurrentStage == 1 then -- 进入了第1处理阶段

            npc_act_stage.CastAnimAction (1, 21, 0) -- 播放哭泣动作
            scene.TalkingWithNearbyPlayers (gmcls.GetSelf (), 0, 14, 23, 0) -- 发言

        elseif nCurrentStage == 2 then -- 进入了第2处理阶段

            stage.sleep (1, 3.0)

        elseif nCurrentStage == 3 then -- 进入了第3处理阶段

            return 0 -- 返回更新成功,退出本动作.

        end

    elseif nSubActionIndex == 1 then -- 为子动作1(最终决战任务执行)?

        if nCurrentStage == 1 then -- 进入了第1处理阶段

            scene.TalkingAtZone (11794, 15, 0, 2, 25, 4) -- 老钻风发言
            stage.sleep (1, 2.0)

        elseif nCurrentStage == 2 then -- 进入了第2处理阶段

            scene.TalkingWithNearbyPlayers (gmcls.GetSelf (), 0, 0, 26, 0) -- 飞扬发言
            npc_act_stage.CastAnimAction (0, 10, 0) -- 播放飞扬的说话动作
            stage.sleep (1, 2.0)

        elseif nCurrentStage == 3 then -- 进入了第3处理阶段(飞扬去取武器)

            scene.TalkingWithNearbyPlayers (gmcls.GetSelf (), 0, 0, 27, 0) -- 飞扬发言
            npc_act_stage.MoveAlongPath (1, "fy1")

        elseif nCurrentStage == 4 then -- 进入了第4处理阶段(飞扬在武器放置位置前站立一会)

            stage.sleep (1, 2.0)

        elseif nCurrentStage == 5 then -- 进入了第5处理阶段(拿起柜面上的武器)

            scene.ShowObject (11793, false) -- 隐藏柜面上的武器
            npc.SetWeapon (111) -- 装备对应的武器
            stage.sleep (1, 2.0)

        elseif nCurrentStage == 6 then -- 进入了第6处理阶段(出门)

            scene.DisableObject (gmcls.GetSelf (), true) -- 暂时禁止住自身,避免出门后在对话结束前发生战斗.
            scene.DisableObject (10469, true) -- 禁止住朵朵,避免其参与战斗.
            stage.StartupNPCAction (0, 10469, 0) -- 朵朵开始执行动作
            npc_act_stage.MoveAlongPath (1, "fy2") -- 移动到门口

        elseif nCurrentStage == 7 then -- 进入了第7处理阶段(飞扬发言)

            scene.TalkingWithNearbyPlayers (gmcls.GetSelf (), 0, 0, 30, 0) -- 飞扬发言
            npc_act_stage.CastAnimAction (0, 10, 0) -- 播放飞扬的说话动作
            stage.sleep (1, 2.0)

        elseif nCurrentStage == 8 then -- 进入了第8处理阶段(启用所埋伏的士兵,开始战斗)

            -- 取消禁止状态,以开始战斗.
            scene.DisableObject (gmcls.GetSelf (), false) -- 取消前面对自身的禁止
            scene.DisableObject (11794, false) -- 取消设计时对老钻风的禁止状态

            scene.SetNPCCurrentEnemy (gmcls.GetSelf (), 11794) -- 将飞扬的敌人设置为老钻风
            scene.SetNPCCurrentEnemy (11794, gmcls.GetSelf ()) -- 将老钻风的敌人设置为飞扬

            -- 显示所有士兵并让其入场
            for idSolider = 11896, 11901 do

                scene.ShowObject (idSolider, true) -- 显示该士兵
                stage.StartupNPCAction (0, idSolider, 0, 0, false) -- 士兵开始执行动作

            end

            scene.TalkingWithNearbyPlayers (11898, 0, 2, 34, 0) -- 军官发言
            stage.sleep (1, 1.0) -- 等待战斗结束

        elseif nCurrentStage == 9 then -- 进入了第9处理阶段(战斗完毕)

            scene.TalkingWithNearbyPlayers (gmcls.GetSelf (), 0, 0, 33, 0) -- 飞扬发言
            npc_act_stage.CastAnimAction (0, 10, 0) -- 播放飞扬的说话动作
            stage.sleep (1, 2.0)

        elseif nCurrentStage == 10 then -- 进入了第10处理阶段

            return 0 -- 返回更新成功,退出本动作.

        end

    end

    return 1 -- 返回需要继续更新

end
 

这个动作的执行过程很长,请大家实际完成一次"最终决战"任务,对比上面的代码进行分析.代码中有调用"StartupNPCAction"启动其它NPC的动作的语句,请结合前面对"飞扬"动作的实现分析进行具体分析.

"飞扬"的动作成功执行完毕后("飞扬"在中途一旦死亡或脱离玩家人物视野,其动作将自动中断且执行失败,且将此游戏任务置为失败状态),会自动将当前处理阶段加1("stage.StartupNPCAction (1, 10471, 1)"语句),从而继续自动触发相应事件再次调用"OnCurrentStageChanged"方法插槽,其中对处理阶段3的lua代码如下:

gmcls.SetMissionCondValue (0, 1) -- 完成所指定的条件值(保护成功)

该语句将"OnCurrentStageChanged"方法插槽的第一个条件输出参数值设置为1,从而完成了第2个游戏条件: "在最终决战中保护飞扬".

附: 关于NPC之间互相战斗的设置方法: 在世界设计器中设计场景时将所处阵营互相敌对的NPC加入在一个组中即可.

3. 玩家如何完成第3个任务条件:

第3个任务条件的目标为:

goal = "OnKillSpecIDNPC.0"  ; 当OnKillSpecIDNPC方法插槽中的第一个条件输出参数被设置为1时认为本任务条件完成

看一下类"FinalFighting"中,为"OnKillSpecIDNPC"插槽填入的方法为"@OnKillSpecIDNPC".此方法为一个系统内部方法,其实现代码简单地将 其设计时数组参数"arySpecialNPCIDs"中对应成员索引位置处的条件输出参数加1.所以,一旦杀死了所指定ID的NPC从而触发事件调用此方法,就会自动完成本任务条件.

4. 其它

在类"FinalFighting"中,还填写了"OnCanceled"和"OnCommitted"两个方法插槽,其所填写的方法均为"OnFinalFightingMissionEnd",下面是其Lua代码:

function OnFinalFightingMissionEnd ()

    player_stage.EnterScene (0, "FinalFighting", "main") -- 用作当任务中止或提交后从位面空间切换回来
    return 1 -- 当提交任务时返回任务提交成功

end

由此可见,此代码仅用作当玩家人物取消任务或提交任务的时候自动将玩家人物切换回"main"场景.


三. 演示程序中的"main"场景内的任务制作步骤(包括完整的修改已有场景方法描述):

1. 由于场景任务必须同时部署到客户和服务器双端,所以你必须架设并使用自己的游戏服务器.具体方法请见"demo1\bin\client\readme.txt"中的相关内容;

2. 根据前述规则,在"demo1\bin\client\data\vdb\csv\missions.csv"中加入你所制作的任务信息行,在"demo1\bin\client\data\def\GameClasses.cfg_w"文件中定义相关的处理类,在"demo1\bin\client\data\def\GameClassMethods.cfg_w"中定义该类所使用的所有方法,在"demo1\bin\server\script\main.lua"中加入所有类方法对应的lua实现函数;

3. 如果有需要,在"ServerObjectType.csv"中定义任务所需要的场景对象类型;在"GameItemMisc.csv"中定义任务所需要的杂类物品;在"GameUserMessages.csv"中定义需要向玩家客户端显示的文本;在"SpecialSceneObject.csv"中定义任务所需要的特定对象,等等;

4. 用世界设计器打开"main"场景,选择你想向玩家发布此任务的NPC,在其"PublishMissions"属性中加入你所制作任务的ID.选择你想接受玩家提交此任务的NPC,在其"SubmitMissions"属性中加入你所制作任务的ID. 注意如果该NPC同时位于该场景的多个位面空间中(如"飞扬"),则各个位面空间任务ID列表之间需要使用0值进行分隔. 这些属性的设置样例请见"飞扬"NPC;

5. 如果需要实现某NPC的自有动作,使用前述方法创建对应的游戏处理类,在世界设计器中选择该NPC,填写其"GameClassInstance"属性.设置样例请见"飞扬"NPC;

6. 执行"后期处理"中的"创建导航图","连接并精简导航图"(必须在"创建导航图"的后面执行),"创建视线遮挡图"功能建立更新相关数据. 注意如果没有更改场景中任何对象的大小和位置或新增/删除场景对象/修改场景地形,在这些数据尚不存在时,可以先从已经分离出来的服务器端场景数据中拷贝过去,以免重新建立.具体为将相关 数据文件从"demo1\bin\server\scenes\main"拷贝到"demo1\bin\client\scenes\main".当然如果不能满足以上要求,就需要重新建立;

7. 如果新增或修改了场景区域路径,请执行后期处理中的"部署所有区域型路径"功能.

8. 选择"后期处理->分离场景数据"菜单功能,选择"demo1\bin\client\data\config\scene\main\spt_normal.cfg_w"用户配置文件,输出目录随意(不放在游戏目录里面 即可),然后点击"开始分离"进行场景的主位面空间数据分离;

9. 选择"后期处理->分离场景数据"菜单功能,选择"demo1\bin\client\data\config\scene\main\spt_final_fighting.cfg_w"用户配置文件,输出目录同上,然后点击"开始分离"进行场景的"final_fighting"位面空间数据分离. 注: 如果当前所分离的场景不为"main",因为其设计时未设计此位面空间,所以不用进行此分离;

10. 将所分离出来数据目录中的"client"和"server"子目录合并覆盖到"demo1\bin"目录下;

11. 将分离器所建立的"GameClasses.def"文件覆盖到"demo1\bin\client\data\def"和"demo1\bin\server\data\def"目录中;

12. 如果修改了地形,需要重新建立静态阴影和精简地形,请使用世界浏览器"WorldViewer.exe"打开"demo1\bin\client\scenes\main\main.scene"场景文件,执行"后期处理"中的"建立地形光照和阴影","建立地形发布版本"两个功能;

13. 执行世界设计器中的"工具->转换生成VDB文件"菜单功能,选择"demo1\bin\client\data\vdb\csv"目录中所有被修改过的csv文件,不选中对话框左下的"文本字段转换为UTF8编码"选择框,单击开始转换,将所有转换到该目录下的文件剪切覆盖到"demo1\bin\client\data\vdb"目录;

14. 执行世界设计器中的"工具->转换生成VDB文件"菜单功能,选择"demo1\bin\client\data\vdb\csv"目录中所有被修改过的csv文件,选中对话框左下的"文本字段转换为UTF8编码"选择框,单击开始转换,将所有转换到该目录下的文件剪切覆盖到"demo1\bin\server\data\vdb"目录;

15. 至此所有工作结束,重启游戏客户端和服务器即可.


 

2015年8月31日  吴涛

 

 

鄂公网安备 42058302000116号