Handhabung des Tastatureingabesystems

13

Hinweis: Ich muss aufgrund von API-Einschränkungen (SFML) abfragen, anstatt Rückrufe auszuführen. Ich entschuldige mich auch für das Fehlen eines "anständigen" Titels.

Ich glaube, ich habe hier zwei Fragen. Wie registriere ich den Eingang, den ich erhalte, und was mache ich damit?

Umgang mit Eingaben

Ich spreche davon, nachdem Sie registriert haben, dass zum Beispiel die A-Taste gedrückt wurde, und wie es von dort aus gemacht wird.

Ich habe ein Array der gesamten Tastatur gesehen, so etwas wie:

bool keyboard[256]; //And each input loop check the state of every key on the keyboard

Dies scheint jedoch ineffizient. Sie koppeln nicht nur die Taste 'A' mit 'Spieler bewegt sich nach links', sondern überprüfen jede Taste 30-60 Mal pro Sekunde.

Ich habe dann ein anderes System ausprobiert, das nur nach Schlüsseln suchte, die es wollte.

std::map< unsigned char, Key> keyMap; //Key stores the keycode, and whether it's been pressed. Then, I declare a load of const unsigned char called 'Quit' or 'PlayerLeft'.
input->BindKey(Keys::PlayerLeft, KeyCode::A); //so now you can check if PlayerLeft, rather than if A.

Das Problem dabei ist jedoch, dass ich jetzt keinen Namen eingeben kann, ohne jeden einzelnen Schlüssel binden zu müssen.

Dann habe ich das zweite Problem, für das ich mir keine gute Lösung vorstellen kann:

Senden von Eingaben

Ich weiß jetzt, dass die A-Taste gedrückt wurde oder dass playerLeft wahr ist. Aber wie gehe ich von hier aus?

