Eingabehandhabung im komponentenbasierten Design

12

Ich weiß, dass diese Frage mehrmals gestellt wurde, bin mir aber immer noch nicht sicher, wie die Eingabebehandlung in einer komponentenbasierten Engine implementiert werden soll.

Das komponentenbasierte Design, das ich verwendet habe, basierte auf der Blogserie von T = Machine und auf Artemis, in denen Entities nur IDs sind.

Bei der Implementierung der Eingabehandhabung habe ich drei Hauptideen:

  1. Die Eingabekomponente enthält Ereignisse, an denen sie interessiert ist. Das Eingabesystem übersetzt Schlüssel- und Mausereignisse in Spielereignisse und durchläuft die Entitäten mit der Eingabekomponente. Wenn sie an dem Ereignis interessiert sind, ergreift das Eingabesystem eine geeignete Aktion. Diese Aktion wäre für das Eingabesystem fest codiert.
  2. Keine Eingabekomponente. Sie würden Entitäten mit bestimmten Ereignissen im Eingabesystem registrieren. Das Eingabesystem sendet dann Nachrichten (mit der Entitäts-ID und dem Ereignistyp) an andere Systeme, damit diese die entsprechenden Maßnahmen ergreifen können. Oder wie im ersten Fall würden die Aktionen für das Eingabesystem fest codiert.
  3. Ähnlich wie bei der ersten Methode, aber anstatt die Aktion fest in das Eingabesystem zu codieren, würde die Komponente eine Zuordnung von Ereignissen zu Funktionen (dh std::map<std::function>) enthalten, die vom Eingabesystem aufgerufen würden. Dies hat den zusätzlichen Effekt, dass dasselbe Ereignis mit verschiedenen Aktionen gekoppelt werden kann.

Würden Sie eine der oben genannten Methoden empfehlen oder haben Sie Vorschläge, die mir bei der Implementierung eines flexiblen Eingabeverarbeitungssystems helfen könnten? Außerdem bin ich noch nicht mit Multithreading vertraut, aber auch Vorschläge, die die Implementierung threadfreundlich machen würden, sind willkommen.

Hinweis: Eine zusätzliche Anforderung, die die Implementierung erfüllen soll, besteht darin, dass ich die gleiche Eingabe an viele Entitäten übergeben kann, z. B. das gleichzeitige Verschieben einer Kameraentität und des Players.

Trauerherz
quelle
2
Normalerweise (wenn die Kamera dem Player folgt) möchten Sie keine Eingaben in die Kamera empfangen, sondern lassen die Kamera die Position des Players überprüfen und folgen.
Luke B.
1
Konzeptionell spielt es keine Rolle, ob die Kamera dem Player oder "sich selbst" folgt. Ich bin mir jedoch nicht sicher, wie Ihr Vorschlag in einem komponentenbasierten Design umgesetzt werden würde, ohne die Designprinzipien zu verletzen.
Grieverheart
1
@ Luke B.: Nachdem ich darüber nachgedacht habe, sehe ich, dass Sie die Kamera auch als separate Klasse erstellen können, indem Sie einen Zeiger auf eine Entität nehmen, der Sie folgen möchten.
Grieverheart

Antworten:

8

Ich denke, genau wie bei meiner Antwort auf Materialien in einem Komponentensystem stoßen Sie auf ein Problem, bei dem Sie versuchen, alles in eine "Komponente" zu verschieben. Sie müssen dies nicht tun, und dabei erstellen Sie wahrscheinlich eine wirklich umständliche Oberfläche, indem Sie versuchen, ein paar quadratische Stifte in runde Löcher zu stecken.

Es hört sich so an, als hätten Sie bereits ein System, das die Erfassung von Eingaben vom Player übernimmt. Ich würde mich für einen Ansatz entscheiden, der diese Eingaben dann in Aktionen ("vorwärts bewegen" oder "rückwärts bewegen") oder Ereignisse übersetzt und diese an interessierte Parteien weiterleitet. In der Vergangenheit habe ich es Komponenten untersagt, sich für diese Ereignisse zu registrieren , und einen Ansatz bevorzugt, bei dem das übergeordnete System die "kontrollierte Entität" explizit ausgewählt hat. Wenn Sie es vorziehen, könnte es auch anders funktionieren, insbesondere wenn Sie dieselben Nachrichten erneut verwenden, um Aktionen auszuführen, die nicht direkt durch Eingaben angeregt wurden.

Ich würde jedoch nicht unbedingt vorschlagen, das Verhalten der Kameraverfolgung zu implementieren, indem sowohl die Kameraentität als auch die Spielerentität auf die Nachricht "Vorwärts bewegen" (usw.) reagieren. Dies schafft eine extrem starre Verbindung zwischen den beiden Objekten, die sich für den Spieler wahrscheinlich nicht gut anfühlt, und es macht es auch etwas schwieriger, Dinge wie die Umlaufbahn der Kamera mit dem Spieler zu handhaben, wenn sich der Spieler nach links oder rechts dreht: Sie haben eine Entität Antworten auf "Nach links drehen", indem angenommen wird, dass es dem Spieler unterstellt ist, aber das bedeutet, dass es nicht richtig reagieren kann, wenn es jemals versklavt wurde ... es sei denn, Sie führen dieses Konzept als einen Zustand ein, den Sie überprüfen können. Und wenn Sie dies tun möchten, können Sie auch ein geeignetes System zum Zusammenfügen von zwei physischen Objekten implementieren, einschließlich geeigneter Elastizitätsanpassungen und so weiter.

