MUD游戏开发进阶:谓词(verb)语法指令系统介绍

本文节选自《LPC语言基础教程:从零学习游戏开发》,版权归@mudren,欢迎转载,但必须注明来源(mud.ren)。

除了介绍的通过enable_commands();add_action("command_hook", "", 1);实现的指令系统外,游戏驱动还提供了一套完整的谓词(verb)指令系统,这是一套简单而强大的自然语言解析器。

我们以look指令为列,玩家可能有以下的行为:

look sb
look at sb
look in sth
look on sth
look sb on sth
look sth in sth

在我们的指令模式下,需要用sscanf()处理玩家的输入,然后做判断并处理,这套实现方式大家都很熟悉了,看直来简单,但并不灵活,得在代码实现所有可能的匹配。而使用谓词系统,我们有不一样的实现方式,它更灵活,更强大(嗯……代码写的也会更麻烦)。

在我们自然语言中分分主谓宾定状补等,在游戏指令中我们主语就是指令的执行者,也就是玩家自己,直接省略,核心就是谓语verb和宾语(动作对象),另外还有介词修饰。如look at sb,谓词look,介词at,对象宾语sb,而宾语对象在某些指令中还分直接对象(the direct object)和间接对象(the indirect object),如read page in book,直接对象是page,间接对象是book,从语义上理解你要读的是page,不是book,不过MudOS并不是根据真实语义来的,而是简单的优化为指令中第一个标记是直接对象,第二个标记是间接对象。

在游戏开发中我们要实现的除了主语对象(玩家)、宾语对象(指令目标)外,重点是需要实现指令对象(the verb handler),另外还有主控对象(master ob)提供必要的apply方法。除主控对象外,所有需要调用verb的对象都必须调用parse_init()方法初始化。

指令系统的谓词和规则通过parse_add_rule外部函数增加在指令对象中,如:

parse_add_rule("look", ""); // 不指定目标
parse_add_rule("look", "STR"); // 查看字符串目标
parse_add_rule("look", "OBJ"); // 查看对象目标
parse_add_rule("look", "on OBJ"); 
parse_add_rule("look", "at OBJ"); 
parse_add_rule("look", "in OBJ"); 
parse_add_rule("look", "OBJ in OBJ"); 
parse_add_rule("look", "at OBJ on OBJ"); 
parse_add_rule("look", "at STR on OBJ"); 

以上规则中的大写字母OBJSTR是驱动语法限定的标记(token),可用token有:

LIV - one living thing
LVS - one or more living things
OBJ - one object
OBS - one or more objects
STR - a string of characters
WRD - a generic thing that may be a string, object, or living thing

请注意:LIVLVS是根据对象的apply方法is_living判断的(1是0否)。在一条规则中不要超过2个标记,且只能存在1个复数标记。

我们可以用parse_add_synonym为谓词指令增加同义词(别名),如parse_add_synonym("kan", "look")

语法规则的介词并不是随意都可以,而是主控对象中的apply方法parse_command_prepos_list()限定,只能使用这个方法返回的数组中的介词。

在谓词指令对象中,还需要实现can_*()do_*()这二类apply方法,实现指令权限的校验和执行指令的操作内容。另外还需要在对应的对象中实现direct_*indirect_*这二种apply方法实现执行结果相关处理。这里*代表具体verb指令的rule,根据不同指令和规则自动替换,如:can_lookcan_look_at_objdo_look_at_obs_in_obj

需要注意的是,对复数标记规则的can_*方法一样是使用单数apply方法,而复数标记规则的do_*方法是使用复数形式。

需要说明的是,自然语法中针对容器有以下二种常用情况:look in sthget all from sth,驱动内置了二个apply方法来控制对象是否可见和是否有权访问:

  1. inventory_accessible()
  2. inventory_visible()

比如,对一个上箱的箱子,是即不能look in也不能get from,而对一个上锁的透明玻璃盒子,你可以look in但不能get from

下面,我们看看一个谓词执行的顺序:

  1. 调用指令对象中的 can_look_obj_in_obj()
  2. 调用 direct_press_obj_on_obj()
  3. 调用 indirect_press_obj_on_obj()
  4. 调用指令对象中的 do_look_obj_in_obj()

这些指令都有以下参数:

  1. object direct_object
  2. object indirect_object
  3. string direct_object_as_entered_on_command_line
  4. string indirect_object_as_entered_on_command_line

请注意:以上几个方法执行都是前面的成功才会继续执行后面的,而且顺序只是简单演示,并不是唯一的,事实上,如果没有找到以上apply方法时还会调用更多可能存在的apply:

  1. can_look_obj_in_obj()
  2. can_look_obj_word_obj()
  3. can_look_rule()
  4. can_verb_rule()

can_look_obj_in_obj()没找到时会依次调用后面存在的apply方法。

玩家对象在执行指令时会调用parse_sentence()外部函数做语法解析。和add_action()执行指令类似,parse_sentence()在执行后也会返回不同的结果:

1   谓词指令执行成功
0   未找到谓词指令
-1  语法规则不匹配
-2  语法规则匹配,但是无权执行
字符串 语法规则匹配但执行失败的原因(如:目标不对)

需要注意的是,执行错误字符串是主控对象中的apply方法parser_error_message返回的错误内容。

提示:mixed parse_sentence(string cmd, int flag)方法第二个参数flag为1则开始调试模式,会显示verb查找顺序。

下面,我们直接上代码演示,把上面介绍的理论转为实操。

京ICP备13031296号-4