ES2 MUD LIB :: 東方故事二(ES2) 天朝帝國 mudlib 瀏覽展示

/doc/build/room

HOME :: doc :: build :: room
□ 如何編寫房間

如果你玩過 mud 的話﹐應該對 mud 組成一個世界的方式相當熟悉﹐沒錯﹐我們就是
用一個個的房間(room)來構成這個虛擬的世界﹐這裡我們稱「房間」只是傳統的 mud
的稱呼﹐因為早期的 mud 全名是 Multi-User Dungeon﹐Dungeon 就是「地牢」的意
思﹐因此用房間來稱呼地牢的最小單位而沿用至今。

當然﹐我們現在的 mud 早已脫離地牢的時代﹐而是一個沒有限制的創作空間﹐如何
將你想像中的人、事、地規劃成以房間為單位的舞臺﹐就看你的經驗和功力了﹐ 通
常 mud 並不硬性規定一個房間跟另一個房間的距離、位置等關係﹐而是根據實際需
要而決定房間的連結方式。不可否認的﹐大多數巫師在從事區域的創作時都會或多
或少加上距離跟位置的考量﹐但是在此仍然要提醒一下﹐為了位置跟距離的關係而
製作的房間是不具意義的。

建造房間﹐首先你必須先自己規劃一個「區域」的構想﹐我們通常把一個村莊、洞穴
、城鎮等地理上能夠明確區隔其範圍的單位稱為一個區域(area)﹐理想的區域設計﹐
是將所有該區域用到的程式放在同一個目錄﹐並且和其絕對路徑無關﹐可以隨時掛上
或搬移到其他的目錄下﹐因為區域是一個獨立的地理單位﹐具有一致的風格跟設定﹐
對於大多數 mud 的管理者而言都希望萬一因為區域的擴展而有需要移動或整理現有
區域時﹐能不必一個個地去修改檔案﹐能夠配合管理者要求的巫師﹐自然就會受到歡
迎。

當你有了構想之後﹐相信很多巫師會用「畫地圖」當成第二步﹐根據我的觀察﹐一個
用這種方法寫出來的區域通常會有太多不必要的房間﹐因為畫地圖時往往會不自覺地
將房間想像成「具有相同面積的方塊」﹐因而大量增加了房間的個數﹐你如果不信﹐
可以試著比較接下來我所建議的方法﹐看看哪一種方法會有比較多的房間。

我所建議的方法是「身歷其境」的創作方式﹐當你構想好一個區域大致的內容之後﹐
可以先寫出這個區域的入口﹐想像你是第一個「發現」這個地方的探險者﹐然後跟著
你的想像力所至﹐開一個出口到下一個房間﹐繼續探險﹐千萬要注意的是﹕

 ◎ 不要畫地圖﹐甚至想像中也不要用「空中俯瞰」區域的方式去設計你的區域﹐因為
    一個探險者是很難有機會俯瞰他發現的區域的﹐mud 在空間概念上較傾向於冒險遊
    戲而非角色扮演/模擬遊戲。

 ◎ 寫完一個房間的敘述再寫下一個﹐不要用 roommaker造出空房間之後就急著連接出
    口到下一個房間﹐因為這樣到最後往往你會需要苦思半天才能想出一些無聊的敘述
    填滿你用 roommaker造出來的一堆多餘房間﹐當你發現不知道要在一個房間的敘述
    裡寫什麼的時候﹐就表示這個房間其實是多餘的。

 ◎ 讓你的想像力引導你的創造力﹐不要因為覺得一座城堡「應該」有四座塔樓﹐而寫
    出你的故事中根本不必出現的場景﹐或者是因為一條長廊「應該」比國王的大廳長
    很多而用五、六個房間來「堆砌」這樣的場景﹐要記得每間房間的存在都需要耗費
    電腦的一些資源去儲存﹐浪費太多的資源在無意義的場景上將使你的區域在難以過
    關。

 ◎ 適當地使用「動作」連接你的場景﹐例如有一棵樹可以爬上去﹐你可能開一個 up
    的出口直接連到樹上的房間﹐這時候如果花點心思﹐讓使用者必須從環境的敘述中
    發現提示﹐如樹旁消失的腳印﹐樹皮新近剝落的痕跡等引導玩家發現一個 climb
    的動作﹐不是能使你的區域更有內容嗎﹖

不過要提醒一些懶惰的新巫師﹐就是像這樣 code 片段不要直接從一些範例中「
拷貝」下來﹐修改一下文字字串就到處適用﹐如何設計一個有趣的「動作」將是
你成為有實力的巫師最佳的鍛鍊方式。

好了﹐雖然上面囉裡囉唆地談了一大堆﹐想必還沒寫過區域的巫師可能已經一頭霧水
了﹐我們現在就來看看如何寫一個最基本的房間。

□ 簡單的房間

// example_room.c

