[WinAPI] Prüfen, ob eine Taste gedrückt ist

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
Krishty
Establishment
Beiträge: 8260
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

[WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von Krishty »

(Es klingt total banal, aber ich gehe echt dran kaputt. Ich habe gegoogelt, finde aber immer nur Antworten für Konsolenanwendungen.)

Wie kann ich herausfinden, ob der Anwender im Augenblick eine bestimmte Taste auf der Tastatur drückt? Und als Bonus: das gleiche für Maustasten bitte!

Was ich bisher weiß:
  • Die WinAPI ist ereignisorientiert; sie sendet mir WM_KEYDOWN und WM_KEYUP (und die entsprechenden Mausnachrichten). Das ist aber scheiße kompliziert: Ich muss ein Array von Zuständen für alle virtuellen Tasten vorhalten und bei jeder Nachricht entsprechend aktualisieren. Dann kommen tausend Sonderfälle, wie: Anwender drückt in meiner Anwendung eine Taste und wechselt zu einer anderen Anwendung, bevor er sie wieder loslässt.
    Do. Not. Want.
  • GetAsyncKeyState() gibt mir den Tasten-/Mauszustand immer – auch, während der Anwender garnicht mehr in meiner Anwendung ist. Klick auf ein Fenster auf dem anderen Bildschirm – Magazin leergeschossen. Ich müsste also testen, ob mein Fenster noch im Vordergrund ist. Wie schnell reagiert Windows bei sowas? Wenn der Anwender auf ein Fenster klickt während ich mitten in der Abfrage bin, habe ich dann noch Fokus oder nicht? Außerdem soll man GetKeyState() verwenden, weil viele Virenscanner bei der asynchronen Version einen Keylogger vermuten.
  • GetKeyState() – wie GetAsyncKeyState(), nur schlimmer. Hier wird der Zustand gemäß dem Abarbeitungsfortschritt meines Nachrichtenstapels zurückgegeben. Was soll ich da machen? Nach jeder verarbeiteten Nachricht aufs Neue prüfen? Oder alle Nachrichten durchkauen und dann erst prüfen? Funktioniert das, wenn man schneller klickt und wieder loslässt als die Frequenz der Eingabeverarbeitung ist?
  • Raw Input. Schon länger her, dass ich damit gearbeitet habe – darum wäre es schön, wenn jemand seine Erfahrungen mit mir teilen könnte. Soweit ich das in Erinnerung habe, brauche ich die Zeit und die Textmenge, die ich bei den drei Punkten zuvor ins Stopfen der Löcher stecken müsste, hier schon für Initialisierung und Kapselung. Ist es das zumindest wert?
  • DirectInput. Microsoft sagt: Finger weg und stattdessen die Windows-Nachrichtenschleife nutzen.
Habe ich irgendeine wichtige Lösung übersehen? Liegt’s an mir, dass das alles ganz eklig aussieht? Bei Get[Async]KeyState() bin ich tatsächlich unsicher, ob ich nicht selber schuld an der Misere bin, weil meine Anwendung nicht in einen Leerlaufzustand wechselt, sobald sie den Fokus verliert … eigentlich würde ich das gern vermeiden; State is the Enemy und so.

Gruß, Ky
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
FlorianB82
Beiträge: 70
Registriert: 18.11.2010, 05:08
Wohnort: Darmstadt
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von FlorianB82 »

kurze zusammenfassung meiner erfahrungen mit den möglichkeiten:
  • ereignisse/GetKeyState/GetAsyncKeyState machen konzeptuell etwas anderes als raw input/DI. erstere verarbeiten die daten viel mehr als letzere. so werden durch erstere z.b. key-repeats hinzugefügt, und das tastaturlayout berücksichtigt. bei letzteren nicht unbedingt, und du bekommst keine konstanten für virtuelle keys, sondern scancodes der realen keys. das heisst für dich: was willst du genau? nur ein snapshot der tastatur zum augenblicklichen zeitpunkt? oder doch lieber ne einfache texteingabe mit tastaturlayout etc? (ich hatte mir mal eine texteingabe für eine in-game GUI gebastelt auf basis von DI, da hatte ich dann bei drücken der 'z' taste ein 'y' in meinem textfeld... es war echt nervig, selber keyrepeat einzubauen oder mithilfe von weiteren WINAPI-funktionen die scancodes in VK_... übersetzt zu bekommen!)
  • DI: ist nicht ohne grund depreacted. auf win7 wird das nämlich emuliert, und bekommt seine daten aus dem ereignissystem. als kann man da dann gleich ebendieses nehmen, es sei denn, man möchte noch nen sinnlosen layer zwischendrin samt dessen initialisierung und handling...
  • raw input: habe ich noch nie probiert, ist ja wohl was neueres. könnte aber durchaus ne idee sein, sofern du auf die raw-daten und keinen text-input aus bist.
  • über die WM_...-nachrichten wäre mir auch zu blöd, aus den von dir genannten gründen.
  • GetKeyState()/GetAsyncKeyState() fand ich einfach in der verwendung einfach und gut. da du wie bei den WM-nachrichten wieder virtuelle keys + repeat + keyboard-layout-verarbeitung bekommst, schön einfach für text. eregbnis das gleiche wie über die nachrichten, nur sicherer (deine genannten sonderfälle sind sicher richtig implementiert, was bei einer eigenimplementierung erstmal nicht so sein wird).
also unterm strich: wenn keine texteingabe nötig, schnapp dir raw input. wenn nicht, schnapp dir GetKeyState(), und schicke bei fokusverlust die anwendung in den leerlauf (das ist jetz ja wohl nicht so schlimm...! klar sollte man zombie-states u.ä. vermeiden, und keinen text für irgendwelche nicht-funktionalitäten verbraten, aaaber: es ist hier halt notwendig.).
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von dot »

Warum genau musst du denn pollen?
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von BeRsErKeR »

Die WinAPI ist ereignisorientiert; sie sendet mir WM_KEYDOWN und WM_KEYUP (und die entsprechenden Mausnachrichten). Das ist aber scheiße kompliziert: Ich muss ein Array von Zuständen für alle virtuellen Tasten vorhalten und bei jeder Nachricht entsprechend aktualisieren. Dann kommen tausend Sonderfälle, wie: Anwender drückt in meiner Anwendung eine Taste und wechselt zu einer anderen Anwendung, bevor er sie wieder loslässt.
Do. Not. Want.
Ich nutze eigentlich immer die WM_* Messages, bau mir darin Events zusammen und leite sie an mein internes Event-System weiter. Wenn man sich einmal die Mühe macht und ein ordentliches Event-System bastelt kann man dies ja für alle späteren Projekte nutzen und auch für andere Window-Systeme wie XWindows usw nutzen. Die Sonderfälle lassen sich in der Regel auch recht einfach über andere WM_* Messages abfangen z.B. WM_MINIMIZE usw. Klar mag das nicht das schönste Verfahren sein, allerdings wüsste ich auch nicht wie es mit der WINAPI besser gehen sollte. Ich nutze für Tastatur-Keys ein Array und das wird auch in vielen Tutorials so gehandhabt (spontan fallen mir z.B. die NeHE OpenGL Tutorials ein auch wenn diese vielleicht nicht gerade die besten sind; aber auch in diversen Büchern sieht man das oft in dieser Form). Es ist auch ein wenig knifflig wenn so Sachen wie Strg+Taste, Shift+Taste oder Doppelklicks ins Spiel kommen. Da gibts ja dann Messages wie WM_*DBLCKICK (oder so ähnlich) und WM_SYSKEYUP/DOWN, wobei ich mir Doppelklicks selbst aus normalen KeyDown/Up Messages bastel (allerdings haben diese Nachrichten auch ihre Eigenarten). Wie gesagt ist das Fummelkram, aber einmal implementiert kann man dies in der Regel wiederverwenden. Schreib dir halt eine WindowProc, die die gängigen Features verarbeiten kann. Alle weiteren Nachrichten leitest du dann halt wie gewohnt an DefWindowProc weiter oder gibts die Möglichkeit, dass die Nachrichten an eine individuelle WindowProc (je nach Projekt) weitergeleitet werden oder umgekehrt können projektspezifische WindowProcs an deine StandardWindowProc weiterleiten, die sich um alles kümmert. Dann hast du mehr Flexibilität.

Wie gesagt würde ich aber intern einen plattformunabhängigen EventManager nutzen oder wenigstens einen der unabhängig davon ist, wie du Key- und Mouse-Events verarbeitest. Ich glaube eine Abstraktionsebene ist gerade hier von Vorteil, denn deine Projekt muss sich dann halt nicht mit den angesprochenen Problemen rumärgern, sondern kann eine einheitliche Event-Schnittstelle nutzen; diese muss halt nur korrekt arbeiten.
Ohne Input kein Output.
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von dot »

Ich seh das Problem generell darin, dass du versuchst zu pollen, anstatt auf Events zu reagieren. Polling ist imo keine besonders tolle Lösung wenn es um Input geht. Wie ein Prof von mir es einmal erklärt hat: Polling ist, wenn man jede Sekunde mal den Telefonhörer abnimmt, um zu schauen ob schon jemand angerufen hat...
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von CodingCat »

Wie soll das denn bei kontinuierlicher Tastensteuerung (z.B. Kamera) ohne Polling gehen, ohne den Spieler dabei mit der systemeigenen Tastenwiederholrate vorwärts zu ruckeln?! Ich habe mir letztes Mal einfach alle Tastenzustände in einen Byte-Puffer mit Tastencodeindizierung gehauen und per Windows-Nachrichten aktualisiert, wie oben beschrieben. Bei Fokusverlust habe ich dann den gesamten Puffer mit Null überschrieben.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von dot »

Dazu brauch ich nicht alle Tastenzustände, sondern einfach nur eine Kamerageschwindigkeit, die von meinen Tasten gesteuert wird...
Zuletzt geändert von dot am 29.08.2011, 18:22, insgesamt 1-mal geändert.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von CodingCat »

Naja OK, dann pollst du halt die Kamerageschwindigkeit. :P
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von dot »

CodingCat hat geschrieben:Naja OK, dann pollst du halt die Kamerageschwindigkeit. :P
Die nichts mehr mit dem Inputhandling zu tun hat... ;)
Benutzeravatar
Krishty
Establishment
Beiträge: 8260
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von Krishty »

