Klassen für Monster/Einheiten in Spielen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Karl
User
Beiträge: 252
Registriert: Freitag 29. Juni 2007, 17:49

Hallo.
Ich überlege gerade, wie man ein Spiel designen könnte (damit meine ich nur die Logik :p), in dem viele verschiedene Monster vorkommen, die verschiedene Eigenschaften haben.

Also was ich konkret will ist eine Art Kampfsystem.
Monster A mit irgendwelchen Eigenschaften kämpft gegen Monster B mit anderen Eigenschaften.
Am Ende kommt ein Sieger heraus.

Eigentlich ist das ja kein Problem aber ich suche nach einem Ansatz der möglichst flexibel ist, so dass man ganz simple Dinge damit realisieren kann aber auch etwas komplexere.

Also es müsste ja zum Beispiel eine grundlegende Einheiten/Lebewesen Klasse geben, die alles zwangsläufig notwendige wie Hitpoints implementiert hat.
Aber dann weiß ich auch nicht mehr wirklich weiter ... Müsste ein Rüstungstyp auch ein Objekt sein oder soll ich da einfach einen String bzw ein Tupel mit Strings nehmen das sagt "Deine Rüstung ist vom Typ X und diese ist gegen Y immun aber gegen Z sehr anfällig" usw?
Oder was wenn jetzt eine Einheit eine Spezialfähigkeit hat? Das muss man ja auch irgendwie einbauen können.
Das könnte ich zwar alles programmieren aber ich weiß nicht wie man sowas objekt orientiert gut angeht damit es flexibel und übersichtlich bleibt ... Weil wenn ich das einfach so in eine Python Datei haue funktioniert es vielleicht irgendwann sogar aber bei so viel unstrukturiertem Code und komischen Konstrukten usw die ich da reinbauen würde, würde ich am Ende den Code selbst nicht mehr verstehn ...
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Hmm, ja, was erwartest du jetzt für eine Antwort? Ich würde es mit Klassen lösen, weil es eben einfacher ist das einheitlich zu lösen als mit Strings oder ähnlichem.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Karl
User
Beiträge: 252
Registriert: Freitag 29. Juni 2007, 17:49

Leonidas hat geschrieben:Hmm, ja, was erwartest du jetzt für eine Antwort?
:D
Na ja irgendwas das hilft ... ja ich weiß das ist eine sinnlose Bemerkung.

Du sagst ja du würdest es mit Klassen amchen weil es einfacher und einheitlicher ist. So hab ich mir das auch gedacht, besonders weil es dann einheitlich wäre und ich nicht für jeden Sonderfall ein if aufschreiben müsste.
ich weiß auch wie OOP funktioniert (also Klassen, Konstruktoren, Objekte/Instanzen, ..) nur weiß ich nicht wie man das dann sinnvoll in Klassen aufteilt damit das möglichst flexibel ist und einigermaßen übersichtlich bleibt.
Das wär dann zum Beispiel eine Antwort auf die Frage was für eine Antwort ich erwarten würde :D Ein Vorschlag zum sinnvollen Aufteilen der Problematik in Klassen
CM
User
Beiträge: 2464
Registriert: Sonntag 29. August 2004, 19:47
Kontaktdaten:

Karl hat geschrieben:ich weiß auch wie OOP funktioniert ... nur weiß ich nicht wie man das dann sinnvoll in Klassen aufteilt damit das möglichst flexibel ist und einigermaßen übersichtlich bleibt.
Dann ist Dein Wissen eher theoretischer Natur. Nur zu: Versuche es in die Praxis umzusetzen und stelle, wenn es hakt, hier Fragen, wie man Dein - dann konkretes - Problem lösen könnte.

Es ist nämlich leider wenig sinnvoll, wenn jemand hier einen Lösungsansatz posten würde: Nur Du kennst Dein Spieldesign. Eine Lösung aus dritter Hand würde verlangen, dass Du Deine Ideen diesem Konstrukt unterordnen müsstest - oder eben weiter Python lernst. ;-)

Gruß,
Christian
Karl
User
Beiträge: 252
Registriert: Freitag 29. Juni 2007, 17:49

