[閒聊] 副本系統
這個最近剛完成,分享一下。
首先,取遊戲裡頭某個現成區域的某幾個房間,例如取底下
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
04/17 13:14, 1F
推
04/18 03:58, , 2F
04/18 03:58, 2F
推
04/23 03:13, , 3F
04/23 03:13, 3F
推
05/15 11:46, , 4F
05/15 11:46, 4F
討論串 (同標題文章)
mud 近期熱門文章
PTT遊戲區 即時熱門文章