【问题标题】:can't serialized pygame.Surface objects with pickle无法用 pickle 序列化 pygame.Surface 对象
【发布时间】:2018-05-03 08:57:36
【问题描述】:

我正在使用 python 3.6 开发游戏,我希望在其多人游戏版本中将由客户端(玩家)修改的服务器对象发送到我想将它们序列化以进行传输的服务器。我在我的对象中使用 pygame 和因此 pygame.Surface

我有这种结构的对象:

class Cargo(Bateau):
  dictCargos = dict()
  def __init__(self, map, nom, pos, armateur=None):
    Bateau.__init__(self, map, nom, armateur, pos)
    self.surface = pygame.image.load(f"images/{self.nom}.png").convert_alpha()
    self.rect = self.map.blit(self.surface, self.pos)
    ...
    Cargo.dictCargos[self.nom] = self

当我序列化另一个没有 pygame 实例的对象时,没关系 但是对于上述对象,我会收到以下错误消息:

import pickle as pickle
pickle.dump(Cargo.dictCargos, open('file2.pkl', 'wb'), protocol=pickle.HIGHEST_PROTOCOL)

Traceback (most recent call last):
  File "./pytransit.py", line 182, in <module>
    encreG(joueur, event)
  File "/home/patrick/Bureau/PyTransit/modulesJeu/tests.py", line 25, in encreG
    pickle.dump(Cargo.dictCargos, open('file2.pkl', 'wb'), protocol=pickle.HIGHEST_PROTOCOL)
TypeError: can't pickle pygame.Surface objects

您知道如何将这些项目传输到服务器吗?或者绕过这个泡菜限制?
如果我想保存一个零件也会出现同样的问题,所以保存这些对象

【问题讨论】:

  • 由于您无法对 Surface 对象进行腌制,因此您需要找到一种方法在腌制之前将其移除并在之后重新创建它们。为此,我建议您查看pickle 文档中的Handling Stateful Objects。我过去用这个来deal with file logging handlers,和你的问题很相似。
  • 您能告诉我们您为什么要发送表面吗?你的游戏中到底发生了什么?这个问题听起来有点像XY problem,可能有更好的方法来实现你想要的。另外请注意,从不受信任的来源中提取数据存在巨大的安全风险,因此不应在多人游戏中使用 pickle。
  • "BTW" 的意思是“顺便说一句”,如果您在评论中写上@username,用户将在顶部栏中看到红色的小新评论图标。我只看到了你以前的 cmets,因为我还打开了这个标签。
  • 如果你只发送位置和其他可以用json模块序列化的简单数据类型,你可以使用JSON格式。我会避免发送表面,因为它们可能非常大。

标签: python pygame


【解决方案1】:

这是@IonicSolutions 在 cmets 中指出的示例:

import pickle
import pygame


class Test:
    def __init__(self, surface):
        self.surface = surface
        self.name = "Test"

    def __getstate__(self):
        state = self.__dict__.copy()
        surface = state.pop("surface")
        state["surface_string"] = (pygame.image.tostring(surface, "RGB"), surface.get_size())
        return state

    def __setstate__(self, state):
        surface_string, size = state.pop("surface_string")
        state["surface"] = pygame.image.fromstring(surface_string, size, "RGB")
        self.__dict__.update(state)


t = Test(pygame.Surface((100, 100)))
b = pickle.dumps(t)
t = pickle.loads(b)

print(t.surface)

要查看可以使用哪些模式将数据存储为字符串(此处为“RGB”),请查看 into the documentation

【讨论】:

  • 一个具体的例子更清楚......只是一个问题:我使用.convert_alpha(),在你的例子中你写的是(surface_string,size,“RGB”)我必须用alpha 还是别的什么?
【解决方案2】:

根据@MegaIng 的答案,我开发了他/她的答案,这样您就可以正常使用 pygame.Surface,但添加了 pickle 功能。它不应该打扰您的任何代码。我已经在 python 3.7、64 位上对其进行了测试,并且可以正常工作。也已经在我的项目中尝试/实施它,没有受到任何干扰。

import pygame as pg

pgSurf = pg.surface.Surface

class PickleableSurface(pgSurf):
    def __init__(self, *arg,**kwarg):
        size = arg[0]

        # size given is not an iterable,  but the object of pgSurf itself
        if (isinstance(size, pgSurf)):
            pgSurf.__init__(self, size=size.get_size(), flags=size.get_flags())
            self.surface = self
            self.name='test'
            self.blit(size, (0, 0))

        else:
            pgSurf.__init__(self, *arg, **kwarg)
            self.surface = self
            self.name = 'test'

    def __getstate__(self):
        state = self.__dict__.copy()
        surface = state["surface"]

        _1 = pg.image.tostring(surface.copy(), "RGBA")
        _2 = surface.get_size()
        _3 = surface.get_flags()
        state["surface_string"] = (_1, _2, _3)
        return state

    def __setstate__(self, state):
        surface_string, size, flags = state["surface_string"]

        pgSurf.__init__(self, size=size, flags=flags)

        s=pg.image.fromstring(surface_string, size, "RGBA")
        state["surface"] =s;
        self.blit(s,(0,0));self.surface=self;
        self.__dict__.update(state)

这是一个例子

pg.Surface = PickleableSurface
pg.surface.Surface = PickleableSurface

surf = pg.Surface((300, 400), pg.SRCALPHA|pg.HWSURFACE)
# Surface, color, start pos, end pos, width
pg.draw.line(surf, (0,0,0), (0,100), (200, 300), 2)  

from pickle import loads, dumps

dump = dumps(surf)
loaded = loads(dump)
pg.init()
screen = pg.display.set_mode((300, 400))
screen.fill((255, 255, 255))
screen.blit(loaded, (0,0))
pg.display.update()

然后在我的屏幕上:

谢谢@MegaIng

附注: 我还添加了将 unpickle-able pygame surface 转换为 pickle-able surface 的功能,方法是 newSurface = PickleableSurface(PygameSurface) 但是,它只测试过一次,因此可能存在一些错误。如果你找到了,请随时告诉我!我希望它会帮助你! :D

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-22
    • 2018-04-25
    • 1970-01-01
    相关资源
    最近更新 更多