这个是我最近接触音游后(6/2 起),觉得可以写进 mud 的东西。
它的 demo 类似底下影片
https://www.youtube.com/watch?v=5zYmrBORZKs
以 mud 来说,就类似在一座城镇里头,的某一个房间,里面会看
到两只 npc 在做交谈的动作,我的想法是:
道具店
这里是位于城镇中心,喷水池广场旁的的一间小小的道具店,
里头贩售著一些好用的东西,可以来看看喔(list)。
明显出口有: south.
(listen)两位村民正在这里交谈著。
>
我希望玩家看到这样的画面,会想下 listen 指令,下了之后,
会看到如下的画面呈现
> listen
冒险者A: 听说这里有卖小镇地图,我们进去看看好不好
冒险者B: 有需要买吗, 你不是早就把地图都背下来了?
冒险者A: 就买来研究看看咩,说不定会发现什么隐藏入口
冒险者B: 厚~ 有那种东西的话早就一传十、十传百了
冒险者A: 不管, 我就是要买......什么! 一张地图要5000!?
冒险者B: 你今天才知道喔? =.=
[ 你获得了 0.1% 的经验值。 ]
那上面要怎么写呢?
首先,跟道具店这样的房间有关的对话,假设都储存在一个叫
grocery.o 的物件储存盘内,那么它的格式就类似底下
mapping talks=([
"一个适当的key名":@LONG
冒险者A: 听说这里有卖小镇地图,我们进去看看好不好
冒险者B: 有需要买吗, 你不是早就把地图都背下来了?
冒险者A: 就买来研究看看咩,说不定会发现什么隐藏入口
冒险者B: 厚~ 有那种东西的话早就一传十、十传百了
冒险者A: 不管, 我就是要买......什么! 一张地图要5000!?
冒险者B: 你今天才知道喔? =.=
LONG
,
.
.
]);
(资料不是透过宣告建立的,真正只有一行宣告 mapping talks)
之所以使用 mapping 而不使用 array,是因为这样要做对话资
料的指定动作(比方删除),会比较方便而且精确。
我目前是以 time() 的字串( ""+time() )当 key 名,当我这样
做的时候,在 mud 内透过指令去编辑对话资料,按 . 储存时,
就能以储存当下的 ""+time() 做为 key 名,甚少会发生 key名
重复的情况,还能依 key 名看出建立的时间。
从这里来逆推,建资料的指令可以这样写
> listen -log grocery
请开始编辑, 按 ~q 离开, 按 . 结束
===================================================
后面指定 grocery,它就会将编好的东西假设叫 talk_str:
if(ob=find_object(TALK_DIR+"grocery"))
ob->log_talks(talk_str);
而 log_talks 的函数大致长这样
int log_talks(string str)
{
string times=""+time();
// 如果真的有重复 key 就不储存
if(!undefinedp(talks[times])) return 1;
talks[times]=str;
save_object(TALK_DIR+"grocery");
return 1;
}
这样就能储存对话资料。
那现在假设我们储存了很多笔 grocery 这类房间可以用的对话
,那很自然的会写提取对话的指令,其提取到的对话是随机的,
那可以这样写
// user = 下指令的玩家
void listen_talks(object user)
{
int r,s;
mixed tmps=({});
if(!user) return 1;
tmps=keys(talks);
s=sizeof(tmps);
r=random(s);
// 依分行符号 \n 将字串拆成阵列 tmps
tmps=explode(talks[tmps[r]],"\n");
call_out("dump_talks",1,user,tmps,0,s);
return ;
}
void dump_talks(object user,mixed tmps,int i,int s)
{
if(!env) return ;
tell_object(user,tmps[i]);
i=i+1;
if(i>s) // 代表对话结束
{
i=user->query("exp_up");
user->add_exp(i/1000); // 0.1%
tell_object(user,HIW"[ 你获得了 0.1% 的经验值. ]"NOR"\n");
return ;
}
// loop call_out
call_out("dump_talks",1,env,tmps,i,s);
return ;
}
以上是这东西的简单写法,给 sanc 用的部份我已经写得差不多
了,预计八月就会正式实装,只是我不是用 call_out,而是用
我以前提过的 times_check 系统,就是用 heart_beat 心跳的
方式。
各家写法不同很正常,这没有说一定要怎么写。
sanc 的 listen 指令实际说明长底下这样
listen 指令语法:
============================================================
listen -export 房间档->资料档的指向列表
listen -add 房间档 = 资料档 设定房间档指向特定资料档
listen -del 房间档 删除房间档的指向设定
listen -check 房间档or资料档 列出资料指向设定状况
listen -log 资料档 储存某一资料档的对话资料
listen -here 资料档 储存某一资料档的此地限定对话
listen -clean 资料档 编号 清除某一资料档的对话资料
listen -list 资料档 列出某一资料档的对话资料
============================================================
上面都是给 wiz 使用的指令格式,玩家则只要下 listen 即可。
顺便藉这个例子来说明,我想大家都同意,要把看到的什么
东西,写进 mud 里头,对于像我这种程度的 mud coder 来
说并不难,这样的人很多。
但是对 coder 来说我觉得最头痛的还是内容的扩充、扩充到
一定程度后的资料管理(增删改)、以及后续如何不间断地再
扩充其内容、..
内容才是重点。
我个人是先玩了音游,然后觉得这东西可以写进 sanc 里面
,然后多年经验下来,我第一优先思考的就是:
我有没有能力及时间,建立足够的资料量?
之后,我觉得我应该有能力及时间,那接着,我才开始写这
个系统,包括储存用物件、listen 指令等。
反之,如果我觉得我没有这个能力,或是没有这个时间,那
我就不会去写,因为写了也没意义,它能 work 但是内容会
很贫乏。
那我评估“我可以”的其中一个依据,是网络上能找到很多
东西,基于 sanc 的开放性风格,很多找到的东西都能拿来
用,所以我预期资料量是不会太低的...大概。
然后我的想法是,只要对话是有内容的,那对话本身就不是
重点了,玩家固然会听看看对话在说些啥,但重点仍会放在
听完对话后能取得的东西上面,也就是说,只要先确保对话
是有内容的,然后这时对话就变成只是一个过程了,讲白一
点今天就是要送玩家 0.1% 经验值,但总是要透过一些机制
来给予,才会比较像是在玩游戏。
那,可以将这种东西的游戏性设计到什么程度呢?
比方,我可以在一百则对话里面,穿插一则重要的情报,例
如这座小镇真的有一个隐藏出口存在,则玩家如果幸运地听
到这则对话,他就有机会发现到这个隐藏出口。
(因为对话基本上是随机的,不过,这同样是各家写法)
我想说的就是,搜集资料、消化资料(才能变成可用的东西)
、思考资料(夏天很容易想到脑袋发烧发热)、....这些是很
烦人的,反过来说,这种事有其它人做,并且将我要的东西
依我要的格式(这里的格式指的是"至少要对话六句"这种)提
供给我时,要建多少资料、或是要让资料做怎样的呈现,都
不会是问题。
分工的重要性,就在这个地方,最好 coder 跟非 coder 角
色要分开,各司其职。
校长兼撞钟,开发效率一定差。
补充一下,除了 loop callout,还有另一种也算常见的写法
for(i=1;i<=s;i++)
call_out("dump_talks",user,i,tmps[i-1]);
一次做 n 个 call_out 让它们前后都差 1 秒的时间。