Ich habe darüber nachgedacht, nur zu prüfen. if(input->IsKeyDown(Key::PlayerLeft) { player.MoveLeft(); }
Dadurch werden die Eingaben in hohem Maße mit den Entitäten verknüpft, und ich finde es ziemlich chaotisch. Ich würde es vorziehen, wenn der Spieler seine eigene Bewegung ausführt, wenn er aktualisiert wird. Ich dachte, eine Art Ereignissystem könnte funktionieren, aber ich weiß nicht, wie ich damit umgehen soll. (Ich habe gehört, dass Signale und Slots für diese Art von Arbeit gut sind, aber es ist anscheinend sehr langsam und ich kann nicht sehen, wie es passen würde).

Vielen Dank.

Die kommunistische Ente
quelle

Antworten:

7

Ich würde das Beobachtermuster verwenden und eine Eingabeklasse haben, die eine Liste von Rückrufen oder Objekten für die Eingabebehandlung enthält. Andere Objekte können sich beim Eingabesystem registrieren, um benachrichtigt zu werden, wenn bestimmte Dinge geschehen. Es gibt verschiedene Arten von Rückrufen, die Sie je nach Art der Eingabeereignisse registrieren können, über die Beobachter benachrichtigt werden möchten. Dies kann so niedrig sein wie "Taste x ist unten / oben" oder spielspezifischer wie "ein Sprung- / Schieß- / Bewegungsereignis ist passiert".

Im allgemeinen Spielobjekte haben eine Eingangsförderkomponente , die für die Registrierung von mir selbst mit dem Eingangsklasse verantwortlich ist , und das Bereitstellen von Verfahren , die Ereignisse verarbeiten sie interessiert ist. Für Bewegung, ich habe einen Eingang mover - Komponente , die eine Bewegung (x, y) Methode hat. Es registriert sich beim Eingabesystem und wird über Bewegungsereignisse benachrichtigt (wobei x und y -1 und 0 sind, um eine Bewegung nach links zu kennzeichnen). Die Bewegungskomponente ändert dann ihre übergeordneten Spielobjektkoordinaten basierend auf der Bewegungsrichtung und der Objektgeschwindigkeit.

Ich weiß nicht, ob dies eine gute Lösung ist, aber es funktioniert bisher für mich. Insbesondere kann ich verschiedene Arten von Eingabesystemen bereitstellen, die denselben logischen Spielereignissen zugeordnet sind, sodass die Gamepad-Taste x und der Tastaturbereich beispielsweise ein Sprungereignis generieren (dies ist etwas komplizierter, sodass der Benutzer die Taste neu zuweisen kann Zuordnungen).

Firas Assaad
quelle
Das interessiert mich definitiv; Würde sich die Eingabekomponente für bestimmte Ereignisse registrieren (so etwas wie "playerInput" registriert "left", "right" usw.) oder würde jede registrierte Eingabekomponente alle Nachrichten empfangen?
Die kommunistische Ente
Es hängt wirklich von Ihrer Anforderung ab und davon, wie Sie sie implementieren möchten. In meinem Fall registrieren sich Listener für bestimmte Ereignisse und implementieren die entsprechende Eingabehandler-Schnittstelle. Es sollte funktionieren, dass jede Eingabekomponente alle Nachrichten empfängt, aber das Nachrichtenformat muss allgemeiner sein als das, was ich habe.
Firas Assaad
5

Ich denke, dass die Diskussion im OP ein Bündel von Dingen zusammenwirft und dass das Problem wesentlich vereinfacht werden kann, indem man es ein wenig aufschlüsselt.

Mein Vorschlag:

Behalten Sie den Status Ihrer Schlüssel bei. Gibt es keinen API-Aufruf, um den gesamten Tastaturstatus auf einmal abzurufen? (Der Großteil meiner Arbeit ist auf Konsolen und selbst unter Windows für einen angeschlossenen Controller wird der gesamte Controller-Status auf einmal angezeigt.) Ihre Tastenzustände sind eine "harte" Zuordnung auf den Tastaturtasten. Nicht übersetzt ist am besten, aber Sie erhalten es am einfachsten aus den APIs.

Behalten Sie dann ein paar parallele Arrays bei, die angeben, ob der Schlüssel in diesem Frame nach oben gegangen ist, ein weiteres, das angibt, dass er nach unten gegangen ist, und ein drittes, das einfach angibt, dass der Schlüssel nach unten gegangen ist. Wirf vielleicht einen Timer ein, damit du nachverfolgen kannst, wie lange sich der Schlüssel im aktuellen Zustand befindet.

Erstellen Sie als Nächstes eine Methode, um eine Zuordnung von Schlüsseln zu Aktionen zu erstellen. Sie werden dies ein paar Mal tun wollen. Einmal für UI-Sachen (und achten Sie darauf, die Windows-Zuordnungen abzufragen, da Sie den Scancode nicht annehmen können, wenn Sie 'A' drücken, wird ein A auf dem Computer des Benutzers angezeigt). Ein anderer für jeden "Modus" im Spiel. Dies sind die Zuordnungen von Schlüsselcodes zu Aktionen. Sie können dies auf verschiedene Arten tun. Eine Möglichkeit besteht darin, eine vom empfangenden System verwendete Enumeration beizubehalten. Eine andere besteht aus einem Funktionszeiger, der die Funktion angibt, die bei einer Statusänderung aufgerufen werden soll. Vielleicht sogar eine Mischung aus beidem, je nach Ihren Zielen und Bedürfnissen. Mit diesen "Modi" können Sie sie auf einen Controller-Modus-Stapel verschieben und dort einfügen. Mit diesen Flags können ignorierte Eingaben den Stapel oder was auch immer weiter ablaufen lassen.

Behandeln Sie diese Schlüsselaktionen schließlich irgendwie. Für Bewegungen möchten Sie vielleicht etwas Kniffliges tun, indem Sie "W" nach unten übersetzen, um "vorwärts" zu bedeuten, aber es muss keine binäre Lösung sein. "W ist unten" bedeutet "Geschwindigkeit um X bis maximal Y erhöhen" und "W ist oben" bedeutet "Geschwindigkeit um Z verringern bis Null". Im Allgemeinen möchten Sie, dass Ihre "Controller-Handling-Schnittstelle in Spielsystemen" ziemlich schmal ist. Führen Sie alle wichtigen Übersetzungen an einem Ort aus und nutzen Sie die Ergebnisse dann an jedem anderen Ort. Dies steht im Gegensatz zu der direkten Überprüfung, ob die Leertaste an einer beliebigen Stelle im Code gedrückt wird. Wenn sie an einer zufälligen Stelle gedrückt wird, wird sie wahrscheinlich zu einer zufälligen Zeit getroffen, wenn Sie dies nicht möchten. damit will ich mich nicht befassen ...

Ich hasse es wirklich, Muster zu entwerfen, und ich denke, dass Komponenten mehr Entwicklungskosten verursachen als gute, deshalb habe ich auch keine erwähnt. Muster entstehen, wenn sie dazu bestimmt sind, ebenso wie Komponenten, aber wenn Sie sich von Anfang an dafür entscheiden, erschweren Sie nur Ihr Leben.

Dash-Tom-Bang
quelle
Finden Sie es nicht schmutzig, Ereignisse an Grafikrahmen zu binden? Ich denke das sollte getrennt werden.
Jo So
Ich verstehe, dass der Player in fast allen Fällen langsamer auf Eingabetasten reagiert als die Grafikkarte Frames ausgibt, aber eigentlich sollten sie unabhängig sein, und in der Tat sind sie für andere Ereignistypen gedacht, wie diejenigen, die aus dem Netzwerk stammen .
Jo So
Tatsächlich; Es gibt keinen Grund, warum das Abrufen der Tastatur (oder eines anderen Eingabegeräts) nicht mit einer anderen Geschwindigkeit als der Anzeige ausgeführt werden kann. Wenn Sie Eingaben bei 100 Hz abfragen, können Sie unabhängig von der Häufigkeit der Aktualisierung der Video-Hardware eine wirklich konsistente Benutzersteuerung erzielen. Sie müssen etwas schlauer sein, wie Sie mit Ihren Daten umgehen (in Bezug auf das Zwischenspeichern usw.), aber es macht es einfacher, Dinge wie Gesten konsistenter zu machen.
Dash-Tom-Bang
3

In SFML haben Sie die Tasten in der Reihenfolge, in der sie mit dem Ereignistyp KeyPressed gedrückt wurden. Sie verarbeiten jeden Schlüssel in einer Weile (getNextEvent ()).

Befindet sich die gedrückte Taste in Ihrer Key-> Action-Map, führen Sie die entsprechende Aktion aus. Wenn dies nicht der Fall ist, leiten Sie sie an jedes Widget / Material weiter, das sie möglicherweise benötigt.

In Bezug auf Ihre zweite Frage würde ich Ihnen empfehlen, diese so beizubehalten, da es einfacher ist, Ihr Gameplay zu optimieren. Wenn Sie Signale oder ähnliches verwenden, müssen Sie einen Steckplatz für den "RunKeyDown" und einen anderen für den "JumpKeySlot" erstellen. Aber was passiert, wenn in Ihrem Spiel eine spezielle Aktion ausgeführt wird, wenn beide gedrückt werden?

Wenn Sie Eingaben und Entitäten dekorrelieren möchten, können Sie Ihre Eingabe zu einem Status (RunKey = true, FireKey = false, ...) machen und an den Player senden, so wie Sie jedes andere Ereignis an Ihre AIs senden.

Calvin1602
quelle
Ich bearbeite Tastaturereignisse nicht mit sf :: Event, sondern mit sf :: Input, da es sich um eine IIRC-Echtzeiterkennung handelt. Ich habe versucht, es so API-unabhängig wie möglich zu halten. : P (Die Frage, das ist)
Die kommunistische Ente
1
Aber ich denke, der Punkt, den Calvin1602 anführt, ist, dass Ihre ursprüngliche Aussage in der Frage "Ich muss abfragen, anstatt Rückrufe aufgrund von API-Einschränkungen (SFML) durchzuführen" nicht wahr ist; Wenn Sie Ereignisse haben, können Sie einen Rückruf ausgeben, wenn Sie ein Ereignis behandeln.
Kylotan,
3

Die richtige Lösung ist oft eine Kombination der beiden von Ihnen diskutierten Methoden:

Für Gameplay-Funktionen mit einem vordefinierten Satz von Schlüsseln ist das Abrufen jedes Frames völlig angemessen und Sie sollten nur nach Schlüsseln abrufen, die tatsächlich an etwas gebunden sind. Dies entspricht der Abfrage von Controller-Eingaben in einem Konsolenspiel. Insbesondere bei analogen Eingabegeräten wird eine vollständige Abfrage durchgeführt. Während des allgemeinen Spiels möchten Sie wahrscheinlich Tastendruckereignisse ignorieren und nur die Abfrage verwenden.

Wenn Sie Eingaben für Dinge wie Namen eingeben, sollten Sie das Spiel in einen anderen Eingabeverarbeitungsmodus schalten. Sobald Sie zum Beispiel aufgefordert werden, Ihren Namen einzugeben, können Sie die Funktion des eingegebenen Codes ändern. Es kann entweder über eine Rückrufschnittstelle auf Tastendruckereignisse warten oder bei Bedarf nach jeder Taste abrufen. Es stellt sich heraus, dass Sie während des Tippens normalerweise sowieso nicht so viel Wert auf die Leistung legen, sodass das Polling nicht so schlecht ist.

Daher möchten Sie die selektive Abfrage für die Spielereingabe und die Ereignisbehandlung für Eingaben zur Namenseingabe verwenden (oder die gesamte Tastaturabfrage, wenn die Ereignisbehandlung nicht verfügbar ist).

Ben Zeigler
quelle