Trennen der Game Engine vom Spielcode in ähnlichen Spielen mit Versionsverwaltung

15

Ich habe ein fertiges Spiel, das ich in anderen Versionen ablehnen möchte. Dies wären ähnliche Spiele mit mehr oder weniger dem gleichen Design, aber nicht immer, im Grunde könnten sich die Dinge ändern, manchmal klein, manchmal groß.

Ich möchte, dass der Kerncode separat vom Spiel versioniert wird. Wenn ich also einen Fehler in Spiel A behebe, ist der Fehler in Spiel B vorhanden.

Ich versuche, den besten Weg dafür zu finden. Meine ersten Ideen sind folgende:

  • Erstellen Sie ein engineModul / Ordner / was auch immer, das alles enthält, was verallgemeinert werden kann und zu 100% unabhängig vom Rest des Spiels ist. Dies würde einige Codes, aber auch allgemeine Assets enthalten, die von Spielen gemeinsam genutzt werden.
  • Stellen Sie diese Engine in ein eigenes gitRepository, das in den Spielen alsgit submodule

Der Teil, mit dem ich zu kämpfen habe, ist, wie der Rest des Codes verwaltet wird. Angenommen, Sie haben Ihre Menüszene, dieser Code ist spielspezifisch, aber der Großteil davon ist in der Regel allgemein gehalten und könnte in anderen Spielen wiederverwendet werden. Ich kann es nicht einfügen engine, aber eine Neucodierung für jedes Spiel wäre ineffizient.

Vielleicht könnte es effektiv sein, eine Art Git-Verzweigungsvariation zu verwenden, aber ich bin nicht der Meinung, dass dies der beste Weg ist.

Hat jemand Ideen, Erfahrungen oder etwas darüber zu teilen?

Malharhak
quelle
In welcher Sprache ist Ihr Motor? Einige Sprachen haben dedizierte Paketmanager, die möglicherweise sinnvoller sind als die Verwendung von Git-Submodulen. Zum Beispiel hat NodeJS npm (das Git-Repos als Quellen verwenden kann).
Dan Pantry
Fragen Sie sich, wie Sie den generischen Code am besten konfigurieren oder wie Sie den "semi-generischen" Code konfigurieren oder wie Sie den Code entwerfen, wie Sie den Code entwerfen oder was?
Dunk
Dies kann in jeder Programmiersprachenumgebung unterschiedlich sein, Sie können jedoch nicht nur die Steuerungsversionssoftware berücksichtigen, sondern auch wissen, wie die Spiel-Engine vom Spielcode (wie Paketen, Ordnern und APIs) und später getrennt wird , Control Version anwenden.
umlcat
So führen Sie einen sauberen Verlauf eines Ordners in einem Zweig durch: Korrigieren Sie Ihre Engine so, dass sich separate (zukünftige) Repos in separaten Ordnern befinden. Dies ist Ihre letzte Übergabe. Erstellen Sie dann eine neue Verzweigung, löschen Sie alles außerhalb dieses Ordners und übernehmen Sie das Commit. Gehen Sie dann zum ersten Commit des Repos und führen Sie das mit Ihrem neuen Zweig zusammen. Sie haben jetzt einen Zweig mit nur diesem Ordner: Ziehen Sie ihn in andere Projekte und / oder führen Sie ihn wieder mit Ihrem vorhandenen Projekt zusammen. Dies hat mir beim Trennen von Engines in Zweigen sehr geholfen, wenn Ihr Code bereits getrennt ist. Ich brauche keine Git-Module.
Barry Staes

Antworten:

13

Erstellen Sie ein Modul / einen Ordner / was auch immer, das alles enthält, was verallgemeinert werden kann und zu 100% unabhängig vom Rest des Spiels ist. Dies würde einige Codes, aber auch allgemeine Assets enthalten, die von Spielen gemeinsam genutzt werden.

Stellen Sie diese Engine in ein eigenes Git-Repository, das in den Spielen als Git-Submodul enthalten sein wird

Genau das mache ich und es funktioniert sehr gut. Ich habe ein Anwendungsframework und eine Renderingbibliothek, und jede davon wird als Submodul meiner Projekte behandelt. Ich finde, SourceTree ist nützlich, wenn es um Submodule geht, da es sie gut verwaltet und Sie nichts vergessen lässt. Wenn Sie z. B. das Engine-Submodul in Projekt A aktualisiert haben, werden Sie aufgefordert, die Änderungen in Projekt B zu übernehmen.

