为玩家创造效果?
Creating effects for a player?
我会尽量保持简短:
- 我有一个游戏引擎提供的
PlayerEntity
class,它有一个move_type
属性
move_type
一次可以设置为一个:MoveTypes.NONE, MoveTypes.FLY, MoveTypes.WALK
- 我已经 class 编辑了
PlayerEntity
class,并且我希望我的子class 能够执行 player.freeze(duration=5)
- 我也想要自定义效果,例如
player.burn(duration=3)
这是我的第一印象:
class Player(game.PlayerEntity):
def __init__(self):
super().__init__()
self._effects = []
def add_effect(self, effect, duration=None):
self.effects.append(effect)
if duration is not None:
delayed(duration, self.remove_effect, effect)
self._apply_effects()
def remove_effect(self, effect):
if effect in self.effects:
self.effects.remove(effect)
self._apply_effects()
def _apply_effects(self):
if 'freeze' in self.effects:
self.move_type = MoveTypes.NONE
elif 'fly' in self.effects:
self.move_type = MoveTypes.FLY
else:
self.move_type = MoveTypes.WALK
if 'burn' in self.effects:
self.ignite()
else:
self.extinguish()
freeze = lambda self, duration: self.add_effect('freeze', duration)
fly = lambda self, duration: self.add_effect('fly', duration)
burn = lambda self, duration: self.add_effect('burn', duration)
不过,我想让它更灵活,例如有一个 Effect
class.
我遇到的几个问题:
-
Player
有 _effects
列表并调用 self._effects[0].apply(self, duration)
,或者 Effect
class 有 player
属性并调用它自己的属性apply(duration)
?
- 我将如何与多种效果互动?
Player
只能飞行或被冻结,不能同时飞行(由于move_type
)。然而burn
是完全独立的效果。
我会有一个 Effect
class,然后将 Effect
个对象的列表存储在 Player
对象中。调用 Player
中的方法将 Effect
个对象添加到此列表。浏览每个 turn/tick 上的 Effect
列表以检查堆栈、更新剩余持续时间、删除任何没有剩余持续时间的列表,等等。
您可以通过创建自定义 Effect
对象与多种效果进行交互。例如,如果您想要一个允许移动的冻结状态并且只是增加 Player
获得暴击的机会,请创建一个新的 Effect
来定义您正在寻找的行为。
以下代码提供了使用 activate/deactivate 定义新效果并创建 "categories" 的可能性。如果两种效果属于同一类别,则优先级较高的效果会影响玩家。不同类别的效果是独立的。
实现中有一个"flaw",试想如果出现以下情况:
- 应用效果a
- 效果a被移除没有因为超时
- 效果a再次应用在第一次管理超时
之前
- 效应 a 被移除,因为达到了第一个给药持续时间
如果 Effect
变成 class/metaclass,这可能很容易解决。
这里的效果真的应该是class/metaclass,刚开始就这么写,想写完。为什么是 metaclass?解决上述问题的一种方法是让 Effect
成为一个元 class,这样 Freeze
就是一个 class 并且一个管理部门将是这个 class,与此实例相关的延迟。
from collections import namedtuple, defaultdict
Effect = namedtuple("Effect", ["activate", "deactivate", "category", "priority", "name"])
class Player(game.PlayerEntity):
def __init__(self):
super().__init__()
# Instead of list a priority list could/should be used
self._effects = defaultdict(list)
def add_effect(self, effect, duration=None):
# Check if player allready suffers from effect
if effect not in self._effects[effect.category]:
# If the new effect has a higher priority than all others, activate it
max_priority_effect = max(self._effects, key = lambda effect: effect.priority):
if effect.priority > max_priority_effect
# Deactivate the old effect to savely change status
max_priority_effect.deactivate(self)
effect.activate(self)
self._effects[effect.category].append(effect)
if duration is not None:
delayed(duration, remove_effect, effect)
def remove_effect(self, effect):
if effect.name in self._effects[effect.category]:
priority_element = max(self._effects, key = lambda effect: effect.priority)
# Might not be optimal?
# If Effect is implemented as class do proper __eq__
if effect.name == priority_element.name:
# If the effect is the currently active effect deactive it and activate
# the effect with the secondmost priority
self._effects[effect.category][effect.name].deactivate()
max(self._effects, key = lambda effect: effect.priority).activate()
# If Effect is implemented as class do proper max() handling
del self._effects[effect.category][effect.name]
#Effects:
freeze = Effect(
activate = lambda player: player.move_type = MoveTypes.None,
deactivate = lambda player: player.move_type = MoveTypes.WALK,
category = "movement",
priority = 2,
name = "freeze")
fly = Effect(
activate = lambda player: player.move_type = MoveTypes.FLY,
deactivate = lambda player: player.move_type = MoveTypes.WALK,
category = "movement",
priority = 1,
name = "fly")
burn = Effect(
activate = lambda player: player.ignite(),
deactivate = lambda player: player.extinguish(),
category = "other",
priority = 0,
name = "burn")
编辑:
看来我错了,这里不需要 metaclasses。以下是 Effect
为 class 的实现。未经测试。
from collections import namedtuple, defaultdict
from functools import total_ordering
@total_ordering
class Effect:
def __init__(self):
super().__init__(self)
self.deactivate = self.if_fresh(deactivate)
def if_fresh(self, f):
if self.fresh:
f()
self.fresh = False
def activate(self, player):
# Overwrite me!
pass
def deactivate(self, player):
# Overwrite me!
pass
def __eq__(self, other):
return(self is other)
def __lt__(self, other):
return(self.priority < other.priority)
class Burn(Effect):
category = "other"
priority = "0"
def activate(self, player):
player.ignite()
def deactivate(self, player):
player.extinguish()
[... other effects ... ]
class Player(game.PlayerEntity):
def __init__(self):
super().__init__()
# Instead of list a priority list could/should be used
self._effects = defaultdict(list)
def add_effect(self, effect, duration=None):
# Check if player allready suffers from effect
if effect not in self._effects[effect.category]:
# If the new effect has a higher priority than all others, activate it
max_priority_effect = max(self._effects[effect.category])
if effect > max_priority_effect:
# Deactivate the old effect to savely change status
max_priority_effect.deactivate(self)
effect.activate(self)
self._effects[effect.category].append(effect)
if duration is not None:
delayed(duration, remove_effect, effect)
def remove_effect(self, effect):
if effect in self._effects[effect.category]:
priority_element = max(self._effects)
if effect is priority_element:
# If the effect is the currently active effect deactive it and activate
# the effect with the secondmost priority
self._effects[effect.category][effect].deactivate()
max(self._effects.activate())
del self._effects[effect.category][effect]
我会尽量保持简短:
- 我有一个游戏引擎提供的
PlayerEntity
class,它有一个move_type
属性 move_type
一次可以设置为一个:MoveTypes.NONE, MoveTypes.FLY, MoveTypes.WALK
- 我已经 class 编辑了
PlayerEntity
class,并且我希望我的子class 能够执行player.freeze(duration=5)
- 我也想要自定义效果,例如
player.burn(duration=3)
这是我的第一印象:
class Player(game.PlayerEntity):
def __init__(self):
super().__init__()
self._effects = []
def add_effect(self, effect, duration=None):
self.effects.append(effect)
if duration is not None:
delayed(duration, self.remove_effect, effect)
self._apply_effects()
def remove_effect(self, effect):
if effect in self.effects:
self.effects.remove(effect)
self._apply_effects()
def _apply_effects(self):
if 'freeze' in self.effects:
self.move_type = MoveTypes.NONE
elif 'fly' in self.effects:
self.move_type = MoveTypes.FLY
else:
self.move_type = MoveTypes.WALK
if 'burn' in self.effects:
self.ignite()
else:
self.extinguish()
freeze = lambda self, duration: self.add_effect('freeze', duration)
fly = lambda self, duration: self.add_effect('fly', duration)
burn = lambda self, duration: self.add_effect('burn', duration)
不过,我想让它更灵活,例如有一个 Effect
class.
我遇到的几个问题:
-
Player
有_effects
列表并调用self._effects[0].apply(self, duration)
,或者Effect
class 有player
属性并调用它自己的属性apply(duration)
? - 我将如何与多种效果互动?
Player
只能飞行或被冻结,不能同时飞行(由于move_type
)。然而burn
是完全独立的效果。
我会有一个
Effect
class,然后将Effect
个对象的列表存储在Player
对象中。调用Player
中的方法将Effect
个对象添加到此列表。浏览每个 turn/tick 上的Effect
列表以检查堆栈、更新剩余持续时间、删除任何没有剩余持续时间的列表,等等。您可以通过创建自定义
Effect
对象与多种效果进行交互。例如,如果您想要一个允许移动的冻结状态并且只是增加Player
获得暴击的机会,请创建一个新的Effect
来定义您正在寻找的行为。
以下代码提供了使用 activate/deactivate 定义新效果并创建 "categories" 的可能性。如果两种效果属于同一类别,则优先级较高的效果会影响玩家。不同类别的效果是独立的。
实现中有一个"flaw",试想如果出现以下情况:
- 应用效果a
- 效果a被移除没有因为超时
- 效果a再次应用在第一次管理超时 之前
- 效应 a 被移除,因为达到了第一个给药持续时间
如果 Effect
变成 class/metaclass,这可能很容易解决。
这里的效果真的应该是class/metaclass,刚开始就这么写,想写完。为什么是 metaclass?解决上述问题的一种方法是让 Effect
成为一个元 class,这样 Freeze
就是一个 class 并且一个管理部门将是这个 class,与此实例相关的延迟。
from collections import namedtuple, defaultdict
Effect = namedtuple("Effect", ["activate", "deactivate", "category", "priority", "name"])
class Player(game.PlayerEntity):
def __init__(self):
super().__init__()
# Instead of list a priority list could/should be used
self._effects = defaultdict(list)
def add_effect(self, effect, duration=None):
# Check if player allready suffers from effect
if effect not in self._effects[effect.category]:
# If the new effect has a higher priority than all others, activate it
max_priority_effect = max(self._effects, key = lambda effect: effect.priority):
if effect.priority > max_priority_effect
# Deactivate the old effect to savely change status
max_priority_effect.deactivate(self)
effect.activate(self)
self._effects[effect.category].append(effect)
if duration is not None:
delayed(duration, remove_effect, effect)
def remove_effect(self, effect):
if effect.name in self._effects[effect.category]:
priority_element = max(self._effects, key = lambda effect: effect.priority)
# Might not be optimal?
# If Effect is implemented as class do proper __eq__
if effect.name == priority_element.name:
# If the effect is the currently active effect deactive it and activate
# the effect with the secondmost priority
self._effects[effect.category][effect.name].deactivate()
max(self._effects, key = lambda effect: effect.priority).activate()
# If Effect is implemented as class do proper max() handling
del self._effects[effect.category][effect.name]
#Effects:
freeze = Effect(
activate = lambda player: player.move_type = MoveTypes.None,
deactivate = lambda player: player.move_type = MoveTypes.WALK,
category = "movement",
priority = 2,
name = "freeze")
fly = Effect(
activate = lambda player: player.move_type = MoveTypes.FLY,
deactivate = lambda player: player.move_type = MoveTypes.WALK,
category = "movement",
priority = 1,
name = "fly")
burn = Effect(
activate = lambda player: player.ignite(),
deactivate = lambda player: player.extinguish(),
category = "other",
priority = 0,
name = "burn")
编辑:
看来我错了,这里不需要 metaclasses。以下是 Effect
为 class 的实现。未经测试。
from collections import namedtuple, defaultdict
from functools import total_ordering
@total_ordering
class Effect:
def __init__(self):
super().__init__(self)
self.deactivate = self.if_fresh(deactivate)
def if_fresh(self, f):
if self.fresh:
f()
self.fresh = False
def activate(self, player):
# Overwrite me!
pass
def deactivate(self, player):
# Overwrite me!
pass
def __eq__(self, other):
return(self is other)
def __lt__(self, other):
return(self.priority < other.priority)
class Burn(Effect):
category = "other"
priority = "0"
def activate(self, player):
player.ignite()
def deactivate(self, player):
player.extinguish()
[... other effects ... ]
class Player(game.PlayerEntity):
def __init__(self):
super().__init__()
# Instead of list a priority list could/should be used
self._effects = defaultdict(list)
def add_effect(self, effect, duration=None):
# Check if player allready suffers from effect
if effect not in self._effects[effect.category]:
# If the new effect has a higher priority than all others, activate it
max_priority_effect = max(self._effects[effect.category])
if effect > max_priority_effect:
# Deactivate the old effect to savely change status
max_priority_effect.deactivate(self)
effect.activate(self)
self._effects[effect.category].append(effect)
if duration is not None:
delayed(duration, remove_effect, effect)
def remove_effect(self, effect):
if effect in self._effects[effect.category]:
priority_element = max(self._effects)
if effect is priority_element:
# If the effect is the currently active effect deactive it and activate
# the effect with the secondmost priority
self._effects[effect.category][effect].deactivate()
max(self._effects.activate())
del self._effects[effect.category][effect]