@FlorianB82: Okay, also ist DirectInput schonmal gestrichen. Was meintest du mit „repeat“ bei Get[Async]KeyState()? Da kriegst du doch nur zurück, dass die Taste gedrückt ist und nicht, wie oft sie seit der letzten Abfrage gedrückt wurde?
FlorianB82 hat geschrieben:… und schicke bei fokusverlust die anwendung in den leerlauf (das ist jetz ja wohl nicht so schlimm...! klar sollte man zombie-states u.ä. vermeiden, und keinen text für irgendwelche nicht-funktionalitäten verbraten, aaaber: es ist hier halt notwendig.).
Bei Leerlauf fühle ich mich stark doppelgebunden – einerseits ist er in Sachen wie dieser sehr nützlich; andererseits versucht Windows seit geraumer Zeit immer mehr, diesen Zustand von der Anwendung ins Betriebssystem zu ziehen – mit DXGI 1.1 wurde z.B. der Occluded-Zustand für 3D-Anwendungen abgeschafft (verdeckte Anwendungen bekommen automatisch weniger GPU-Leistung, wie auch CPU-Leistung, falls das System ausgelastet ist) … da weiß ich nicht, ob die nun wollen, dass ich zusätzlich von mir aus leer laufe, oder nicht …


Aber mit dieser Frage hast du es exzellent auf den Punkt getroffen:
FlorianB82 hat geschrieben:was willst du genau?
Ich brauche Eingabe für eine Echtzeit-3D-Anwendung; d.h. keine Textverarbeitung und Takte statt Ereignissen.


