Entwickler-Ecke

Windows API - Wie Tastaturverzögerung ausschalten?


galagher - Mo 07.05.18 19:52
Titel: Wie Tastaturverzögerung ausschalten?
Hallo!

Ich möchte in meinem Programm - und nur dort! - die Tastaturverzögerung ausschalten! Wie geht das denn?

lg
galagher


Moderiert von user profile iconNarses: Topic aus Delphi Language (Object-Pascal) / CLX verschoben am Mo 07.05.2018 um 22:07


Narses - Mo 07.05.18 22:09

Moin!

Womit dein Programm, unterstellt, dass man Windows überhaupt sowas begebogen kriegt, faktisch unbenutzbar wird. :shock: Welchen Sinn sollte das haben? :gruebel: :nixweiss:
Suchst du nicht eher sowas? Suche im MSDN GETASYNCKEYSTATE :lupe: :think:

cu
Narses


Delete - Di 08.05.18 00:37

- Nachträglich durch die Entwickler-Ecke gelöscht -


galagher - Di 08.05.18 08:14

user profile iconNarses hat folgendes geschrieben Zum zitierten Posting springen:
Welchen Sinn sollte das haben? :gruebel: :nixweiss:
Für ein Spiel, das man auf die altmodische Art per Pfeiltasten steuert, da wäre es nützlich, keine Verzögerung der Tasten zu haben!

user profile iconNarses hat folgendes geschrieben Zum zitierten Posting springen:
Suchst du nicht eher sowas? Suche im MSDN GETASYNCKEYSTATE :lupe: :think:
Wenn da etwas dabei ist, das das kann, ja!


user profile iconFrühlingsrolle hat folgendes geschrieben Zum zitierten Posting springen:
eine solche Einstellung gilt systemweit und kann daher auch nicht für die Anwendung allein umgestellt werden.
Das ist eher schlecht, denn der unschöne Umweg wäre dann, bei Programmstart den aktuellen Wert zu sichern, dann auf 0 zusetzen und bei Programmende den gesicherten Wert wieder zurück zu setzen, bzw. ist das ja jedesmal notwendig, wenn das Programm den Fokus verliert.


OlafSt - Di 08.05.18 08:42

Tut auch überhaupt nicht Not, sowas zu machen.

Per AsyncKeyState kann man feststellen, ob eine Taste gedrückt ist. Nun kann man seine Spielfigur so lang ein die passende Richtung bewegen, bis man merkt, das die Taste eben nicht mehr gedrückt ist.

Geht auch einigermaßen per OnKeyDown, OnKeyUp.


Delete - Di 08.05.18 16:34

- Nachträglich durch die Entwickler-Ecke gelöscht -


Symbroson - Di 08.05.18 17:45

Also bisher hat bei mir TForm1.FormKeyDown bzw TForm1.FormKeyUp immer ausgereicht bei dem, was ich an Delphi an Spielchen gemacht habe.
Da man in Delphi sowiso nur langsamere Spiele hinbekommt, sollte die Tastatur im Endeffekt nicht dein größtes Pronblem sein.

solltest du wirklich vorhaben ein schnelles Spiel zu entwickeln empfehle ich dir entweder eine Spielengine wie Unity oder Unreal etc. oder C++ oder Java und OpenGL oä. Muss ja nichtmal 3D sein...
Ist nur für den Anfang mit sehr großen Kanonen geschossen ;)


galagher - Di 08.05.18 18:43

user profile iconFrühlingsrolle hat folgendes geschrieben Zum zitierten Posting springen:
GetAsyncKeyState() hat seine Berechtigung. Es reagiert "schneller" auf Tasteneingaben.

Ich finde die Lösung mit Msg.CharCode am Besten, da läuft alles flüssiger:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
protected
  procedure Steuerung(var Msg: TWMKEYDOWN); message WM_KEYDOWN;
end;

//..

procedure TForm1.Steuerung(var Msg: TWMKEYDOWN);
begin
  if (Msg.CharCode = VK_LEFT) or (Msg.CharCode = VK_RIGHT) or //..
