我们正在与一些Top-Down-RPG类似的多人游戏进行研究,目的是学习(和娱乐!)。我们已经在游戏中有了一些实体,输入正在起作用,但是网络实现让我们头疼:D
当尝试使用 dict 进行转换时,某些值仍将包含pygame.Surface,我不想传输该值,并且在尝试对其进行jsonfy转换时会导致错误。我想以简单方式传输的其他对象(例如矩形)无法自动转换。
新玩家连接到服务器,并希望获得所有对象的当前游戏状态。
我们使用基于“实体组件”的体系结构,因此我们将游戏逻辑非常严格地分为“系统”,而数据存储在每个实体的“组件”中。实体是一个非常简单的容器,仅包含一个ID和一个组件列表。
示例实体(为提高可读性而简称):
Entity |-- Component (Moveable) |-- Component (Graphic) | |- complex datatypes like pygame.SURFACE | `- (...) `- Component (Inventory)
我们尝试了不同的方法,但是所有方法似乎都不尽人意或让人感到“ hacky”。
非常接近Python,因此将来很难实现其他客户端。而且,我已经读到了以这种动态方式从网络创建项目时,其腌制方式所带来的一些安全风险。它甚至不能解决“曲面/矩形”问题。
__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 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()函数(例如图形,移动,库存等)。以更准确的方式保护状态-将这些代码段放在一个类中对于将来的代码维护来说是一件大事。
下一步是实现用于从同一Class中的状态创建项目的静态方法。这样可以使工作非常顺利。
非常感谢您的懒惰。
答案 0 :(得分:2)
我们真的必须将所有逻辑放入EntityManager来过滤正确的对象吗?
否,您应该使用polymorphism。
您需要一种可以在不同系统之间共享的形式来表示游戏状态的方法;因此,也许为您的组件提供一个将返回其所有状态的方法,并为工厂方法提供一个允许您从该状态创建组件实例的工厂方法。
(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
创建实例。