In Bezug auf Multithreading sehe ich hier keine Notwendigkeit, es einzusetzen, da es wahrscheinlich mehr Komplikationen verursachen würde, als es wert ist, und Sie haben es mit einem inhärent seriellen Problem zu tun, sodass Sie nur viel Thread einbeziehen müssen Synchronisationsprimitive.

Gemeinschaft
quelle
Ich spiele meine Frage einige Gedanken und wollte sie selbst beantworten. Ich bin auch zu dem Schluss gekommen, dass ich die Eingabehandhabung besser vom EC-System entkoppeln sollte, damit es schön ist, eine Bestätigung dafür zu sehen. Ich habe mir das überlegt, indem ich Signale verwendet und mehrere Entitäten einem Ereignistyp zugeordnet habe. Ich habe mich auch entschlossen, die Kamera zu entkoppeln, obwohl dies nicht wirklich notwendig ist und es ebenso sinnvoll wäre, sie als Einheit zu haben. Ich denke, wenn man noch ein Neuling bei ECs ist, muss man sich wirklich überlegen, welche Vorteile es hat, etwas zu einer Komponente oder Einheit zu machen.
Grieverheart
4

Meine Erfahrung mag voreingenommen sein, aber in Projekten mit mehreren Plattformen sind die Eingabegeräte nicht direkt dem Entitätssystem ausgesetzt.

Die Eingabegeräte werden von einem untergeordneten System verwaltet, das die Ereignisse von den Tasten, Tasten, Achsen, Mäusen, Berührungsflächen, Beschleunigungsmessern ... empfängt.

Diese Ereignisse werden dann über eine Schicht kontextabhängiger Absichtsgeneratoren gesendet.

Jeder Generator registriert für Zustandsänderungen von Komponenten, Entitäten und Systemen, die für seine Funktionen relevant sind.

Diese Generatoren senden dann Nachrichten / Absichten zum Weiterleiten an das Absichtssystem, in dem Entitäten eine Komponente haben, oder direkt an die richtigen Komponenten.

Auf diese Weise können Sie sich einfach darauf verlassen, dass "immer" dieselbe Eingabe vorliegt, dh JUMP_INTENT (1), JUMP_INTENT (0), AIM_INTENT (1) ...

Und "alle" schmutzigen plattformabhängigen Eingabearbeiten bleiben außerhalb Ihres Entitätssystems.


Wenn Sie die Kamera im Player bewegen möchten, kann sie ihre eigene Absichtskomponente registrieren und die von Ihnen gesendeten Absichten anhören.

Andernfalls sollte der Player, wenn er ihm folgt, niemals Eingaben hören, die für den Player bestimmt sind. Es sollte die vom Player ausgegebenen Statusänderungen abhören (ENTITY_MOVED (transformieren)) ... und sich entsprechend bewegen. Wenn Sie ein Physiksystem verwenden, können Sie die Kamera sogar über eines der verschiedenen Gelenke am Player befestigen.

Kojote
quelle
Coyote, danke für deine Antwort. Ich habe auch Ihren anderen Beitrag hier gelesen . Meine größte Sorge ist nicht, wie man die Eingabe abstrahiert. Ich habe bereits ein Konstrukt auf niedrigerer Ebene, das Tastendrücke und dergleichen handhabt, und das Hinzufügen einer weiteren Indirektionsebene wäre nicht schwierig. Mein Problem ist die Behandlung der Ereignisse, die beispielsweise von Ihrem Intent-System generiert werden. Wenn ich das richtig verstehe, haben Sie überhaupt keine Eingabekomponenten in Ihrer Methode. Woher wissen Sie, welche Entitäten Eingaben benötigen und wie gehen Sie damit um? Könnten Sie konkretere Beispiele nennen?
Grieverheart
2

Welchen Nutzen bringt eine InputComponent? Sicherlich ist es das Vorrecht des Eingabebefehls, zu entscheiden, für welche Entitäten eine Aktion ausgeführt wird. Das klassische Beispiel ist das, den Spieler zum Springen zu bringen. Anstatt eine InputComponent für jede Entität zu haben, die auf "Sprung" -Ereignisse wartet, lassen Sie den Sprungbefehl die Entität mit der Bezeichnung "Spieler" suchen und die erforderliche Logik selbst ausführen.

Action jump = () =>
{
    entities["player"].Transform.Velocity.Y += 5;
};

Ein weiteres Beispiel aus dem OP:

Action moveRight = () =>
{
    foreach (var entity in entities.Tagged("player", "camera"))
        entity.Transform.Position.X += 5;
};
AlexFoxGill
quelle