Der beste Weg, um In-Game-Events zu managen?

13

Ich arbeite an einem Spiel, in dem gelegentlich einige Ereignisse im Spiel stattfinden müssen. Ein schönes Beispiel wäre ein Tutorial. Sie starten das Spiel und an mehreren Punkten im Spiel tritt ein Ereignis auf:

  • Du triffst auf deinen ersten Feind, das Spiel pausiert und du erhältst eine Erklärung, wie du es tötest.
  • Sie haben den ersten Feind getötet und erhalten die Nachricht "Gute Arbeit".
  • Sie erhalten einen neuen Gegenstand, ein Menü mit dem Gegenstandstatistik-Popup.
  • usw. usw.

Das Spiel, an dem ich arbeite, ist ein Puzzlespiel, bei dem die Regeln im Spiel fast alle gleich sind. Es scheint daher ineffizient zu sein, alle diese Ereignisse in separaten Ebenen fest zu codieren.

Sollte ich diese Ereignisse irgendwie in einer externen Quelle wie XML definieren? Dann schreiben Sie einen Interpreter, der das XML ausliest und die Ereignisanforderungen für das Level festlegt? Ich bin mir nicht sicher, wie ich ein Ereignis definieren könnte, das eintreten sollte, wenn Sie zum Beispiel zwei Feinde getötet haben.

Um es klar zu sagen, ich suche nicht die beste Programmiersprache oder Skriptsprache, um dies zu tun, sondern eher die beste Methode, um damit umzugehen.

Vielen Dank!


Edit: Ein zweites Beispiel, da meine Frage ziemlich schwer zu verstehen war:

Das Problem, das ich habe, ist, einige zusätzliche Aktionen in das Spiel einzufügen, die immer ziemlich gleich sind. Wie bei einem RPG-Kampf ist jeder an der Reihe, wählt eine Fertigkeit usw. - es ist immer dasselbe. Aber was ist, wenn es einen Fall gibt, in dem ich irgendwo dazwischen eine Zwischensequenz zeigen möchte? Es scheint sehr ineffizient zu sein, die gesamte Spielstruktur zu modifizieren, um eine veränderte Kampfklasse mit der enthaltenen Zwischensequenz zu bestehen. Ich frage mich, wie das normalerweise gemacht wird.

omgnoseat
quelle
8
Versuchen Sie nicht, Dinge zu stark zu verallgemeinern. Tutorials zum Beispiel sind sehr spezifisch und enthalten viele verschiedene Trigger / Ereignisse. An Hardcoding / Scripting ist nichts auszusetzen.
Maik Semder
1
@Maik Wenn du das in eine Antwort-ID +1 gibst .. Einfach und gelöst ist besser als hübsch jeden Tag.
James
Ihr zweites Beispiel macht deutlich, dass ein abstraktes Nachrichtensystem ein großer Gewinn wäre. In einem Tutorial können Sie die Dinge nur hart codieren, da sie gleich zu Beginn nur einmal vorkommen. Bei laufenden Ereignissen, die jederzeit während der gesamten Spieldauer stattfinden können, ist dies jedoch anders.
jhocking
Es ist immer noch ein bisschen vage, bitte liste mindestens 3 Trigger für 3 verschiedene Zwischensequenzen auf. Es ist im Allgemeinen sehr schwer zu beantworten. Grundsätzlich müssen Sie ein gemeinsames Muster finden, um zu verstehen, wie es am besten implementiert werden kann.
Maik Semder
Was willst du? Sie möchten die Aktionen anhalten und die zusätzlichen Aktionen ausführen und dann die Aktion wieder freigeben?
User712092

Antworten:

7