Ja mein Wissen ist eher theoretischer Natur.
So kleine Sachen sind kein Problem wenn ich zum Beispiel einen Datentyp brauche den es nicht in der Standardbibliothek gibt, kann ich mir den programmieren ... Also so kleine überschaubare Dinge sind kein Problem.
Nur ist das ja jetzt eher ein ganzes System und nicht nur ein kleines Detail wie wenn ich 'list' ein paar Methoden hinzufüge.
Und wenn ich jetzt einfach anfange kann ich das ganze in ner halben stunde wieder vergessen weil alles völliger Müll ist :D Und dann probier ichs wieder neu aber komm auf keine vernünftigere Idee und versuch das selbe noch mal nur ein bisschen anders ...

Ich will ja nicht dass ihr mir alles "löst" aber so ein paar stupser in die Richtige Denkrichtung oder so wären nicht schlecht.
Das ganze ist ja auch noch nicht "gelöst" wenn ich weiß welche Klassen ich brauche ;)
Versuche es in die Praxis umzusetzen und stelle, wenn es hakt, hier Fragen, wie man Dein - dann konkretes - Problem lösen könnte.
Und dann sagt ihr mir nämlich: dein Problem ist dein Design das kannst du vergessen, mach noch mal alles neu.
Und dann bin ich genau an dem selben Punkt. Und wenn du mir dann wieder sagt: Du hast ja kein konkretes Problem, versuchs doch noch mal ... Kann ichs ja auch gleich lassen :D
BlackJack

Letztendlich lernt man bei Entwürfen, die man wieder wegwerfen muss, etwas. Das sollte also keine total verschwendete Zeit sein.

IMHO solltest Du Dir die Ziele vielleicht auch etwas kleiner stecken. Superflexible Entwürfe, mit denen man am Ende alles ausdrücken *könnte*, haben oft die Eigenschaft einfach nicht fertig zu werden.

Ich würde erst einmal einfach anfangen und das dann erweitern. So verhindert man Komplexitätsmonster und behält die Motivation aufrecht, wenn es immer einen spielbaren Zustand gibt.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Naja, du hast eine Klasse ``Held`` und diese hat dann ggf Unterklassen wie ``Magier`` oder ``Kämpfer`` oder was auch immer. Dann hat jeder Held ein Attribut ``rüstung`` an das man eine Instanz von ``Rüstung`` (oder eher eine Unterinstanz wie ``Kettenhemd``) usw. bindet. Dann hat ``Held`` eine Methode ``kämpfe(self, anderer_held)``, dort wird der Kampf ausgetragen und eine Methode ``treffer(self, schaden)`` falls der Kämpfer Schaden davonträgt. Nun wird von dem verursachten Schaden noch ggf. der Rüstungsbonus oder generell andere Boni angerechnet und das dann mit den Trefferpunkten des Helden verrechnet. Usw.

Du kannst ja gerne irgendeine Klassenstruktur aufbauen und sie hier zur Diskussion reinstellen. Sowas bekommt man sowieso nicht beim ersten Anlauf richtig hin, daher erfordert es eben ein wenig Erfahrung.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Karl
User
Beiträge: 252
Registriert: Freitag 29. Juni 2007, 17:49

BlackJack hat geschrieben:Letztendlich lernt man bei Entwürfen, die man wieder wegwerfen muss, etwas. Das sollte also keine total verschwendete Zeit sein.
okay das stimmt auch wieder
BlackJack hat geschrieben:IMHO solltest Du Dir die Ziele vielleicht auch etwas kleiner stecken. Superflexible Entwürfe, mit denen man am Ende alles ausdrücken *könnte*, haben oft die Eigenschaft einfach nicht fertig zu werden.
Ja das kann auch sein ... von mir aus muss es nicht superflexibel sein, mir reicht auch ein "deutlich flexibler als alles in eine Datei mit 50 Funktionen zu stecken" ;) Aber dann kann man ja wenigstens einen vernünftigen Ansatz nehmen anstatt nacher sinnlose Objekte zu haben die das ganze viel komplizierter machen. Aber da kommt dann natürlich auch wieder der erste Punkt deines Posts ins spiel.
Du kannst ja gerne irgendeine Klassenstruktur aufbauen und sie hier zur Diskussion reinstellen. Sowas bekommt man sowieso nicht beim ersten Anlauf richtig hin, daher erfordert es eben ein wenig Erfahrung.
Ja werd ich dann eventuell tun .. Mir fehlt vorallem noch die Vorstellung wie alle Klassen letztendlich miteinander korrelieren und was jetzt zB das Kettenhemd wirklich für Dinge implementieren muss.
Warum wäre eine Klasse für ein Kettenhemd zB sinnvoller als eine Instanz von "Rüstung" die einfach andere Grundeigenschaften besitzt.
also sowas wie
Kettenhemd = Rüstung(name=kettenhemd, schutz_vor_feuer=20%, ... etc)
?
Oder was könnte dann eine Unterklasse mehr, was die Instanz nicht kann?
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Du kannst von einer Unterklasse erben, was du von einer Instanz nicht kannst. Außerdem kann dein Kettenhemd noch allerhand Methoden überschreiben, etwa kann es bei der Berechnung des Schadens noch zum Beispiel die Spielzeit einrechnen. Vielleicht ist es ja ein Werwolf-Kettenhemd, dass in der Nacht stärker ist. Oder es zerfällt nach irgendwieviel absorbierten Schaden. Oder, oder, oder. Kann ich ja nicht wissen, was du da planst.

