3) 理解篇


  編程終究是一件技朮性的活,我們總不能一直通俗下去吧!在這一章,我們該開始回過頭來,對我們工作系統里的一些基本的概念進行一一的理解,當然要接觸一些深一些的概念了。
  通常我們在大陸所見到的中文MUD大多是LpMUD,一般是一些角色養成型的游戲模式。LpMUD使用Unix的指令和文件結構。而很多版本也是運行在Unix環境下(目前WINNT的版本也不少)。

&&--理解LPC
  LPC是什么東東?就是寫LPMUD程序的C語言啦!它看起來和一般的C語言區別不大,語法基本一樣,只是簡單得多啦,可以說,它是我所見到的最簡單的一種C語言。他們的根本不同點在于,Lpc程序是編寫一個一個的"Object"。 這有什么區別呢?一般的程序是在執行過程中,通常有一個明顯的開始和結束。程序從一個地方開始,然后順序執行下去,到了結束的地方就中斷了。而Lpc的Object不是這樣的。Lpc的Object可能沒有明顯的開始和結束的標志,它可能永遠在工作。在有些游戲中,整個游戲包括Driver和游戲世界都用C寫好,這樣處理的速度能快一些,但是游戲的擴充性很差,巫師們不可能在游戲進行中添加任何東西。 LpMud則相反。Driver理論上應該和玩家所接觸的世界几乎沒有任何直接的聯系。游戲世界應該是自己獨立的,而且是“即玩即加”的。舉個例子,我們在玩三國志系列時,在你沒能取得赤壁之戰的勝利之前,你是不可能去打六出祁山的,甚至連四川都去不了,但是在MUD游戲中,卻可以任由玩家選擇,而且巫師也可視其需要,在任何時候、在任何地方再加上一場另外的戰役。所以,在你寫完一個Lpc的文件時,它就存于主機的硬盤上。在游戲進行中,當需要整個Object時,Driver就從硬盤中讀入這個文件,然后放在內存中,一個特殊的函數被調用來初始化這個Object的一些變量。當然這要建立在這個文件沒有任何錯誤的基礎上。否則,輕則出錯,重則宕機。
  在這里,既然我們不斷談到“變量”和“函數”,則有必要談談在LPC中它們之間的關系。變量就是一個能夠變化的值,函數通常是用來操縱那些變量的一段程序。Lpc 的Object就是一些變量和函數的組合。在前面我們講過,Object沒有開始、也沒有結束的地方,更不會有一個特別的地方讓Driver去執行它。那當一個Object如果要被內存中的另一個Object調用時將怎么辦呢?Driver一般會去找這個Object的那堆變量放在哪里。如果這些變量沒有值,那么Driver就會調用一個特定的函數,即create()來初始化這些變量。(看到這里,你可以回去看看第二章的有關部分)。
  但是要強調一點,create()并不是 Lpc代碼開始執行的地方,只是大多數的Object從這里開始。事實上,create()是可以不存在的。如果這個Object不需要對變量初始化,那么create()可以不存在。那么這樣的Object開始執行的地方就完全的不同于一般的Object,它可以從任何地方開始。所以在Lpc的Object中,函數的排列順序是無所謂的,隨便那個排在前面對這個Object的特性沒有影響。只不過在各個MUD中的巫師品質要求中稍作規定而已。

&&--理解Object
  既然LPC中最不同的就是Object,因此,當你想在程序中設計任何動作時, 都應當要考慮到這個動作是哪一個 object 所做的, 不然很容易導致錯誤。LPC 的語法并不嚴謹, 有些場合為了省事可以將函數是由哪個Object所做的省略掉, 例如我們在 create() 函數中最常看到的 set(),事實上嚴謹的寫法應為this_object()->set()。因此我們首先要學習的就是如何利用系統提供的函數,去正確快捷地尋找Object。
  MUD系統為我們提供了兩個最好用的兩個函數this_object()與this_player(),在你寫作一個對象(房間、物品......)時,this_object()表示自己這個對象(房間、物品......),也就是你寫的這個文件本身啦。在這里,要提及一下object的所謂封閉性。每一個object有自己獨立的數據結構,它能與其他object嚴格區分開來。即使是同一個長劍程序,在同一個房間里你復制了兩把時,在內存中就會有兩個完全獨立的長劍object,你對其中一把無論進行改名、改威力都不會影響到另一把的數據,這就是object的封閉性。而this_player()則比較復雜, 它會傳回一個屬于玩家類型的對象。這個玩家在init中就是觸發init的那個玩家。this_player()會跟著函數呼叫一直傳遞給所有被init呼叫的函數, 包括add_action中所定義出來的函數, 在這些函數中, 它又表示做動作的那個人。
  除此之外,當我們只知道一個對象的名字,而不是它的文件名時,是無法用個 object 類型的變量指向它,這時我們就要用到[present() 函數]
  用法:
    object=present(string "id",object env)
  函數在此時就從名字(id)找到這個object,有了這個object才好對它做操作。就可以派上用場。簡單的想, present 函數其實就是在一個房間或者一個人身上(房間與人對于程序其實是一樣子的)里找出叫某個名字的物品的函數,它是同類型找物品的函數中最有用的一個, 其余的函數還有:find_player(), find_living() 等等
  再看一組很實用的函數:[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 的下一個物品。
  很簡單, all_inventory(object ob) 所傳回的是包含了所有物品的一整個陣列。一個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];

&&--理解Efun
  從上面你也許可以看到了,象environment()這樣的各種函數,也許會在你編程時時不時地發現,而且用的地方特別地多,最常見的就是this_player()、this_object()、還有strcmp()、implode(),左看右看找不到在哪里定義的,而你就算是找遍你下載下來的單機版的所有文件,都找不到這些函數,其實它們就是efun。efun就是MUD外部定義的函數,也就是externally defined function 的縮寫。是由Mud Driver定義好的。它是以計算機直接能理解的二進制的形式存在著的,所以它們執行起來要比一般的Object帶有的函數速度快的多。而對于Object內部定義的函數,通常叫作lfun(local function)。一個巫師的主要工作也就是編寫一些lfun組成的Object。
那么為什么要創立efun呢?
  1) 處理一些很常用的、經常會有許多函數會調用的。
  2) 處理internet socket的輸入輸出。
  3) 以及一些Lpc很難處理的事,因為畢竟Lpc是C的很小的子集。
  efun是用C寫好的,內嵌在Driver里面的。在Mud起來之前,和Driver一起編譯好的,它們的調用和你寫的函數的調用方法是完全一樣的。總的來說,你只需要關心:它需要傳入什么參數、將會返回什么的東西就行了。
  通常在一個Mud里面,你可以在類似這樣的/doc/efun的目錄底下找到有關efun的說明和幫助,或者直接用help <efun名>指令就可以得到幫助。因為efun依賴于你所在的Mud的Driver,所以不同的Driver帶有的efun區別很大。不要想當然地將別的MUD中的經驗帶到這里來,也不要在我們這里理解了某個efun,就自以為什么都懂了。對于一個新的巫師不說,你只需要簡單地理解,并會使用一些常見的efun就行了