@BeRsErKeR:
BeRsErKeR hat geschrieben:Ich nutze eigentlich immer die WM_* Messages, bau mir darin Events zusammen und leite sie an mein internes Event-System weiter. Wenn man sich einmal die Mühe macht und ein ordentliches Event-System bastelt kann man dies ja für alle späteren Projekte nutzen und auch für andere Window-Systeme wie XWindows usw nutzen.
Nee, ich habe hier ein anderes Projekt, das ereignisgesteuert ist – und mir seitdem geschworen, das nie wieder zu tun außer für Texteingabe.
BeRsErKeR hat geschrieben:allerdings wüsste ich auch nicht wie es mit der WINAPI besser gehen sollte.
Na, dann sitzen wir ja im selben Boot :)
BeRsErKeR hat geschrieben:wenn so Sachen wie Strg+Taste, Shift+Taste oder Doppelklicks ins Spiel kommen
Das ist eine der wenigen Sachen, wo ich zur WinAPI mit Nachrichtenverarbeitung keine Alternative sehe, so sehr ich das auch will. Was in diesem Aspekt unter der Haube der WinAPI abläuft – Zeitmessung, Ortsbestimmung für leichten Versatz beim Doppelklick usw. usf. – wünsche ich tatsächlich niemandem zum Selberimplementieren :(


Zumindest sehe ich schon, was die Standardlösung für mein Problem ist:
BeRsErKeR hat geschrieben: Ich nutze für Tastatur-Keys ein Array und das wird auch in vielen Tutorials so gehandhabt (spontan fallen mir z.B. die NeHE OpenGL Tutorials ein auch wenn diese vielleicht nicht gerade die besten sind; aber auch in diversen Büchern sieht man das oft in dieser Form).
CodingCat hat geschrieben:Ich habe mir letztes Mal einfach alle Tastenzustände in einen Byte-Puffer mit Tastencodeindizierung gehauen und per Windows-Nachrichten aktualisiert, wie oben beschrieben. Bei Fokusverlust habe ich dann den gesamten Puffer mit Null überschrieben.

@dot & CodingCat:
dot hat geschrieben:Ich seh das Problem generell darin, dass du versuchst zu pollen, anstatt auf Events zu reagieren. Polling ist imo keine besonders tolle Lösung wenn es um Input geht. Wie ein Prof von mir es einmal erklärt hat: Polling ist, wenn man jede Sekunde mal den Telefonhörer abnimmt, um zu schauen ob schon jemand angerufen hat...
CodingCat hat geschrieben:Wie soll das denn bei kontinuierlicher Tastensteuerung (z.B. Kamera) ohne Polling gehen, ohne den Spieler dabei mit der systemeigenen Tastenwiederholrate vorwärts zu ruckeln?!
Genau das wäre jetzt auch meine Frage gewesen.
dot hat geschrieben:Dazu brauch ich nicht alle Tastenzustände, sondern einfach nur eine Kamerageschwindigkeit, die von meinen Tasten gesteuert wird...
Du meinst, WM_KEYDOWN versetzt der Kamera Geschwindigkeit und WM_KEYUP nimmt sie ihr wieder? Auf den ersten Blick riecht das wirklich brilliant; erzähl mir bitte mehr – insbesondere, wie das mit dem Anwendungsfokus unter einen Hut zu bringen ist.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von dot »

Krishty hat geschrieben:Du meinst, WM_KEYDOWN versetzt der Kamera Geschwindigkeit und WM_KEYUP nimmt sie ihr wieder? Auf den ersten Blick riecht das wirklich brilliant; erzähl mir bitte mehr – insbesondere, wie das mit dem Anwendungsfokus unter einen Hut zu bringen ist.
Ja, genau das mein ich. Wenn du willst, dass die Kamera stehenbleibt, wenn die Anwendung den Fokus verliert, dann setz eben die Geschwindigkeit einfach im entsprechenden WM_ACTIVATE zurück auf 0!?

EDIT: bzw. eben WM_KILLFOCUS
Benutzeravatar
Krishty
Establishment
Beiträge: 8260
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von Krishty »

Okay; und was ist, wenn ich die Kamera schon durch andere Einflüsse bewege? Die Kamerabewegung ist so ja eine binäre Sache – entweder, sie bewegt sich, oder nicht. Bei jedem WM_KEYDOWN aufaddieren und bei WM_KEYUP abziehen kann ich nicht, weil das 10× pro Sekunde kommt und die Kamera dann immer schneller würde. Alternativ müsste man ein bool setzen, ob die Kamera Tastenwirkung unterliegt – und dann wären wir ziemlich nah am Array von Zustand pro Taste …
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von dot »

Schau mal, für was Bit 30 im lParam von WM_KEYDOWN ist ;)

Und: Rein semantisch sind wir, auch wenn es nach dem selben Prinzip funktioniert, sehr weit vom Arrayzustand pro Taste entfernt, wenn du mich fragst. Arrayzustand pro Taste würde bedeuten, dass der Code, der die Kamera bewegt, zumindest entfernt eine Ahnung haben muss, was überhaupt eine Taste is. Imo sollte er das nicht...
Zuletzt geändert von dot am 29.08.2011, 19:34, insgesamt 2-mal geändert.
Benutzeravatar
FlorianB82
Beiträge: 70
Registriert: 18.11.2010, 05:08
Wohnort: Darmstadt
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von FlorianB82 »

Krishty hat geschrieben:Okay, also ist DirectInput schonmal gestrichen
Besser ist das. Als ich letztens las, DI sei deprecated, wunderte ich mich, und habe nach den Ursachen gesucht. Und das ist sie.
Krishty hat geschrieben:Was meintest du mit „repeat“ bei Get[Async]KeyState()? Da kriegst du doch nur zurück, dass die Taste gedrückt ist und nicht, wie oft sie seit der letzten Abfrage gedrückt wurde?
Mein Fehler, sorry. Ich war verwirrt. Zuviel abenteurliches Zeug programmiert. Zwar bekommst du mit besagten Funktionen ja nicht den wirklichen Hardware-Keyboardstatus, sondern das, was Windows draus gemacht hat (also alles auf die virtuellen Tasten gemappt, und manche Tasten gar nicht). Aber der Key-Repeat hat an der Stelle nix zu suchen;).
Krishty hat geschrieben:Bei Leerlauf fühle ich mich stark doppelgebunden – einerseits ist er in Sachen wie dieser sehr nützlich; andererseits versucht Windows seit geraumer Zeit immer mehr, diesen Zustand von der Anwendung ins Betriebssystem zu ziehen – mit DXGI 1.1 wurde z.B. der Occluded-Zustand für 3D-Anwendungen abgeschafft (verdeckte Anwendungen bekommen automatisch weniger GPU-Leistung, wie auch CPU-Leistung, falls das System ausgelastet ist) … da weiß ich nicht, ob die nun wollen, dass ich zusätzlich von mir aus leer laufe, oder nicht …
Ah, interessant, das wusste ich noch nicht. Aber sagen wir mal so: Kaputt machst du damit nix, und im Zweifelsfall würde ich lieber einmal zu viel selbst leer laufen, als einmal zuwenig, und deswegen das System unnötig belasten.
Krishty hat geschrieben:Ich brauche Eingabe für eine Echtzeit-3D-Anwendung; d.h. keine Textverarbeitung und Takte statt Ereignissen.
Na wunderbar, dann ist doch alles klar. Ereignisse stehen dir hier meiner Meinung nach nur im Wege, und Polling ist da einfacher und vor allem natürlicher. Ich würde dann wirklich den Raw-Input nehmen: Da kommst du a) superschnell und b) an alle Tasten/Achsen/whatever ran. Ich habe das zwar nur überflogen, als ich das mit DI erfuhr, und hatte da interessehalber nach Alternativen geschaut. Die ganze API sieht ganz mächtig aus, allerdings ist die Initialisierung ein Graus.
Krishty hat geschrieben:Du meinst, WM_KEYDOWN versetzt der Kamera Geschwindigkeit und WM_KEYUP nimmt sie ihr wieder? Auf den ersten Blick riecht das wirklich brilliant;
Das finde ich nun überhaupt nicht. Naiv implementiert beschleunigt die Kamera dann je nach benutzerspezifisch eingestellter Key-Repeat-Rate schneller oder langsamer, und obendrein sind die Ruckler noch drin (Okeh, jetzt ist die Geschwindigkeit immerhin stetig, die Beschleunigung aber wohl kaum... kann sich also immer noch unnatürlich anfühlen). Schreibt man dann noch Logik, um die Sache von dieser Rate zu entkoppeln, dann hat man gleich wieder nen Haufen Code für etwas, was man mit Polling wesentlich einfacher hinbekommen hätte (KISS! sage ich da nur).
Benutzeravatar
FlorianB82
Beiträge: 70
Registriert: 18.11.2010, 05:08
Wohnort: Darmstadt
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von FlorianB82 »

