所以我正在制作游戏,所有对象都来自一个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
中保存的函数。 Bullet
并不意味着自己创造;如果这是C ++,它将是一个抽象类。所以会发生什么,Bullet.actions
的子类每帧搜索Bullet.actions
以决定接下来要做什么,具体取决于他们的状态(他们是否移动,他们只是被枪杀等)。但是,由于Bullet
的元素是{{1}}的自己的方法,因此它的子类正在执行那些而不是它们自己的扩展版本(调用父方法)。出于内存使用的原因,我不想复制这个回调函数。所以,我问这个; 如何让子类的实例通过它的父类字典查看完整的回调方法,如果存在则执行自己的版本,如果不存在则执行它们的父版本?
答案 0 :(得分: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__
中的代码使用getattr
,因此可以将其放在Bullet.__init__
中,并仅由其他类扩展。正如您已经调用了超级构造函数,就没有必要更改扩展类甚至注释它们。
答案 1 :(得分: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
答案 2 :(得分:2)
扩展BoppreH的答案,你可以通过在类创建时使用正确的方法填充活动dict来摆脱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
,则None
将填充无操作lambda,这意味着您可以删除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):
...
答案 3 :(得分: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"
答案 4 :(得分: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 to instansedef move(self, to_x, to_y): #as classic method
....
# or
def move(whom, to_x, to_y): #as "free-function"
....