Дизайн, керований даними
Нещодавно я подав щось на зразок цього питання до коду .
Після декількох пропозицій та вдосконалень, результатом став простий код, який дозволив би відносну гнучкість у створенні зброї на основі словника (або JSON). Дані інтерпретуються під час виконання, а прості перевірки проводяться самим Weapon
класом, без необхідності покладатися на цілого інтерпретатора сценарію.
Дизайн, керований даними, незважаючи на те, що Python є інтерпретованою мовою (і вихідні, і файли даних можна редагувати без необхідності перекомпілювати їх), здається правильним робити у таких випадках, як представлений вами. Це питання детальніше описує концепцію, її плюси та мінуси. Є також приємна презентація про Корнельський університет про це.
У порівнянні з іншими мовами, такими як C ++, яка, ймовірно, використовує мову сценаріїв (наприклад, LUA) для обробки взаємодії двигуна даних x та сценаріїв загалом, і певний формат даних (наприклад, XML) для зберігання даних, Python може насправді зробити все це самостійно (враховуючи стандарт, dict
але також weakref
, останній спеціально для завантаження ресурсів та кешування).
Однак незалежний розробник може не сприймати керований даними крайній підхід, як пропонується в цій статті :
Наскільки я орієнтований на дані, що стосується дизайну? Я не думаю, що ігровий движок повинен містити один рядок ігрового коду. Не один. Немає жорсткої коду зброї. Немає жорсткого коду макета HUD. Немає штучного коду AI. Нада. Zip. Зільч.
Можливо, за допомогою Python можна було б отримати вигоду від найкращого як об'єктно-орієнтованого, так і керованого даними підходу, спрямованого як на продуктивність, так і на розширення.
Проста обробка зразка
У конкретному випадку, обговореному при перегляді коду, словник зберігав би як "статичні атрибути", так і логіку, яку слід інтерпретувати - якщо зброя має будь-яку умовну поведінку.
На прикладі нижче меч повинен мати деякі здібності та статистику в руках персонажів класу "антипаладин", без ефектів, із меншими статистичними показниками при використанні інших символів):
WEAPONS = {
"bastard's sting": {
# magic enhancement, weight, value, dmg, and other attributes would go here.
"magic": 2,
# Those lists would contain the name of effects the weapon provides by default.
# They are empty because, in this example, the effects are only available in a
# specific condition.
"on_turn_actions": [],
"on_hit_actions": [],
"on_equip": [
{
"type": "check",
"condition": {
'object': 'owner',
'attribute': 'char_class',
'value': "antipaladin"
},
True: [
{
"type": "action",
"action": "add_to",
"args": {
"category": "on_hit",
"actions": ["unholy"]
}
},
{
"type": "action",
"action": "add_to",
"args": {
"category": "on_turn",
"actions": ["unholy aurea"]
}
},
{
"type": "action",
"action": "set_attribute",
"args": {
"field": "magic",
"value": 5
}
}
],
False: [
{
"type": "action",
"action": "set_attribute",
"args": {
"field": "magic",
"value": 2
}
}
]
}
],
"on_unequip": [
{
"type": "action",
"action": "remove_from",
"args": {
"category": "on_hit",
"actions": ["unholy"]
},
},
{
"type": "action",
"action": "remove_from",
"args": {
"category": "on_turn",
"actions": ["unholy aurea"]
},
},
{
"type": "action",
"action": "set_attribute",
"args": ["magic", 2]
}
]
}
}
Для тестування я створив прості Player
та Weapon
класи: перший, який тримає / озброює зброю (таким чином називаючи її умовне налаштування on_equip), а останній - єдиний клас, який би отримував дані зі словника на основі назви елемента, переданого як аргумент під час Weapon
ініціалізації. Вони не відображають належний дизайн ігрових класів, але все ж можуть бути корисні для перевірки даних:
class Player:
"""Represent the player character."""
inventory = []
def __init__(self, char_class):
"""For this example, we just store the class on the instance."""
self.char_class = char_class
def pick_up(self, item):
"""Pick an object, put in inventory, set its owner."""
self.inventory.append(item)
item.owner = self
class Weapon:
"""A type of item that can be equipped/used to attack."""
equipped = False
action_lists = {
"on_hit": "on_hit_actions",
"on_turn": "on_turn_actions",
}
def __init__(self, template):
"""Set the parameters based on a template."""
self.__dict__.update(WEAPONS[template])
def toggle_equip(self):
"""Set item status and call its equip/unequip functions."""
if self.equipped:
self.equipped = False
actions = self.on_unequip
else:
self.equipped = True
actions = self.on_equip
for action in actions:
if action['type'] == "check":
self.check(action)
elif action['type'] == "action":
self.action(action)
def check(self, dic):
"""Check a condition and call an action according to it."""
obj = getattr(self, dic['condition']['object'])
compared_att = getattr(obj, dic['condition']['attribute'])
value = dic['condition']['value']
result = compared_att == value
self.action(*dic[result])
def action(self, *dicts):
"""Perform action with args, both specified on dicts."""
for dic in dicts:
act = getattr(self, dic['action'])
args = dic['args']
if isinstance(args, list):
act(*args)
elif isinstance(args, dict):
act(**args)
def set_attribute(self, field, value):
"""Set the specified field with the given value."""
setattr(self, field, value)
def add_to(self, category, actions):
"""Add one or more actions to the category's list."""
action_list = getattr(self, self.action_lists[category])
for action in actions:
if action not in action_list:
action_list.append(action)
def remove_from(self, category, actions):
"""Remove one or more actions from the category's list."""
action_list = getattr(self, self.action_lists[category])
for action in actions:
if action in action_list:
action_list.remove(action)
З деяким майбутнім вдосконаленням я сподіваюся, що це навіть дозволить мені коли-небудь мати динамічну систему крафт, обробляти компоненти зброї замість цілої зброї ...
Тест
- Персонаж А підбирає зброю, оснащуємо її (друкуємо її статистику), а потім скидаємо її;
- Персонаж Б підбирає ту саму зброю, озброює її (і ми знову друкуємо її статистику, щоб показати, чим вони відрізняються).
Подобається це:
def test():
"""A simple test.
Item features should be printed differently for each player.
"""
weapon = Weapon("bastard's sting")
player1 = Player("bard")
player1.pick_up(weapon)
weapon.toggle_equip()
print("Enhancement: {}, Hit effects: {}, Other effects: {}".format(
weapon.magic, weapon.on_hit_actions, weapon.on_turn_actions))
weapon.toggle_equip()
player2 = Player("antipaladin")
player2.pick_up(weapon)
weapon.toggle_equip()
print("Enhancement: {}, Hit effects: {}, Other effects: {}".format(
weapon.magic, weapon.on_hit_actions, weapon.on_turn_actions))
if __name__ == '__main__':
test()
Він повинен надрукувати:
Для барда
Покращення: 2, Ефекти потрапляння: [], Інші ефекти: []
За антипаладин
Підсилення: 5, Ефекти ударів: ['нечесні'], Інші ефекти: ['нечесна аурея']