void create()
{
    set("short", "茅屋中");
    set("long", "你現在正站在一間小茅屋中。\n");
    setup();
}

上面這就是一間最簡單的房間了﹐set() 這個函數用來設定物件的一些屬性﹐short
和 long 分別是房間的簡稱跟敘述﹐當玩家用 brief 模式移動的時候﹐只會看到沿途
所經房間的簡稱﹐而不會看到完整的敘述。在有些用 roommaker 產生的房間中﹐你也
許會看到 @LONG .... LONG 的字串常數表示法﹐這是一項由 LPC 的提供的功能﹐格式
是﹕

@tag text
tag

開始一段 text block 的 tag 可以為任何 identifier (和 #define 格式一樣)﹐
而結束 text block 的 tag 跟開始 tag 需一模一樣﹐且位於一行的第一個字元﹐
例如﹕

    set("long",
@TEXT This is a text example.
This is line #2.
TEXT);

相當於 set("long", "This is a text example.\nThis is line#2.\n");

setup() 是大部分物件在 create() 裡的最後一個步驟﹐這個函數通常由你 inherit
的物件所定義﹐呼叫這個函數可以讓你繼承的物件檢查你在 create() 裡所作的設定﹐
並補足不足或不合的部份。

在此要補充的一點就是﹕房間的 short 和 long 被玩家看到的過程是直接經由 "look"
這個指令處理的﹐和其他大多數物件、NPC 由 F_NAME 的 short()、long() 間接處理
的是有所區別的﹐你在房間中定義 short() 或 long() 無法改變玩家看到的敘述( 因
為房間根本不繼承 F_NAME )。


□ 其他房間屬性

在房間的 create() 中除了 short﹐long 之外﹐你還可以用 set() 設定許多其他的
屬性﹐以下是簡短的介紹﹕

 ◎ 出口

    set("exits/north", "/d/snow/inn_hall");

    可以設定房間 north (北方) 這個出口連向 /d/snow/inn_hall.c 這個檔案所定
    義的房間﹐不過通常我們不鼓勵直接指定絕對路徑﹐因為萬一所連接的房間檔案
    被搬動到其他地方﹐你就必須修改這個檔案﹐因此常用 __DIR__ 這個由 LPC 的
    preprocessor 提供的巨集表示這個檔案所在的目錄( 通常我們要連接的房間都
    在同一個目錄下 )﹐假設這個房間位於 /d/snow/ 下﹐上面的程式可以寫成﹕
    
    set("exits/north", __DIR__"inn_hall");

    當然﹐我們也可以一次設定很多個出口﹕

    set("exits", ([
        "north" : __DIR__"inn_hall",
        "south" : __DIR__"room2",
        "out" : __DIR__"subarea/entry",
    ]) );

    這裡的 ([ .... ]) 是 LPC 的 mapping 型態常數表示法﹐在後面的屬性設定中
    經常會用到。

 ◎ 物件

    set("objects", ([
        __DIR__"obj/stone" : 1,
        __DIR__"npc/guard" : 2
    ]) );

    上面的敘述會在這個房間裡放置一個由 __DIR__ + "obj/stone.c" 定義的物件跟
    兩個由 __DIR__ + "npc/guard.c" 定義的 NPC﹐想必你已經注意到在這裡我們用
    的是直接設定整個 objets 屬性的方式﹐而非 objects/xxxx﹐那是因為物件檔名
    中的 '/' 字元如果放在 set() 的第一個參數﹐會被解釋成屬性路徑﹐因此我們
    直接定義 objects 這個屬性為一個 mapping 型態的物件表﹐這和前面定義出口
    屬性是有所不同的。

    由 objects 所設定的物件會在 setup() 之後以及每次房間的 reset() 被呼叫後
    補齊所設定的個數。

 ◎ 細部敘述

    set("detail/樹幹", "這棵樹的樹幹又老又皺﹐像是八十歲的老太婆。\n");

    這個屬性可以設定房間敘述中的細部描述﹐當玩家在這個房間裡下

    > look 樹幹

    的指令時﹐就會看到你所設定的敘述﹐我們使用中文做為參考細部敘述的關鍵字﹐
    是希望讓謎題的難度更有挑戰性﹐而不要像以往一樣在房間敘述裡寫著「....樹幹
    (trunk)...」使得玩家看都不看就知道要打 look trunk。

至於其他的屬性設定﹐請參考有關房間屬性文件目錄下個別屬性的文件。

□ 基本變化

上面所介紹的設定方法應該已經足夠你寫出第一個「普通」的房間了﹐但是那樣的程度
對 LPmud 來說只能算是幼稚園的程度﹐因為如果你夠聰明的話﹐很可能只需要看幾個
別人寫好的範例﹐而不必看上面的說明就可以寫出一樣水準的房間﹐接下來我們所要介
紹的是一些常見的變化。


□ 進階變化
HOME :: doc :: build :: room