繁体   English   中英

Python:如何为多人游戏序列化对象?

[英]Python: How to serialize objects for multiplayer game?

我们正在与一些朋友一起开发一款类似RPG的多人游戏,用于学习目的(和乐趣!)。 我们已经在游戏中有一些实体并且输入正在运行,但网络实现让我们头疼:D

问题

当尝试使用dict进行转换时,某些值仍将包含pygame.Surface,我不想传输它,并且在尝试jsonfy时会导致错误。 我想以简单的方式传输的其他对象(如Rectangle)无法自动转换。

已经功能齐全

  • 客户端 - 服务器连接
  • 在两个方向上传输JSON对象
  • 异步网络和同步放入队列

情况

新玩家连接到服务器并希望获得所有对象的当前游戏状态。

数据结构

我们使用基于“实体 - 组件”的架构,因此我们将游戏逻辑严格分为“系统”,而数据则存储在每个实体的“组件”中。 实体是一个非常简单的容器,只有一个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。

如何以最通用的方法转换所有这些复杂数据?

感谢您的阅读,非常感谢您的帮助!

我们已经在工作的有趣文章投掷了,也许对有类似问题的其他人有帮助

更新:解决方案 - thx 2懒惰

我们使用了以下架构的组合,到目前为止工作非常好,并且维护起来也很好!

实体管理器现在调用实体的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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM