Мы работаем над многопользовательской игрой типа Top-Down-RPG для целей обучения (и веселья!) С некоторыми друзьями.У нас уже есть некоторые сущности в игре, и входы работают, но сетевая реализация доставляет нам головную боль: D
Проблемы
При попытке конвертировать с помощью dict некоторые значения будут по-прежнему содержать pygame.Surface, который я не хочу передавать, и это вызывает ошибки при попытке их jsonfy.Другие объекты, которые я хотел бы перенести простым способом, например Rectangle, не могут быть автоматически преобразованы.
Уже работоспособно
- Клиент-серверное соединение
- Передача JSONобъекты в обоих направлениях
- Асинхронное сетевое взаимодействие и синхронное помещение в очередь
Ситуация
Новый игрок подключается к серверу и хочет получить текущее состояние игры свсе объекты.
Структура данных
Мы используем архитектуру на основе «сущности-компонента», поэтому мы очень строго разделили игровую логику на «системы», в то время как данные хранятся в «компоненты "каждого субъекта.Entity - это очень простой контейнер, в котором нет ничего, кроме идентификатора и списка компонентов.
Пример Entity (сокращено для лучшей читаемости):
Entity
|-- Component (Moveable)
|-- Component (Graphic)
| |- complex datatypes like pygame.SURFACE
| `- (...)
`- Component (Inventory)
Мы пробовали разные подходы, но все, кажется, не очень хорошо подходят или чувствуют себя "хаки".
pickle
Очень близко к Python, поэтому нелегко внедрить других клиентов в будущем.И я читал о некоторых рисках безопасности при создании элементов из сети таким динамичным образом, как это предлагает.Он даже не решает проблему Поверхность / Прямоугольник.
__dict__
Все еще содержит ссылку на старые объекты, поэтому в источнике также происходит «очистка» или «фильтр» для нежелательных типов данных.При глубоком копировании создается исключение.
...\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 sloth
Мы использовали комбинацию следующегоАрхитектура, которая до сих пор прекрасно работает и также хороша в обслуживании!
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 () от своих новых компонентов суперкласса, чтоh просто заботится обо всех простых типах данных:
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):
# (...)
NКаждый разработчик имеет возможность наложения этой функции на создание более подробной функции get_state () для сложных типов непосредственно в классах компонентов (таких как Графика, Движение, Инвентарь и т. д.), если это необходимо для обеспечения безопасности. более точное состояние - что очень важно для поддержания кода в будущем, чтобы эти куски кода были в одном классе.
Следующим шагом является реализация статического метода для создания элементов из состояния в том же классе. Это делает эту работу действительно гладкой.
Большое спасибо ленивцу за помощь.