Mit der Erfahrung geht das Wissen einher, welcher Code in der Engine enthalten sein sollte und welcher pro Projekt. Ich schlage vor, dass Sie es fürs Erste in jedem Projekt behalten, wenn Sie sogar ein bisschen unsicher sind. Im Laufe der Zeit werden Sie bei Ihren verschiedenen Projekten feststellen, was gleich bleibt, und können dies schrittweise in Ihren Motorcode einbeziehen. Mit anderen Worten: Kopieren Sie den Code, bis Sie zu 100% sicher sind, dass er sich nicht diskret pro Projekt ändert, und verallgemeinern Sie ihn dann.

Hinweis zur Quellcodeverwaltung und zu Binärdateien

Denken Sie daran, dass Sie, wenn Sie davon ausgehen, dass sich Ihre binären Assets häufig ändern, diese möglicherweise nicht in eine textbasierte Quellcodeverwaltung wie git einfügen möchten. Sagen Sie einfach ... es gibt bessere Lösungen für Binärdateien. Das Einfachste, was Sie jetzt tun können, um Ihr "Engine-Source" -Repository sauber und performant zu halten, ist, ein separates "Engine-Binaries" -Repository zu haben, das nur Binaries enthält, die Sie auch als Submodul in Ihr Projekt aufnehmen. Auf diese Weise verringern Sie den Performance-Schaden, der Ihrem "Engine-Source" -Repository zugefügt wurde, das sich ständig ändert und für das Sie schnelle Iterationen benötigen: Commit, Push, Pull usw. Quellcodeverwaltungssysteme wie git verarbeiten Text-Deltas und sobald Sie Binärdateien einführen, führen Sie massive Deltas aus Textsicht ein - was Sie letztendlich Entwicklungszeit kostet.GitLab Annex . Google ist dein Freund.

Ingenieur
quelle
Sie ändern sich nicht oft, aber das interessiert mich. Ich weiß nichts über binäre Versionierung. Welche Lösungen gibt es?
Malharhak,
@Malharhak Bearbeitet, um Ihren Kommentar zu beantworten.
Ingenieur
@Malharak Hier gibt es ein paar nette Infos zu diesem Thema.
Ingenieur
1
+1, damit die Dinge so lange wie möglich im Projekt bleiben. Allgemeiner Code sorgt für höhere Komplexität. Es sollte vermieden werden, bis es unbedingt erforderlich ist.
Gusdor,
1
@Malharhak Nein, zumal Ihr Ziel darin besteht, "Kopien" nur so lange aufzubewahren, bis Sie feststellen, dass der Code unveränderlich ist und als häufig eingestuft werden kann. Gusdor wiederholte dies - seien Sie gewarnt - man kann leicht viel Zeit verschwenden, indem man Dinge zu früh ausklammert und dann versucht, Wege zu finden, um diesen Code allgemein genug zu halten, um allgemein zu bleiben, und dennoch anpassungsfähig genug, um verschiedenen Projekten zu entsprechen Eine ganze Reihe von Parametern und Schaltern und es wird zu einem hässlichen Durcheinander, das immer noch nicht das ist, was Sie brauchen, weil Sie es ohnehin für jedes neue Projekt ändern . Nicht zu früh ausrechnen . Hab Geduld.
Ingenieur
6

Irgendwann MUSS sich eine Engine spezialisieren und etwas über das Spiel wissen. Ich werde hier auf einen Tangens gehen.

Nehmen Sie Ressourcen in einem RTS. Ein Spiel kann haben Creditsund ein Crystalanderes MetalundPotatoes

Sie sollten die OO-Konzepte richtig anwenden und max. Code-Wiederverwendung. Es ist klar, dass hier ein Konzept von Resourceexistiert.

Daher entscheiden wir, dass Ressourcen Folgendes haben:

  1. Ein Haken in der Hauptschleife, um sich selbst zu erhöhen / zu verringern
  2. Eine Möglichkeit, den aktuellen Betrag abzurufen (gibt ein zurück int)
  3. Eine Möglichkeit, willkürlich zu subtrahieren / hinzuzufügen (Spieler, die Ressourcen übertragen, Einkäufe tätigen ...)

Beachten Sie, dass diese Vorstellung von a ResourceAbschüsse oder Punkte in einem Spiel darstellen könnte! Es ist nicht sehr mächtig.