Nachtrag: Ich wäre vorsichtig, wenn es um ein System geht, dass ich mithile von Beschleunigung kontrolliere. Erfahrungsgemäss liefert sowas sehr schnell recht unterschiedliche Ergebnisse, wenn sich nur ein bischen was an den Eingabedaten oder derem Zeitverhalten ändert. Und gerade in punkto Zeitverhalten vertraue ich den WM_KEYDOWN-Nachrichten nicht so ganz, wann und in welcher Reihenfolge sie kommen... aber nennt mich ruhig paranoid =)
kristof
Beiträge: 91
Registriert: 19.01.2009, 13:05

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von kristof »

Krishty hat geschrieben:Okay; und was ist, wenn ich die Kamera schon durch andere Einflüsse bewege? Die Kamerabewegung ist so ja eine binäre Sache – entweder, sie bewegt sich, oder nicht. Bei jedem WM_KEYDOWN aufaddieren und bei WM_KEYUP abziehen kann ich nicht, weil das 10× pro Sekunde kommt und die Kamera dann immer schneller würde. Alternativ müsste man ein bool setzen, ob die Kamera Tastenwirkung unterliegt – und dann wären wir ziemlich nah am Array von Zustand pro Taste …
Du kannst ja über die KeyDown bzw. KeyUp Events auch die Beschleunigung der Kamera steuern. Bei einem KeyDown Event setzt du die Beschleunigung auf a_0 und bei einem KeyUp Event auf -a_0. In jedem Frame musst du dann halt die Kamerageschwindigkeit aus allen Einflüssen die sonst noch so dazu kommen könnten berechnen. Oder was meinst du mit binärem Verhalten?
Benutzeravatar
Krishty
Establishment
Beiträge: 8260
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von Krishty »

Sooooo. Es hat jetzt fast zwei Jahre gedauert, aber ich bin wirklich mal dem Vorschlag nachgekommen, die Eingaben ereignisbasiert zu verarbeiten statt immer nur zu pollen. Und bin episch gescheitert.

Für alle, die nicht von Anfang an gelesen haben, zusammengefasst: Statt in jedem Frame zu prüfen, ob die W-Taste gedrückt ist, und mich dann fortzubewegen, addiere ich bei dem Ereignis, dass die W-Taste gedrückt wurde, v zur Geschwindigkeit und beim Ereigniss, dass W losgelassen wurde, subtrahiere ich v wieder.





Jetzt nahm die Katastrophe ihren Lauf, als ich Steuerung über die Pfeiltasten realisieren wollte. Wir haben also zwei Tasten für den selben Befehl: W und Pfeiltaste oben. Okay.

Drückt man jetzt W, wird v zur Laufgeschwindigkeit addiert. Fein fein. Jetzt ist man aber ein fieser Cheater und drückt gleichzeitig Pfeiltaste oben. Wieder wird v zur Geschwindigkeit addiert. Man kann jetzt doppelt so schnell rennen, indem man beide Tasten gleichzeitig drückt.

Jetzt kommt zuerst in den Sinn, v zu limitieren: Falls die Laufgeschwindigkeit zu groß wird, einfach auf erlaubtes Maximum zurücksetzen! Arschlecken. Denn dann lässt der Spieler W wieder los; v wird subtrahiert, und die Geschwindigkeit ist null. Aber er hat ja noch Pfeiltaste oben gedrückt! Lässt er die los, wird v erneut subtrahiert – und jetzt rennt der Charakter rückwärts, so lange man keine Taste drückt. Totaler Abschiss.

Also bauen wir einen Puffer dazwischen: Einen Zähler, wie oft vorwärts angefordert wurde. W drücken inkrementiert ihn; W loslassen ebenfalls. Selbe Chose für Pfeiltaste oben. Wenn der Zähler geändert wird, passen wir die Laufgeschwindigkeit an: Springt er von 0 auf 1, setzen wir die Laufgeschwindigkeit auf maximal nach vorne. Springt er von 1 auf 0, setzen wir die Laufgeschwindigkeit auf 0. Sonst machen wir nichts weil mehrere Tasten für denselben Befehl gesetzt wurden.

Besser. Aber nun kommen wir in die selbe Bredouille, falls ein anderes Fenster in den Vordergrund kommt, während der Spieler eine Taste drückt: Wir kriegen dann zwar einen Hinweis, dass die Taste gedrückt wurde; aber weil danach der Fokus fehlt, merken wir nicht, falls sie losgelassen wurde! In diesem Fall bleibt der Zähler immer positiv und der Spieler rennt weiter nach vorn. Quasi für immer.

Also setzen wir einfach den Zähler auf Null, falls wir den Fokus verlieren. Aber: Halt! Was ist, falls der Benutzer die Taste garnicht loslässt, sondern gedrückt hält, während er das Popup schließt und zu unserem Spiel zurückkehrt? Dann registrieren wir nur das Loslassen der Taste, aber der Zähler ist bereits null. Jetzt bewegt sich die Figur nur noch, falls man W und Pfeiltaste oben zugleich drückt, weil nur dann der Zähler wieder positiv wird.

Also (zum dritten Mal!) speichern wir beim Deaktivieren des Fensters die Eingabezustände aller Tasten. Bei Reaktivierung gucken wir dann, welche Tasten sich geändert haben, und senden nur dafür Nachrichten, damit alles wieder synchron ist.

Dann funktioniert es tatsächlich stabil. Aber guckt euch mal an, was wir jetzt für eine Komplexität aufgebaut haben: Zusätzlich zur Position haben wir die Laufgeschwindigkeit einführen müssen (die es beim Polling nicht gäbe); die Puffervariable (falls mehrere Tasten dieselbe Funktion haben können); haben den Zustand bei Deaktivierung und Aktivierung gespeichert und verglichen. Das sind locker 10× so viele Zeilen wie beim Polling. Dafür ist es aber zumindest nicht so böse wie Polling!!!111

