Conception basée sur les données
J'ai récemment soumis quelque chose comme cette question à la révision du code .
Après quelques suggestions et améliorations, le résultat a été un code simple qui permettrait une certaine flexibilité relative sur la création d'armes basée sur un dictionnaire (ou JSON). Les données sont interprétées au moment de l'exécution et de simples vérifications sont effectuées par la Weapon
classe elle-même, sans avoir besoin de s'appuyer sur un interpréteur de script complet.
La conception pilotée par les données, bien que Python soit un langage interprété (les fichiers source et de données peuvent être modifiés sans avoir besoin de les recompiler), semble être la bonne chose à faire dans des cas tels que celui que vous avez présenté. Cette question va plus en détail sur le concept, ses avantages et ses inconvénients. Il y a aussi une belle présentation sur Cornell University à ce sujet.
Par rapport à d'autres langages, tels que C ++, qui utiliseraient probablement un langage de script (tel que LUA) pour gérer l'interaction data x engine et les scripts en général, et un certain format de données (comme XML) pour stocker les données, Python peut réellement faire le tout seul (compte tenu de la norme dict
mais aussi weakref
, cette dernière spécifiquement pour le chargement et la mise en cache des ressources).
Un développeur indépendant, cependant, peut ne pas pousser à l'extrême l'approche basée sur les données comme suggéré dans cet article :
Combien suis-je sur la conception basée sur les données? Je ne pense pas qu'un moteur de jeu devrait contenir une seule ligne de code spécifique au jeu. Pas une. Aucun type d'arme codé en dur. Pas de mise en page HUD codée en dur. Pas d'unité AI codée en dur. Nada. Zip *: français. Rien.
Peut-être qu'avec Python, on pourrait bénéficier du meilleur de l'approche orientée objet et orientée données, visant à la fois la productivité et l'extensibilité.
Traitement simple des échantillons
Dans le cas spécifique discuté lors de la révision du code, un dictionnaire stockerait à la fois les "attributs statiques" et la logique à interpréter - si l'arme avait un comportement conditionnel.
Dans l'exemple ci-dessous, une épée devrait avoir des capacités et des statistiques entre les mains des personnages de la classe `` antipaladin '', et aucun effet, avec des statistiques inférieures lorsqu'elles sont utilisées par d'autres personnages):
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]
}
]
}
}
À des fins de test, j'ai créé des classes simples Player
et Weapon
: la première pour tenir / équiper l'arme (appelant ainsi son paramètre on_equip conditionnel) et la seconde en tant que classe unique qui récupérerait les données du dictionnaire, en fonction du nom de l'élément passé en tant que lors de l' Weapon
initialisation. Ils ne reflètent pas la conception appropriée des classes de jeu, mais peuvent néanmoins être utiles pour tester les données:
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)
Avec quelques améliorations futures, j'espère que cela me permettra même d'avoir un jour un système d'artisanat dynamique, traitant des composants d'armes au lieu d'armes entières ...
Tester
- Le personnage A choisit une arme, l'équiper (nous imprimons ses statistiques), puis la laisser tomber;
- Le personnage B choisit la même arme, l'équiper (et nous imprimons à nouveau ses statistiques pour montrer comment elles sont différentes).
Comme ça:
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()
Il devrait imprimer:
Pour un barde
Amélioration: 2, Effets de touche: [], Autres effets: []
Pour un antipaladin
Amélioration: 5, Effets de touche: [«impie»], Autres effets: [«aurée impie»]