Dies hängt stark davon ab, wie die Ereignisse tatsächlich zwischen Objekten in Ihrem Spiel übertragen werden. Wenn Sie beispielsweise ein zentrales Nachrichtensystem verwenden, können Sie über ein Lernprogrammmodul verfügen, das bestimmte Nachrichten abhört und Lernprogramm-Popups erstellt, wenn bestimmte Nachrichten abgehört werden. Anschließend können Sie festlegen, auf welche Nachricht und welches Popup in einer XML-Datei oder in einer Datei, die vom Lernprogramm-Modul analysiert wird, gewartet werden soll. Durch ein separates Tutorial-Objekt, das den Spielstatus überwacht und Tutorial-Popups anzeigt, wenn es Dinge im Spiel bemerkt, können Sie das Tutorial-Objekt nach Belieben ändern, ohne etwas anderes an Ihrem Spiel ändern zu müssen. (Ist dies das Observer-Muster? Ich bin nicht mit allen Entwurfsmustern vertraut.)

Insgesamt hängt es jedoch von der Komplexität Ihres Tutorials ab, ob es sich lohnt, sich darüber Gedanken zu machen. Das Hardcodieren der Ereignisse in Ihrem Code und / oder in Ihren Levels scheint mir keine große Sache zu sein, nur für eine Handvoll Tutorial-Popups. Ich bin gespannt, was genau Sie vorhaben, um zu vermuten, dass es ineffizient ist, da Sie bei jedem Trigger lediglich eine Nachricht an das Lernmodul senden sollten, etwa TutorialModule.show ("1st_kill").

jhocking
quelle
Ich denke, da es sich um ein Puzzlespiel handelt, befindet sich seine Logik an einem Ort für mehrere Ebenen und so. Wenn wir ein Tutorial für dieses Thema machen, ist es etwas, das von Dauer ist. Ehrlich gesagt, wenn es ein Puzzlespiel ist, denke ich nicht, dass dies einen großen Erfolg bedeuten wird, auch wenn es nicht der schönste Code ist, und am Ende des Tages ist der Code, der in einem Spiel funktioniert, immer -immer- 100% besser als hübscher Code, der nie das Licht der Welt erblickt;)
James
Ich habe noch nie an so etwas wie das Beobachtermuster gedacht, das klingt nach einer schönen Lösung. Ich werde es versuchen, danke :)
Omgnoseat
7

Hier sind die Designbeschränkungen, wie ich sie verstehe:

  1. Der Kern-Gameplay-Code kümmert sich nicht um Level-Anforderungen und sollte nicht an den Code gekoppelt werden, der sich mit ihnen befasst.

  2. Gleichzeitig ist es der Kern des Gameplays, der weiß, wann die spezifischen Ereignisse eintreten, die diese Anforderungen erfüllen (einen Gegenstand beschaffen, einen Feind töten usw.).

  3. Unterschiedliche Level haben unterschiedliche Anforderungen, die irgendwo beschrieben werden müssen.

Unter diesen Umständen würde ich wahrscheinlich Folgendes tun: Bilden Sie zunächst eine Klasse, die einem Spiellevel entspricht. Es fasst die spezifischen Anforderungen eines Levels zusammen. Es verfügt über Methoden, die aufgerufen werden können, wenn Spielereignisse eintreten.

Geben Sie dem Kern-Gameplay-Code einen Verweis auf das aktuelle Level-Objekt. Wenn Gameplay Ereignisse auftreten, wird es der Ebene erzählt von Methoden und fordert sie auf: enemyKilled, itemPickedUpusw.

Intern Levelbraucht ein paar Dinge:

  • Status, um zu verfolgen, welche Ereignisse bereits aufgetreten sind. Auf diese Weise kann es den ersten getöteten Feind von anderen unterscheiden und weiß, wann Sie einen bestimmten Gegenstand zum ersten Mal aufgegriffen haben.
  • Eine Liste von LevelRequirementObjekten, die die spezifischen Ziele beschreiben, die Sie für diese Ebene benötigen.

Wenn Sie ein Level betreten, erstellen Sie ein Levelmit dem richtigen LevelRequirements, richten den Gameplay-Code ein und geben ihm dieses Level.

Jedes Mal, wenn ein Spielereignis eintritt, wird es vom Gameplay an weitergegeben Level. Dadurch werden wiederum aggregierte Daten berechnet (Gesamtzahl der getöteten Feinde, getötete Feinde dieses Typs usw.). Anschließend werden die Anforderungsobjekte durchlaufen und die aggregierten Daten für jedes Objekt angegeben. Eine Anforderung prüft, ob sie erfüllt ist, und erzeugt in diesem Fall das entsprechende Verhalten (mit dem Tutorial-Text usw.).