end;

Das behebt aber leider nicht die Verzögerung der Pfeiltasten, GetAsyncKeyState() übrigens auch nicht!


Delete - Di 08.05.18 19:00

- Nachträglich durch die Entwickler-Ecke gelöscht -


Sinspin - Di 08.05.18 19:05

Warum soll es mit GetAsyncKeyState() Verzögerungen geben?
Ich vermute, das hängt nur am Intervall der Abfrage und wie Du die gewonnen Informationen verwendest um eine Reaktion zu visualisieren.

user profile iconSymbroson hat folgendes geschrieben Zum zitierten Posting springen:
Da man in Delphi sowiso nur langsamere Spiele hinbekommt, sollte die Tastatur im Endeffekt nicht dein größtes Pronblem sein.

Das hier unter Delphi zu posten halte ich schon ein bisschen für Blasphemie. Versuch es doch erstmal!


galagher - Di 08.05.18 19:27

user profile iconFrühlingsrolle hat folgendes geschrieben Zum zitierten Posting springen:
Schwer zu sagen, was du mit der Abfrage bezwecken möchtest?![/delphi]

Naja, die Spielfigur bewegen! Funktioniert auch sehr gut, nur nach dem Drücken einer Pfeiltaste passiert, was eben normalerweise passiert, wenn man eine Taste drückt: Die Spielfigur bewegt sich in die gewünschte Richtung (die Taste "wirkt sich also aus"), dann folgt eine kurze Pause, dann geht's weiter. Die Tastaturverzögerung eben!

user profile iconFrühlingsrolle hat folgendes geschrieben Zum zitierten Posting springen:
Das ginge noch etwas kürzer: if Msg.CharCode in [VK_LEFT, VK_RIGHT, ...] then

Das ist natürlich noch eleganter!


Symbroson - Di 08.05.18 20:56

Nun, in dem Fall hast du einen einfach zu behebenden "Denkfehler". Denn du verwendest die Funktion der Tastatur, dass das OnKey Event bei langem Drücken oft ausgelöst wird. In der Spieleentwicklung macht man das aber anders:
Wenn eine Taste gedrückt wird, setzt man eine entsprechende Flag, die markiert, dass die Taste gedrückt wurde. Die verwendest du dann auch für deine Tastenabfrage. Und wenn schließlich das OnKeyUp Event ausgelöst wurde entfernst du die Flag wieder. Die Flag kann ein einfacher Boolean oder ein Integer (im Binärsystem) sein.


Delete - Di 08.05.18 23:31

- Nachträglich durch die Entwickler-Ecke gelöscht -


Symbroson - Di 08.05.18 23:41

@Frühlingsrolle Beispiel: Öffne den Text-Editor, halte einen beliebigen Buchstaben gedrückt und schau, was passiert. Genau das gleiche Verhalten zeigt sich in seinem Spiel. Das meint er


t.roller - Di 08.05.18 23:52


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Label1: TLabel;
    Button1: TButton;
    procedure Button1KeyPress(Sender: TObject; var Key: Char);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;
  i : Integer;

implementation

{$R *.dfm}

//beliebige Taste drücken
procedure TForm1.Button1KeyPress(Sender: TObject; var Key: Char);
begin
// hier kann man die Taste auswerten
// z.B. if Key=...
  if True then inc(i);
  Label1.Caption:= INTTOSTR(i);
end;

end.


Eine Verzögerung kann ich bei diesem Beispiel nicht feststellen.


Delete - Mi 09.05.18 00:00

- Nachträglich durch die Entwickler-Ecke gelöscht -


t.roller - Mi 09.05.18 01:12

