2

所以我正在制作一个游戏,所有对象都派生自一个 GameObject 类,看起来像这样;

class GameObject(pygame.sprite.DirtySprite):
    actions = dict()

    def __init__(self):
        pygame.sprite.DirtySprite.__init__(self)
        self.rect  = None
        self.state = None

    def update(self):
        if callable(self.__class__.actions[self.state]):
        #If this key has a function for its element...
            self.__class__.actions[self.state](self)

现在,我遇到了另一个继承问题。观察下面的类,以及派生自它的两个类;

class Bullet(gameobject.GameObject):
    FRAME  = pygame.Rect(23, 5, 5, 5)
    STATES = config.Enum('IDLE', 'FIRED', 'MOVING', 'RESET')

    def __init__(self):
        gameobject.GameObject.__init__(self)
        self.image = config.SPRITES.subsurface(self.__class__.FRAME)
        self.rect  = self.__class__.START_POS.copy()
        self.state = self.__class__.STATES.IDLE

    actions = {
               STATES.IDLE   : None        ,
               STATES.FIRED  : start_moving,
               STATES.MOVING : move        ,
               STATES.RESET  : reset       ,
              }

class ShipBullet(bullet.Bullet):
    SPEED     = -8
    START_POS = pygame.Rect('something')

    def __init__(self):
        super(self.__class__, self).__init__()
        self.add(ingame.PLAYER)

class EnemyBullet(bullet.Bullet):
    SPEED     = 2
    START_POS = pygame.Rect('something else')

    def __init__(self):
        super(self.__class__, self).__init__()
        self.add(ingame.ENEMIES)

(请注意,是一个静态成员)的每个元素Bullet.actions除了None是一个包含在Bullet. Bullet不是要自己创建的;如果这是 C++,它将是一个抽象类。所以发生的事情是,Bullet的子类搜索Bullet.actions每一帧以决定下一步做什么,这取决于它们的状态(它们是否在移动,它们是否刚刚拍摄等)。但是,由于 的元素Bullet.actions自己的Bullet方法,它的子类正在执行这些方法而不是它们自己的扩展版本(调用父方法)。由于内存使用原因,我不想复制这个回调字典。所以,我问这个;我怎样才能让一个子类的实例通过它的充满回调方法的父字典来查看,如果存在则执行它们自己的版本,如果不存在则执行它们的父版本?

4

5 回答 5

2

一种可能的解决方案是存储函数的名称而不是直接引用并使用getattr来检索正确的引用:

actions = {
           STATES.IDLE   : None          ,
           STATES.FIRED  : 'start_moving',
           STATES.MOVING : 'move'        ,
           STATES.RESET  : 'reset'       ,
          }

[...]

def update(self):
    method_name = self.__class__.actions[self.state]
    if method_name and callable(getattr(self, method_name)):
        getattr(self, method_name)(self)

为了加快速度,您可以在初始化对象时预先计算此表:

class Bullet(gameobject.GameObject):

    FRAME  = pygame.Rect(23, 5, 5, 5)
    STATES = config.Enum('IDLE', 'FIRED', 'MOVING', 'RESET')

    action_names = {
                     STATES.IDLE   : None          ,
                     STATES.FIRED  : 'start_moving',
                     STATES.MOVING : 'move'        ,
                     STATES.RESET  : 'reset'       ,
                    }

    def __init__(self):
        gameobject.GameObject.__init__(self)
        self.image = config.SPRITES.subsurface(self.__class__.FRAME)
        self.rect  = self.__class__.START_POS.copy()
        self.state = self.__class__.STATES.IDLE

        # Update actions table using getattr, so we get the correct
        # method for subclasses.
        self.actions = {}
        for state, method_name in self.action_names.items():
            if method_name and callable(getattr(self, method_name)):
                self.actions[state] = getattr(self, method_name)
            else:
                self.actions[state] = lambda self: None


    def update(self):
        self.actions[self.state]()

请注意,由于__init__uses中的代码getattr,它可以放置在其他类中Bullet.__init__并仅由其他类扩展。由于您已经调用了超级构造函数,因此无需更改扩展类甚至注释它们。

于 2012-11-22T04:08:27.617 回答
2

为什么不使用python内置机制进行继承?

派生类的实例函数actions是相同的B。它self在调用时获取实例,然后就像在实例本身上调用函数一样:Python 的继承机制调用B' 方法(如果存在)或回退到A' 实现。

编辑: l4mpi 建议指出这将每次创建地图,所以我将 action_map 更改为一个属性。