Denken wir jetzt über ein Spiel nach. Wir können eine Art Währung haben, indem wir mit Pennies handeln und der Ausgabe einen Dezimalpunkt hinzufügen. Was wir nicht können, sind "augenblickliche" Ressourcen. Wie sagen "Stromnetzerzeugung"

Nehmen wir an, Sie fügen eine InstantResourceKlasse mit ähnlichen Methoden hinzu. Sie verschmutzen nun Ihren Motor mit Ressourcen.


Das Problem

Nehmen wir noch einmal das RTS-Beispiel. Angenommen, der Spieler spendet etwas Crystalan einen anderen Spieler. Sie möchten etwas tun wie:

if(transfer.target == engine.getPlayerId()) {
    engine.hud.addIncoming("You got "+transfer.quantity+" of "+
        engine.resourceDictionary.getNameOf(transfer.resourceId)+
        " from "+engine.getPlayer(transfer.source).name);
}
engine.getPlayer(transfer.target).getResourceById(transfer.resourceId).add(transfer.quantity)
engine.getPlayer(transfer.source).getResourceById(transfer.resourceId).add(-transfer.quantity)

Dies ist jedoch wirklich ziemlich chaotisch. Es ist allgemeiner Zweck, aber chaotisch. Schon resourceDictionaryjetzt, obwohl es ein vorschreibt, was bedeutet, dass Ihre Ressourcen Namen haben müssen! UND es ist pro Spieler, so dass Sie keine Teamressourcen mehr haben können.

Dies ist "zu viel" Abstraktion (ich gebe zu, kein brillantes Beispiel). Stattdessen solltest du einen Punkt erreichen, an dem du akzeptierst, dass dein Spiel Spieler und Kristall hat, dann kannst du nur (zum Beispiel)

engine.getPlayer(transfer.target).crystal().receiveDonation(transfer)
engine.getPlayer(transfer.source).crystal().sendDonation(transfer)

Mit einer Klasse Playerund einer Klasse, CurrentPlayerin der CurrentPlayerdas crystalObjekt automatisch das Material auf dem HUD für die Übertragung / das Senden von Spenden anzeigt.

Dies verschmutzt den Motor mit Kristall, dem Spenden von Kristall, den Nachrichten auf dem HUD für aktuelle Spieler und so weiter. Es ist sowohl schneller als auch einfacher zu lesen / schreiben / warten (was wichtiger ist, da es nicht wesentlich schneller ist)


Schlussbemerkungen

Der Ressourcenfall ist nicht brillant. Ich hoffe, dass Sie den Punkt dennoch sehen können. Wenn überhaupt, dann habe ich gezeigt, dass "Ressourcen gehören nicht in die Engine" , was ein bestimmtes Spiel benötigt und was für alle Begriffe von Ressourcen gilt, SEHR unterschiedliche Dinge sind. Was Sie normalerweise finden, sind 3 (oder 4) "Schichten"

  1. Der "Kern" - das ist die Lehrbuchdefinition der Engine, es ist ein Szenendiagramm mit Ereignis-Hooks, es handelt sich um Shader und Netzwerkpakete und eine abstrakte Vorstellung von Spielern
  2. Der "GameCore" - Dies ist ziemlich allgemein für die Art des Spiels, aber nicht für alle Spiele - zum Beispiel Ressourcen in RTS oder Munition in FPS. Die Spielelogik beginnt hier einzusickern. Hier würde sich unsere frühere Vorstellung von Ressourcen befinden. Wir haben diese Dinge hinzugefügt, die für die meisten RTS-Ressourcen sinnvoll sind.
  3. "GameLogic" ist SEHR spezifisch für das aktuelle Spiel. Sie finden Variablen mit Namen wie creatureoder shipoder squad. Mit Vererbung Sie Klassen erhalten , die alle drei Schichten erstrecken (zum Beispiel Crystal eine Resource , die eine ist GameLoopEventListener sagen wir)
  4. "Assets" sind für jedes andere Spiel nutzlos. Nehmen wir zum Beispiel die kombinierten KI-Skripte in Half Life 2, sie werden nicht in einem RTS mit derselben Engine verwendet.

Aus einer alten Engine ein neues Spiel machen