Insgesamt bin ich aber auch BlackJacks Meinung: besser immer von spielbarer Version zu spielbarer Version migrieren statt alles im Vorraus planen zu wollen. Das ist ja hier keine UML-Lektion.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Karl
User
Beiträge: 252
Registriert: Freitag 29. Juni 2007, 17:49

Leonidas hat geschrieben:Vielleicht ist es ja ein Werwolf-Kettenhemd, dass in der Nacht stärker ist.
:D
Dass man von einer Instanz nicht erben kann, war mir vorher auch schon klar. Ich habe mich nur gefragt, was die Unterklasse für Vorteile bieten würde ... Gutes Beispiel ;)
Insgesamt bin ich aber auch BlackJacks Meinung: besser immer von spielbarer Version zu spielbarer Version migrieren statt alles im Vorraus planen zu wollen. Das ist ja hier keine UML-Lektion.
Wobei die bestimmt auch nicht schlecht wär :p

Okay dann werde ich eben erstmal nur das grundlegendste in eine Klasse für Einheiten packen und dann nach und nach mehr implementieren und eventuell noch neue Klassen für Rüstung und Angriff etc. implementieren ...
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Vor sehr vielen Jahren (oh man) habe ich für mich beschlossen, dass die beste mir bekannte Lösung für das beschrieben Programm Prototypen und Multimethoden sind. Wenn fast alle Objekte einzigartig sind, ist es wenig sinnvoll, für alles extra noch Klassen definieren zu müssen. Daher Prototypen. Da bei einem Kampf zwei Lebewesen, zwei Waffen und ihre Rüstungen beteiligt sind und es unklar ist, welches dieser Objekte sich nun um die Kampfabwicklung kümmern sollte, sollte man die Methoden nicht (wie gewohnt) nur von einem Empfänger sondern gleich von einer Reihe von Objekten abhängig machen. Daher Multimethoden. Es gab da mal so eine Sprache namens Cecil, die Ideen aus Self (einem prototypbasierten Dialekt von Smalltalk und einem der Vorbilder für JavaScript) mit dem Objektsystem von CommonLisp kombinierte und die ich ganz toll fand. Also schrieb ich einen Interpreter für Cecil in Java... zu dem eigentlichen Spiel bin ich nie gekommen.

Hat man nur Python und Klassen und nur einfache Methoden, würde ich wohl eine möglichst generische Klasse für Lebewesen und Gegenstände (die ja auch belebt sein könnten - ist ein Golem ein Lebewesen oder ein Gegenstand? - und was ist mit einem mechanischen Pferd?), vielen, vielen Eigenschaften und einem eigenen Mechanismus für das Finden von passenden Funktionen zu bestimmten Objekten (also selbstgemachten Multimethoden oder einer Rules Engine) arbeiten.

Gegenstände wie Rüstungen oder Waffen modifizieren die Eigenschaften des Lebewesens. Richtet ein Mensch etwa mit seiner Faust 1D4 Punkte Schaden an, so ersetzt ein Speer diesen Schaden durch 1D6+1. Ist dies ein magischer Speer der Drachentötung, so richtet er nach wie vor 1D6+1 Punkte Schaden an - außer der Gegner ist ein Drache, dann ist der Schaden 1D6+1000. Wann ist ein Lebewesen ein Drache? Es könnte eine Eigenschaft sein oder aber es gibt eine Klassifizierungsfunktion. Und nun darf man sich fragen, ob ein Krieger, der sich mit einem Zaubertrank in einen Drachen verwandelt hat, ein Drachen ist und von der Lanze getötet würde.

