[英]Python: How to serialize objects for multiplayer game?
我們正在與一些朋友一起開發一款類似RPG的多人游戲,用於學習目的(和樂趣!)。 我們已經在游戲中有一些實體並且輸入正在運行,但網絡實現讓我們頭疼:D
當嘗試使用dict進行轉換時,某些值仍將包含pygame.Surface,我不想傳輸它,並且在嘗試jsonfy時會導致錯誤。 我想以簡單的方式傳輸的其他對象(如Rectangle)無法自動轉換。
新玩家連接到服務器並希望獲得所有對象的當前游戲狀態。
我們使用基於“實體 - 組件”的架構,因此我們將游戲邏輯嚴格分為“系統”,而數據則存儲在每個實體的“組件”中。 實體是一個非常簡單的容器,只有一個ID和一個組件列表。
示例實體(縮短以獲得更好的可讀性):
Entity |-- Component (Moveable) |-- Component (Graphic) | |- complex datatypes like pygame.SURFACE | `- (...) `- Component (Inventory)
我們嘗試了不同的方法,但似乎都不太適合或感覺“hacky”。
非常接近Python,因此將來很難實現其他客戶端。 我已經從這個動態的方式從網絡創建項目時了解了一些安全風險。 它甚至沒有解決Surface / Rectangle問題。
__dict__仍包含對舊對象的引用,因此對於不需要的數據類型的“清理”或“過濾器”也會在原點中發生。 deepcopy拋出異常。
...\\Python\\Python36\\lib\\copy.py", line 169, in deepcopy rv = reductor(4) TypeError: can't pickle pygame.Surface objects
“EnitityManager”類的方法,它應該生成所有實體的快照,包括它們的組件。 應該將此快照轉換為JSON而不會出現任何錯誤 - 如果可能的話,在此核心類中沒有太多配置。
class EnitityManager: def generate_world_snapshot(self): """ Returns a dictionary with all Entities and their components to send this to the client. This function will probably generate a lot of data, but, its to send the whole current game state when a new player connects or when a complete refresh is required """ # It should be possible to add more objects to the snapshot, so we # create our own Snapshot-Datastructure result = {'entities': {}} entities = self.get_all_entities() for e in entities: result['entities'][e.id] = deepcopy(e.__dict__) # Components are Objects, but dictionary is required for transfer cmp_obj_list = result['entities'][e.id]['components'] # Empty the current list of components, its going to be filled with # dictionaries of each cmp which are cleaned for the dump, because # of the errors directly coverting the whole datastructure to JSON result['entities'][e.id]['components'] = {} for cmp in cmp_obj_list: cmp_copy = deepcopy(cmp) cmp_dict = cmp_copy.__dict__ # Only list, dict, int, str, float and None will stay, while # other Types are being simply deleted including their key # Lists and directories will be cleaned ob recursive as well cmp_dict = self.clean_complex_recursive(cmp_dict) result['entities'][e.id]['components'][type(cmp_copy).__name__] \\ = cmp_dict logging.debug("EntityMgr: Entity#3: %s" % result['entities'][3]) return result
我們可以找到一種手動覆蓋我們不想要的元素的方法。 但隨着組件列表的增加,我們必須將所有過濾器邏輯放入此核心類中,該核心類不應包含任何組件特化。
我們是否真的必須將所有邏輯放入EntityManager以過濾正確的對象? 這感覺不太好,因為我希望在沒有任何硬編碼配置的情況下完成所有轉換為JSON。
如何以最通用的方法轉換所有這些復雜數據?
感謝您的閱讀,非常感謝您的幫助!
我們使用了以下架構的組合,到目前為止工作非常好,並且維護起來也很好!
實體管理器現在調用實體的get_state()函數。
class Component:
def __init__(self):
logging.debug('generic component created')
def get_state(self):
state = {}
for attr, value in self.__dict__.items():
if value is None or isinstance(value, (str, int, float, bool)):
state[attr] = value
elif isinstance(value, (list, dict)):
# logging.warn("Generating state: not supporting lists yet")
pass
return state
class GraphicComponent(Component):
# (...)
實體只有一些基本屬性可以添加到狀態,並將get_state()調用轉發給所有組件:
class Entity: def get_state(self): state = {'name': self.name, 'id': self.id, 'components': {}} for cmp in self.components: state['components'][type(cmp).__name__] = cmp.get_state() return state
組件本身現在從其新的超類組件繼承它們的get_state()方法,這些組件只關心所有簡單的數據類型:
class Component: def __init__(self): logging.debug('generic component created') def get_state(self): state = {} for attr, value in self.__dict__.items(): if value is None or isinstance(value, (str, int, float, bool)): state[attr] = value elif isinstance(value, (list, dict)): # logging.warn("Generating state: not supporting lists yet") pass return state class GraphicComponent(Component): # (...)
現在,每個開發人員都有機會覆蓋此函數,以便直接在組件類 (如圖形,移動,庫存等)中為復雜類型創建更詳細的get_state()函數,如果需要更准確地保護狀態方式 - 這對於將來維護代碼來說是一件巨大的事情,將這些代碼片段放在一個類中。
下一步是實現靜態方法,以便從同一個類中的狀態創建項目。 這使得這項工作非常順利。
非常感謝你的幫助。
我們是否真的必須將所有邏輯放入EntityManager以過濾正確的對象?
不,你應該使用多態 。
您需要一種以可在不同系統之間共享的形式表示您的游戲狀態的方法; 所以也許給你的組件一個返回所有狀態的方法,以及一個允許你創建那個狀態的組件實例的工廠方法。
(Python已經有__repr__
魔術方法,但你不必使用它)
因此,不要在實體管理器中進行所有過濾,只需讓他在所有組件上調用這個新方法,讓每個組件決定結果如何。
像這樣的東西:
...
result = {'entities': {}}
entities = self.get_all_entities()
for e in entities:
result['entities'][e.id] = {'components': {}}
for cmp in e.components:
result['entities'][e.id]['components'][type(cmp).__name__] = cmp.get_state()
...
一個組件可以像這樣實現它:
class GraphicComponent:
def __init__(self, pos=...):
self.image = ...
self.rect = ...
self.whatever = ...
def get_state(self):
return { 'pos_x': self.rect.x, 'pos_y': self.rect.y, 'image': 'name_of_image.jpg' }
@staticmethod
def from_state(state):
return GraphicComponent(pos=(state.pos_x, state.pos_y), ...)
並且從服務器接收狀態的客戶端的EntityManager
將迭代每個實體的組件列表並調用from_state
來創建實例。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.