Und dann bricht es euch das Genick, wenn ihr so Sachen wie Kamerasteuerung einbaut. Wenn man z.B. im Rennauto fährt und Q und E lassen euch nach links und rechts über die Schulter blicken; Loslassen lässt euch wieder nach vorne sehen. Wobei gleichzeitig die Maus beim Umgucken hilft. Denn dann drückt der Benutzer erst Q und die Kamera geht 90° nach links. Das ist noch nicht genug um das Auto etwas dahinter zu sehen; und dann stupst er die Maus nach links, um seinen Kopf nochmal 20° nach links zu drehen. Wenn er jetzt Q loslässt, geht die Kamera auf 20° zurück (Gameplay-Design-Frage, aber braucht man manchmal so). Ihr kriegt es mit Events nicht funktionierend implementiert. Es ist mit Polling schon knifflig; aber mit Events ist es unmöglich in einem akzeptablen Rahmen von Zeit und Quelltext implementierbar.





Ich wollte es bei meinem Projekt endlich mal richtig™ machen, und nun bin ich ein halbes Jahr hinter dem Plan weil ich monatelang nichts anderes gemacht habe als mir immer abstrusere und bizarrere Datenstrukturen auszudenken um die Eingaben ohne Polling zu realisieren. Nie wieder. Das Bescheuerteste: Bevor ich die Events eingebaut hatte, habe ich prototyp-mäßig mit Polling alles hingeklatscht und es funktionierte. Mit nur einem einzigen Tag Aufwand.

This story was brought to you by: Post mortems von Fehlschlägen sind genau so wichtig wie die von Erfolgen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
NytroX
Establishment
Beiträge: 365
Registriert: 03.10.2003, 12:47

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von NytroX »

Hi,

vielleicht habe ich was verpasst, aber ich sehe dein/ein Problem darin, dass du die Tasteneingabe nicht von der Steuerung getrennt hast.

Ich mache das bei mir so (achsoja: ich gehe mal von Windows und einem Spiel aus), dass ich eine Key-Layout-Klasse (KeyMap) habe, die Tastatureingaben in Steuerungsbefehle umsetzt.

Wenn ich ein Event bekomme, dass eine bestimme Taste gedrückt wird, dann heißt das für meine Spielfigur erstmal garnix, es sei denn die Taste ist einem Steuerungsbefehl zugeordnet.
Also z.B. der Steuerungsbefehl "RUN_FORWARD" kann durch die Tasten "W" und "Pfeil oben" ausgelöst werden. Im nächsten Schritt schaue ich dann auf die Liste der zurzeit aktiven/ausgelösten Steuerungsbefehle, um den Spieler zu bewegen. Das eigentliche Bewegen mache ich ja nicht immer, wenn der Spieler drückt, sondern das ist ja von der Spielmechanik abhängig.

Damit hättest du dir auf jeden Fall schonmal das Gefrickel mit dem addieren und subtrahieren der Geschwindigkeit komplett sparen können.
Zudem muss man nur die Tasten bei Fokusverlust/Re-Focus im Auge behalten, die auch einen KeyMap Eintrag haben, also auch wirklich im aktuellen Tastaturlayout für das Spiel verwendet werden.

Eine weitere Idee, die ich benutze: Man kann den Tasten unterschiedliche Arten von Befehlen geben.
Dann merkt man auch, dass z.B. "vorwärts laufen" eine andere Befehlsart ist als "benutzen"; das eine macht man NUR SOLANGE die Taste gedrückt ist, das andere macht man NUR EINMAL, egal wie lange die Taste gedrückt ist (das Steuerungs-Event wird nur neu ausgelöst, wenn man die Taste zwischendurch loslässt).
Dann es ist auch relativ leicht, z.B. ein RUN_LOCK oder sowas umzusetzen. Oder in einem Weltraum-Shooter einen Thrust/Throttle, d.h. ein Tastendruck ändert die gewünschte Geschwindigkeit, die tatsächliche Geschwindigkeit des Raumschiffs passt sich dann aber langsam an die gewünschte Geschwindigkeit an, uvm.
Ganz zu schweigen von normalen Texteingaben in eine GUI, die man evtl auch mal zwischendurch hat. Spätestens dann macht es auch Sinn, auf die Events zurückzugreifen und mit dem Polling aufzuhören, falls man es benutzt; ich will ja den Buchstaben haben, und am besten ohne V_KEY-Übersetzungs-Gedöhns. Und wenn dann die GUI den Focus bekommt (oder ein Menü aufpoppt o.ä.) ändere ich einfach meine KeyMap -> und fertig.

Und ein Focus-Verlust des Fensters löscht dann einfach alle aktiven Steuerungsbefehle (RUN_FORWARD, evtl. auch RUN_LOCK, aber das kann man diskutieren, der Spieler könnte dann auch bei inaktivem Fenster weiterlaufen).

Klar ist das Ganze mehr Aufwand, aber dann lässt sich auch das Key-Layout vom Benutzer selbst festlegen; ein tolles Feature, welches gerade bei Hobby-Spielen und/oder HTML/Flash-Games oft vernachlässigt wird.

PS: Halflife z.B. macht in der cfg fürs Tastenlayout was ganz ähnliches, z.B. gibts da ein "+" für ein kontinuierliches Event, also z.B. "+RUN" zum laufen, oder aber ein "SLOT1" (ohne "+"), um eine Waffe zu selektieren.

Und letztlich lässt sich das Konzept auch auf andere Eingabe-Geräte erweitern; dann ist es für das Spiel selbst egal, ob ein Gamepad oder die Tastatur zum Einsatz kommt. Oder ob es einen Netzwerk-Spieler gibt, dessen Befehle über garkeine (lokalen) Tasten/Buttons kommen.

Kommentare dazu natürlich jederzeit erwünscht :-)
Benutzeravatar
Krishty
Establishment
Beiträge: 8260
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von Krishty »

NytroX hat geschrieben:vielleicht habe ich was verpasst, aber ich sehe dein/ein Problem darin, dass du die Tasteneingabe nicht von der Steuerung getrennt hast.
[…]
Also z.B. der Steuerungsbefehl "RUN_FORWARD" kann durch die Tasten "W" und "Pfeil oben" ausgelöst werden. Im nächsten Schritt schaue ich dann auf die Liste der zurzeit aktiven/ausgelösten Steuerungsbefehle, um den Spieler zu bewegen.
Ich glaube, dass wir hier über so ziemlich den gleichen Algorithmus reden. Ich wollte nicht direkt in die vollen Details gehen damit mein Rant nicht noch länger wird, aber:

Ich gehe davon aus, dass du, falls der Befehl RUN_FORWARD sowohl über W als auch über Pfeil oben zugleich ausgelöst wird, du ihn auch doppelt in deine Liste einträgst. (Du musst ja sicherstellen, dass er auch erst dann wieder aufhört, wenn man beide Tasten loslässt.) Stimmt das so weit? Dann würde ich sagen, dass das und mein
Krishty hat geschrieben:Zähler, wie oft vorwärts angefordert wurde. W drücken inkrementiert ihn; W loslassen ebenfalls. Selbe Chose für Pfeiltaste oben. Wenn der Zähler geändert wird, passen wir die Laufgeschwindigkeit an: Springt er von 0 auf 1, setzen wir die Laufgeschwindigkeit auf maximal nach vorne. Springt er von 1 auf 0, setzen wir die Laufgeschwindigkeit auf 0. Sonst machen wir nichts weil mehrere Tasten für denselben Befehl gesetzt wurden.
Prinzipiell das gleiche sind – nur statt dass du einen Zähler auf zwei setzt und prüfst, ob er größer als Null ist, schreibst du deinen Befehl zwei Mal in die Liste und prüfst, ob er darin vorhanden ist.

Dass man auch nicht unbedingt sofort die Geschwindigkeit aufaddieren und wieder subtrahieren sollte, ist auch schnell offensichtlich (z.B. hängt die auch davon ab, ob man bergauf läuft; oder man will sie glätten damit man nicht abrupt stoppt; usw). Ich habe es ebenfalls der Einfachheit halber weggelassen.

Mutmaße ich so weit richtig?
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von BeRsErKeR »

Krishty hat geschrieben:Jetzt kommt zuerst in den Sinn, v zu limitieren: Falls die Laufgeschwindigkeit zu groß wird, einfach auf erlaubtes Maximum zurücksetzen! Arschlecken. Denn dann lässt der Spieler W wieder los; v wird subtrahiert, und die Geschwindigkeit ist null. Aber er hat ja noch Pfeiltaste oben gedrückt! Lässt er die los, wird v erneut subtrahiert – und jetzt rennt der Charakter rückwärts, so lange man keine Taste drückt. Totaler Abschiss.
Wieso manipulierst du direkt die Geschwindigkeit? Nimm doch einen Vektor/ein Skalar AdditionalWalkSpeed oder WalkSpeedByInput oder weiß der Geier. Das kannst du bei Loslassen von W auf das Minimum von 0 begrenzen. Beim Drücken auf das Maximum MaxSpeedByInput. Bei Taste S dann auf 0 maximieren und auf -MaxSpeedByInput minimieren.

Die tatsächliche Geschwindigkeit ist dann SpeedBySomeCausesButInput + WalkSpeedByInput. Man darf es natürlich auch gern schöner benennen. ;)

Theoretisch könnte man auch zwei Geschwindigkeitsvariablen für vorwärts und rückwärts verwenden. In dem Fall würde das gleichzeitige Drücken von Vorwärts- und Rückwärts-Taste (egal wie viele) zum Verhalten von "nicht bewegen" führen, es sei denn die "normale" Geschwindigkeit sagt was anderes (z.B. freier Fall, Beschleunigung durch andere Ursache).

Du kannst mMn so auch das Bewegungsverhalten besser steuern. Z.B. kannst du den zusätzlichen Vektor (oder Skalar) auf einem festen Wert lassen (z.B. 0.0, 1.0 oder -1.0, je nach Tasten-Aktionen) und dann je nach aktueller Bewegung diesen Wert noch weiter skalieren. Im Sumpf kann man nicht so schnell gehen, im freien Fall sich nicht so schnell nach vorn oder hinten bewegen, usw. Wenn du das gleich auf die Geschwindigkeit addierst, müsstest du ggf. die Skalierung auch schon mit einberechnen und dann viel Spaß beim wieder abziehen, vorallem wenn sich bereits die Gegebenheiten geändert haben (ausm Sumpf raus, Taste noch immer gedrückt). Wenn du Pech hast rennt dein Spieler dann langsam wie eine Schnecke nachdem er den Sumpf verlassen hat. ;) Ist nur unproblematisch wenn es keine Geschwindigkeitsveränderung aus anderen Quellen gibt.
Ohne Input kein Output.
Benutzeravatar
Krishty
Establishment
Beiträge: 8260
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von Krishty »

Ich wollte für die Diskussion ein möglichst einfaches Beispiel geben. Klar hängt die Fortbewegungsgeschwindigkeit auch von der Spielphysik ab – und darüber hinaus von der Kalibrierung des Eingabegeräts; von möglicher Eingabeglättung; von Kunststücken in der Spielmechanik; wandert vielleicht erst in einen Schrittsimulator; usw usf. Klar gibt es verschiedene Tastenarten; bei vielen muss man bedenken, dass man sowohl sie selber als auch ihr Gegenteil einzeln oder zusammen ansprechen kann (im Rennspiel seperate Pedale für Gas und Bremse, oder auf dem Gamepad eine einzelne Achse für beides zugleich). Aber das hier ist nicht konkret meine Implementierung. Wenn ich die beschreiben würde, wäre ich morgen noch nicht fertig, und niemand würde das alles lesen wollen ;-)

Der Punkt ist: Mit Polling sind solche Sachen noch recht einfach zu lösen (bei großen Anwendungen ist das aber schon schwer genug) – drei oder vier Bedingungen im if verknüpfen und fertig. Mit Events muss man einen riesigen Haufen Datenstrukturen und Algorithmen draufschmeißen, und findet dann doch immer wieder einen Fall, wo es kaputtgeht. Das ist mir in den letzten Monaten enorm auf die Nerven gegangen.

Wenn jemand sagt, „Aber dann musst du doch nur eine Variable anlegen, die, sobald …“, dann weiß ich schon, dass ich doppelt so viele Zeilen haben werde beim Polling, und endlos debuggen darf bis es funktioniert, weil jetzt nicht nur die gedrückten Tasten eine Rolle spielen, sondern auch, in welcher Reihenfolge man sie drückt und loslässt – was den Testaufwand direkt um ein paar Größenordnungen steigen lässt. Es gibt natürlich auch Vorteile; aber eines nach dem anderen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
NytroX
Establishment
Beiträge: 365
Registriert: 03.10.2003, 12:47

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von NytroX »

NytroX hat geschrieben:vielleicht habe ich was verpasst, aber [...]
Dann habe ich das wohl doch gedanklich nicht so komplett erfasst ;)
Krishty hat geschrieben:Dann würde ich sagen, dass das und mein [...] Prinzipiell das gleiche sind
Ääh, ja. Sehe ich jetzt auch :D

PS: Ich finds cool, dass du solche Sachen hier immer postest. Da kriegt man doch einiges mit und hat einen Einblick, mit was andere Leute sich so rumschlagen... find ich gut!