Ich würde übrigens auf keinen Fall Charakterklassen wie Magier oder Kämpfer als Unterklassen realisieren. Am besten vergisst man, dass es Vererbung in Programmiersprachen gibt. Jedenfalls in diesem Fall. Besser ist es, die Charakterklasse als eigenes Objekt (Strategie-Entwurfsmuster) zu realisieren oder sie wird wird mit einer Typfunktion (ein Konzept, was Cecil hatte) bestimmt.

Eine Werkreatur ist ein menschliches Wesen, welches sich in ein Tier verwandeln kann. Diese Eigenschaft auf ein Kettenhemd projizieren zu wollen, widerstrebt mir übrigens. Wenn dessen Rüstungsschutz bei Vollmond stärker ist, dann man das ein von einer Mondgöttin gesegneter Gegenstand sein, aber kein Wer-irgendwas.

Ansonsten biete ich noch http://www.python-forum.de/post-112630.html#112630 und http://www.python-forum.de/post-112724.html#112724 als Beispiel an.

Stefan
Karl
User
Beiträge: 252
Registriert: Freitag 29. Juni 2007, 17:49

Danke an Dauerbaustelle und sma für eure Codebeispiele, ich werde mal gucken, was ich damit anfangen kann :)
Ich weiß zwar nicht was die hälfte der Begriffe in deinem (sma) Posting bedeutet aber ich werd mich mal informieren :D

PS: Ich glaube das mit dem Werwolf-Kettenhemd hatte Leonidas nicht ganz so ernst gemeint ;)
Karl
User
Beiträge: 252
Registriert: Freitag 29. Juni 2007, 17:49

Hi, da bin ich wieder.
Ich hatte bisher noch nicht so viel Zeit aber ich habe mich mal ein bisschen rangesetzt und überlegt und sogar schon einen Anfang (damit ist wirklich Anfang gemeint) gemacht.

Ihr könnt euch das Ergebnis ja mal ansehen:
http://paste.pocoo.org/show/131987/
Dazu erst mal ein paar Fragen:
1. Zeile 62 und 63 sind irgendwie unschön. Da erstelle ich erst mal eine Instanz der übergebenen AttackType/DefenseType Klasse. Macht man das so oder sollte man das irgendwie anders regeln? Oder vielleicht komplett anders, siehe 2.

2. Die DefenseType und AttackType Klassen sind sowieso etwas überflüssig glaube ich. Sie sind ja nur ein Dictionary. Aber vielleicht weite ich das ja noch mal aus und die Klasse ist dann notwendig. Was haltet ihr allgemein davon? Es ist schon umständlich, wenn ich

Code: Alles auswählen

other.defense[1] -= self.attack[1] * self.attack[0].dmg_against[type(other.defense[0]).__name__]/100.0
schreiben muss :D Vorallem sind alle Informationen doppelt enthalten da ich entweder die Information aus AttackType oder DefenseType nehmen kann.

@sma: Du meintest ja ich sollte das mit der Vererbung ganz vergessen.
Wenn sich ein Drache in ein Schaf verwandelt hat es logischerweise keine Dracheneigenschaften mehr, deshalb klappt das mit der Vererbung in dem Fall nicht. Aber andererseits ist ein Schaf auch ein anderes Objekt, dann könnte ich den Drachen doch einfach mit einem Schaf ersetzen, oder? Und wenn er sich wieder in einen Drachen verwandelt, kann ich ja wieder ein Drachen-Objekt erstellen oder das alte benutzen. Streng genommen müsste es zwar das gleiche Objekt/Lebewesen sein, aber das würde sich ja nur im Hintergrund abspielen.
Oder was für Gründe würden deiner Meinung nach noch gegen Vererbung in diesem Falle sprechen?

Übrigens habe ich mir mal die Idee, immer ein "spielbares" bzw funktionierendes Programm zu haben, zu Herzen genommen. Deshalb auch diese etwas sehr plumpe hit-Methode. Die bitte nicht kommentieren, dient nur der "Spielbarkeit" ;)