LevelRequirement Grundsätzlich braucht zwei Dinge:

  1. Eine Beschreibung eines Tests, um festzustellen, ob die Anforderung erfüllt wurde. Dies kann nur dann eine Funktion sein, wenn Ihre Sprache dies erleichtert, andernfalls können Sie sie in Daten modellieren. (Ich habe eine RequirementTypeAufzählung mit Sachen wie FIRST_KILLund dann eine große switch, die weiß, wie man jede Art überprüft.)
  2. Eine Aktion, die ausgeführt werden soll, wenn die Anforderung erfüllt ist.

Es bleibt die Frage, wo diese Anforderungen beschrieben werden. Sie könnten so etwas wie XML oder ein anderes Textdateiformat machen. Das ist nützlich, wenn:

  1. Nicht-Programmierer werden Autorenebenen sein.
  2. Sie möchten in der Lage sein, Anforderungen zu ändern, ohne sie neu zu kompilieren und / oder neu zu starten.

Wenn beides nicht der Fall ist, würde ich sie wahrscheinlich nur direkt in Code konstruieren. Einfacher ist immer besser.

herrlich
quelle
Die ersten 3 Punkte sind eine sehr genaue Beschreibung der Methode, die ich jetzt verwende, beeindruckend! Ja, ich habe mit den meisten Problemen zu kämpfen, indem ich die Anforderung beschreibe und wie ich sie in das Spiel übersetze (da es höchstwahrscheinlich etwas Äußeres sein wird). Vielen Dank für die ausführliche Erklärung :)
Omgnoseat
5

Ich dachte, Sie müssen wissen, wie man diese Ereignisse erstellt, und der Rest des Beitrags handelt davon. Wenn Sie diese Ereignisse nur speichern möchten, verwenden Sie eine relationale Datenbank oder beschreiben Sie sie durch Text und verwenden Sie Skriptsprache (er analysiert und wertet sie aus) Du). :)

Sie möchten, dass Ereignisse erkannt werden (1) und dann einige Aktionen ausführen, die von diesen Ereignissen verlangt werden (Meldung drucken, auf Tastendruck prüfen ...) (2). Sie möchten diese Ereignisse auch nur einmal ausführen (3).

Grundsätzlich möchten Sie nach Bedingungen suchen und dann ein bestimmtes Verhalten festlegen.

Wie erkennt man Ereignisse (1)

  • Sie möchten Ereignisse wie diesen "zuerst angetroffenen Feind" oder "gewonnenen Gegenstand" erkennen.
  • wenn generischer Teil geschieht, „ Feind begegnet “, „ Punkt gewonnen “ Sie können nach bestimmtem Teil überprüfen „ zuerst ...“, „ neues Element gewonnen“

Woraus bestehen Ereignisse?

Im Allgemeinen besteht jedes dieser Ereignisse aus:

  • Voraussetzungen , Sie überprüfen sie
  • Aktionen, die ausgeführt werden, wenn die Voraussetzungen erfüllt sind (sagen Sie "Sie haben den ersten Feind getötet!", sagen Sie "Kombos durch Drücken der Tasten A und B", sagen Sie "Drücken Sie die Eingabetaste, um fortzufahren", erfordern Sie die Eingabetaste)

So speichern Sie diese Ereignisse

In einigen Datenstrukturen:

  • Liste der Voraussetzungen haben (Zeichenfolgen oder Code, wenn Sie es in einer höheren Sprache schreiben)
  • Liste von Aktionen haben (sie könnten Zeichenfolgen sein, Quake Engine verwendet Zeichenfolgen für Ereignisse)

Sie können es auch in einer relationalen Datenbank speichern, auch wenn es so aussieht, als ob es nicht notwendig ist. Wenn Sie dieses Spiel in großen Dimensionen erstellen möchten, müssen Sie möglicherweise eine erstellen.

