Architekturfrage zu "Game Objects"
Architekturfrage zu "Game Objects"
Hallo Zusammen,
ich bin gerade (wieder einmal) an dem Punkt an dem ich mir Gedanken über meine "Game Objects" (Entities, Actors, whatev,...) mache.
Als GameObject bezeichne ich demnach alles, was irgendwie als Objekt im Spiel vorkommen kann. Ob Character, NPC, Bäume, Monster, Schatztruhen oder auch nur Trigger (unsichtbare Marker welche Ereignisse auslösen), mal animiert, mal mit Physik, mal mit AI, alles kann nichts muss ;-).
Die allgemeine Recherche bei Google sowie div. Büchern (Game Engine Architecture, Game Design, ...) ergibt, dass eine statische Klassenhierarchie nicht funktioniert. Also sie funktioniert schon, solange man nichts ändert oder hinzufügt, sprich, sie ist zu unflexibel gegenüber Änderungen. Außerdem kommt man hier schnell in die Bedrängnis multiple inerhitance verwenden zu müssen und dem Deadly Diamond in die Falle zu gehen.
Oft vorgeschlagen wird Component Based Object oder bzw. auch in Verbindung mit Data Driven Components. Die generelle Idee dahinter ist, dass man so wenig (C/C++)Code wie möglich hat und den Rest in Datenbanken oder Textdateien (Scripts, XML, INI, ...) auslagert, als Vorteile werden angegeben, dass man das Spiel nicht neu kompilieren muss und der "Designer" nicht zwingend einen Entwickler braucht und somit Rapid Prototyping betreiben kann. Als Beispiel für ein Spiel welches so entwickelt wurde, ist Dungeon Siege zu nennen (link kann ich auf Anfrage raussuchen).
Mir stellt sich jetzt die Frage ob dies nicht im Gegensatz zu dem Ansatz steht, den Speicherbedarf vorher genau wie möglich zu kennen und nur den Speicher zu reservieren den man benötigt, ausserdem die Datentypen so anzuordnen dass man keine unnötigen Cache Misses im FLC hat, etc. (ausführlich beschrieben in "Game Engine Architecture, 2nd Edition (by Jason Gregory)". Hier werden ebenfalls die oben angesprochenen Konzepte vorgestellt. Leider verrät er nicht (oder überlas ich dies?) wie das in The Last of Us bzw. Uncharted gelöst wurde, ansonsten gibt er sehr viele interessante Dinge über die Architektur beider Spiele preis.
Um das mal abzukürzen :-D :
Wie löst ihr das?
Welche Ideen/Gedanken habt ihr hierzu?
PS:
[Edit]: Für einen Einstieg in die Materie hier noch ein sehr guter und häufig hierzu verlinkter Artikel: http://cowboyprogramming.com/2007/01/05 ... -heirachy/
ich bin gerade (wieder einmal) an dem Punkt an dem ich mir Gedanken über meine "Game Objects" (Entities, Actors, whatev,...) mache.
Als GameObject bezeichne ich demnach alles, was irgendwie als Objekt im Spiel vorkommen kann. Ob Character, NPC, Bäume, Monster, Schatztruhen oder auch nur Trigger (unsichtbare Marker welche Ereignisse auslösen), mal animiert, mal mit Physik, mal mit AI, alles kann nichts muss ;-).
Die allgemeine Recherche bei Google sowie div. Büchern (Game Engine Architecture, Game Design, ...) ergibt, dass eine statische Klassenhierarchie nicht funktioniert. Also sie funktioniert schon, solange man nichts ändert oder hinzufügt, sprich, sie ist zu unflexibel gegenüber Änderungen. Außerdem kommt man hier schnell in die Bedrängnis multiple inerhitance verwenden zu müssen und dem Deadly Diamond in die Falle zu gehen.
Oft vorgeschlagen wird Component Based Object oder bzw. auch in Verbindung mit Data Driven Components. Die generelle Idee dahinter ist, dass man so wenig (C/C++)Code wie möglich hat und den Rest in Datenbanken oder Textdateien (Scripts, XML, INI, ...) auslagert, als Vorteile werden angegeben, dass man das Spiel nicht neu kompilieren muss und der "Designer" nicht zwingend einen Entwickler braucht und somit Rapid Prototyping betreiben kann. Als Beispiel für ein Spiel welches so entwickelt wurde, ist Dungeon Siege zu nennen (link kann ich auf Anfrage raussuchen).
Mir stellt sich jetzt die Frage ob dies nicht im Gegensatz zu dem Ansatz steht, den Speicherbedarf vorher genau wie möglich zu kennen und nur den Speicher zu reservieren den man benötigt, ausserdem die Datentypen so anzuordnen dass man keine unnötigen Cache Misses im FLC hat, etc. (ausführlich beschrieben in "Game Engine Architecture, 2nd Edition (by Jason Gregory)". Hier werden ebenfalls die oben angesprochenen Konzepte vorgestellt. Leider verrät er nicht (oder überlas ich dies?) wie das in The Last of Us bzw. Uncharted gelöst wurde, ansonsten gibt er sehr viele interessante Dinge über die Architektur beider Spiele preis.
Um das mal abzukürzen :-D :
Wie löst ihr das?
Welche Ideen/Gedanken habt ihr hierzu?
PS:
[Edit]: Für einen Einstieg in die Materie hier noch ein sehr guter und häufig hierzu verlinkter Artikel: http://cowboyprogramming.com/2007/01/05 ... -heirachy/
- Schrompf
- Moderator
- Beiträge: 5044
- Registriert: 25.02.2009, 23:44
- Benutzertext: Lernt nur selten dazu
- Echter Name: Thomas
- Wohnort: Dresden
- Kontaktdaten:
Re: Architekturfrage zu "Game Objects"
Man kommt mit ner statischen Ableitungshierarchie ziemlich weit. Aber irgendwann wird es hässlich. Man kann Komponenten nehmen, die in einer flachen Ableitungshierarchie strukturiert sind, um sie zur Laufzeit oder auch zur Kompilierzeit in Objekten zusammenzustecken. Kann sehr praktisch sein, kann ab bei allzu feiner Granularität auch zu ungültigen Zwischenzustanden in Objekten führen, die man nur sehr mühsam abgefangen oder verhindert bekommt. Eine gesunde Mischung ist angenehm zum Arbeiten, finde ich - mach Dir bitte bitte nicht *zu* viele Gedanken über die Architektur. Wenn Du den Anspruch hast, vorab die perfekte Struktur zu finden, mit der alles sauber zu machen ist, wirst Du scheitern.
Und mach Dir keine Gedanken über den Speicherbedarf oder die Allokationsstrategien. Vielmehr sollten die Besitzverhältnisse feststehen. Dann ergibt sich der Rest, denke ich.
Und mach Dir keine Gedanken über den Speicherbedarf oder die Allokationsstrategien. Vielmehr sollten die Besitzverhältnisse feststehen. Dann ergibt sich der Rest, denke ich.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Re: Architekturfrage zu "Game Objects"
Danke für deine Antwort!
Ich denke auch dass zu viele Gedanken eher schädlich sind, man kann nicht jeden erdenklichen Fall abdecken. Aber ich will die Architektur wenigstens so offen halten, dass ich an dem Punkt an dem ich merke "hier gehts nicht weiter" relativ problemlos etwas ändern kann. Misch-Strategie klingt nach einem guten Ansatz!
Wie machst du es denn? Wie macht ihr anderen es? :-)
Ich denke auch dass zu viele Gedanken eher schädlich sind, man kann nicht jeden erdenklichen Fall abdecken. Aber ich will die Architektur wenigstens so offen halten, dass ich an dem Punkt an dem ich merke "hier gehts nicht weiter" relativ problemlos etwas ändern kann. Misch-Strategie klingt nach einem guten Ansatz!
Wie machst du es denn? Wie macht ihr anderen es? :-)
- Schrompf
- Moderator
- Beiträge: 5044
- Registriert: 25.02.2009, 23:44
- Benutzertext: Lernt nur selten dazu
- Echter Name: Thomas
- Wohnort: Dresden
- Kontaktdaten:
Re: Architekturfrage zu "Game Objects"
Für Splatter hatte ich eine ziemlich verzweigte Ableitungshierarchie. Und die hat mir lange Zeit gute Dienste geleistet. Aber irgendwann kamen dann Probleme. Zuerst Luxusprobleme wie "ich will, dass dieses Objekt auch eine Lichtquelle ist", dann Konzeptprobleme wie "Dekoobjekte sollen auch kollidieren und bei Treffer reagieren können". Deswegen gab es zuletzt zusätzlich ein Komponentensystem, mit dem man kleine Zusatzverhalten an Objekte dranhängen konnte. Und die Editorschnittstelle dafür war etwas... mühsam.
Beim nächsten Mal würde ich die Komponenten dann prominenter einsetzen, um Verhalten im Idealfall "zusammenzustecken". Allerdings weiß ich von den seligen Splitterwelten damals noch, dass es ein Großer Kram Im Arsch (tm) sein kann, wenn Du versuchst, mehrere Komponenten mit Abhängigkeiten untereinander zum Laufen zu bewegen. Es gibt während jeder Erstellung, sei es beim Weltstart, beim Laden eines Spielstandes oder am Schlimmsten beim Levelbearbeiten, unendlich viele kleine Hässlichkeiten und Unwägbarkeiten, die das Gesamtkonstrukt crashen lassen oder sonstwie ungültig werden lassen. Daher schreibst Du dann in jede Deiner Komponenten unmassen an "Dings d = BesorgeDings(); if( d != nullptr )...", nur damit das Gesamtkonstrukt halt nicht crasht in diesem einen besonderen Fall, sondern stattdessen nur stillschweigend scheitert. Seinen befohlenen Job tut's aber trotzdem nicht.
Ich habe noch keine Ahnung, wie man das geschickt lösen könnte. Ich weiß, dass CodingCat hier mal eine Architektur beschrieben hat, bei der alle Komponenten verlässlich zur Ladezeit verknüpft wurden. Der hatte dann aber wieder das Problem, dass Änderungen an der Szene zur Laufzeit schwer bis mühsam zu integrieren waren. Und Spiele bestehen nunmal nur aus Weltstruktur-Änderungen.
Beim nächsten Mal würde ich die Komponenten dann prominenter einsetzen, um Verhalten im Idealfall "zusammenzustecken". Allerdings weiß ich von den seligen Splitterwelten damals noch, dass es ein Großer Kram Im Arsch (tm) sein kann, wenn Du versuchst, mehrere Komponenten mit Abhängigkeiten untereinander zum Laufen zu bewegen. Es gibt während jeder Erstellung, sei es beim Weltstart, beim Laden eines Spielstandes oder am Schlimmsten beim Levelbearbeiten, unendlich viele kleine Hässlichkeiten und Unwägbarkeiten, die das Gesamtkonstrukt crashen lassen oder sonstwie ungültig werden lassen. Daher schreibst Du dann in jede Deiner Komponenten unmassen an "Dings d = BesorgeDings(); if( d != nullptr )...", nur damit das Gesamtkonstrukt halt nicht crasht in diesem einen besonderen Fall, sondern stattdessen nur stillschweigend scheitert. Seinen befohlenen Job tut's aber trotzdem nicht.
Ich habe noch keine Ahnung, wie man das geschickt lösen könnte. Ich weiß, dass CodingCat hier mal eine Architektur beschrieben hat, bei der alle Komponenten verlässlich zur Ladezeit verknüpft wurden. Der hatte dann aber wieder das Problem, dass Änderungen an der Szene zur Laufzeit schwer bis mühsam zu integrieren waren. Und Spiele bestehen nunmal nur aus Weltstruktur-Änderungen.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
- Chromanoid
- Moderator
- Beiträge: 4273
- Registriert: 16.10.2002, 19:39
- Echter Name: Christian Kulenkampff
- Wohnort: Lüneburg
Re: Architekturfrage zu "Game Objects"
Ich weiß nicht wie das in anderen Sprachen ist, aber vielleicht reicht es ja mit Interfaces a'la Java zu arbeiten. Wenn man dann aus einem Endgegner auch eine Lichtquelle machen will, kann man das entsprechende Lichtquellen-Interface so implementieren, dass man einfach an ein richtiges Lichtobjekt delegiert.
Das ganze mutet vielleicht erst mal wie ein hartkodiertes Komponentenmodell an, aber genau das hartkodierte ist, wenn man nicht gerade eine Wollmilchsau-Engine entwickeln will, ein großer Vorteil. Das ganze wäre so natürlich sehr objektorientiert, wie sich das mit datenorientiertem Design verträgt, weiß ich nicht...
Das Komponieren von völlig neuen Spielelementen aus verschiedenen Komponenten ist dann natürlich weiterhin Aufgabe der Programmierer, aber ich glaube das macht erst mal nichts.
Das ganze mutet vielleicht erst mal wie ein hartkodiertes Komponentenmodell an, aber genau das hartkodierte ist, wenn man nicht gerade eine Wollmilchsau-Engine entwickeln will, ein großer Vorteil. Das ganze wäre so natürlich sehr objektorientiert, wie sich das mit datenorientiertem Design verträgt, weiß ich nicht...
Das Komponieren von völlig neuen Spielelementen aus verschiedenen Komponenten ist dann natürlich weiterhin Aufgabe der Programmierer, aber ich glaube das macht erst mal nichts.
Re: Architekturfrage zu "Game Objects"
Ich denke das könnte man umgehen wenn man mit Nachrichten arbeitet. Klar, da hat man natürlich wieder andere Probleme, man muss halt entscheiden welche Probleme man lieber hat :DDaher schreibst Du dann in jede Deiner Komponenten unmassen an "Dings d = BesorgeDings(); if( d != nullptr )...", nur damit das Gesamtkonstrukt halt nicht crasht in diesem einen besonderen Fall, sondern stattdessen nur stillschweigend scheitert. Seinen befohlenen Job tut's aber trotzdem nicht.
Ich arbeite beruflich sehr viel in C# und verwende dort Interfaces geradezu inflationär. In C++ zum Zwecke der Spieleentwicklung habe ich sie bisher noch nicht verwendet, aber an einigen Stellen könnte dies bestimmt nützlich sein. Guter Gedanke!Ich weiß nicht wie das in anderen Sprachen ist, aber vielleicht reicht es ja mit Interfaces a'la Java zu arbeiten.
Nein, solange man alleine arbeitet ist das egal, so etwas kommt eher zum Tragen wenn man mit einem Grafiker bzw. Designer zusammen arbeitet.Das Komponieren von völlig neuen Spielelementen aus verschiedenen Komponenten ist dann natürlich weiterhin Aufgabe der Programmierer, aber ich glaube das macht erst mal nichts.
Re: Architekturfrage zu "Game Objects"
Warnung vorweg: Ich bin vollkommen neu in dem Bereich Spieleentwicklung und fange gerade erst an mich damit auseinander zu setzen (möchte vielleicht in den nächsten Jahren von SAP auf Game Dev umsteigen, weil BWL-Themen sind irgendwie langweilig) Von daher kann es sein, dass ich an euch vorbei rede.
Aber zum Thema:
nehmen wir doch mal das Beispiel Bäume. Du möchtest vielleicht mehrere Bäume auf einer Karte verwenden. Dabei sollen unterschiedliche Baumarten angezeigt werden, da überall nur Tannen aufstellen wäre ja langweilig. nun könntest du eine Klasse Baum definieren, und davon Unterklassen ableiten wie Eiche, Birke, Tanne, etc. Alle Unterklassen erben von Baum und haben Art spezifische statische Eigenschaften (Bezeichnung, Darstellungsgröße, zu verwendende Grafik/Modell/Animation, usw. die du dann direkt in der Klasse definierst. Der Ansatz würde sicher für die meisten Baum-Anwendungen reichen.
Ein anderer Ansatz wäre es allerdings du definierst eine Klasse Baum. Alle Art spezifischen Eigenschaften schreibst du Datensatzweise in eine Tabelle (CSV-Dateien bieten sich aus meiner Sicht an) Da stehen dann z.B. BaumID;Name;Größe X;Größe Y;Größe Z;Grafikdatei;(zu verwendendes AI Script, was bei Bäumen wahrscheinlich keinen Sinn macht). In der Klasse baust du dir eine geeignete Factorymethode, die diese Tabelle ausliest und dir zur gegebenen BaumID ein passendes Objekt Baum instanziert. Natürlich liest man solche Dateien einmal zum Beginn in den Hauptspeicher. Niemand will zur Laufzeit permanent Dateien auf und zu machen müssen. Treibt man das Ganze auf die Spitze könnte man bei der Baumklasse sogar komplett auf die nicht änderbaren Attribute (mit Ausnahme der BaumID) verzichten und statt dessen in den Methoden direkt auf die Tabelle zugreifen.
Der zweite Ansatz bietet verschiedene Vorteile. Mal angenommen du willst deine Bäume nun von einem Holzfäller schlagen lassen, der im Gegenzug mit unterschiedlich vielen Einheiten Holz belohnt wird. Außerdem soll das Schlagen des Baumes unterschiedlich viel Zeit beanspruchen. Im ersten Ansatz musst du zunächst deine Klasse Baum um eine geeignete Methode baum_faellen erweitern. Diese anschließend in sämtlichen Klassen so überschreiben, dass du pro Baumart das gewünschte Resultat bekommst. Im zweiten änderst du eine Klasse und erweiterst deine Tabelle um zwei zusätzliche Spalten (Anzahl Holz;Arbeitszeit)
Das Beispiel Baum ist jetzt recht trivial. Aber wenn du später in einer Kategorie einen Katalog von fünfzig verschiedenen Objekten mit X Eigenschaften und wiederum Y Ausprägungen aufgebaut hast (z.B. Waffen mit unterschiedlichen Eigenschaften) bist du froh, wenn du beim Rebalancing ein paar Tabelleneinträge ändern musst anstatt in zig Klassen herumwerkeln zu müssen.
Weitere Vorteile, die mir spontan einfallen sind vereinfacht Multilingualität, unterstützt modding, ist leichter zu deployen, zu erweitern und updates werden kleiner. Stelle ich nun fest, dass Steine im Grunde ähnlich ticken wie Bäume, kann ich die Eigenschaften von Bäumen und Steinen auch über die gleiche Tabelle abbilden.
Aber zum Thema:
nehmen wir doch mal das Beispiel Bäume. Du möchtest vielleicht mehrere Bäume auf einer Karte verwenden. Dabei sollen unterschiedliche Baumarten angezeigt werden, da überall nur Tannen aufstellen wäre ja langweilig. nun könntest du eine Klasse Baum definieren, und davon Unterklassen ableiten wie Eiche, Birke, Tanne, etc. Alle Unterklassen erben von Baum und haben Art spezifische statische Eigenschaften (Bezeichnung, Darstellungsgröße, zu verwendende Grafik/Modell/Animation, usw. die du dann direkt in der Klasse definierst. Der Ansatz würde sicher für die meisten Baum-Anwendungen reichen.
Ein anderer Ansatz wäre es allerdings du definierst eine Klasse Baum. Alle Art spezifischen Eigenschaften schreibst du Datensatzweise in eine Tabelle (CSV-Dateien bieten sich aus meiner Sicht an) Da stehen dann z.B. BaumID;Name;Größe X;Größe Y;Größe Z;Grafikdatei;(zu verwendendes AI Script, was bei Bäumen wahrscheinlich keinen Sinn macht). In der Klasse baust du dir eine geeignete Factorymethode, die diese Tabelle ausliest und dir zur gegebenen BaumID ein passendes Objekt Baum instanziert. Natürlich liest man solche Dateien einmal zum Beginn in den Hauptspeicher. Niemand will zur Laufzeit permanent Dateien auf und zu machen müssen. Treibt man das Ganze auf die Spitze könnte man bei der Baumklasse sogar komplett auf die nicht änderbaren Attribute (mit Ausnahme der BaumID) verzichten und statt dessen in den Methoden direkt auf die Tabelle zugreifen.
Der zweite Ansatz bietet verschiedene Vorteile. Mal angenommen du willst deine Bäume nun von einem Holzfäller schlagen lassen, der im Gegenzug mit unterschiedlich vielen Einheiten Holz belohnt wird. Außerdem soll das Schlagen des Baumes unterschiedlich viel Zeit beanspruchen. Im ersten Ansatz musst du zunächst deine Klasse Baum um eine geeignete Methode baum_faellen erweitern. Diese anschließend in sämtlichen Klassen so überschreiben, dass du pro Baumart das gewünschte Resultat bekommst. Im zweiten änderst du eine Klasse und erweiterst deine Tabelle um zwei zusätzliche Spalten (Anzahl Holz;Arbeitszeit)
Das Beispiel Baum ist jetzt recht trivial. Aber wenn du später in einer Kategorie einen Katalog von fünfzig verschiedenen Objekten mit X Eigenschaften und wiederum Y Ausprägungen aufgebaut hast (z.B. Waffen mit unterschiedlichen Eigenschaften) bist du froh, wenn du beim Rebalancing ein paar Tabelleneinträge ändern musst anstatt in zig Klassen herumwerkeln zu müssen.
Weitere Vorteile, die mir spontan einfallen sind vereinfacht Multilingualität, unterstützt modding, ist leichter zu deployen, zu erweitern und updates werden kleiner. Stelle ich nun fest, dass Steine im Grunde ähnlich ticken wie Bäume, kann ich die Eigenschaften von Bäumen und Steinen auch über die gleiche Tabelle abbilden.
- xq
- Establishment
- Beiträge: 1589
- Registriert: 07.10.2012, 14:56
- Alter Benutzername: MasterQ32
- Echter Name: Felix Queißner
- Wohnort: Stuttgart & Region
- Kontaktdaten:
Re: Architekturfrage zu "Game Objects"
Im Endeffekt macht man die Kombination aus beiden Methoden. Alle Dinge, die sich nur in "Daten" unterscheiden, aber nicht in "Verhalten", definiert man auch über Datensätze (jedenfalls machen das einige Engines so und es ist auch sinnvoll).
Aber man muss halt immer abwägen zwischen Sinnhaftigkeit und Wahnsinn :P Ein Auto sowie ein Baum können verschieden parametrisiert werden, sind aber sicher nicht die selbe Art von Spielobjekt. Hier muss man aber auch wieder die Unterscheidung treffen zwischen scriptbaren Spieleobjekten (welche durchaus die selbe "Programmklasse" sein können) und hart kodierten Spieleobjekten, welche sich im Verhalten unterscheiden.
Aber man muss halt immer abwägen zwischen Sinnhaftigkeit und Wahnsinn :P Ein Auto sowie ein Baum können verschieden parametrisiert werden, sind aber sicher nicht die selbe Art von Spielobjekt. Hier muss man aber auch wieder die Unterscheidung treffen zwischen scriptbaren Spieleobjekten (welche durchaus die selbe "Programmklasse" sein können) und hart kodierten Spieleobjekten, welche sich im Verhalten unterscheiden.
War mal MasterQ32, findet den Namen aber mittlerweile ziemlich albern…
Programmiert viel in Zig und nervt Leute damit.
Programmiert viel in Zig und nervt Leute damit.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Architekturfrage zu "Game Objects"
@DerBurger: Der erste Ansatz ist objektorientiert; den zweiten nennt man data-driven (nicht zu verwechseln mit datenorientiert). Tatsächlich sind die meisten Spiele-Engines zu einem gewissen Grad data driven, weil
- die Leveldesigner meist nicht gut genug programmieren können um für jede Art von Objekt im Spiel eine Klasse zu schreiben, aber CSV ist quasi Idiotensicher
- man nicht für jede Kleinigkeit den Quelltext des Spiels anrühren will (vor allem, wenn man auf einer Engine entwickelt, die man nicht selber programmiert, sondern lizensiert hat! – Unreal Engine oder so)
- Daten, im Gegensatz zu Quelltext, zustandslos sind und man damit die Gefahr senkt, dass ein Leveldesigner Abstürze in die Engine einbaut
- man bei Expansion auf neue Plattformen (Spiel auf Android portieren?) weniger Code portieren muss, und die Daten quasi nie anrühren muss