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.
quelle
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.
quelle
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.
quelle
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).
quelle
Ich mag die Antwort von Dash-Tom-Bang sehr, sie hebt einige Aspekte hervor, die Sie beim Entwerfen eines Eingabesystems berücksichtigen sollten.
Ich möchte ein kleines Tutorial hinzufügen, auf das ich gestoßen bin und das in die gleiche Richtung geht (einschließlich Eingabekontexten, 3 Ebenen, ...):
http://www.gamedev.net/blog/355/entry-2250186-designing-a-robust-input-handling-system-for-games/
quelle