Zurück zum Thema:
Events ist glaube ich Grundsätzlich so eine Sache. Ich reagiere meistens auf Events sofort, indem ich sie ggf. irgendwie übersetze (in diesem Fall die Keys in Steuerungsbefehle), dann in eine Queue/Buffer/wasauchimmer packe, und dann möglichst schnell den "Event-Handler" beende. Und danach mache ich ja quasi doch eine Art polling auf die Queue/KeyMap/Buffer/wasauchimmer im Gameloop, wenn ich den Quatsch tatsächlich benutze (also z.B. beim "tick()" in der Spielmechanik/Physik). Am Ende hab ich dann wohl auch nicht viel gewonnen, was die Code-Größe/Komplexität angeht, da haste vermutlich schon recht.
Aber dass das jetzt so astronomisch mehr mögliche Fehlerquellen hat, ist zumindest mir persönlich bisher nicht so sehr aufgefallen; ...vielleicht, wenn ich da das nächste Mal drin abtauche :mrgreen:
Benutzeravatar
BeRsErKeR
Establishment
Beiträge: 689
Registriert: 27.04.2002, 22:01

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von BeRsErKeR »

Und wie wärs mit ner Mischung? Jeder Tastendruck/jedes Loslassen (Tastatur, Joystick, etc) erzeugt einfach ein NeedPoll-Event oder SomeKeyStateChanged-Event. Nur wenn das kommt, pollst du (von mir aus auch aus einem Zwischenbuffer, in dem nur die Tasten-Flags liegen, die in dem Fall nötig sind). Da kannste dann wieder deine if-Verzweigung reinbasteln und sparst dir dennoch das permanente Polling.

Edit: Da war wohl jemand schneller. :)
Ohne Input kein Output.
Helmut
Establishment
Beiträge: 237
Registriert: 11.07.2002, 15:49
Wohnort: Bonn
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von Helmut »

Irgendwie versteh ich den ganzen Thread nicht.
Polling ist natürlich im Prinzip nicht das edelste Design, aber wenn man in einer Gameloop jeden Frame einen Kern der CPU meist sowieso zu 100% auslastet werden die paar Taktzyklen zum Aufruf von GetKeyState auch nicht schaden. Falls die Framerate niedrig ist und der Benutzer die Taste nur kurz drückt kriegt man das dann vielleicht nicht mit, aber die resultierende Bewegung des Spielers von so einem kurzen Tastenschlag wäre auch vernachlässigbar. Ob man GetKeyState oder GetAsyncKeyState benutzt macht in einer Gameloop keinen Unterschied. Ich würde aber GetAsyncKeyState benutzen, da es etwas aktuellere Werte liefert. Und wenn man vor dem Aufruf prüft (was man auch unbedingt machen sollte), ob das aktuelle Fenster im Vordergrund ist, wird auch kein Virenscanner meckern. Falls es in dem Kontext schwierig ist das zu prüfen (weil man zB das Inputsystem kapselt und dieses nicht auf das Fensterhandle zugreifen kann), kann man auch mit GetForgroundWindow() == GetActiveWindow() prüfen, ob der aktuelle Prozess den Vordergrund hat.
Bleiben noch die Tasten, die man absichtlich nur kurz drückt. Z.B. die linke Maustaste zum Schießen. In dem Fall wäre Polling natürlich die falsche Wahl. Ich würde einfach in WM_LBUTTONDOWN eine entsprechende Methode aufrufen, die die entsprechende Spielmechanik umsetzt. Kein Grund irgendein kompliziertes Eventsystem aufzubauen. Falls man auf Exceptions steht und die Spielmechanik nicht in der Windowsprozedur ausführen möchte, kann man diese Nachrichten in einer queue zwischenspeichern.
Benutzeravatar
Krishty
Establishment
Beiträge: 8260
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von Krishty »

BeRsErKeR hat geschrieben:Und wie wärs mit ner Mischung? Jeder Tastendruck/jedes Loslassen (Tastatur, Joystick, etc) erzeugt einfach ein NeedPoll-Event oder SomeKeyStateChanged-Event. Nur wenn das kommt, pollst du (von mir aus auch aus einem Zwischenbuffer, in dem nur die Tasten-Flags liegen, die in dem Fall nötig sind). Da kannste dann wieder deine if-Verzweigung reinbasteln und sparst dir dennoch das permanente Polling.

Edit: Da war wohl jemand schneller. :)
Ja; darauf wird es wohl hinauslaufen. So eine Implementierung habe ich hier tatsächlich noch irgendwo rumliegen, und ich prüfe jetzt, ob sie dafür taugt.
Helmut hat geschrieben:Falls die Framerate niedrig ist und der Benutzer die Taste nur kurz drückt kriegt man das dann vielleicht nicht mit, aber die resultierende Bewegung des Spielers von so einem kurzen Tastenschlag wäre auch vernachlässigbar.
Absolut falsch! Die Feuertaste. Da steht der Gegner Klick Klick Klick aber kein Schuss kommt. Oder ich stehe an einer Hauskante und will kurz rausspähen, ob da ein Gegner auf mich wartet – werden dann meine feinen Eingaben verschluckt, frustriert das. Wenn die Aktualisierungsrate mal kurz unter 25 fps rutscht, wird außerdem noch viel mehr verschluckt. Ich hab’s schonmal ausprobiert. Nicht zu tolerieren.
Helmut hat geschrieben:Ob man GetKeyState oder GetAsyncKeyState benutzt macht in einer Gameloop keinen Unterschied. Ich würde aber GetAsyncKeyState benutzen, da es etwas aktuellere Werte liefert.
Synchrone und asynchrone Eingaben mischen ist ganz ganz ganz schlecht. Für Maus und Tastatur gibt es asynchrone Alternativen: GetAsyncKeyState() und GetCursorPos() statt GetKeyState() und GetMessagePos(); ja. Für die Maus will ich die beiden aber garnicht benutzen, weil die Maus dann auf Pixelgenauigkeit limitiert und nichtlinear ist. Meine ganzen HID-Eingaben kommen synchron durch die HID API, und das würde bedeuten, dass die Reihenfolge der Eingaben am Ende völlig durcheinander ist. In einer vernünftigen Hauptschleife sollte die Latenz der synchronen Eingaben nicht allzu groß sein; da ist mir die Integrität der Eingaben wichtiger.
Helmut hat geschrieben:Bleiben noch die Tasten, die man absichtlich nur kurz drückt. Z.B. die linke Maustaste zum Schießen. In dem Fall wäre Polling natürlich die falsche Wahl. Ich würde einfach in WM_LBUTTONDOWN eine entsprechende Methode aufrufen, die die entsprechende Spielmechanik umsetzt. Kein Grund irgendein kompliziertes Eventsystem aufzubauen.
Siehe oben: Jetzt schießt deine Waffe daneben, weil der Zeiger zum Zeitpunkt von WM_LBUTTONDOWN woanders war, als das asynchrone Modell dir sagt.

————

