2011-12-03 | #1 |
高级会员
注册: 08年04月11日
来自: 盘丝洞
帖子: 311
声望力: 20
声望:
50
现金:29两梁山币
资产:1245两梁山币
致谢数: 0
获感谢文章数:0
获会员感谢数:0 |
LPC精讲(1)
【 原文由 btulip 所发表 】 以下文章转自XO,特此声明: 嗯,下面是我给6666的新巫师们的一篇入门读物,大家可以看看。是我从清华BBS巫师版 弄来的,是台湾人写的,有些词语我改成大家比较熟悉的,很多地方加了注,可能更好 懂一些。 这篇说明是为了新上任的巫师所写的, 我假设读著这篇说明文件的新巫师已经读过 hel p new_wiz 中的内容, 并对巫师专有的指令如 clone,update, cd, ls, cp... 等能熟练 地加以使用, 但对如何开始写作自己的区域感到茫然, 不知所措的新进巫师 [在开始制作之前] 让我们大略看一下在 LP MUD 中, 世界的构成方式 这个世界是由(目前大陆上玩的 mu d ,包括 xkx,fy,es2 都是 lp mud 即所谓战斗 mud。---lnwm 注。)一个个的对象 (object) 所组成, 每个对象有一个对应 的程序来描(比如你在mud里见到的每个房间,每个npc,每个物品,甚至你自己,都是一 个 object,都是一段程序。----lnwm 注。)述它的特性 我们可以藉由写作一段程序来 创造出一个全新的对象, 可以利用update来更新对象所属的程序, 用 clone 来实际造出 一个可 (比如你敲 update here,就是将你现在所在的房间更新,这里需要强调的是,所谓更新 ,就是将硬盘里这个文件编译后形成一段代码,这段代码是存在在内存中的。因此当你 修改了一个房间,那只表明你在硬盘上改动了这个文件,你必须做一下 update ,将它 编译放入内存,你的修改才正式生效。hehe,当然,编译有错误例外。----lnwm 注。) 用的对象 .(clone其实就是 update,只不过它update的是一个物品或npc,这个物品 或npc还需要有地方放,所以clone就是 update+move---lnwm注。) 在这里, 我们有各式各样的对象, 但是可以将之区分为三大类: 房间 (ROOM), 物品 (O BJECT), 与生物 (LIVING) 在我们制作区域的惯(这个OBJECT不过是个名词以和ROOM LIVING区分,和我们mud的object是两码事。---lnwm 注。) 例上, 我们习惯将房间的档案直接摆在区域的目录下, 生物与物品则摆(区域的习惯放置 位置是根目录下的 /d 目录。特别是大陆的,你可以在 /d下找到所有的房间和npc---- ---lnwm注.) 在这个区域中名为 npc 及 obj (也有人喜欢用 object 或item为名, 看个人习惯 ) 的 子目录中 。 以下我将以这三大类对象来分别说明该如何实作出区域 [如何制作房间] 一个房间必定继承了 ROOM (inherit ROOM), 这是在(对,如果你要写一个房间,那么首 先要做的就是在这个程序的开头写上 inherit ROOM;在有些时候,比如在xkx里,你可能还要加上一句#include因为房间的门 是定义在那里的。---lnwm注。) mudlib.h中被 #define 好的一个档案, 它表示一个特别的档案, 所有的房间都必须继承 它. 才能拥有属於房间的一切特性! 一个房间有三个非常重要的函数: create(), init(), 与 refresh()这三个函数会在某 些特定的时机被系统所呼叫, 并且可以由你自行改写,以达成千变万化的效果。 [create 函数] create() 是房间在一被创造出来时必定要呼叫的一个函数, 通常我(如果你学过C语言, 那么在lpc中,void create()就相当于 void main(),即主函数。---lnwm注。)们都在 里面做一大堆设定初值的动作 随便找一个房间来看, 我们可以发现 create() 函数中 总是有一大堆的 set("something",somevalue);这些 set 的意义在此不详述, 你可以自 己猜, 也可以问问较资深的巫师.(在我们这个mud中,你可以敲help STD_ROOM来看看这 些设置是干什么的。---lnwm注。) 有时你的房间并不直接继承 ROOM 而是继承了一个有继承 ROOM 的特别房间, 像是商店 或是公会房间什麽的 这时候你所写的 create()(在我们的mud中,钱庄就是个典型的例 子,在钱庄的前面,你必须inherit SHOP;这个SHOP就是在ROOM的基础上加了钱庄的公用 功能。---lnwm注。) [init 函数] init() 函数被呼叫的时机在於有生物 (npc及玩家) 进入这个房(这里的意思是,当有玩 家或npc等活物进入房间时,可以是走进来,扔进来,或clone进来,房间中的这个init ()函数就被触发,函数实现的功能就被实现。----lnwm注。)间的时候 这时有个常用的 函数 this_player() 会传回走进房间的这个人, 或是npc。 this_player() 的概念容 後再谈, 你现在只要记住这个函数在每个生物走进来时都会被呼叫一次就可以了(这里的 意思是,如果你要对进入房间的玩家做一些动作,比如弄晕他 或给他中毒等时,那么就在init()函数里对 this_player()作操作就可以了,this_pla yer()就是触发init()函数的玩家。---lnwm注。) 在 init() 中最常见的的函数莫过於 add_action"function", "action");了 它的作用 是在进来的生物身上添加上一个指令 (注意, 系统只认指令的第一个字), 并在玩家下达 这个指令时去呼叫 "function" 中所给定名称的函数 举例而言, 如果我们写了这样的 init(): init() { add_action("do_climb", "climb"); } 当玩家走进这个房间时, 系统会帮他多出 climb 这个指令 当他下达(如果这个指令不 是cmds,那么在没有add_action的房间里就不会存在。)了 climb tree 这个指令时, 系 统会去寻找 do_climb() 这个函数, 并(do_clone()这个函数当然是你去写了---lnwm注 。)加以执行。 同时, 系统会将玩家所输入的 "climb" 这个指令後的所有文字以字串 类型的变量传给do_climb() 你可以将 do_climb 这个函数宣告为int do_climb(stri ng arg)(注意,这个带函数类型和参数类型的函数才是完整写法。---lnwm注。)这样一 来, 当玩家下达 climb tree, 或是 climb the red wall 这种指令时,"tree" 或是 "t he red wall" 就会被存进字串变量 arg 之中供你处理 。 由 add_action() 所宣告的函数必定要是一个整数类型的函数, 因为系统会根据这个函 数的传回值采取不同的动作 如果你传回的是 0,那麽系统会认定这个命令与你这个处理 函数无关, 而对其他也有 climb命令的函数一个一个尝试著去执行, 直到所有的 climb 命令都传回 0。(这段话很重要,当你要玩家在使用某个cmds时出现其他的用处,比如 你要玩家eat一个果子不仅仅涨食物,而且涨别的东西,那么你就要在果子上用 add_ac tion来给eat赋予更多的内容,然后在你处理eat的那个函数最后 return 0;这就是告诉 系统,我这个指令并没有完,系统就会寻找本来的那个 eat 命令的功能函数,也就是加 上食物。这称为重载。----lnwm注。) 若你的函数传回值为 1, 表示这个指令已经由你所写的函数处理掉了, 系统不会再尝试 著往下面继续寻找其他的 climb 指令 (给出一个例子。 void init() { add_action("do_eat","eat"); } int do_eat( string arg ) { object me; me=this_player();//触发指令的玩家。 if (arg!="果子") return notify_fail("你要吃什么?\n"); //notify_fail() 就是 return 0 me->set("str",1000); //这里是你要使这个果子与众不同的地方。 return 0; //这里使这个果子和其他食物一样仍然可以涨 //食物,你可以试试 return 1 //那样你发现你的果子吃了不涨食物。 } ----lnwm 注。) 在你的函数侦测到玩家输入的变量有问题时 (例如你要他们 climb tree, 但他们却输入 了一些错误的指令如 climb three 之类的) 想给他们一些特别的错误讯息时, 你可以 用notify_fail(string errormsg)来写这个讯息, 如notify_fail("climb what?\n"); notify_fail()这个函数也是 int 类型, 固定会传回 0, 所以我们最常用的写法是: if (条件不合) return notify_fail(错误讯息); if (另一个条件不合) return notify_fail(另一个错误讯息); ............................. <所有可能导致错误的输入都过滤光了> 开始真正干活的部份.... return 1; (可以参考一下上面的例子。---lnwm注。) [refresh() 函数] refresh 呼叫的时机是系统定时 (约每半个小时一次) 呼叫主 要的用途在於房间中npc 物品的再生 如果你改写了 refresh()函数, 千万记得要串接 ::refresh(), 否则可能导致严重的後果 (门一打开就不会自动关上, npc打死後也不会 再生...由於有 set("objects", (["name1" : "file1", "name2" :"file2",... ]) ); 这种写法的存在 (在 create() 里面这麽写) 所(set("objects",([]))用来在房间里放 东西,比如npc或物品什么的。---lnwm注。)以 refresh() 被用到的机会不多了 (因 为 set("objects", ) 这个写法可以帮你作出自动定时refresh npc 物品) 但是在制作 一些必须定时回复原始状态的小机关时, 仍然有必要用到这个函数( refresh 函数用到 的地方并不是很多,因为它太僵硬,要十分固定的半个小时(准确的是24分钟)才被调用 一次,可以用在一些周期较长的秘密中。---lnwm注。) 有关房间的部份就写到这里, 接下来是物品 [ 物品的制造] 要制作物品, 首先必须 inherit OBJECT; 理由与做房间时必须inherit ROOM 一样 O BJECT 是最基本的物品, 如果你要做的东西是武器 防具 地图等, 你必须 inherit W EAPON, ARMOR, MAP 等等才能获得这种类别的物品所拥有的特性 物品的重要函数只有 create() 与 init(), 作用与 ROOM 中的同名函数大致相同 [create() 函数] 要写 create() 函数, 最好的方法是拿现成的同类物品来修改 因为不同类的物品往往 可以 set 不同的属性, 而且特性极多, 有重量价格 攻击力(武器) 防御能力(防具) 使用寿命(火把)等等 很难记得完整, 所以我劝你找一个较完整档案来修改 [init() 函数] 与 ROOM 中的 init 函数类似, 但是被呼叫的时机多了许多, 共有下列的几种情况: 1. 物品摆在房间中, 有一个玩家走进来 2. 一个物品突然出现在某个玩家所在的房间中 3. 一个物品突然出现在某个玩家的物品栏中 物品的 init 函数大多还是用在写 add_action 上面, 这些 action 会 生效的场合归结起来很简单, 就是: 「玩家用 l 或是 i 指令看得到这个物品的时候」 同一个房间中他人或npc身上的东西时不算, 装在袋子的东西不算 这点要注意一下(其 实既然都是object,那么实际上房间和物品并没有本质的不同,因此在房间上的 creat e() init()函数用法和房间相同,正如前面说的,可能有一点区别的就是物品的init() 函数被触发的机会多一些而已。---lnwm注。) [npc] 简单的npc很好做, 连 init 都不用写, 只需要写 create(), 唯一的问题是属性太多了 , 要一一理解得花上相当的时间才行 会做复杂动作的npc则需要相当的技巧, 并且了解有哪些变量可以被拦截下来改写利用 等你有一定的程度时, 再来找个npc参考参考较好 npc要 inherit NPC 也没有 refresh() 这个函数 [程序必须的概念] 你必须了解, 在 LPC 中最重要的一个概念是对象(object) 当你想做任何动作时, 都要 考虑到这个动作是哪一个 object 所做的, 不然很容易导致错误 LPC 的语法并不严谨 , 有些场合为了省事可以将函数是由哪个对象所作的省略掉, 例如我们在 create() 函 数中最常看到的set(), 事实上最严谨的写法应为 this_object()->set() write() 则 为 this_player()->write()(对,我们必须弄清楚,object 的概念是整个lpc的核心。 往深一点说,每个object无非是一个包含了数据结构和内部外部函数的对象,他的数据 结构决定了它的属性,比如房间的 名称,玩家的skills等等,而他的外部函数提供了修改这些数据结构的方法,比如 set _skill("xkx",1000)比如 set("title","大侠")这里set_skill()和set()函数就是obje ct和外部的接口。数据结构和接口函数就拼出了一个object。---lnwm注。) 说这麽多只是为了强调一件事: 你能抓出一个物品的 object 变量就能让他干一切他所 能做的事 [this_object() 与 this_player()] 这两个函数是系统所提供的函数, 也是最最好用的两个函数 在你写作一个对象 (房间 物品...etc.)时, this_object() 表示自己这个对象(讲清object的结构后,你必须清 楚object的所谓封闭性。当我们在程序里写 set("name","长剑")时,实际上是this_ob ject()->set("name","长剑")的简略写法。这样set("name")的不是其他的东西,每一个 object有自己独立的数据结构,它能与其他object严格区分开来。即使是同一个长剑程 序,当你clone了两把时,在内存中就有两个完全独立的长剑object,你对其中一把做的 任何处理如改名,改威力完全不影响另一把的数据,这就是object的封闭性。---lnwm注 。) this_player() 则比较复杂, 它会传回一个属於玩家类型的对象这个玩家在 init 中就 是触发 init 的那个玩家this_player() 会跟著函数呼叫一直传递给所有被 init 呼叫 的函数, 包括 add_action 中所定义出来的函数, 在这些函数中, this_player() 就是 表示做动作的那个人 [present() 函数] 常常, 我们只知道一个对象的名字, 却不能用个 object 类型的变量指向它 object= present(string "id",object env) 函数在此时就可以派上用场, 你给定你要找的对象的名字,与它的所在地 (某个房间或某 个人), 函数就会传回他所找到的对 简单的想, present 函数其实就是在一个房间里找出某个名字的物品的函数 它是同类 型找物品的函数中最有用的一个, 其余的函数还有find_player(), find_living() 等等 (这里再强调一次,名字不过是一个object的数据结构中的一个部分,单单一个名字没有 任何意义。present,find_living,find_player函数就是从名字(id)找到这个object,有 了这个object才好对它做操作。在mud里你 help present;help find_living;hrlp f ind_player能看到这几个函数的详细用法。 ---lnwm注。) [environment(), first_inventory(), next_inventory(), all_inventory()] 这一组函数跟对象所处在的位置有关 environment(object ob)传回了对象 ob 所处在 的地点, 例如 ob 是个玩家或生物, 那麽这个函数会传回 ob 所在的房间; 如果 ob 是 个物品, 那麽传回的就是携带著ob 的生物, 或是 ob 所在的房间 (如果没有任何人带著 它) first_inventory(object ob) 所传回的是 ob 中的第一个对象,如果 ob 是房间, 则传 回第一个物品或是生物, 如果 ob 是生物, 则传回他身上所带的第一个物品 next_inventory(object ob) 通常跟著 first_inventory() 一起使用 它的功用是传回 ob 的下一个物品, 在同一个environment 中 all_inventory(object ob) 类似於 first_inventory(), 但是它所传回的是包含了所有 物品的一整个阵列(上面谈的是环境问题,一个object(除房间外)都要有自己的环境,所 谓环境就是这个object在什么地方放着,是一间房间还是一口箱子还是一个人身上,环 境通常是另一个object。比如物品A和B放在一个人M身上,那么上面的函数就给出了它们 的关系 M=environment(A); M=environment(B); A=first_inventory(M); B=next_inventory(M); A=all_inventory(M)[0]; B=all_inventory(M)[1]; ---lnwm注。) [更进一步的提示] LPC 的函数群有三个, efun, lfun, simul_efun 它们提供了绝大部分的功能。 [关於输出输入讯息的各个函数的提示] can_read_chinese printf, sprintf scanf, sscanf write, say, shout tell_object, tell_room [关於对象操作的函数] clone, new (变出新object ) destruct, remove (摧毁object ) move, move_player, move_around (移动object ---lnwm注。) (这些函数不可能一一讲解了,你可以在mud里用help <函数名> 来看(英文呦),然后再放到程序里自己试试。 ---lnwm 注。) |
|