6. 角色生成¶
在先前的課程中,我們已經確定了角色的外觀。現在我們需要給玩家一個創造機會。
6.1. 它將如何運作¶
當您登入時,全新的 Evennia 安裝將自動建立與您的帳戶同名的新角色。這快速又簡單,並模仿舊的 MUD 樣式。您可以想像這樣做,然後就地自訂角色。
不過我們會更複雜一些。我們希望使用者在登入時能夠使用選單建立角色。
我們透過編輯 mygame/server/conf/settings.py 並新增行來做到這一點
AUTO_CREATE_CHARACTER_WITH_ACCOUNT = False
執行此操作時,使用新帳戶連線遊戲將使您進入「OOC」模式。 look 的 ooc 版本(位於帳戶 cmdset 中)將顯示可用字元清單(如果有)。您也可以輸入charcreate來建立新角色。 charcreate 是一個簡單的指令,附帶 Evennia,它只允許您建立一個具有給定名稱和描述的新角色。我們稍後將對其進行修改以啟動我們的充電。現在我們只需記住這就是我們從選單開始的方式。
在_Knave_中,大部分的角色產生都是隨機。這意味著這個教學會很漂亮 緊湊,同時仍然展示基本思想。我們將建立一個如下所示的選單:
Silas
STR +1
DEX +2
CON +1
INT +3
WIS +1
CHA +2
You are lanky with a sunken face and filthy hair, breathy speech, and foreign clothing.
You were a herbalist, but you were pursued and ended up a knave. You are honest but also
suspicious. You are of the neutral alignment.
Your belongings:
Brigandine armor, ration, ration, sword, torch, torch, torch, torch, torch,
tinderbox, chisel, whistle
----------------------------------------------------------------------------------------
1. Change your name
2. Swap two of your ability scores (once)
3. Accept and create character
如果選擇 1,您將獲得一個新的選單節點:
Your current name is Silas. Enter a new name or leave empty to abort.
-----------------------------------------------------------------------------------------
現在您可以輸入新名稱。當按回車鍵時,您將返回到第一個選單節點 顯示你的性格,現在用新名字。
如果選擇 2,您將轉到另一個選單節點:
Your current abilities:
STR +1
DEX +2
CON +1
INT +3
WIS +1
CHA +2
You can swap the values of two abilities around.
You can only do this once, so choose carefully!
To swap the values of e.g. STR and INT, write 'STR INT'. Empty to abort.
------------------------------------------------------------------------------------------
如果您在此輸入WIS CHA,WIS 將變為+2 和CHA +1。然後,您將再次返回主節點檢視您的新角色,但這次交換選項將不再可用(您只能執行一次)。
如果您最終選擇Accept and create character選項,角色將被建立並且您將離開選單;
Character was created!
6.2. 隨機表¶
建立一個新模組
mygame/evadventure/random_tables.py。
由於 Knave 的大部分角色生成都是隨機的,我們需要在隨機表上滾動 來自_Knave_規則手冊。雖然我們新增了在隨機表上滾動的功能 規則教學,我們還沒有加入相關表格。
# in mygame/evadventure/random_tables.py
chargen_tables = {
"physique": [
"athletic", "brawny", "corpulent", "delicate", "gaunt", "hulking", "lanky",
"ripped", "rugged", "scrawny", "short", "sinewy", "slender", "flabby",
"statuesque", "stout", "tiny", "towering", "willowy", "wiry",
],
"face": [
"bloated", "blunt", "bony", # ...
], # ...
}
這些表只是從 Knave 規則中複製的。我們將各個方面分組到一個字典中
character_generation 將僅充電表與其他隨機表分開,我們也將
留在這裡。
6.5. 主節點:選擇要做什麼¶
這是第一個選單節點。它將充當一個中心樞紐,人們可以從中選擇不同的 行動。
# in mygame/evadventure/chargen.py
# ...
# at the end of the module, but before the `start_chargen` function
def node_chargen(caller, raw_string, **kwargs):
tmp_character = kwargs["tmp_character"]
text = tmp_character.show_sheet()
options = [
{
"desc": "Change your name",
"goto": ("node_change_name", kwargs)
}
]
if tmp_character.ability_changes <= 0:
options.append(
{
"desc": "Swap two of your ability scores (once)",
"goto": ("node_swap_abilities", kwargs),
}
)
options.append(
{
"desc": "Accept and create character",
"goto": ("node_apply_character", kwargs)
},
)
return text, options
# ...
這裡有很多東西要解壓縮!在 Evennia 中,依照慣例將節點函式命名為 node_*。同時
不是必需的,它可以幫助您追蹤什麼是節點,什麼不是節點。
每個選單節點都應該接受 caller, raw_string, **kwargs 作為引數。這裡 caller 是您傳遞到 EvMenu 呼叫中的 caller。 raw_string 是使用者為了_到達此節點_而給出的輸入,因此目前為空。 **kwargs 是傳遞到 EvMenu 的所有額外關鍵字引數。它們也可以在節點之間傳遞。在本例中,我們將關鍵字 tmp_character 傳遞給 EvMenu。現在,我們在節點中擁有了可用的臨時字元表!
請注意,上面我們使用
startnode="node_chargen"和元組startnode_input=("", {"tmp_character": tmp_character})建立了選單。假設我們將上述函式註冊為節點"node_chargen",它將開始被呼叫為node_chargen(caller, "", tmp_character=tmp_character)(EvMenu 將自行新增caller)。這是我們在選單啟動時將外部資料傳遞到選單中的一種方法。
EvMenu 節點必須始終傳回兩個內容 - text 和 options。 text 是什麼
當使用者檢視此節點時顯示給使用者。 options 是,嗯,應該是什麼選項
提出從這裡前往其他地方。
對於文字,我們只需獲得臨時字元表的漂亮列印即可。單一選項定義為 dict,如下所示:
{
"key": ("name". "alias1", "alias2", ...), # if skipped, auto-show a number
"desc": "text to describe what happens when selecting option",.
"goto": ("name of node or a callable", kwargs_to_pass_into_next_node_or_callable)
}
多個選項字典以列表或元組形式傳回。 goto 選項鍵很重要
明白。其作用是直接指向另一個節點(透過給出其名稱),或者
透過指向一個Python可呼叫(如函式)然後傳回該名稱。您還可以
透過 kwargs (作為字典)。這將在可呼叫節點或下一個節點中作為 **kwargs 提供。
雖然選項可以有 key,但您也可以跳過它而只獲取執行數字。
在我們的node_chargen節點中,我們透過名稱指向三個節點:node_change_name,
node_swap_abilities和node_apply_character。我們也確保傳遞kwargs
到每個節點,因為它包含我們的臨時字元表。
這些選項的中間僅在我們尚未切換兩種能力時才會出現 - 要了解這一點,我們檢查 .ability_changes 屬性以確保它仍然為 0。
6.6. 節點:更改你的名字¶
如果您選擇在 node_chargen 中更改您的姓名,這就是您最終的位置。
# in mygame/evadventure/chargen.py
# ...
# after previous node
def _update_name(caller, raw_string, **kwargs):
"""
Used by node_change_name below to check what user
entered and update the name if appropriate.
"""
if raw_string:
tmp_character = kwargs["tmp_character"]
tmp_character.name = raw_string.lower().capitalize()
return "node_chargen", kwargs
def node_change_name(caller, raw_string, **kwargs):
"""
Change the random name of the character.
"""
tmp_character = kwargs["tmp_character"]
text = (
f"Your current name is |w{tmp_character.name}|n. "
"Enter a new name or leave empty to abort."
)
options = {
"key": "_default",
"goto": (_update_name, kwargs)
}
return text, options
這裡有兩個函式 - 選單節點本身 (node_change_name) 和
helper goto_function (_update_name) 來處理使用者的輸入。
對於(單一)選項,我們使用名為 _default 的特殊 key。這使得這個選項
包羅永珍:如果使用者輸入的內容與任何其他選項都不匹配,則這是
將使用的選項。由於我們這裡沒有其他選項,因此無論使用者輸入什麼,我們都將始終使用此選項。
另請注意,選項的 goto 部分指向 _update_name 可呼叫函式,而不是指向
節點的名稱。重要的是我們要不斷傳遞 kwargs 給它!
當使用者在此節點寫入任何內容時,將呼叫 _update_name 可呼叫函式。這有
與節點相同的引數,但它_不是_節點 - 我們只會用它來_找出_哪個
轉到下一個節點。
在 _update_name 中,我們現在可以使用 raw_string 引數 - 這是使用者在前一個節點上編寫的內容,還記得嗎?現在它要麼是一個空字串(意味著忽略它)要麼是角色的新名稱。
像 _update_name 這樣的 goto 函式必須傳回要使用的下一個節點的名稱。還可以
可選擇返回 kwargs 以傳遞到該節點 - 我們希望始終這樣做,所以我們不這樣做
放開我們的臨時角色表。在這裡我們總是會回到node_chargen。
提示:如果從 goto-callable 回傳
None,您將始終回到最後一個節點 是在。
6.7. 節點:交換能力¶
您可以從 node_chargen 節點中選擇第二個選項來到達此處。
# in mygame/evadventure/chargen.py
# ...
# after previous node
_ABILITIES = {
"STR": "strength",
"DEX": "dexterity",
"CON": "constitution",
"INT": "intelligence",
"WIS": "wisdom",
"CHA": "charisma",
}
def _swap_abilities(caller, raw_string, **kwargs):
"""
Used by node_swap_abilities to parse the user's input and swap ability
values.
"""
if raw_string:
abi1, *abi2 = raw_string.split(" ", 1)
if not abi2:
caller.msg("That doesn't look right.")
return None, kwargs
abi2 = abi2[0]
abi1, abi2 = abi1.upper().strip(), abi2.upper().strip()
if abi1 not in _ABILITIES or abi2 not in _ABILITIES:
caller.msg("Not a familiar set of abilites.")
return None, kwargs
# looks okay = swap values. We need to convert STR to strength etc
tmp_character = kwargs["tmp_character"]
abi1 = _ABILITIES[abi1]
abi2 = _ABILITIES[abi2]
abival1 = getattr(tmp_character, abi1)
abival2 = getattr(tmp_character, abi2)
setattr(tmp_character, abi1, abival2)
setattr(tmp_character, abi2, abival1)
tmp_character.ability_changes += 1
return "node_chargen", kwargs
def node_swap_abilities(caller, raw_string, **kwargs):
"""
One is allowed to swap the values of two abilities around, once.
"""
tmp_character = kwargs["tmp_character"]
text = f"""
Your current abilities:
STR +{tmp_character.strength}
DEX +{tmp_character.dexterity}
CON +{tmp_character.constitution}
INT +{tmp_character.intelligence}
WIS +{tmp_character.wisdom}
CHA +{tmp_character.charisma}
You can swap the values of two abilities around.
You can only do this once, so choose carefully!
To swap the values of e.g. STR and INT, write |wSTR INT|n. Empty to abort.
"""
options = {"key": "_default", "goto": (_swap_abilities, kwargs)}
return text, options
這是更多程式碼,但邏輯是相同的 - 我們有一個節點 (node_swap_abilities) 和
和一個 goto 可呼叫的助手 (_swap_abilities)。我們捕獲使用者在上面寫的所有內容
節點(例如 WIS CON)並將其輸入到助手中。
在_swap_abilities中,我們需要分析來自使用者的raw_string,看看他們做了什麼
想做。
幫助程式中的大多數程式碼都會驗證使用者沒有輸入廢話。如果他們這樣做了,
我們使用 caller.msg() 告訴他們,然後返回 None, kwargs,這會再次重新執行相同的節點(名稱選擇)。
由於我們希望使用者能夠編寫“CON”而不是更長的“憲法”,因此我們需要一個對映 _ABILITIES 來輕鬆地在兩者之間進行轉換(它在臨時字元表上儲存為 consitution)。一旦我們知道他們想要交換哪些能力,我們就會這樣做並勾選 .ability_changes 計數器。這意味著主節點將不再提供此選項。
最後,我們再次回到node_chargen。
6.8. 節點:建立角色¶
我們透過選擇完成 chargen 從主節點到達這裡。
node_apply_character(caller, raw_string, **kwargs):
"""
End chargen and create the character. We will also puppet it.
"""
tmp_character = kwargs["tmp_character"]
new_character = tmp_character.apply(caller)
caller.account.add_character(new_character)
text = "Character created!"
return text, None
進入節點時,我們會取出臨時角色表,並使用其.apply方法建立一個帶有所有裝備的新角色。
這就是所謂的_結束節點_,因為它傳回 None 而不是選項。此後,選單將退出。我們將回到預設的角色選擇畫面。在該螢幕上找到的字元是 _playable_characters Attribute 中列出的字元,因此我們還需要將新字元新增到其中。
6.9. 將節點連線在一起¶
def start_chargen(caller, session=None):
"""
This is a start point for spinning up the chargen from a command later.
"""
menutree = { # <----- can now add this!
"node_chargen": node_chargen,
"node_change_name": node_change_name,
"node_swap_abilities": node_swap_abilities,
"node_apply_character": node_apply_character,
}
# this generates all random components of the character
tmp_character = TemporaryCharacterSheet()
EvMenu(
caller,
menutree,
session=session,
startnode="node_chargen", # <-- make sure it's set!
startnode_input=("", {"tmp_character": tmp_character}),
)
現在我們有了所有節點,我們將它們新增到之前留空的 menutree 中。我們只加入節點,而不是 goto-helpers!我們在 menutree 字典中設定的鍵是我們應該用來從選單內部指向節點的名稱(我們確實這樣做了)。
我們也新增一個關鍵字引數 startnode 指向 node_chargen 節點。這告訴 EvMenu 在選單啟動時首先跳到該節點。
6.10. 結論¶
本課程教我們如何使用EvMenu製作互動式字元產生器。在比 Knave 更複雜的 RPG 中,選單會更大、更複雜,但同樣的原則也適用。
結合之前的課程,我們現在已經掌握了有關玩家的大部分基礎知識 角色 - 他們如何儲存他們的統計資料,處理他們的裝備以及如何建立它們。
在下一課中,我們將討論 EvAdventure Rooms 的工作原理。