Für jedes Spiel, das ich gemacht habe, lege ich einfach alle meine Spielobjekte (Kugeln, Autos, Spieler) in einer einzigen Array-Liste ab, die ich durchlaufe, um zu zeichnen und zu aktualisieren. Der Aktualisierungscode für jede Entität wird in ihrer Klasse gespeichert.
Ich habe mich gefragt, ob dies der richtige oder der effizientere Weg ist.
software-engineering
Derek
quelle
quelle
Antworten:
Es gibt keinen richtigen Weg. Was Sie beschreiben, funktioniert und ist vermutlich schnell genug, sodass es keine Rolle spielt, da Sie anscheinend gefragt werden, weil Sie sich Gedanken über das Design machen und nicht, weil es zu Leistungseinbußen führt. Also ist es sicher ein richtiger Weg.
Sie können dies sicherlich auch anders machen, indem Sie beispielsweise für jeden Objekttyp eine homogene Liste erstellen oder sicherstellen, dass alle Elemente in Ihrer heterogenen Liste so gruppiert sind, dass Sie die Kohärenz für den im Cache befindlichen Code verbessern oder parallele Aktualisierungen zulassen. Ob Sie auf diese Weise eine nennenswerte Leistungsverbesserung erzielen, ist schwer zu sagen, ohne den Umfang und die Größe Ihrer Spiele zu kennen.
Sie können auch die Verantwortlichkeiten Ihrer Entitäten weiter herausarbeiten - es klingt so, als würden sich Entitäten im Moment selbst aktualisieren und rendern -, um das Prinzip der einheitlichen Verantwortung besser zu befolgen. Dies kann die Wartbarkeit und Flexibilität Ihrer einzelnen Schnittstellen erhöhen, indem diese voneinander entkoppelt werden. Aber auch hier ist es für mich schwierig zu sagen, ob Sie von der zusätzlichen Arbeit profitieren oder nicht, wenn ich weiß, was ich über Ihre Spiele weiß (was im Grunde genommen nichts ist).
Ich würde empfehlen, nicht zu viel darüber zu streichen und im Hinterkopf zu behalten, dass dies ein potenzieller Bereich für Verbesserungen sein könnte, wenn Sie jemals Leistungs- oder Wartbarkeitsprobleme bemerken.
quelle
Wie andere gesagt haben, wenn es schnell genug ist, dann ist es schnell genug. Wenn Sie sich fragen, ob es einen besseren Weg gibt, müssen Sie sich folgende Frage stellen:
Wiederhole ich alle Objekte, aber arbeite ich nur mit einer Teilmenge davon?
Wenn Sie dies tun, ist dies ein Hinweis darauf, dass Sie möglicherweise mehrere Listen möchten, die nur relevante Referenzen enthalten. Wenn Sie beispielsweise jedes Spielobjekt durchlaufen, um es zu rendern, aber nur eine Teilmenge von Objekten gerendert werden muss, empfiehlt es sich möglicherweise, eine separate renderbare Liste nur für die Objekte zu führen, die gerendert werden müssen.
Dies würde die Anzahl der Objekte verringern, die Sie durchlaufen, und die unnötigen Überprüfungen überspringen, da Sie bereits wissen, dass alle Objekte in der Liste verarbeitet werden sollen.
Sie müssen es profilieren, um zu sehen, ob Sie einen erheblichen Leistungszuwachs erzielen.
quelle
Es hängt fast ausschließlich von Ihren spezifischen Spielanforderungen ab. Es kann perfekt für ein einfaches Spiel funktionieren, aber auf einem komplexeren System versagen. Wenn es für Ihr Spiel funktioniert, sorgen Sie sich nicht darum und versuchen Sie nicht, es zu überarbeiten, bis die Notwendigkeit entsteht.
Was den Grund für das Scheitern des einfachen Ansatzes in manchen Situationen angeht, lässt sich dies nur schwer in einem einzigen Beitrag zusammenfassen, da die Möglichkeiten endlos sind und ausschließlich von den Spielen selbst abhängen. In anderen Antworten wurde beispielsweise erwähnt, dass Sie Objekte, die Verantwortlichkeiten teilen, in einer separaten Liste zusammenfassen möchten.
Dies ist eine der häufigsten Änderungen, die Sie je nach Spieldesign vornehmen können. Ich werde die Gelegenheit nutzen, einige andere (komplexere) Beispiele zu beschreiben, aber denken Sie daran, dass es noch viele andere mögliche Gründe und Lösungen gibt.
Für den Anfang weise ich darauf hin, dass das Aktualisieren und Rendern Ihrer Spielobjekte in einigen Spielen unterschiedliche Anforderungen haben kann und daher separat gehandhabt werden muss. Einige mögliche Probleme beim Aktualisieren und Rendern von Spielobjekten:
Aktualisieren von Spielobjekten
Hier ist ein Auszug aus der Game Engine Architecture, den ich empfehlen würde:
Mit anderen Worten, in einigen Spielen ist es möglich, dass Objekte voneinander abhängig sind und eine bestimmte Aktualisierungsreihenfolge erfordern. In diesem Szenario müssen Sie möglicherweise eine komplexere Struktur als eine Liste erstellen, um Objekte und ihre gegenseitigen Abhängigkeiten zu speichern.
Spielobjekte rendern
In einigen Spielen bilden Szenen und Objekte eine Hierarchie von Eltern-Kind-Knoten und sollten in der richtigen Reihenfolge und mit Transformationen relativ zu ihren Eltern gerendert werden. In diesen Fällen benötigen Sie möglicherweise eine komplexere Struktur wie ein Szenendiagramm (eine Baumstruktur) anstelle einer einzelnen Liste:
Andere Gründe können beispielsweise darin bestehen, dass Sie Ihre Objekte mithilfe einer räumlichen Partitionierungsdatenstruktur so organisieren, dass die Effizienz beim Durchführen von View-Frustum-Culling verbessert wird.
quelle
Die Antwort lautet "Ja, das ist in Ordnung." Als ich an großen Eisensimulationen arbeitete, bei denen die Leistung nicht verhandelbar war, war ich überrascht, alles in einem großen, fetten (BIG und FAT) globalen Array zu sehen. Später arbeitete ich an einem OO-System und musste die beiden vergleichen. Obwohl es super hässlich war, war die "Ein Array, um alle zu regieren" -Version in einfachem Single-Thread-C, das beim Debug kompiliert wurde, um ein Vielfaches schneller als das "hübsche" Multithread-OO-System, das in C ++ -O2 kompiliert wurde . Ich denke, ein bisschen davon hat mit der exzellenten Cache-Lokalität (sowohl räumlich als auch zeitlich) in der Big-Fat-Array-Version zu tun.
Wenn Sie Leistung wünschen, wird ein großes, fettes globales C-Array die Obergrenze sein (aus Erfahrung).
quelle
Grundsätzlich möchten Sie Listen so erstellen, wie Sie sie benötigen. Wenn Sie Geländeobjekte auf einmal neu zeichnen, kann eine Liste mit nur diesen nützlich sein. Aber damit sich das lohnt, braucht man Zehntausende von ihnen und viele Zehntausende von Dingen, die kein Gelände sind. Andernfalls ist es schnell genug, die gesamte Liste zu durchsuchen und ein "Wenn" zum Herausziehen von Geländeobjekten zu verwenden.
Ich habe immer eine Hauptliste benötigt, unabhängig davon, wie viele andere Listen ich hatte. Normalerweise mache ich daraus eine Karte, eine Schlüsseltabelle oder ein Wörterbuch, damit ich willkürlich auf einzelne Elemente zugreifen kann. Aber eine einfache Liste ist viel einfacher. Wenn Sie nicht zufällig auf Spielobjekte zugreifen, benötigen Sie die Karte nicht. Und das gelegentliche Durchsuchen von ein paar Tausend Einträgen nach dem richtigen dauert nicht lange. Also würde ich sagen, was auch immer Sie tun, planen Sie, bei der Master-Liste zu bleiben. Erwägen Sie, eine Karte zu verwenden, wenn sie groß wird. (Wenn Sie jedoch Indizes anstelle von Schlüsseln verwenden können, können Sie sich an Arrays halten und haben etwas Besseres als eine Map. Ich hatte immer große Lücken zwischen den Indexnummern, also musste ich darauf verzichten.)
Ein Wort zu Arrays: Sie sind unglaublich schnell. Aber wenn sie ungefähr 100.000 Elemente haben, geben sie Garbage Collectors Fits. Wenn Sie den Speicherplatz einmal zuweisen und ihn anschließend nicht mehr berühren können, ist dies in Ordnung. Wenn Sie das Array jedoch kontinuierlich erweitern, werden Sie kontinuierlich große Speicherblöcke zuweisen und freigeben, und es besteht die Gefahr, dass Ihr Spiel sekundenlang hängen bleibt und sogar ein Speicherfehler auftritt.
So schnell und effizienter? Sicher. Eine Karte für den wahlfreien Zugriff. Bei sehr großen Listen schlägt eine verknüpfte Liste ein Array, wenn Sie keinen Direktzugriff benötigen. Mehrere Karten / Listen, um die Objekte nach Bedarf auf verschiedene Arten zu organisieren. Sie könnten die Aktualisierung mit mehreren Threads durchführen.
Aber es ist alles viel Arbeit. Dieses einzelne Array ist schnell und einfach. Sie können andere Listen und Dinge nur bei Bedarf hinzufügen, und Sie werden sie wahrscheinlich nicht benötigen. Sei dir nur bewusst, was schief gehen kann, wenn dein Spiel groß wird. Möglicherweise möchten Sie eine Testversion mit 10-mal so vielen Objekten wie in der Realversion haben, um eine Vorstellung davon zu bekommen, wann Sie auf Probleme stoßen. (Tun Sie dies nur , wenn Ihr Spiel ist groß zu bekommen.) (Und versuchen Sie nicht das Multi-Threading , ohne es zu vielen Gedanken. Alles anderes leicht , mit zu beginnen, und Sie können , wie Sie so viel oder so wenig tun , wie Mit Multithreading zahlen Sie einen hohen Preis für den Zugang, die Gebühren für den Aufenthalt sind hoch und es ist schwierig, wieder herauszukommen.)
Mit anderen Worten, mach einfach weiter, was du tust. Ihre größte Grenze ist wahrscheinlich Ihre eigene Zeit, und Sie machen den bestmöglichen Nutzen daraus.
quelle
Ich habe eine ähnliche Methode für einfache Spiele verwendet. Das Speichern von allem in einem Array ist sehr nützlich, wenn die folgenden Bedingungen erfüllt sind:
Auf jeden Fall habe ich diese Lösung als Array mit fester Größe implementiert, das eine
gameObject_ptr
Struktur enthält . Diese Struktur enthielt einen Zeiger auf das Spielobjekt selbst und einen Booleschen Wert, der angibt, ob das Objekt "lebendig" war oder nicht.Der Zweck des Booleschen Werts besteht darin, die Erstellungs- und Löschvorgänge zu beschleunigen. Anstatt Spielobjekte zu zerstören, wenn sie aus der Simulation entfernt werden, würde ich einfach das "lebendig" -Flag auf setzen
false
.Bei der Ausführung von Berechnungen für Objekte durchläuft der Code das Array und führt Berechnungen für Objekte durch, die als "lebendig" gekennzeichnet sind. Es werden nur Objekte gerendert, die als "lebendig" gekennzeichnet sind.
Eine weitere einfache Optimierung besteht darin, das Array nach Objekttyp zu organisieren. Beispielsweise könnten alle Aufzählungszeichen in den letzten n Zellen des Arrays leben. Durch Speichern eines int mit dem Wert des Index oder eines Zeigers auf das Array-Element können bestimmte Typen zu reduzierten Kosten referenziert werden.
Es versteht sich von selbst, dass diese Lösung nicht für Spiele mit großen Datenmengen geeignet ist, da das Array unannehmbar groß sein wird. Es ist auch nicht für Spiele mit Speicherbeschränkungen geeignet, die verhindern, dass nicht verwendete Objekte Speicher belegen. Die Quintessenz ist, dass, wenn Sie eine bekannte Obergrenze für die Anzahl der Spielobjekte haben, die Sie zu einem bestimmten Zeitpunkt haben werden, nichts falsch daran ist, sie in einem statischen Array zu speichern.
Dies ist mein erster Beitrag! Ich hoffe es beantwortet deine Frage!
quelle
Es ist akzeptabel, wenn auch ungewöhnlich. Begriffe wie "schneller" und "effizienter" sind relativ; In der Regel werden Spielobjekte in separate Kategorien unterteilt (z. B. eine Liste für Kugeln, eine Liste für Feinde usw.), um die Programmierung besser zu organisieren (und damit effizienter zu gestalten). Es ist jedoch nicht so, als würde der Computer alle Objekte schneller durchlaufen .
quelle
Sie sagen, einzelnes Array und Objekte.
Also (ohne deinen Code), ich vermute, sie haben alle etwas mit 'uberGameObject' zu tun.
Also vielleicht zwei Probleme.
1) Möglicherweise haben Sie ein "Gott" -Objekt - erledigt Tonnen von Dingen, die nicht wirklich nach dem segmentiert sind, was sie tun oder sind.
2) Durchlaufen Sie diese Liste und geben Sie das Casting ein? oder Unboxing jeweils. Dies hat einen großen Leistungsverlust zur Folge.
Überlegen Sie, ob Sie verschiedene Typen (Kugeln, Bösewichte usw.) in ihre eigenen stark typisierten Listen aufteilen möchten.
quelle
update()
).Ich kann einen Kompromiss zwischen einer allgemeinen Einzelliste und separaten Listen für jeden Objekttyp vorschlagen.
Behalten Sie den Verweis auf ein Objekt in mehreren Listen bei. Diese Listen werden durch das Basisverhalten definiert. Zum Beispiel
MarineSoldier
wird Objekt referenziert inPhysicalObjList
,GraphicalObjList
,UserControlObjList
,HumanObjList
. Wenn Sie also die Physik bearbeiten, durchlaufen Sie siePhysicalObjList
, wenn Sie durch Gift beschädigte Prozesse ausführen -HumanObjList
wenn Soldier stirbt - entfernen Sie sie,HumanObjList
aber behalten Sie sie beiPhysicalObjList
. Damit dieser Mechanismus funktioniert, müssen Sie das automatische Einfügen / Entfernen von Objekten beim Erstellen / Löschen organisieren.Vorteile des Ansatzes:
AllObjectsList
mit Casting on Update)MegaAirHumanUnderwaterObject
würde in entsprechende Listen eingefügt, anstatt eine neue Liste für ihren Typ zu erstellen)PS: Bei der Selbstaktualisierung treten Probleme auf, wenn mehrere Objekte interagieren und jedes von anderen abhängig ist. Um dies zu lösen, müssen wir das Objekt von außen beeinflussen, nicht von innen selbst aktualisieren.
quelle