【发布时间】:2021-01-23 23:53:23
【问题描述】:
我们正在开发一款类似于 Top-Down-RPG 的多人游戏,用于与一些朋友一起学习(而且很有趣!)。我们已经有一些游戏中的实体和输入正在工作,但是网络实现让我们头疼:D
问题
当尝试使用 dict 进行转换时,某些值仍将包含 pygame.Surface,我不想转移它,并且在尝试 jsonfy 时会导致错误。我想以简单的方式传输的其他对象,例如 Rectangle 无法自动转换。
已经可以使用
- 客户端-服务器连接
- 双向传输 JSON 对象
- 异步网络和同步放入队列
情况
一个新玩家连接到服务器并想要获取所有对象的当前游戏状态。
数据结构
我们使用基于“实体-组件”的架构,因此我们将游戏逻辑非常严格地划分为“系统”,而数据存储在每个实体的“组件”中。 Entity 是一个非常简单的容器,只有一个 ID 和一个组件列表。
示例实体(缩短以提高可读性):
我们尝试了不同的方法,但所有方法似乎都不太合适或感觉“老套”。
非常接近 Python,因此将来很难实现其他客户端。而且我已经阅读了有关以这种动态方式从网络创建项目时它提供的泡菜的一些安全风险。它甚至不能解决曲面/矩形问题。
__dict__仍然包含对旧对象的引用,因此对不需要的数据类型的“清理”或“过滤”也会在源中发生。 deepcopy 抛出异常。
...\Python\Python36\lib\copy.py", line 169, in deepcopy
rv = reductor(4)
TypeError: can't pickle pygame.Surface objects
显示一些代码
“实体管理器”类的方法应该生成所有实体的快照,包括它们的组件。此快照应无任何错误地转换为 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 的转换。
如何以最通用的方法转换所有这些复杂数据?
感谢您到目前为止的阅读,非常感谢您提前提供的帮助!
我们已经在工作的有趣文章抛出,可能对其他有类似问题的人有所帮助
- https://gafferongames.com/post/what_every_programmer_needs_to_know_about_game_networking/
- http://code.activestate.com/recipes/408859/
- https://docs.python.org/3/library/pickle.html
更新:解决方案 - thx 2 懒惰
我们使用了以下架构的组合,到目前为止效果非常好,而且维护起来也很好!
Entity Manager 现在调用实体的 get_state() 函数。
class EntitiyManager:
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] = e.get_state()
return result
实体只有一些基本属性可以添加到状态中,并将 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() 函数以更准确的方式保护状态 - 这对于将来维护代码来说是一件大事,将这些代码片段放在一个类中。
下一步是实现从同一类中的状态创建项目的静态方法。这使得这项工作非常顺利。
非常感谢您的帮助。
【问题讨论】:
-
我没有遵循
EnitityManager类的代码中的所有逻辑,所以我不确定你在哪里/如何进行任何过滤,但无论如何,听起来你应该让它配置为关于过滤正确的对象。即让每个不同的Enitity类型向它注册自己并告诉它它的基本组件是什么,以允许使用该信息来剥离/跳过组件专业化。 -
感谢您的回答。在此函数中“清理”了组件字典:
self.clean_complex_recursive(cmp_dict)。它只是删除所有不是 list、dict、str、int、None 的值(及其键)。过滤会导致在 EnitityManger 中使用转换规则进行较长的配置,因为 EntitiyManager 不应该关心如何详细处理组件内容并决定什么对安全/传输重要。
标签: python pygame game-engine