這個是我最近接觸音遊後(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 秒的時間。