Als nächstes wollte ich dann Armeen bzw Gruppen von Monstern implementieren und Helden, welches Einheiten sind, die Items (die wiederum auch implementiert werden müssen) tragen können und eine Armee führen können.
Dazu noch eine Frage: Ein Held ist ja eine Einheit. Sollte ich jetzt in der Unterklasse realisieren, dass der Held Items tragen kann oder direkt in der Unit Klasse (also höchste Ebene), aber nur Helden tatsächlich Items geben?

Danke schon mal für Antworten und die Mühe, mein Geschwafel durchzulesen ;)
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

`__str__` könntest du einmal in der AttackType-Klasse definieren:

Code: Alles auswählen

def __str__(self):
    return self.__class__.__name__
Karl
User
Beiträge: 252
Registriert: Freitag 29. Juni 2007, 17:49

Stimmt, sehr gute Idee :)
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

In der Regel halte ich es nicht für praktikabel, ein Objekt durch ein anderes auszutauschen. Objekte haben (neben Verhalten und Zustand) eine Identität, die Teil ihres Objektseins ist. Wenn du einen Drachen in ein Schaf verwandelst, bleibt es ja das SELBE Lebewesen, also muss es bei einer Objektmodellierung auch das SELBE Objekt bleiben. Ganz praktisch gesprochen ist es zudem schwer (ohne eine #become:-Methode wie bei Smalltalk zu haben), ein beliebig häufig referenziertes Objekt auszutauschen indem man alle Referenzen ändert. Meist benutzt man daher in diesem Fall das "Strategie"-Entwurfsmuster.

Stefan
Karl
User
Beiträge: 252
Registriert: Freitag 29. Juni 2007, 17:49

Du hast natürlich schon recht, dass es ein und das selbe Lebewesen ist, habe ich auch schon gesagt. Aber alle Referenzen auf das neue Objekt zu ändern, wäre vermutlich nicht weiter schwer, da im Normalfall eine einzige reichen sollte. Nur das Ding mit dem Objekt-Zustand wäre ein kleines Problem. Aber Dinge wie Hitpoins etc. kann man ja auch auf recht einfache Art prozentual auf das andere Objekt umrechnen.

Aber trotzdem hört es sich vernünftig an, mal über eine Alternative nachzudenken, auch wenn ich wahrscheinlich irgendwie mit dem Vererben durchkommen würde (und mir das ehrlich gesagt auf den ersten Blick am besten gefällt).
Kannst du mir vielleicht das Strategie Entwurfsmuster etwas näher bringen? Auf Wikipedia erfährt man nur sehr oberflächliche Dinge dazu.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Dieser Absatz beschreibt IMHO ausreichend, wie das Strategie-Entwurfsmuster funktioniert. Du ersetzt Vererbung durch Delegation.

Code: Alles auswählen

class Monster:
    def __init__(self, behavior):
        self.behavior = behavior
        self.behavior.monster = self
    
    def attack(self):
        self.behavior.attack()

class DragonBehavior:
    def attack(self):
        return "Breath fire!"

class MouseBehavior:
    def attack(self):
        return "Bite!"
Stefan
Zuletzt geändert von sma am Sonntag 2. August 2009, 16:21, insgesamt 1-mal geändert.
Karl
User
Beiträge: 252
Registriert: Freitag 29. Juni 2007, 17:49

Okay stimmt, die englische Wikipedia ist meistens ausführlicher ...
Und durch dein Beispiel und deine simple Erklärung in einem Satz wird es mir sowieso klar :) Danke.

Es wird ja immer gesagt, dass man mit OOP Objekte so modelliert, dass sie möglichst der Realität entsprechen. Und mein Ansatz hätte wahrscheinlich eher der Realität entsprochen (wenn an das von fiktiven Monstern behaupten kann). Aber das ist wohl zu idealistisch, ich werde mal versuchen dieses Strategie Entwurfsmuster sinnoll umzusetzen.

Dann werde ich Vererbung für diese Zwecke, wie du sagtest, erst mal vollkommen "vergessen". Soll ich das dann auch auf Dinge wie AttackType etc. anwenden, oder würdest du das komplett anders regeln bzw. überhaupt nicht über Klassen? Das alles nicht über Vererbung zu regeln zerstört quasi alle meine bisherigen gedanklichen Resultate.
Antworten