设为首页 加入收藏

  • 网站首页
  • 最新开服列表
  • 游戏装备展示
  • 神途官方新闻
  • 游戏介绍
  • 背景故事
  • 经验心得
  • 神途家族专区
  • 相关工具下载
  • 站内搜索

    TOP

    神途脚本引擎运行原理及案例讲解

    作者:神途开服表 来源:www.smxzt.com 发布时间:2017-08-23 11:25:14 浏览量: 评论:0

       当我们畅游在激情的神途游戏当中时,你是否会好奇这些形形色色的游戏功能,如:任务系统、打金回收、神途副本等等这些,是如何实现的,今天给大家介绍下,神途脚本引擎后台运行原理及相关案例讲解。


    一.   神途脚本引擎的机制

    脚本引擎分为二部分: 

    1).脚本回调代理层:回调可以说是游戏脚本开发的核心,因为所有对游戏逻辑的控制都是围绕着回调展开的。

    2).脚本接口封装层:接口可以说是为游戏脚本开发而提供的工具,所有的游戏逻辑都要通过各种不同的工具组合使用来实现。

    1.            什么是回调函数:

    所谓回调函数,是指由脚本宿主(游戏服务器)在某个事件发生时自动执行的函数。针对不同的事件,回调函数的格式是不同的。所以要想开始编写游戏脚本,作为脚本程序员的你,必须牢牢的记住各种事件对应的回调函数格式。

    比如,用户使用道具“天书”时,服务器会回调该道具挂载的脚本“天书.lua”中的使用回调(main)函数,回调时会自动填充相应的参数。

    例子(天书.lua)可在envir/script/item目录下找到:

    function main(player, item) --天书的使用回调,使用天书时会触发  --player 使用天书的玩家(GUID)  --item   天书 (GUID)  return true --返回true告诉逻辑层道具使用成功,false使用失败 end  

    模仿上面的例子,就可以写出很多简单的道具脚本了。因为大部分情况下,脚本程序员只关心道具被使用时的事件触发。怎么样,是不是很简单呢?

    当然了,游戏服务器脚本肯定不仅仅只有道具相关的操作,也许很多脚本程序员已经迫不及待的想写NPCMonsterMap相关的脚本了。别担心,所有的脚本回调机制万变不离其宗,只要弄明白了回调的机制,写出任何复杂的脚本都不在话下。

    在我们学习具体事件回调函数之前,先来了解一下其它理论知识。

    2.          什么是接口函数?

     

    所谓接口函数,是指由脚本宿主(游戏服务器)针对某一特定的功能提供的实现。这些实现都尽量保持原子(单一功能性),通过这些接口的组合可以实现各种复杂的功能。它隐藏了功能的实现细节,这样脚本程序员不用关心功能对应的服务器逻辑,只要调用接口就能完成想要的功能。

    举个例子:

    void NPCTalk(const std::string& player_guid, const std::string& strTalk);

    这是经常用到的一个接口函数的C++原型,功能就是弹出NPC对话面板。脚本程序员不用理会面板是如何弹出来的,想弹面板了就调它吧,不用客气。

     

    还是接着刚才的例子来讲解一个详细用法吧。

    例子(天书.lua)可在envir/script/item目录下找到:

     

    function main(player, item) --天书的使用回调,使用天书时会触发  --player 使用天书的玩家(GUID)  --item   天书 (GUID) lualib:NPCTalk(player, “你使用天书!”)—弹出NPC对话面板,显示“你使用天书!” return true --返回true告诉逻辑层道具使用成功,false使用失败 end 

    调用接口函数时一定要严格遵循lualib:接口函数(参数。。。)这种格式,否则服务器会提示函数是nil值的错误日志。

    为什么是lualib:XXXX?

     

    因为脚本引擎所谓的注册,实际上就是让所有的接口函数成为某元表的元方法。这个元表被命名为lualib。所以lualib:XXXX实际上就是在调用lualib这个元表的元方法XXXX.

    下面是一段用LUA实现的模拟代码,通过这段代码可以看出端倪。(如果看不懂也没关系,有空多看看lua的书籍吧。)

     

    local mt = {} --创建mt元表 function mt:NPCTalk(msg) --mt元方法  print(msg) end function mt:new(instance) --继承元表  instance = instance or {} setmetatable(instance, self)
        self.__index = self return instance end lualib = mt:new() --lualib将mt做为自己的元表 lualib:NPCTalk("你点了NPC!") --调用元方法 

    通过前面的介绍,相信你已经弄清楚了回调函数、接口函数的特点,现在来进一步了解服务器脚本引擎的分层架构吧。

     

    3.            服务器脚本引擎的分层架构

    神途脚本引擎运行原理及案例讲解

    以刚才的天书为例,我们可以分析上面服务器脚本引擎的分层架构图:

    1)           当玩家使用道具时,客户端通知服务器触发道具使用事件。

    2)           服务器逻辑层收到事件后,查找该道具挂载的lua脚本。如果道具没有挂载脚本,则逻辑层不走脚本流程。反之,通过脚本回调代理层找到函数main。如果main不存在,逻辑层会提出抱怨(出错日志)。

    3)           脚本回调代理层执行main函数(同时将玩家和道具做为参数传进去),执行完成后返回到服务器逻辑层。如果执行出错,服务器会提示lua某一行出错了,并将错误原因通过日志打印出来。

    4)           服务器逻辑层将main函数的执行结果通知到客户端。

     

    作为一名脚本程序员,在写脚本之前请熟悉脚本的执行流程。这样当你调试脚本错误时,也可以比别人更快的找到出错原因。

     

    4.            关于脚本的执行效率

     

    正如服务器脚本引擎分层架构图所示,可以看到脚本的执行要经过如下步骤:

     

    触发回调事件------>逻辑层接收事件------>查找事件对象挂载的脚本------>脚本回调代理层回调------>执行脚本------>调用脚本接口封装层完成功能------>返回逻辑层------>响应事件

    首先LUA自身的执行效率是低效的(相对于服务器),这一点我们无法改变,暂时不去考虑。通过上面的流程我们可以了解到,任何一个事件的产生都需要回调脚本,事件的响应要经过好几步必要的步骤才能得到处理。所以我们有理由认为回调函数的处理相较于服务器本身逻辑来说,速度是非常缓慢的。

    既然如此,我们为什么还是选择了这种运行机制呢?有如下几点理由:

    1).使用脚本开发速度快,语法简单而且强大,可以用很少的语句实现你的想法。对开发人员的要求非常低。

    2).调试方便,修改也非常方便,不需要通过漫长的编译测试流程。可以实现在服务器运行期间,动态加载或关闭某些功能,实现hotfix。具有强大的容错能力。

    3).扩展灵活,任何功能的需求都可以通过添加接口封装来实现。并且这些对于脚本开发人员来说都是透明的,不会造成任何影响。

    4).服务器逻辑与脚本逻辑分离,服务器使用通用的框架为细枝末节的功能提供支持。提高服务器的稳定性。并且脚本程序员可以专心自己的玩法逻辑,不用关心服务器的运作。

    5).脚本在一个限定的框架中运行。绝大部分情况下,脚本错误不会对服务器的运行造成灾难性的后果。

    但是凡事有利必有蔽,要实现快速开发、降低开发成本、实现高灵活性、高安全性,必然要牺牲一部分运行效率作为代价。可喜的是,虽然相对于服务器逻辑来说脚本的执行效率很慢,但是仍然远远超过一款游戏正常运作所需求的效率。

    作为脚本开发人员,在实现功能的前提下如何提高脚本的运行效率是永远都应该摆在第一位的命题。因为服务器的高效稳定运行与你写的每一行代码都习习相关,所以请务必认真对待每一个细节。

     

    5.          脚本引擎提供的安全性

    前面已经介绍过,脚本具有强大灵活的特性,但是又在一个限定的框架中运行。现在让我们来看看,为了避免过于灵活的编码或接口使用不当给服务器带来灾难,脚本引擎都做了哪些限定。

    1).LUA脚本语言自身的安全性,这部分不是这篇教程所能概括的。如果感兴趣的话,请自行购买资料。

    2).出于服务器的安全性的考虑,我们禁用了LUAOS库、IO库。如果你在服务器脚本开发中用到上述二种库,脚本将不能执行。当然我们也提供了部分替代接口,来实现OSIO的常用功能。比如时间接口、文件操作接口(只能操作指定沙盒目录:data目录)。

    3).出于服务器安全性的考虑,我们将LUA C Packages功能完全禁用,也没有提供替代方案。请不要尝试去加载libdll

    4).为了能尽早发现影响服务器执行性能的脚本代码,我们加入了脚本执行性能探测。所有执行时间超过16毫秒的函数,服务器都将提醒你该代码不够高效。当你看到这些提示时,或许你该审查一下自己的代码,尝试着去优化一下。

    5).为了避免不良编码造成循环链,脚本引擎加入了循环链探测功能。如果你不小心让代码进入了循环链,脚本引擎会强制结束脚本运行,并在错误日志中打印出可能是异常代码的位置。如果发现日志提示循环链,请仔细检查你的代码。这个问题虽然不会造成服务器当机,但是玩家的体验绝对不会很愉快。(例如:在物品的创建回调中再创建该物品,就会造成循环链。)

    6).脚本死循环探测。如果脚本执行了10W次并且超过1秒都没有返回逻辑层,我们有理由认为你的代码陷入了死循环,脚本引擎会探测到这种异常,并强制强结束脚本执行。不管引擎是否误判,这样的代码绝对不允许在服务器中执行(会迅速造成服务器卡死)。

    如果你在脚本编写过程中发现其它异常,请尽快联系我们。

     

    6.          脚本底层提供的扩展功能

    为了提供更多的灵活性,对脚本底层进行定制是不可避免的。为了方便脚本程序员,我们目前扩展了一种LUA底层库,新增了一种LUA底层库。

    1).string库扩展:

    t_str string.trim(str),去掉str字符串首尾所有的空白字符(不包括中间),返回新字符串t_str。空白字符包括:换行符(\n)、制表符(\t)、回车符(\r)、回退符(\b)、换页符 (\f),空格符(\32)

    table string.split(master, sub),以子串sub为分隔符,对主串进行分隔。分隔后的串放入table中,并返回这个table。比如:string.split(”ABC#DE”, “#”),执行的结果就是{“ABC”,”DE”}

    t_pos string.indexOf(master, sub),在主串master中查找sub。返回查找到的位置(数组下标),出错返回-1。比如:string.indexOf(“abcdef”, “de”),执行的结果就是3

    t_pos string.indexOf(master, sub, offset) ,在主串master中从offset位置开始查找sub。返回查找到的位置(数组下标),出错返回-1。比如:string.indexOf(“abcdef”, “de”, 1),执行的结果就是3

    t_str string.left(str, len),从字符串str的最左边位置复制len个长度的字符到目标串中,并返回目标串。

    t_str string.left(str, len, offset),从字符串str的左边第offset个位置复制len个长度的字符到目标串中,并返回目标串。比如:string.left(“abcdef”, 2, 1),执行的结果就是”bc”

    t_str string.right(str, len),从字符串str的最右边位置复制len个长度的字符到目标串中,并返回目标串。

    t_str string. right (str, len, offset),从字符串str的右边第offset个位置复制len个长度的字符到目标串中,并返回目标串。比如:string.right(“abcdef”, 2, 1),执行的结果就是”de”

    t_str string.mid(str, offset, len),从字符串str的开始偏移offset个位置复制len个长度的字符到目标串中,并返回目标串(功能同string.left)。比如:string.mid(“abcdef”, 1, 2),执行的结果就是”bc”

    i_asscii string.ord(str),返回字符的ASSCII,字符串str的长度必须为1.比如:string.ord(“A”),执行的结果就是65.

    t_str string.chr(i_asscii),返回数值i-_asscii的字符格式。比如:string.chr(65),执行的结果就是”A”

    t_str string.replace(master, find, replace),在字符串master中查找find,找到后将find替换成replace。比如:string.replace(“aaabcdef”, “abcd”, “g”),执行的结果就是”aagef”

    2).新增json库,json是一种轻量级的数据交换格式,使用键值对来描述数据。Json可以将任意数据转换成纯文本的json字符串,也可以将json字符串转换成之前对应的数据。更详细的资料请搜索网络资料。

    json_text json.encode(object) 将任意对象转换成json字符串,对象可以是数字、字符串、table

    object json.decode(json_text) json字符串转换成对应的对象,根据json字符串的描述,可能转换成数字、字符串、table.

     

    7.          核心回调函数

    通过前面几章的介绍,是时候学习具体的事件回调函数了。虽然服务器脚本接口封装层提供了大量的接口函数,但是脚本回调代理层提供的回调函数却是非常少,少到一手指头就可以数过来。

    1).道具使用回调:

     

    bool main(const std::string& player, const std::string& item);

    玩家使用道具后,该回调函数将会自动执行(道具挂载了脚本,并且有main函数)。同时,服务器会传递二个参数,player=玩家GUIDitem=道具GUID。该回调函数的返回值非常重要:如果你想告诉服务器该道具使用成功,请让回调函数返回true。返之,返回false代表道具使用失败。

    先看一段使用中的例子吧(item/祈福之书.lua)。

     

    function main(player, item) --玩家使用祈福之书时自动执行  local map = lualib:MapGuid(player) --取得玩家当前地图的GUID if "祈福之地" == lualib:KeyName(map) then --判断当前地图是不是祈福之地  return true --在祈福之地使用就成功 end return false --反之,祈福之书使用失败 end 

    2).道具创建回调:

     

    void on_create(const std::string& item);

    当道具被创建出来后,该回调函数将会自动执行(道具挂载了脚本,并且有on_create函数)。同时,服务器会传递一个参数,item=创建道具的GUID。该函数没有返回值。

    先看一段使用中的例子吧(item/藏宝图.lua

     

    function on_create(item) --藏宝图创建时自动执行   if not treasure_map:set_dest_info(item) then --生成藏宝图的挖宝地图坐标  lualib:Error(“藏宝图:on_create调用失败!”) end end 

    3).道具自检回调:

     

    void on_timer_check(const std::string& item);

    服务器每运行一秒就会自动回调一次,以满足自检需求。

     

    道具自检回调使用的情况非常特殊,要使用这个回调有一个前提:道具必须属于某一个玩家(道具挂载了脚本,并且有on_timer_check函数)。如果道具掉落在地图上或在拍卖行中,自检回调不会被执行。回调时,服务器会传递一个参数,item=创建道具的GUID。该函数没有返回值。

    先看一段使用中的例子吧(item/神歌.lua)

     

    local castle_weapon = "神歌" function on_timer_check(item) local self = lualib:Item_GetRole(item) if self == "" then return end local count = lualib:ItemCountByKey(self, castle_weapon, true, true, true, 2) if count > 1 then lualib:DelItemEx(self, castle_weapon, count - 1, 2, true, true, true, true, "删物品:神歌自检", "神歌")
            lualib:SysMsg_SendWarnMsg(self, "这世间只存在一把神歌!") end local weapon_owner = lualib:IO_GetCustomVarStr("神歌城_leader_weapon") if weapon_owner ~= self then lualib:Item_Destroy(self, item, "删物品:神歌自检", "神歌")
            lualib:SysMsg_SendWarnMsg(self, "填城神器神歌已另择良主,望你好自为之!")
            lualib:SysMsg_SendCenterMsg(1, "填城神器神歌已另择良主,玩家"..lualib:Name(self).."失去了对它的拥有权! ", "") return end end 

    4).NPC对话回调

     

    std::string main(const std::string& npc, const std::string& player);

    玩家点击NPC后,该回调函数将会自动执行(NPC挂载了脚本,并且有main函数)。同时,服务器会传递二个参数,npc=对话NPCGUIDplayer=玩家的GUID。该回调函数的返回值非常重要: 返回NPC与玩家对话的内容。

    先看一段使用中的例子吧(npc/士兵.lua

     

    function main(npc, player)
        msg = "我们不怕死,但是怕死得毫无价值! \n \n" msg = msg.."<@likai#1 *01*离开> \n" return msg end function likai(npc, player, param) return param end 

    当玩家跟NPC对话后,会弹出NPC对话面板:

     

    让我们把视线放到高亮的那块代码:<@likai#1 *01*离开>,敏感的你可能已经嗅到了某种味道。没错,这是一种语法约定。也许你已经无数次的见过这种写法,但从未了解过他们的具体含意。请让我暂且将这种约定命名为:《回调自描述约定》,关于这个约定的细节,我们在后面会详细讲解。

    结合上图的例子,当玩家选择“离开”时,服务器会自动回调likai函数。注意,回调时的参数传递顺序,第一个参数npc=对话NPCGUID,第二个参数player=玩家GUID,第三个参数param=1.

    神途脚本引擎运行原理及案例讲解

    5).NPC创建回调

    NPC被创建出来后,该回调函数将会自动执行(NPC挂载了脚本,并且有on_create函数)。同时.

     

    void on_create(const std::string& npc);

    服务器会传递一个参数,npc=创建NPCGUID。该函数没有返回值。

    先看一段使用中的例子吧(npc/天下第一.lua

     

    function on_create(npc) --天下第一NPC创建时自动执行  npc_update(npc) --给NPC自动改名  lualib:AddTimer(npc, 1, 15000, -1, "on_timer_talk") end 

    6).怪物对话回调

    std::string main(const std::string& monster, const std::string& player);

    怪物对话回调也是一种特殊回调。你必须将怪物配置成可对话类型(怪物挂载了脚本,并且有main函数),该回调才会生效。回调时,服务器会传递二个参数,monster=对话怪物的GUIDplayer=玩家的GUID。该回调函数的返回值非常重要: 返回怪物与玩家对话的内容。

    先看一段使用中的例子吧(monster/野马.lua

     

    function main(monster, player) --判断距离  if lualib:Distance(monster, player) > catch_distance then lualib:SysWarnMsg(player, ""..lualib:Name(monster).."太远无法抓捕! ") return "" end --判断是否有绳索  if lualib:ItemCountByKey(player, "套索", true, false, false, 2) <= 0 then lualib:SysWarnMsg(player, "没有套索,无法抓捕!") return "" end --开启抓捕进度条   if lualib:ProgressBarStart(player, catch_wait_time, "抓捕", "catch_complete", "catch_abort", monster) == false then lualib:SysWarnMsg(player, "无法开记抓捕"..lualib:Name(monster).."进度条! ") return "" end return "" end 

    这个例子很简单,选择野马时开启对话,但是没有对话内容。同时开启抓捕进度条。具体的逻辑如果看不懂也没关系,只要牢记怪物对话回调函数的用法就行了。

    7).怪物创建回调

     

    void on_create(const std::string& monster);

    当怪物被创建出来后,该回调函数将会自动执行(怪物挂载了脚本,并且有on_create函数)。同时,服务器会传递一个参数,monster=创建怪物的GUID。该函数没有返回值。

    先看一段使用中的例子吧(monster/三重天妖盗.lua

     

    function on_create(monster)
        lualib:AddTrigger(monster,  lua_trigger_attack, "on_attack") --受攻击时触发on_attack end 

    例子中用到了触发器,当怪物创建时给它添加“受攻击触发的触发器。触发器的功能非常全面,当脚本程序员对系统有了全面的认识以后就会发现触发器也是开发一大利器。但是在这一章它们不是重点,太过深入的细节只会让初学者望而脚步。

    8).地图创建回调

     

    void on_map_create(const std::string& map);

    当地图或副本被创建出来后,该回调函数将会自动执行(地图挂载了脚本,并且有on_map_create函数)。同时,服务器会传递一个参数,map=创建地图的GUID。该函数没有返回值。

    先看一段使用中的例子吧(map/漂流岛.lua

     

    function on_map_create(map)
        lualib:Print("漂流岛地图创建回调!") if not lualib:AddTimer(map, 1, 1740000, 1, "on_timer_close") then lualib:Error("漂流岛:添加关闭通知定时器失败!") return end lualib:Print("漂流岛:添加关闭通知定时器成功!") local monster_table = {{"恶魔守护者", 60}} for i = 1, table.getn(monster_table) do lualib:Map_BatchGenMonster(map, monster_table[i][1], monster_table[i][2], true) end lualib:Print("漂流岛:随机刷怪!") local npc_table = {{"恶魔之匣", 1}} for i = 1, table.getn(monster_table) do for j = 1, npc_table[i][2] do lualib:Map_GenNpcRnd(map, npc_table[i][1]) end end lualib:Print("漂流岛:随机刷宝匣!") end 

    这个例子实现的功能很简单,漂流岛创建之后刷一批怪再刷一批宝匣。

    9).地图关闭回调

     

    void on_map_destroy(const std::string& map);

    当地图或副本被销毁之前,该回调函数将会自动执行(地图挂载了脚本,并且有on_map_destroy函数)同时,服务器会传递一个参数,map=销毁地图的GUID。该函数没有返回值。

     

    关键点:回调时地图中的角色、NPC、怪物、道具都已经被销毁,但是地图资源还存在。正因为如此,在这个回调中不要尝试去操作地图中的角色、NPC、怪物、道具,因为它们已经都不存在了。

    先看一段使用中的例子吧(map/漂流岛.lua

     

    function on_map_destroy(map)
    lualib:Print("漂流岛:地图关闭!") end 

    10).系统启动回调

     

    void on_system_start(void);

    当游戏服务器开始启动,各个功能模块都初始化完成以后,系统会找到system/system.lua(如果存在),并且回调on--_system_start函数用来通知脚本系统服务器已经启动完毕。该回调函数没有参数也没有返回值。

    关键点:如果需要在系统启动完成后,处理一些额外任务。唯一做法就是关注该事件。

    11).VIP物品使用回调

    void main(const std::string& player, int vipid);

    当玩家申请使用VIP物品功能时,系统会自动回调该函数(如果VIP物品有挂载脚本,并且函数存在)。通过该回调函数可以实现各种各样的VIP功能。player=使用VIP物品的玩家,vipid=VIP物品的ID

     

    关键点:要实现不同的VIP功能,唯一的做法就是关注该事件。

    12).玩家接到任务时回调

     

    void on_accept(const std::string& player, int quest_id);

    当玩家接到某个任务时,系统会自动回调该函数(如果任务有挂载脚本,并且函数存在)。通过该回调函数可以实现某些任务配置完成不了的功能。player=接受任务的玩家,quest_id=任务ID

    关键点:如果某些需求用任务配置完成不了,可以回调脚本来实现需求。

    13).玩家完成任务时回调

     

    void on_accomplish (const std::string& player, int quest_id, int select);

    当玩家完成某个任务时,系统会自动回调该函数(如果任务有挂载脚本,并且函数存在)。通过该回调函数可以实现某些任务配置完成不了的功能。player=完成任务的玩家,quest_id=任务IDselect=可选奖励索引(0-3,共4个)。

     

    关键点:如果某些需求用任务配置完成不了,可以回调脚本来实现需求。

    14).玩家放弃任务时回调

     

    void on_drop (const std::string& player, int quest_id);

    当玩家放弃某个任务时,系统会自动回调该函数(如果任务有挂载脚本,并且函数存在)。通过该回调函数可以实现某些任务配置完成不了的功能。player=放弃任务的玩家,quest_id=任务ID

    关键点:如果某些需求用任务配置完成不了,可以回调脚本来实现需求。

    15).任务是否可接判断回调

     

    bool can_accept (const std::string& player, int quest_id);

    当系统对某个玩家进行任务是否可接判断时,会自动回调该函数(如果任务有挂载脚本,并且函数存在)。通过该回调函数可以实现某些任务配置完成不了的功能。player=判断任务的玩家,quest_id=任务ID。当返回值=true,结制任务可接。当返回值=false,强制任务不可接。

     

    关键点:如果某些需求用任务配置完成不了,可以回调脚本来实现需求。该回调函数开销很大,请尽量少用。

    8.   关于《回调自描述约定》

    前面讲述NPC对话回调时,我们已经引入了《回调自描述约定》这个概念,现在到了详细了解它的时候了。回调描述规则定义如下:

     

    <@回调函数名#参数1#参数2...#参数n *01*函数描述符> <@回调函数名 *01*函数描述符> 

    < >:代表中间的内容是语法约定。

    @代表后面的是函数名。

    #代表后面的是参数值,参数值可以有多个用#号分隔开,注意所有的参数值都会转换成字符串值。

    *01*保留关键字,暂时按默认填写即可。紧跟后面的是函数描述文字。

     

    当客户端收到对话内容后会解析其中的《回调自描述约定》,并且按照相应的格式显示出来:函数描术符会变成菜单项,点击该菜单项服务器就会执行对应的回调函数名,同时传递参数

    执行回调函数的规则,根据不同的使用场合有不同的定义:

    1).NPC脚本中使用该约定

     

    std::string 回调函数名(const std::string& npc,const std::string& player,const std::string& 参数1...,const std::string& 参数n);
    
    std::string 回调函数名(const std::string& npc,const std::string& player); 

    2).道具脚本中使用该约定

     

    std::string 回调函数名(const std::string& player,const std::string& 参数1...,const std::string& 参数n);
    
    std::string 回调函数名(const std::string& player); 

    3).怪物脚本中使用该约定

     

    std::string 回调函数名(const std::string& monster,const std::string& player,const std::string& 参数1...,const std::string& 参数n);
    
    std::string 回调函数名(const std::string& monster,const std::string& player); 

    下面再来看看为《回调自描述约定》而服务的扩展标记:

    1).颜色扩展标记

     

    #颜色描述符#内容#COLOREND#

    神途脚本引擎运行原理及案例讲解

    举例如下:

     

    <@likai *01*#COLORCOLOR_GOLD#离开#COLOREND#>

    客户端显示金色的“离开”菜单项

    2).图标扩展标记

    #IMAGE图标编号#

    图标编号就是客户端图标资源的编号,具体设置请查看各自的客户端资源目录,或咨询策划。

    举例如下:

     

    <@likai *01*#IMAGE1000##COLORCOLOR_GOLD#离开#COLOREND#>

    客户端显示资源编号为1000的图标和金色的“离开”菜单项

    3).偏移扩展标记

     

    #OFFSET<X:nx,Y:ny>#

    nxnx<0,标记之后的所有内容向左偏移nx的绝对值个像素;nx>0,标记之后的所有内容向右偏移nx个像素。

    nyny<0,标记之后的所有内容向上偏移ny的绝对值个像素;ny>0,标记之后的所有内容向下偏移ny个像素。

    举例如下:

    <@likai *01*#OFFSET##IMAGE1000##COLORCOLOR_GOLD#离开#COLOREND#>

     

    客户端向上偏移5个像素,显示资源编号为1000的图标和金色的“离开”菜单项。

    9.   脚本底层隐藏回调函数

    1).脚本初始化完成回调

    void OnStateInit(void);

    LUA脚本初始化完成以后,系统会自动回调OnStateInit函数(如果该函数存在).该回调可以让脚本作者有机会做一些初始化动作。注意:OnStateInit回调里只能调用日志、定时器、GS变量、DB变量、DB事件相关的接口,否则会造成服务器启动时当机。

    2).脚本释放前回调

    void OnStateUnInit (void);

    LUA脚本释放系统资源以前,系统会自动回调OnStateUnInit函数(如果该函数存在).该回调可以让脚本作者有机会做一些资源清除动作。

    10.   结束语

    本章详细介绍了服务器脚本引擎的方方面面,请认真理解每一小节所讲的内容。通过这些预备知识的学习,写脚本时你心中应该有一个很清晰很完整的实现思路了,接下来要做的就是去熟悉脚本引擎提供的形形色色的接口函数了,有兴趣的玩家可以查询之前的(神途引擎更新日志)当你对这些接口函数足够了解时,就能在游戏中实现很酷的玩法了。



    最新评论:


    >   共有 0 位网友发表了评论

    随机导读

    ·如何提高神途合击技能命中率
    ·神途操作指南
    ·详细解读神途伤害计算公式
    ·神途十大家族之-布拉格家族
    ·九游神途
    ·孔色传承及晶石介绍

    相关新闻

    ·神途引擎更新日志 [2017-6-27]
    ·神途引擎更新日志