製作持久物件處理程式

handler 是將物件上的功能分組的便捷方法。這使您能夠從邏輯上 將與該事物相關的所有操作集中在一個地方。本教學舉例說明如何使您的 擁有自己的處理程式,並確保您儲存在其中的資料在重新載入後仍然存在。

例如,當您執行 obj.attributes.get("key")obj.tags.add('tagname') 時,您正在喚起 處理程式在 obj 上儲存為 .attributestags。這些處理程式上有方法(get() 以及本例中的add())。

基本處理程式範例

這是設定物件處理程式的基本方法:


from evennia import DefaultObject, create_object
from evennia.utils.utils import lazy_property

class NameChanger:
    def __init__(self, obj):
        self.obj = obj

    def add_to_key(self, suffix):
        self.obj.key = f"{self.obj.key}_{suffix}"

# make a test object
class MyObject(DefaultObject):
    @lazy_property:
    def namechange(self):
       return NameChanger(self)


obj = create_object(MyObject, key="test")
print(obj.key)
>>> "test"
obj.namechange.add_to_key("extra")
print(obj.key)
>>> "test_extra"

這裡發生的是我們建立一個新類別NameChanger。我們使用 @lazy_property 裝飾器來設定它 - 這意味著處理程式將不會 實際建立直到有人真正想要使用它,透過訪問 obj.namechange 稍後。修飾的 namechange 方法傳回處理程式 並確保使用 self 對其進行初始化 - 這將成為 obj 內的 處理程式!

然後我們建立一個愚蠢的方法add_to_key,它使用處理程式來操作 物件的鍵。在這個例子中,處理程式毫無意義,但是 這種方式的分組功能既可以使 API 易於記憶,又可以 還可以允許您快取資料以便於存取 - 這就是 AttributeHandler (.attributes) 和 TagHandler (.tags) 有效。

資料永續性儲存在handler中

假設我們想在處理程式中追蹤「任務」。 「任務」是一個常規課程 這代表著追求。我們舉個簡單的例子:

# for example in mygame/world/quests.py


class Quest:

    key = "The quest for the red key"

    def __init__(self):
        self.current_step = "start"

    def check_progress(self):
        # uses self.current_step to check
        # progress of this quest
        getattr(self, f"step_{self.current_step}")()

    def step_start(self):
        # check here if quest-step is complete
        self.current_step = "find_the_red_key"
    def step_find_the_red_key(self):
        # check if step is complete
        self.current_step = "hand_in_quest"
    def step_hand_in_quest(self):
        # check if handed in quest to quest giver
        self.current_step = None  # finished

我們希望開發人員建立其子類別來實現不同的任務。具體是如何運作的 沒關係,關鍵是我們想要追蹤 self.current_step - should 的屬性 在伺服器重新載入後倖存。但到目前為止Quest還沒有辦法做到這一點,這只是一個 沒有連線到資料庫的普通 Python 類別。

具有儲存/載入功能的處理程式

讓我們建立一個 QuestHandler 來管理角色的任務。

# for example in the same mygame/world/quests.py


class QuestHandler:
    def __init__(self, obj):
        self.obj = obj
        self.do_save = False
        self._load()

    def _load(self):
        self.storage = self.obj.attributes.get(
            "quest_storage", default={}, category="quests")

    def _save(self):
        self.obj.attributes.add(
            "quest_storage", self.storage, category="quests")
        self._load()  # important
        self.do_save = False

    def add(self, questclass):
        self.storage[questclass.key] = questclass(self.obj)
        self._save()

    def check_progress(self):
            quest.check_progress()
        if self.do_save:
            # .do_save is set on handler by Quest if it wants to save progress
            self._save()

這個處理程式只是一個普通的 Python 類,它自己沒有資料庫儲存。但它有一個連結 到.obj,假設它是一個完整的型別實體,我們可以在其上建立 持久屬性來儲存我們喜歡的東西!

我們製作兩種輔助方法 _load_save 處理本地提取並將 storage 儲存到物件上的 Attribute。為了避免 節省超過必要的錢,我們有財產do_save。這個我們將在下面的Quest中設定。

請注意,一旦我們_save資料,我們需要再次呼叫_load。這是為了確保我們儲存在處理程式上的版本已正確反序列化。如果您收到有關資料為 bytes 的錯誤,您可能錯過了此步驟。

使任務可儲存

處理程式會將所有 Quest 物件儲存為 dict,位於 obj 上的 Attribute 中。我們還沒有完成 不過,Quest 物件也需要存取 obj - 這不僅對計算很重要 任務是否完成(Quest 必須能夠檢查任務者的庫存以檢視是否 例如,它們有紅色鑰匙),它還允許 Quest 告訴處理程式其狀態 改變了,應該要儲存。

我們將 Quest 改為:

from evennia.utils import dbserialize


class Quest:

    def __init__(self, obj):
        self.obj = obj
        self._current_step = "start"

    def __serialize_dbobjs__(self):
        self.obj = dbserialize.dbserialize(self.obj)

    def __deserialize_dbobjs__(self):
        if isinstance(self.obj, bytes):
            self.obj = dbserialize.dbunserialize(self.obj)

    @property
    def questhandler(self):
        return self.obj.quests

    @property
    def current_step(self):
        return self._current_step

    @current_step.setter
    def current_step(self, value):
        self._current_step = value
        self.questhandler.do_save = True  # this triggers save in handler!

    # [same as before]

Quest.__init__ 現在採用 obj 作為引數,以符合我們傳遞給它的內容 QuestHandler.add。我們想要監控current_step的變化,所以我們 把它變成property。當我們編輯該值時,我們將 do_save 標誌設為 處理程式,這表示檢查後會將狀態儲存到資料庫 所有任務都取得進展。 Quest.questhandler 屬性允許輕鬆 傳回處理程式(及其所在的物件)。

需要 __serialize__dbobjs____deserialize_dbobjs__ 方法 因為 Attributes 無法儲存「隱藏」資料庫物件(Quest.obj 財產。這些方法可以幫助 Evennia 正確地序列化/反序列化 Quest 處理程式儲存它。 有關詳細資訊,請參閱儲存單個 屬性中的物件

將它們結合在一起

我們需要做的最後一件事是將任務處理程式新增至角色:

# in mygame/typeclasses/characters.py

from evennia import DefaultCharacter
from evennia.utils.utils import lazy_property
from .world.quests import QuestHandler  # as an example


class Character(DefaultCharacter):
    # ...
    @lazy_property
    def quests(self):
        return QuestHandler(self)

現在您可以建立任務類別來描述您的任務並將它們新增至 字元與

character.quests.add(FindTheRedKey)

稍後可以做

character.quests.check_progress()

並確保任務資料在重新載入之間不會遺失。

您可以找到一個成熟的任務處理程式範例,如 EvAdventure 任務 contrib 在 Evennia 儲存庫。