[閒聊] 副本系統

看板mud (網路地下城/文字遊戲)作者 (小太保)時間11年前 (2014/04/16 14:39), 11年前編輯推噓4(400)
留言4則, 4人參與, 最新討論串1/2 (看更多)
這個最近剛完成,分享一下。 首先,取遊戲裡頭某個現成區域的某幾個房間,例如取底下 011 014-015 | | 012-013 011 是「起點」,015 是「終點」,它們位於同一個區域目錄,而且 它們只是 001~100 的「其中一段」,比方 011 可能還有往西的出口 、015 可能還有往東的出口等等。 我定義所取出的這一段區域為「副本基底區域」。 然後我們寫一個 instance_room.c 當做這個副本的專用房間,它會 定義一些東西、宣告一些東西、...。有了這房間,這時就可以這樣 做(比方定義 INSTANCE_ROOM = 這個副本專用房間).. // files = 所取出來的房間檔名 foreach(room_file in files) { iroom=clone_object(INSTANCE_ROOM); iroom->set("origin_file",tmp); oroom=find_object_or_load(room_file); keys_data=keys((mapping)oroom->query_ob_data()); foreach(tmp in keys_data) iroom->set(tmp,oroom->query(tmp)); irooms[tmp]=iroom; } 上面的意思就是說,以 011 為例,「區域的 011(也就是 oroom)」 的所有資料,都複製到「副本的 011(也就是 iroom)」,然後再讓 一個 mapping irooms 用來儲存對映關係: irooms["/x/xxx/room/011"] = 011 的 iroom; irooms["/x/xxx/room/012"] = 012 的 iroom; . . 但是以區域的 011 來說,它可能有兩個出口 "south" : "/x/x/room/012", "west" : "/x/x/room/010", 因為副本的 011 資料是 cp 自區域的 011,所以這時假如在副本 的 011 往南走,會走到「區域的 012」,而不是「副本的 012」 ,因此,做完上述的 foreach 後,還需要再做底下的 foreach foreach(room_file in files) { iroom=irooms[room_file]; keys_data=keys((mapping)iroom->query("exits")); foreach(tmp in keys_data) { if(irooms[iroom->query("exits/"+tmp)]) iroom->set("exits/"+tmp,irooms[iroom->query("exits/"+tmp)]); else iroom->delete("exits/"+tmp); } } 上面的意思就是說,副本的 011,south 這個出口所接的 012 也 在副本區域的範圍內時(在 irooms 找得到 012),就把副本的011 的 south 改成「副本的 012」: 原本 "south":"/x/xxx/room/012" 改成 "south":(物件)副本的 012 而 west 這個出口所接的 010 並沒有在副本區域的範圍內,這時 則把 "west" 這個出口給刪掉。 這樣所取出的副本 011~015 就能確實「截頭去尾」。 要這樣改有個前提,就是要針對遊戲本身房間的移動做出相對映的 修正。例如遊戲一般是把 "south" 後面所接的東西預設為字串, 但是在副本區域裡面,"south" 所接的則是物件,因此要做相對映 的修改,例如.. 原本 exit_file=room->query("exits/"+exit); me->move_player(exit_file,MOVE_MSG); 改成 exit_file 以 mixed 而非 string 宣告 mixed exit_file; 則對 move 或 move_player 來說 exit_file 不管是字串或 是物件,一般都是接受的(它有內設判斷) 產生完副本房間後,要把玩家 move 到起點的方法如下.. ppl->move(irooms["/x/xxx/room/011"]); 到這裡,就能產生副本區域,以及把玩家叫進去副本區域。 這時要說明的是,某一段基底區域,可能同時為多個副本所使用, 因此副本的生怪一定不是套用基底區域的設定,而是由每一個副本 所控制的,這時就產生了一個 mapping 資料如下.. mapping instance_mobs=([ "/x/xxx/room/011":(["/x/xxx/mob/bat1":1,"/x/xxx/mob/bat2":2]), "/x/xxx/room/012":(["/x/xxx/mob/bat1":2,"/x/xxx/mob/bat2":2]), . . ]); 也就是說可能我們某個副本的 011,會產生 bat1 一隻 bat2 兩隻 ,012 會產生 bat1 兩隻 bat2 兩隻,... 那麼,接著上面的 foreach,我們就可以讓副本產生的同時,生怪 亦完成: foreach(room_file in files) { iroom=irooms[room_file]; keys_data=keys(instance_mobs[room_file]); foreach(tmp in keys_data) { for(i=0;i<instance_mobs[room_file][tmp];i++) { mob=clone_object(tmp); mob->add("id",({"INSTACE_MOB"})); mob->move(iroom); } } } 上面的意思就是說,每一個 iroom 都按照 instance_mobs 的配置 ,把怪物 clone_object 出來後移到 iroom,配置幾隻就 clone出 幾隻、配置幾種就 clone 出幾種。 三個 foreach 分別做底下三件事 一、產生出基底區域的副本(clone) 二、將副本區域的出口設定完畢 三、讓每一個副本房間產生出怪物,並讓部份房間有心跳 最後,讓終點有心跳 irooms["/x/xxx/room/015"]->set_heart_beat(1); 為什麼要有心跳呢?比方說 015 是終點,我們希望玩家抵達終點 後會「依序觸發」底下效果 1.怪物大喊:人類!受死吧! 2.玩家把怪物殺光後,出現一隻 /x/xxx/npc/man.c 3.man 說:人類!感謝你解救了我們! 4.玩家拿到 10 金幣 5.終點出現 out 的出口 這時候 INSTANCE_ROOM 的 heart_beat 就可以這樣寫 // 副本房間的這個函數每一秒會被呼叫一次 int heart_beat() { int flags,t; object *usr,ppl,room=this_object(); string origin_file=room->query("origin_file); flags=(int)query("instance_flags"); t=time(); switch(origin_file) { // 針對終點這個房間 case "/x/xxx/room/015": // 去做 flags 流程判斷 switch(flags) { // 流程1: 玩家一開始進入的時候(房間有怪物) case 0: tell_room(room,"怪物:人類!受死吧!\n"); set("instance_flags",1); break; // 流程2: 副本裡的怪物全死光後 case 1: if(!present("INSTANCE_MOB",room)) { clone_object("/x/xxx/npc/man")->move(room); tell_room(room,"man:人類!感謝你解救了我們!\n"); set("instance_flags",2); } break; // 流程3: 終點的所有玩家拿到 10 金幣 case 2: usr=all_inventory(room); foreach(ppl in usr) if(userp(ppl)) ppl->add("gold",10); set("instance_flags",3); break; // 流程4: 房間出現 out 的出口比方接往 001 case 3: room->set("exits/out","/x/xxx/room/001"); // 所有流程結束,讓房間停止心跳 room->set_heart_beat(0); break; } break; } return 1; } 這是一種以 flags 做為流程控管方式的做法,每執行一個流程 ,就讓 flags+1,則透過 heart_beat 的定時被呼叫,就能確保 副本可依序執行每一個流程。基本上終點通常都需要心跳,其它 房間就視自己的需要決定要不要讓它們有心跳。 上面有一行 string origin_file=room->query("origin_file); 因為對該副本來說,每一個房間都是 INSTANCE_ROOM,所以我加 了一個 origin_file 欄位用來紀錄它是 base on 哪個房間,它 不一定要是 base_name(基底房間),只要是足以識別的欄位即可 這樣心跳函數就可以依 origin_file 這個欄位來做 switch,決 定哪個房間要執行什麼流程。 至於其它細節部份就與各遊戲的風格有關,這裡就不詳述。 這個副本系統已經在聖殿完成並實裝,聖殿採取的是以副本腳本 物件做為流程控制,跟上面以 INSTANCE_ROOM 的做法大同小異, 為方便解說所以舉 INSTACE_ROOM 的做法為例,其本質是差不多 的。目前已見到的幾個好處.. 一、任何遊戲內「現存的區域」都可以拿來當成副本區域   └包括國家區域、幫派區域、任務區域、.. 二、任何遊戲內「現存的怪物」都可以拿來當成副本怪物   └包括國家怪物、幫派怪物、任務怪物、.. 三、同一段區域可為不同副本共同使用 └線上遊戲《幻想神域》就有採取這樣的偷懶做法 四、同一基底區域可同時間產生多個副本 └這樣就不會產生玩家在同一個區域搶怪搶資源的問題 五、甚至同一個副本可依情況「取不同的區域」 └這在線上遊戲《暗黑破壞神2、3》有看過 六、以 heart_beat 搭配 flag 的寫法可辦到非常多的事 └亦即現存的其它遊戲的副本「理論上」都能參考到自己的 遊戲內,而且不會花太多時間去做設定就能辦到 原則上遊戲內的區域越大越多、怪物與 NPC 數量越多,能寫成 的副本數量也會越多。線上遊戲《幻想神域》的缺點,就是它們 一開始就是以「副本」為主,導致遊戲本身相當依賴副本的數量 、變化性、有趣度及升級實用度,而 mud 不一樣,mud 本身就 有相當數量的區域.. 數量 :區域多、怪物多、npc 多,可寫成的副本就多 變化性:只要讓每個副本取不同的區域,至少就有基本的變化性 有趣度:透過 random 讓玩家每次進同一副本都能有不同體驗 實用度:副本相對於 mud 只是一個「輔助系統」,例如可設定     玩家在副本裡打怪「可獲得更多金錢、經驗值」「可得 到在一般區域打不到的東西」這類的 以上,一點心得分享。 Laechan@sanc -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 210.61.157.53 ※ 文章網址: http://www.ptt.cc/bbs/mud/M.1397630376.A.BB6.html ※ 編輯: laechan (210.61.157.53), 04/16/2014 14:41:28

04/17 13:14, , 1F
這太專業了 有請msr大大
04/17 13:14, 1F

04/18 03:58, , 2F
推~
04/18 03:58, 2F

04/23 03:13, , 3F
你的 idea 真多, 推~
04/23 03:13, 3F

05/15 11:46, , 4F
!!!!
05/15 11:46, 4F
文章代碼(AID): #1JJYMeks (mud)
討論串 (同標題文章)
文章代碼(AID): #1JJYMeks (mud)