- android - 多次调用 OnPrimaryClipChangedListener
- android - 无法更新 RecyclerView 中的 TextView 字段
- android.database.CursorIndexOutOfBoundsException : Index 0 requested, 光标大小为 0
- android - 使用 AppCompat 时,我们是否需要明确指定其 UI 组件(Spinner、EditText)颜色
我们正在与一些 friend 一起开发一款类似于自上而下的 RPG 多人游戏,以供学习(和娱乐!)之用。我们已经在游戏中有一些实体并且输入正在工作,但是网络实现让我们头疼 :D
当尝试使用 dict 进行转换时,一些值仍将包含 pygame.Surface,我不想传输它,并且在尝试对它们进行 jsonfy 时会导致错误。其他我想以简单方式传输的对象(如 Rectangle)无法自动转换。
一个新玩家连接到服务器并希望获得所有对象的当前游戏状态。
我们使用基于“Entity-Component”的架构,因此我们将游戏逻辑非常严格地分离到“系统”中,而数据则存储在每个 Entity 的“组件”中。实体是一个非常简单的容器,只有一个 ID 和一个组件列表。
示例实体(为了更好的可读性而缩短):
Entity |-- Component (Moveable) |-- Component (Graphic) | |- complex datatypes like pygame.SURFACE | `- (...) `- Component (Inventory)
We tried different approaches, but all seems not to fit very well or feel "hacky".
Very Python near, so not easy to implement other clients in future. And I´ve read about some security risks when creating items from network in this dynamic way how pickle it offers. It does not even solve the Surface/Rectangle issue.
__dict__Still contains the reference to the old objects, so a "cleanup" or "filter" for unwanted datatypes happens also in the origin. A deepcopy throws Exception.
...\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
组件本身现在从它们的新父类(super class)组件继承它们的 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() 函数以更准确的方式保护状态——这对于将来维护代码来说是一件大事,将这些代码片段放在一个类中。
下一步是实现用于从同一类中的状态创建项目的静态方法。这使得这项工作非常顺利。
非常感谢树懒的帮助。
最佳答案
Do we really have to put all the logic into the EntityManager for filtering the right objects?
不,你应该使用 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
来创建实例。
关于Python:如何为多人游戏序列化对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55353114/
我是一名优秀的程序员,十分优秀!