Ein Gedanke, der mir noch gekommen ist: Ich habe hier viele viele Eingaben; um die 200 belegten Tasten. Viele davon sind Einmal-Eingaben (wie die Aktionstaste) und erfordern eine gedrückte SHIFT-, ALT-, oder STRG-Taste. Ich überlege, ob ich für solche Einmalaktionen nicht einen Accelerator Table anlege – dann muss ich mich um die garnicht kümmern, sondern bekomme, falls der Benutzer diese Tastenkombination drückt, nach TranslateAccelerator() ein entsprechendes WM_COMMAND …
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Helmut
Establishment
Beiträge: 237
Registriert: 11.07.2002, 15:49
Wohnort: Bonn
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von Helmut »

Krishty hat geschrieben:
Helmut hat geschrieben:Falls die Framerate niedrig ist und der Benutzer die Taste nur kurz drückt kriegt man das dann vielleicht nicht mit, aber die resultierende Bewegung des Spielers von so einem kurzen Tastenschlag wäre auch vernachlässigbar.
Absolut falsch! Die Feuertaste. Da steht der Gegner Klick Klick Klick aber kein Schuss kommt. Oder ich stehe an einer Hauskante und will kurz rausspähen, ob da ein Gegner auf mich wartet – werden dann meine feinen Eingaben verschluckt, frustriert das. Wenn die Aktualisierungsrate mal kurz unter 25 fps rutscht, wird außerdem noch viel mehr verschluckt. Ich hab’s schonmal ausprobiert. Nicht zu tolerieren.
Für die Feuertaste habe ich ja geschrieben, dass man auf WM_LBUTTONDOWN zurückgreifen sollte. Dass man das merken soll, dass das Spiel feine Eingaben für Spielerbewegungen verschluckt glaub ich dir ehrlich gesagt nicht.
Aber wenn ich so ein Problem lösen müsste würde ich es etwa so machen: Erstmal sollte die Spielmechanik in festen Zeitintervallen aktualisiert werden (sagen wir 100/s). Im while(PeekMessage()) Teil der Gameloop wird nun jede Inputnachricht in eine Queue gepackt. Und schließlich wird, je nachdem wie viel Zeit seit dem letzten Frame vergangen ist, die Spielwelt entsprechend oft weiterbewegt. Für jeden Schritt wird anhand der Nachrichten und der Zeitstempel berechnet, welche Tasten zu dem Zeitpunkt gedrückt waren. Das setzt natürlich eine gewisse Genauigkeit der Zeitstempel voraus, das müsste man mal testen, ob die ausreicht. Alternativ kann man auch in nem separaten Thread die Tasteneingaben pollen.
Aber es würde mich wundern wenn's auch nur ein Spiel da draußen so genau nehmen würde.
Krishty hat geschrieben:
Helmut hat geschrieben:Ob man GetKeyState oder GetAsyncKeyState benutzt macht in einer Gameloop keinen Unterschied. Ich würde aber GetAsyncKeyState benutzen, da es etwas aktuellere Werte liefert.
Synchrone und asynchrone Eingaben mischen ist ganz ganz ganz schlecht.
Diese Problematik ist mir bei normalen Anwendungen durchaus bekannt. Bei Spielen ist der Unterschied aber zu vernachlässigen.
Helmut hat geschrieben:Bleiben noch die Tasten, die man absichtlich nur kurz drückt. Z.B. die linke Maustaste zum Schießen. In dem Fall wäre Polling natürlich die falsche Wahl. Ich würde einfach in WM_LBUTTONDOWN eine entsprechende Methode aufrufen, die die entsprechende Spielmechanik umsetzt. Kein Grund irgendein kompliziertes Eventsystem aufzubauen.
Siehe oben: Jetzt schießt deine Waffe daneben, weil der Zeiger zum Zeitpunkt von WM_LBUTTONDOWN woanders war, als das asynchrone Modell dir sagt.
Das sind maximal Unterschiede von Mausbewegungen, die der Spieler innerhalb eines Frames ausführt. Zu vernachlässigen. Wenn die Framerate so niedrig ist, dass man es merkt, wird der Spieler die Framerate und nicht die Steuerung kritisieren. Die Latenz von Eingabe und fertigem Bild auf dem Monitor und das Lag im Netzwerk sind eher Probleme denen sich Programmierern widmen sollten. Aber egal, das System von oben kann man auch bei Mausbewegungen anwenden.
Benutzeravatar
Krishty
Establishment
Beiträge: 8260
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: [WinAPI] Prüfen, ob eine Taste gedrückt ist

Beitrag von Krishty »

Helmut hat geschrieben:Dass man das merken soll, dass das Spiel feine Eingaben für Spielerbewegungen verschluckt glaub ich dir ehrlich gesagt nicht.
Doch – jedes Mal, wenn es um Feinpositionierung geht. Absolut unbrauchbar.
Helmut hat geschrieben:Aber wenn ich so ein Problem lösen müsste würde ich es etwa so machen: Erstmal sollte die Spielmechanik in festen Zeitintervallen aktualisiert werden (sagen wir 100/s). Im while(PeekMessage()) Teil der Gameloop wird nun jede Inputnachricht in eine Queue gepackt. Und schließlich wird, je nachdem wie viel Zeit seit dem letzten Frame vergangen ist, die Spielwelt entsprechend oft weiterbewegt. Für jeden Schritt wird anhand der Nachrichten und der Zeitstempel berechnet, welche Tasten zu dem Zeitpunkt gedrückt waren. Das setzt natürlich eine gewisse Genauigkeit der Zeitstempel voraus, das müsste man mal testen, ob die ausreicht. Alternativ kann man auch in nem separaten Thread die Tasteneingaben pollen.
Dafür braucht man Hochleistungszeitstempel (Windows’ Standard-Zeitstempel haben nur 10–16 ms Auflösung und können damit oft nicht einmal Druck und Entspannung eines Klicks auseinanderhalten). Hochleistungszeitstempel bringen nur was, wenn man die Nachricht sofort verarbeiten kann; was bedeutet, dass das Eingabefenster einen eigenen Thread haben muss, der ständig bereit ist. Man setzt also Multithreading voraus.

Die Simulationsgeschwindigkeit so stark rotieren lassen, dass sie jedes Zucken mitnimmt, ist durchaus eine Lösung (die meisten Simulatoren machen es so). Das setztaber unbedingt Multithreading mit eigenem Eingabefenster voraus, um an die nötigen Zeitstempel zu kommen.
Helmut hat geschrieben:Diese Problematik ist mir bei normalen Anwendungen durchaus bekannt. Bei Spielen ist der Unterschied aber zu vernachlässigen.
Das sind maximal Unterschiede von Mausbewegungen, die der Spieler innerhalb eines Frames ausführt. Zu vernachlässigen.
Wenn es sowieso vernachlässigbar wäre, könntest du doch gleich synchron bleiben und alles in der richtigen temporalen Reihenfolge verarbeiten, oder?
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Antworten