Управляемый данными дизайн
Я отправил что-то вроде этого вопроса в обзор кода недавно.
После некоторых предложений и улучшений, результатом стал простой код, который позволял бы относительную гибкость при создании оружия на основе словаря (или JSON). Данные интерпретируются во время выполнения, и Weapon
сам класс выполняет простые проверки без необходимости полагаться на весь интерпретатор сценариев.
Проектирование на основе данных, несмотря на то, что Python является интерпретируемым языком (исходные файлы и файлы данных могут редактироваться без необходимости их перекомпиляции), звучит как правильное решение в таких случаях, как тот, который вы представили. Этот вопрос более подробно раскрывает концепцию, ее плюсы и минусы. Theres также хорошая презентация в Корнельском университете об этом.
По сравнению с другими языками, такими как C ++, которые, вероятно, будут использовать язык сценариев (например, LUA) для обработки взаимодействия с механизмом данных и сценариями в целом, а также определенный формат данных (например, 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)
С некоторыми будущими улучшениями я надеюсь, что это позволит мне когда-нибудь иметь динамическую систему крафта, обрабатывая компоненты оружия вместо целого оружия ...
Тест
- Персонаж A подбирает оружие, вооружает его (мы печатаем его статистику), затем бросаем его;
- Персонаж B выбирает то же оружие, экипирует его (и мы снова печатаем его статистику, чтобы показать, как они различаются).
Так:
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, Эффекты попадания: ['Нечестивый'], Другие эффекты: ['Нечестивый Ореол']