Mein Beispiel läuft in 4 sec 100x durch die Routine.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Label1: TLabel;
    Label2: TLabel;
    Button2: TButton;
    Label3: TLabel;
    Button1: TButton;
    procedure Button2Click(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure Button1KeyPress(Sender: TObject; var Key: Char);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;
  i : Integer;

implementation

{$R *.dfm}

procedure TForm1.Button1KeyPress(Sender: TObject; var Key: Char);
begin
//beliebige Taste drücken z.B. SPACE
// hier kann man die Taste auswerten
// z.B. if Key=...
  if i=0 then Label3.Caption:= TimeToStr(Now);

  if True then inc(i);
  Label1.Caption:= INTTOSTR(i);
  Label2.Caption:= TimeToStr(Now);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  i:=0;
  Label1.Caption:= '0';
  Label2.Caption:= '0';
  Label3.Caption:= '0';
  Button1.SetFocus;
end;

procedure TForm1.FormActivate(Sender: TObject);
begin
  Button1.SetFocus;
  Label1.Caption:= '0';
  Label2.Caption:= '0';
  Label3.Caption:= '0';
end;

end.


Symbroson - Mi 09.05.18 07:18

user profile iconFrühlingsrolle hat folgendes geschrieben Zum zitierten Posting springen:
Wenn es das ist, was Symbroson meinte, dann könnte man auf den TTimer ausweichen und im OnTimer Ereignis jene Tasten mit GetAsyncKeyState() abfragen.
Was ist das kleinstmögliche Intervall dafür? 50ms?

Wie gesagt, Flags zu nutzen finde ich ist die beste Variante. Wie ein Lichtschalter.


Sinspin - Mi 09.05.18 09:52

Du kannst bei einem "echten Spiel" ja nicht rein mit den Ereignissen arbeiten wie ein normales Windows Programm. Das reicht nicht um einen gleichmäßigen Spielfluss hinzubekommen.
Du braucht einen Renderer für die Anzeige, am besten in einem eigenen Thread. So bekommst du es auch hin das sich alles gleichmäßig bewegt.
Du brauchst einen weiteren Thread der sich Tastatur und Maus zur Brust nimmt und alles, am besten als Flags bereitstellt.
Und natürlich einen weiteren Thread in dem die ganzen Positionsinformationen in deiner Spielewelt zusammengestellt werden, die dann der Renderer in Bilder umsetzt.

Anstatt mit Threads kann man am Anfang auch mit Timern arbeiten. Aber die können nicht parallel ausgeführt werden, sind also nicht für rechenintensive Aufgaben geeignet.
Was die Anzeige / das Rendern angeht, das ist immer einen Kanonen und Spatzen Frage. OpenGL, Direct2D. Beide können für 2D Ausgabe verwendet werden. Das hinten dran hängt vom Anwendungsfall ab.


galagher - Mi 09.05.18 10:58

user profile iconSymbroson hat folgendes geschrieben Zum zitierten Posting springen:
In der Spieleentwicklung macht man das aber anders:
Wenn eine Taste gedrückt wird, setzt man eine entsprechende Flag, die markiert, dass die Taste gedrückt wurde. Die verwendest du dann auch für deine Tastenabfrage. Und wenn schließlich das OnKeyUp Event ausgelöst wurde entfernst du die Flag wieder. Die Flag kann ein einfacher Boolean oder ein Integer (im Binärsystem) sein.

Klingt gut, werde versuchen, das umuzsetzen!

user profile iconSymbroson hat folgendes geschrieben Zum zitierten Posting springen:
@Frühlingsrolle Beispiel: Öffne den Text-Editor, halte einen beliebigen Buchstaben gedrückt und schau, was passiert. Genau das gleiche Verhalten zeigt sich in seinem Spiel. Das meint er

Genau!


Delete - Mi 09.05.18 14:12

- Nachträglich durch die Entwickler-Ecke gelöscht -


Symbroson - Mi 09.05.18 14:43

Hier mal mein Beispiel mit Integer(Bit)-Flags:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
unit Unit1;

interface

uses
  Windows, SysUtils, Classes, Graphics, Controls, Forms, StdCtrls;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure FormPaint(Sender: TObject);
  end;

const 
    KLef: integer = 1 shl 0;  
    KRig: integer = 1 shl 1;
    KFwd: integer = 1 shl 2;
    KBwd: integer = 1 shl 3;

var
  Form1: TForm1;
  keys: Byte;
  running: boolean;

implementation

{$R *.dfm} 

procedure TForm1.FormCreate(Sender: TObject);
begin keys := 0; running := false;
end;

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); 
begin
    case Key of
        37: keys := keys or KLef;
        38: keys := keys or KFwd;
        39: keys := keys or KRig;
        40: keys := keys or KBwd;
    end;