class A():
    def actions(self, action):
        if not hasattr(self, "actions_map"):
            self.actions_map = {
                   "IDLE"   : self.idle,
                   "FIRED"  : self.fired,
                   "MOVING" : self.move,
                   "RESET"  : self.reset,
                  }
        return self.actions_map[action]

    def idle(self):
        print "A idle"
        pass

    def fired(self):
        print "A fired"

    def move(self):
        print "A move"

    def reset(self):
        print "A reset"

class B(A):       
    def fired(self):
        print "B fired"


a = A()
b = B()

a.actions("FIRED")()
b.actions("FIRED")()
b.actions("MOVING")()

>> A fired
>> B fired
>> A move
于 2012-11-25T12:04:26.203 回答
2

扩展 BoppreH 的答案,您可以通过在创建类时使用正确的方法填充活动字典来摆脱getattr查找,使用这样的类装饰器:

def generateActions(cls):
    cls.actions = {}
    for a, f in cls.genactions.items():
        cls.actions[a] = getattr(cls, f) if f else lambda *_: None
    return cls

请注意,actions如果动作的给定值为 ,则它填充了一个无操作的 lambda None,这意味着您可以if callable(...)update.

现在您只需将装饰器添加到您的类中:

@generateActions
class Bullet(gameobject.GameObject):
    FRAME  = pygame.Rect(23, 5, 5, 5)
    STATES = config.Enum('IDLE', 'FIRED', 'MOVING', 'RESET')

    genactions = {
           STATES.IDLE   :  None         ,
           STATES.FIRED  : 'start_moving',
           STATES.MOVING : 'move'        ,
           STATES.RESET  : 'reset'       ,
          }
    ...

@generateActions
class ShipBullet(bullet.Bullet):
    ...
于 2012-11-25T15:05:39.993 回答
0

考虑为游戏对象定义一个类型类。

这是我的解决方案。我已经简化了所有与我在这里提出的观点无关的内容。

class GameObjectClass(type):
    """A metaclass for all game objects"""

    @staticmethod
    def find(key, bases, dict):
        """Find a member in the class dict or any of the bases"""
        if key in dict:
            return dict[key]
        for b in bases:
            attr = getattr(b, key, None)
            if attr is not None:
                return attr
        return None

    def __new__(mcs, name, bases, dict):
        actions = GameObjectClass.find('actions', bases, dict)
        actionsResolved = {}
        for key, methodname in actions.items():
            if methodname is None:
                actionsResolved[key] = None
            else:
                actionsResolved[key] = GameObjectClass.find(methodname, bases, dict)
        dict['actionsResolved'] = actionsResolved
        return type.__new__(mcs, name, bases, dict)

class GameObject(object):

    # This class and all its subclasses will have
    # GameObjectClass for a metaclass
    __metaclass__ = GameObjectClass
    actions = dict()

    def __init__(self):
        self.state = None

    def update(self):
        if callable(self.__class__.actionsResolved[self.state]):
            self.__class__.actionsResolved[self.state](self)

class Bullet(GameObject):
    STATES = config.Enum('IDLE', 'FIRED', 'MOVING', 'RESET')
    def __init__(self):
        super(Bullet, self).__init__()
        self.state = self.__class__.STATES.IDLE
    # Here, strings are used. They will be resolved to
    # references to actual methods (actionsResolved),
    # and this resolution will happen only once
    # (when the game object class is defined)
    actions = {
        STATES.IDLE: None,
        STATES.FIRED: 'start_moving',
        STATES.MOVING: 'move',
        STATES.RESET: 'reset'
    }
    def start_moving(self):
        print "Bullet.start_moving"
    def move(self):
        print "Bullet.move"
    def reset(self):
        print "Bullet.reset"

class ShipBullet(Bullet):
    # This one will be correctly used for the FIRED state
    def start_moving(self):
        print "ShipBullet.start_moving"
于 2012-11-26T16:05:34.070 回答
0

也许我不太明白你想做什么..

据我了解,您有一个主要描述功能的类(A)和主要描述属性的类(B)。

您想从 B 类的实例调用 A 类的方法吗?

为什么不做这样的事情:

class Bullet(gameobject.GameObject):
    ...

class ShipBullet(bullet.Bullet):
    ABC = bullet.Bullet

    def somefunc(self):
        somekey = 5
        self.ABC.actions[somekey](self, *a, **kw)
        # or
        super(self.__class__, self).actions[somekey](self, *a, **kw)
        # or
        bullet.Bullet.actions[somekey](self, *a, **kw)

您需要在动作定义中将 ref 添加到实例中,例如

def move(self, to_x, to_y): #as classic method
    ....
# or
def move(whom, to_x, to_y): #as "free-function"
    ....
于 2012-11-27T12:47:48.557 回答