Sie müssen dann diese Zeichenfolgen / Dinge analysieren. Oder Sie können eine Skriptsprache wie Python oder LUA oder eine Sprache wie LISP verwenden, die alle für Sie analysieren und ausführen können. :)

Wie benutze ich diese Events in der Spieleschleife? (2)

Sie benötigen diese beiden Datenstrukturen:

  • Warteschlange der Ereignisse (Ereignisse, deren Ausführung geplant ist, werden hier abgelegt)
  • Warteschlange von Aktionen (geplante Aktionen, Ereignisse implizieren, welche Aktionen ausgeführt werden)

Algorithmus:

  • Wenn Sie einige erkennen Veranstaltung ‚s Voraussetzungen erfüllt sind Sie setzen ihn in Ereigniswarteschlange
  • (3) Dann solltest du sicherstellen, dass dieses Ereignis nur einmal passiert ist, wenn du willst :)
  • (Wenn die Aktionswarteschlange leer ist, dann) Wenn sich ein Ereignis in der Ereigniswarteschlange befindet Sie stellen seine Aktionen in die Aktionswarteschlange und entfernen ihn aus der Ereigniswarteschlange
  • Befindet sich eine Aktion in der Aktionswarteschlange, beginnen Sie damit, die von ihr geforderten Aktionen auszuführen
  • Wenn eine solche Aktion ausgeführt wird, entfernen Sie sie aus der Aktionswarteschlange

Wie man diese Aktionen selbst durchführt (2)

Sie erstellen eine Liste von Objekten, die die Funktion "Aktualisieren" haben. Sie werden manchmal als Entities (in der Quake-Engine) oder Actors (in der Unreal-Engine) bezeichnet.

  1. Sie starten diese Objekte, wenn sie in der Aktionswarteschlange gestartet werden sollen.
  2. Dieses Objekt kann für andere Zwecke verwendet werden, z. B. für einige andere Zeitgeber. In Quake werden diese Entitäten für die gesamte Spiellogik verwendet. Ich empfehle Ihnen, etwas Material darüber zu lesen .

Aktion "etwas sagen"

  1. Sie drucken etwas auf dem Bildschirm
  2. Sie möchten, dass diese Meldung einige Sekunden lang angezeigt wird
  3. in "update":
    • Machen Sie die Variable remove_me_after und verringern Sie sie um die abgelaufene Zeit
    • Wenn die Variable 0 ist, entfernen Sie diese Aktion aus der Aktionswarteschlange
    • Sie entfernen dieses Objekt auch (oder planen, dass es entfernt wird ...)

Aktion "Schlüssel anfordern"

  1. Es hängt davon ab, wie Sie es machen wollen, aber ich denke, Sie machen eine Nachricht
  2. in "update" ":
    • Sie müssen nur nach dem gewünschten Tastendruck suchen
    • Sie benötigen wahrscheinlich ein Array / eine Warteschlange, um Tastendruckereignisse zu speichern
    • Dann können Sie es aus der Aktionswarteschlange entfernen und das Objekt entfernen

Welche Methoden zu lernen

user712092
quelle
-1 richtig, oder er ruft nur eine Funktion auf, ernsthaft, OP will nur eine Nachrichtenbox, wenn eine bestimmte Bedingung erfüllt ist
Maik Semder
Das ist eine sehr schöne Zusammenfassung, aber nicht genau das, wonach ich gesucht habe. Es fiel mir schwer zu erklären, was ich will. Ich fügte eine zweite Erklärung hinzu: Das Problem, das ich habe, besteht darin, einige zusätzliche Aktionen im Spiel in einer Prozedur auszuführen, die immer ziemlich gleich ist. Wie bei einem RPG-Kampf ist jeder an der Reihe, wählt eine Fertigkeit usw. - es ist immer dasselbe. Was aber, wenn es einen Fall gäbe, in dem ich irgendwo dazwischen eine Zwischensequenz zeigen würde? Es scheint sehr ineffizient zu sein, die gesamte Spielstruktur zu modifizieren, um in einer veränderten Kampfklasse mit dem enthaltenen Cutsene zu bestehen. Ich frage mich, wie das normalerweise gemacht wird?
Omgnoseat