end;

procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin 
    case Key of
        37: keys := keys and not KLef;
        38: keys := keys and not KFwd;
        39: keys := keys and not KRig;
        40: keys := keys and not KBwd;
    end;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin running := false;
end;

procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
    if running then begin
        running := false;
        Canvas.TextOut(00'Click to Start                                 ');
    end else begin
        running := true;
        Canvas.TextOut(00'Click to Stop                                  ');

        while running do begin
            if keys and KLef > 0 then Left := Left - 1
            if keys and KRig > 0 then Left := Left + 1;
            if keys and KFwd > 0 then Top  := Top  - 1;
            if keys and KBwd > 0 then Top  := Top  + 1;
        
            Application.ProcessMessages;
        end;
    end;    
end;

procedure TForm1.FormPaint(Sender: TObject);
begin Canvas.TextOut(00'Click to toggle key listener                     ');
end;

end.


galagher - Do 10.05.18 19:49

user profile iconSymbroson hat folgendes geschrieben Zum zitierten Posting springen:
Hier mal mein Beispiel mit Integer(Bit)-Flags:

Das ist aber wieder mit KeyDown/KeyUp! Da habe ich wieder die Verzögerung!

user profile iconSinspin hat folgendes geschrieben Zum zitierten Posting springen:
Du kannst bei einem "echten Spiel" ja nicht rein mit den Ereignissen arbeiten wie ein normales Windows Programm. Das reicht nicht um einen gleichmäßigen Spielfluss hinzubekommen.

Für meine Zwecke reichen Ereignisse und Timer völlig!

user profile iconSymbroson hat folgendes geschrieben Zum zitierten Posting springen:
In der Spieleentwicklung macht man das aber anders:
Wenn eine Taste gedrückt wird, setzt man eine entsprechende Flag, die markiert, dass die Taste gedrückt wurde. Die verwendest du dann auch für deine Tastenabfrage. Und wenn schließlich das OnKeyUp Event ausgelöst wurde entfernst du die Flag wieder.

Wo setze ich die Flag und wo verarbeite ich sie für die Tastenabfrage? Ich muss ja erst einmal dem Programm mitteilen, dass eine der Pfeiltasten gedrückt wurde. Da kenne ich nur KeyDown, dort habe ich aber in jedem Fall eine Verzögerung.


Symbroson - Do 10.05.18 20:00

Hast du den Code schon ausprobiert? Ich kann keine Verzögerung feststellen. Das KeyDown Event sollte keine Verzögerung verursachen, da es ja sofort ausgelöst wird, sobald die Taste gedrückt wurde.


galagher - Do 10.05.18 21:15

user profile iconSymbroson hat folgendes geschrieben Zum zitierten Posting springen:
Das KeyDown Event sollte keine Verzögerung verursachen, da es ja sofort ausgelöst wird, sobald die Taste gedrückt wurde.
Nein, ausprobiert nicht, aber was ich meine, ist: Halte mal in irgendeiner Textverarbeitung eine Taste gedrückt, meinetwegen "a". Da kommt dann:
a
Nun kommt eine Pause
dann kommt:
aaaaaaaaaaaaaaaaaaa
Das meine ich!

//Edit:
Ok, ich habe ihn in einer zusätzlichen Form zu meinem Projekt getestet, aber da tut sich gar nichts, einzig Mausklicks bewirken eine Änderung des dargestellten Textes. Also habe ich ein TEdit eingefügt, und im OI KeyPreview := True gesetzt. Deinen Code habe ich wie folgt abgeändert:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
    case Key of
        37:
         begin
           keys := keys or KLef;
           Edit1.Text := Edit1.text+'a';
         end;
//...

Und es passiert, was da immer passiert (ausser, man hat keine Tastaturverzögerung eingestellt): Es folgt ein "a", dann die Pause, dann "aaaaaa". Und die Pause will ich nicht!


