Gibt es eine Standardtechnik zum Verwalten von Eingaben in großen Spielen? Derzeit wird in meinem Projekt die gesamte Eingabebearbeitung in der Spieleschleife ausgeführt:
while(SDL_PollEvent(&event)){
switch(event.type){
case SDL_QUIT:
exit = 1;
break;
case SDL_KEYDOWN:
switch(event.key.keysym.sym){
case SDLK_c:
//do stuff
break;
}
break;
case SDL_MOUSEBUTTONDOWN:
switch(event.button.button){
case SDL_BUTTON_MIDDLE:
//do stuff
break;
}
}
break;
}
(Ich verwende SDL, aber ich gehe davon aus, dass die Hauptpraxis auch für Bibliotheken und Frameworks gilt.) Für ein großes Projekt scheint dies nicht die beste Lösung zu sein. Ich kann mehrere Objekte haben, die alle wissen möchten, was der Benutzer gedrückt hat, daher wäre es sinnvoller, wenn diese Objekte Eingaben verarbeiten. Sie können jedoch nicht alle Eingaben verarbeiten, da sie nach dem Empfang eines Ereignisses aus dem Ereignispuffer entfernt werden, sodass ein anderes Objekt diese Eingaben nicht erhält. Mit welcher Methode wird dem am häufigsten entgegengewirkt?
Antworten:
Da ich vom Threadstarter gefragt werde, gehe ich auf Eventmanager ein. Ich denke, das ist eine gute Möglichkeit, mit Eingaben in einem Spiel umzugehen.
Ein Event Manager ist eine globale Klasse, die es ermöglicht, Rückruffunktionen auf Tasten zu registrieren und diese Rückrufe auszulösen. Der Ereignismanager speichert registrierte Funktionen in einer privaten Liste, die nach ihrem Schlüssel gruppiert ist. Jedes Mal, wenn ein Schlüssel abgefeuert wird, werden alle registrierten Rückrufe ausgeführt.
Die Rückrufe können
std::function
Objekte sein, die Lambdas enthalten können. Die Schlüssel könnten Strings sein. Da der Manager global ist, können sich Komponenten Ihrer Anwendung für Schlüssel registrieren, die von anderen Komponenten abgefeuert wurden.Sie können diesen Ereignismanager sogar erweitern, um die Übergabe von Werten als zusätzliche Argumente zuzulassen. Dafür sind C ++ - Templates hervorragend geeignet. Sie könnten ein solches System verwenden, um beispielsweise für ein
"WindowResize"
Ereignis die neue Fenstergröße zu übergeben, damit die Empfangskomponenten es nicht selbst abrufen müssen. Dies kann die Codeabhängigkeiten erheblich reduzieren.Ich habe einen solchen Event Manager für mein Spiel implementiert. Bei Interesse poste ich hier den Link zum Code.
Mithilfe eines Ereignismanagers können Sie auf einfache Weise Eingabeinformationen in Ihrer Anwendung übertragen. Darüber hinaus können Sie auf diese Weise die Tastenbelegung anpassen. Komponenten lauschen semantischen Ereignissen anstelle von Schlüsseln direkt (
"PlayerJump"
anstelle von"KeyPressedSpace"
). Dann können Sie eine Eingabe-Mapping-Komponente haben,"KeyPressedSpace"
die auf jede Aktion wartet und diese auslöst, die der Benutzer an diesen Schlüssel gebunden hat.quelle
[=]
und Referenzen auf alle lokalen Variablen, auf die vom Lambda zugegriffen wird, werden kopiert. Sie müssen diesen Zeiger oder ähnliches also nicht übergeben. Beachten Sie jedoch , dass Sie Lambdas mit Capture-Klausel nicht in alten C-Funktionszeigern speichern können . Das C ++std::function
funktioniert jedoch einwandfrei.Teilen Sie dies in mehrere Ebenen.
Auf der untersten Ebene haben Sie unformatierte Eingabeereignisse vom Betriebssystem. SDL-Tastatureingabe, Mauseingabe, Joystick-Eingabe usw. Möglicherweise gibt es mehrere Plattformen (SDL ist ein Nenner mit den wenigsten Gemeinsamkeiten, dem beispielsweise mehrere Eingabeformulare fehlen, die Sie später interessieren könnten).
Sie können diese mit einem sehr einfachen benutzerdefinierten Ereignistyp wie "Tastatur gedrückt" oder ähnlichem abstrahieren. Wenn Ihre Plattformebene (SDL-Spieleschleife) Eingaben empfängt, sollte sie diese Ereignisse auf niedriger Ebene erstellen und dann an einen Eingabemanager weiterleiten. Dies kann mit einfachen Methodenaufrufen, Rückruffunktionen, einem komplizierten Ereignissystem geschehen, ganz wie Sie möchten.
Das Eingabesystem hat nun die Aufgabe, Eingaben auf niedriger Ebene in logische Ereignisse auf hoher Ebene zu übersetzen. Die Spielelogik kümmert sich überhaupt nicht darum, dass SPACE gedrückt wurde. Es ist wichtig, dass JUMP gedrückt wurde. Die Aufgabe des Eingabe-Managers besteht darin, diese Eingabeereignisse auf niedriger Ebene zu erfassen und Eingabeereignisse auf hoher Ebene zu generieren. Es ist dafür verantwortlich zu wissen, dass sowohl die Leertaste als auch die A-Schaltfläche des Gamepads dem logischen Befehl Jump zugeordnet sind. Es befasst sich mit Gamepad vs Maus-Look-Steuerelementen und so weiter. Es gibt logische Ereignisse auf hoher Ebene aus, die von den Steuerelementen auf niedriger Ebene so abstrakt wie möglich sind (es gibt hier einige Einschränkungen, aber Sie können die Dinge im allgemeinen Fall vollständig abstrahieren).
Ihr Charakter-Controller empfängt dann diese Ereignisse und verarbeitet diese übergeordneten Eingabeereignisse, um tatsächlich zu reagieren. Die Plattformebene hat das Ereignis "Leertaste drücken" gesendet. Das Eingabesystem hat dies empfangen, überprüft seine Zuordnungstabellen / -logik und sendet dann das Ereignis "Gepresster Sprung". Der Spiellogik- / Charakter-Controller empfängt dieses Ereignis, prüft, ob der Spieler tatsächlich springen darf, und gibt dann das Ereignis "Spieler gesprungen" aus (oder veranlasst direkt einen Sprung), mit dem die übrige Spiellogik alles Mögliche ausführt .
Alles, was von der Spielelogik abhängt, geht in den Player-Controller. Alles, was vom Betriebssystem abhängt, liegt in der Plattformebene. Der Rest geht in die Eingabeverwaltungsebene.
Hier sind einige amateurhafte ASCII-Grafiken, um dies zu beschreiben:
quelle