Dies ist sehr häufig. Phase 1 besteht darin, die Schichten 3 und 4 herauszureißen (und 2, wenn es sich um ein völlig anderes Spiel handelt). Nehmen wir an, wir machen ein RTS aus einem alten RTS. Wir haben immer noch Ressourcen, nur keinen Kristall und so weiter. Die Basisklassen in den Schichten 2 und 1 sind also immer noch sinnvoll. Alles, worauf in 3 und 4 verwiesen wird, kann verworfen werden. So machen wir es. Wir können es jedoch als Referenz für das überprüfen, was wir tun möchten.


Verschmutzung in Schicht 1

Das kann passieren. Abstraktion und Leistung sind Feinde. UE4 bietet zum Beispiel eine Menge optimierter Fälle von Komposition (wenn Sie also möchten, dass X und Y schnell zusammenarbeiten - es weiß, dass es beides tut) und ist daher WIRKLICH ziemlich groß. Das ist nicht schlecht, aber zeitaufwändig. Layer 1 entscheidet, wie "Sie Daten an Shader übergeben" und wie Sie Dinge animieren. Es ist IMMER gut, das Beste für Ihr Projekt zu tun. Versuchen Sie einfach, für die Zukunft zu planen. Die Wiederverwendung von Code ist Ihr Freund. Erben Sie, wo es Sinn macht.


Ebenen klassifizieren

LETZT (ich verspreche es) hab keine Angst vor Schichten. Engine ist ein archaischer Begriff aus den alten Zeiten der Pipelines mit fester Funktion, in denen Engines ähnlich grafisch arbeiteten (und als Ergebnis hatten sie viele Gemeinsamkeiten). Die programmierbare Pipeline stellte dies auf den Kopf und als solche wurde "Layer 1" verschmutzt mit welchen effekten die entwickler erreichen wollten. KI war das Unterscheidungsmerkmal (wegen der Vielzahl von Ansätzen) von Motoren, jetzt ist es KI und Grafik.

Ihr Code sollte nicht in diesen Ebenen abgelegt werden. Sogar die berühmte Unreal-Engine hat VIELE verschiedene Versionen, die jeweils für ein anderes Spiel spezifisch sind. Es gibt nur wenige Dateien (außer vielleicht ähnlichen Datenstrukturen), die unverändert geblieben wären. Das ist okay! Wenn Sie ein neues Spiel aus einem anderen machen möchten, dauert es länger als 30 Minuten. Der Schlüssel ist zu planen, zu wissen, welche Teile zu kopieren und einzufügen sind und was zurückzulassen ist.

Alec Teal
quelle
1

Mein persönlicher Vorschlag für den Umgang mit Inhalten, die eine Mischung aus allgemein und spezifisch sind, ist, sie dynamisch zu gestalten. Ich nehme Ihren Menübildschirm als Beispiel. Wenn ich falsch verstanden habe, wonach Sie gefragt haben, lassen Sie mich wissen, was Sie wissen wollten, und ich werde meine Antwort anpassen.

Es gibt 3 Dinge, die (fast) immer in einer Menüszene vorhanden sind: den Hintergrund, das Spielelogo und das Menü selbst. Diese Dinge unterscheiden sich normalerweise je nach Spiel. Sie können für diesen Inhalt einen MenuScreenGenerator in Ihrer Engine erstellen, der drei Objektparameter enthält: BackGround, Logo und Menu. Die Grundstruktur dieser 3 Teile ist ebenfalls Teil Ihres Motors, aber Ihr Motor sagt nicht wirklich aus, wie diese Teile erzeugt werden, sondern nur, welche Parameter Sie ihnen geben sollten.

Anschließend erstellen Sie in Ihrem eigentlichen Spielcode Objekte für einen Hintergrund, ein Logo und ein Menü und übergeben diese an Ihren MenuScreenGenerator. Auch hier geht Ihr Spiel selbst nicht darum, wie das Menü generiert wird, das ist für die Engine. Ihr Spiel muss der Engine nur mitteilen, wie sie aussehen soll und wo sie sein soll.

Grundsätzlich sollte Ihre Engine eine API sein, die das Spiel angibt, was angezeigt werden soll. Wenn es richtig gemacht wird, sollte Ihre Engine die harte Arbeit verrichten, und Ihr Spiel selbst sollte der Engine nur mitteilen, welche Assets verwendet werden sollen, welche Maßnahmen ergriffen werden müssen und wie die Welt aussieht.

Nzall
quelle