Symbroson - Do 10.05.18 21:17

Wie gesagt, mit Flags sollte das nicht passieren. Probiers einfach aus!


galagher - Do 10.05.18 21:31

user profile iconSymbroson hat folgendes geschrieben Zum zitierten Posting springen:
Wie gesagt, mit Flags sollte das nicht passieren. Probiers einfach aus!
Das mache ich die ganze Zeit! Wo setze ich die Flags denn? Denn immer, wenn ich eine Taste drücke, "gibt Windows an mein Programm die eingestellte Verzögerung weiter" - das soll ja normalerweise auch so sein!

Also, wo setze ich ein Flag?


Symbroson - Do 10.05.18 21:35

Was hast du denn bisher probiert? Kannst du mal deinen cide schicken?

Flags kannst du als einfache Booleans ausdrücken oder wie in meinem Beispiel als Bits von einem Integer-Typ. Da du nur 4 Tasten hast, reicht ein Byte (bis maximal 8 Tasten) aus


galagher - Do 10.05.18 21:44

user profile iconSymbroson hat folgendes geschrieben Zum zitierten Posting springen:
Was hast du denn bisher probiert? Kannst du mal deinen cide schicken?
Den habe ich wieder gelöscht. Wo ausser in KeyDown setze ich ein Boolean auf True, das angibt, dass eine Pfeiltaste gedrückt wurde? Wenn ich das erstmal weiss, kann ich die Prozedur aufrufen, die die Spielfigur steuert.

user profile iconSymbroson hat folgendes geschrieben Zum zitierten Posting springen:
Flags kannst du als einfache Booleans ausdrücken oder wie in meinem Beispiel als Bits von einem Integer-Typ. Da du nur 4 Tasten hast, reicht ein Byte (bis maximal 8 Tasten) aus
Das weiss ich, das ist aber nicht das Problem!

Vielleicht reden wir aneinander vorbei:
Ich möchte, dass mein Programm einen Pfeiltastendruck (die Pfeiltasten werden meistens gedrückt gehalten) verarbeitet, ohne dass dabei die übliche Verzögerung auftritt.


Symbroson - Do 10.05.18 21:48

dein eigenes Programm muss dann irgendwo anders (entweder mit Application.ProcessMessages oder besser als separaten Thread) eigenständig laufen und die Flags abfragen, um etwas zu bewegen. Nur per Event das Spiel zu aktualisieren wird nicht funktionieren, wenn sich zB. etwas von allein bewegen soll.


galagher - Do 10.05.18 22:38

user profile iconSymbroson hat folgendes geschrieben Zum zitierten Posting springen:
dein eigenes Programm muss dann irgendwo anders (entweder mit Application.ProcessMessages oder besser als separaten Thread) eigenständig laufen und die Flags abfragen, um etwas zu bewegen.

Ja, sieht so aus. Mit Threads habe ich mich noch nicht wirklich beschäftigt, vor alem fürchte ich, dass damit dann irgendwelche Probleme im Programm auftauchen.

user profile iconSymbroson hat folgendes geschrieben Zum zitierten Posting springen:
Nur per Event das Spiel zu aktualisieren wird nicht funktionieren, wenn sich zB. etwas von allein bewegen soll.

Mit Timern klappt das hervorragend!


Symbroson - Do 10.05.18 22:52

Dann nimm Timer, wenn es dir gefällt


Delete - Do 10.05.18 23:15

- Nachträglich durch die Entwickler-Ecke gelöscht -


galagher - Mo 14.05.18 12:01

user profile iconFrühlingsrolle hat folgendes geschrieben Zum zitierten Posting springen:
Wie schon mehrmals erwähnt und zitiert, mit dem Timer bekommst du es gelöst.

Ja, aber der Timer stoppt die Spielfigur nicht exakt dann, wenn ich die Taste loslasse. Kann man aber sicher optimieren!


Delete - Di 15.05.18 00:32

- Nachträglich durch die Entwickler-Ecke gelöscht -