Datengetriebenes Design
Ich habe so etwas wie diese Frage zur Codeüberprüfung eingereicht kürzlich .
Nach einigen Vorschlägen und Verbesserungen war das Ergebnis ein einfacher Code, der eine relative Flexibilität bei der Waffenerstellung basierend auf einem Wörterbuch (oder JSON) ermöglichen würde. Die Daten werden zur Laufzeit interpretiert und einfache Überprüfungen werden von der Weapon
Klasse selbst durchgeführt, ohne dass ein vollständiger Skriptinterpreter erforderlich ist.
Datengesteuertes Design, obwohl Python eine interpretierte Sprache ist (sowohl Quell- als auch Datendateien können bearbeitet werden, ohne dass sie neu kompiliert werden müssen), scheint in Fällen wie dem von Ihnen vorgestellten die richtige Vorgehensweise zu sein. Diese Frage geht näher auf das Konzept, seine Vor- und Nachteile ein. Es gibt auch eine schöne Präsentation über die Cornell University .
Im Vergleich zu anderen Sprachen, wie C ++, die wahrscheinlich eine Skriptsprache (wie LUA) verwenden würden, um die Interaktion mit der Daten-Engine und Skripts im Allgemeinen zu verarbeiten, und ein bestimmtes Datenformat (wie XML), um die Daten zu speichern, kann Python dies tatsächlich tun alles für sich allein (unter Berücksichtigung des Standards, dict
aber auch weakref
letzterer speziell für das Laden und Zwischenspeichern von Ressourcen).
Ein unabhängiger Entwickler kann den datengetriebenen Ansatz jedoch möglicherweise nicht bis zum Äußersten verfolgen, wie in diesem Artikel vorgeschlagen :
Wie sehr geht es mir um datengetriebenes Design? Ich denke nicht, dass eine Game Engine eine einzelne Zeile spielspezifischen Codes enthalten sollte. Nicht eins. Keine fest codierten Waffentypen. Kein fest codiertes HUD-Layout. Keine fest codierte Einheit AI. Nada. Postleitzahl. Zilch.
Vielleicht könnte man mit Python vom besten objektorientierten und datengetriebenen Ansatz profitieren, der sowohl auf Produktivität als auch auf Erweiterbarkeit abzielt.
Einfache Probenaufbereitung
In dem speziellen Fall, der bei der Codeüberprüfung erörtert wurde, werden in einem Wörterbuch sowohl die "statischen Attribute" als auch die zu interpretierende Logik gespeichert, falls die Waffe ein bedingtes Verhalten aufweist.
In dem folgenden Beispiel sollte ein Schwert einige Fähigkeiten und Werte in den Händen von Charakteren der Klasse 'Antipaladin' und keine Effekte haben, mit niedrigeren Werten, wenn sie von anderen Charakteren verwendet werden.
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]
}
]
}
}
Zu Testzwecken habe ich einfache Player
und Weapon
Klassen erstellt: die erste, die die Waffe hält / ausrüstet (und damit die bedingte Einstellung on_equip aufruft) und die letztere als einzelne Klasse, die die Daten aus dem Wörterbuch basierend auf dem als übergebenen Objektnamen abruft Argument während der Weapon
Initialisierung. Sie spiegeln nicht das richtige Design der Spielklassen wider, können aber dennoch nützlich sein, um die Daten zu testen:
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)
Mit einigen zukünftigen Verbesserungen hoffe ich, dass ich eines Tages sogar ein dynamisches Handwerkssystem haben kann, das Waffenkomponenten statt ganzer Waffen verarbeitet ...
Prüfung
- Charakter A wählt eine Waffe, rüstet sie aus (wir drucken ihre Statistiken aus) und lässt sie dann fallen.
- Charakter B wählt die gleiche Waffe aus, rüstet sie aus (und wir drucken die Statistiken erneut, um zu zeigen, wie unterschiedlich sie sind).
So was:
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()
Es sollte drucken:
Für einen Barden
Verbesserung: 2, Treffereffekte: [], Andere Effekte: []
Für ein Antipaladin
Verbesserung: 5, Treffereffekte: ['unheilig'], Andere Effekte: ['unheilige Aura']