Hardware Delphi

Title: How to use the keyboard as input for games
Question: How to implement smooth working game control using the keyboard?
Answer:
Using the following code to let the player move in a game works. However, it doesn't really work the way we want. The problem is that when the player, for example, holds down the left arrow key and then fires, then the left movement will stop because another key than left is sent to the FormKeyDown() procedure. This will result in a nearly unplayable game in most cases.
procedure TForm1.FormKeyDown(Sender: TObject;
var Key: Word; Shift: TShiftState);
begin
case Key of
vk_left : Dec(x);
vk_right : Inc(x);
vk_up : Dec(y);
vk_down : Inc(x);
vk_space : BulletFired; // call to another procedure
end;
end;
What we need is to detect key presses without interrupting already present keys which are down. The solution for this is to keep a state of each key. This is done with the following global variables.
var
movingUp : boolean;
movingDown : boolean;
movingRight : boolean;
movingLeft : boolean;
Now, instead of triggering the result directly (i.e. increasing/decreasing x/y position) we use the FormKeyDown() and FormKeyUp() procedures to toggle the state of each key. In this way, multiple keys may be down at once (for example the user may move up and right (diagonally) the same time as he presses fire).
FormKeyDown sets respectively state to True. Because someone can't move both up AND down at the same time the "opposite" key has to be set to False.
procedure TForm1.FormKeyDown(Sender: TObject;
var Key: Word; Shift: TShiftState);
begin
case Key of
vk_left:
begin
movingLeft := True;
movingRight := False;
end;
vk_right:
begin
movingLeft := False;
movingRight := True;
end;
vk_up:
begin
movingUp := True;
movingDown := False;
end;
vk_down:
begin
movingUp := False;
movingDown := True;
end;
vk_space: BulletFired;
end;
end;
When a key is released the state is set to False in the FormKeyUp procedure.
procedure TForm1.FormKeyUp(Sender: TObject;
var Key: Word; Shift: TShiftState);
begin
case Key of
vk_left : movingLeft := False;
vk_right : movingRight := False;
vk_up : movingUp := False;
vk_down : movingDown := False;
end;
end;
The actual moving of the player is then performed from the game loop (Application.OnIdle for example) depending on each state. This is also the place to check to see if the player doesn't move out of bounds or collide into anything else.
procedure TForm1.MovePlayer;
if movingUp then Dec(y);
if movingDown then Inc(y);
if movingLeft then Dec(x);
if movingRight then Inc(x);
end;
This article was an example of how it may be done. I've tried to make it as general as possible so it shouldn't be a problem to apply this to a project, no matter if it's a jump'n run, racing or a shoot'em up game.
(more than one year later...)
As commented below, there is also another approach of doing this by using GetKeyState. The reason for the above, quite lenghty, solution is basically that I didn't know of GetKeyState when I wrote it more than one year ago. Therefore I do this little rewrite and add the GetKeyState solution.
Simply get rid of all the code above, and just replace the MovePlayer procedure with this code:
procedure TMainForm.MovePlayer;
begin
if (GetKeyState(VK_UP) and $80) = $80 then
Dec(y);
if (GetKeyState(VK_DOWN) and $80) = $80 then
Inc(y);
if (GetKeyState(VK_LEFT) and $80) = $80 then
Dec(x);
if (GetKeyState(VK_RIGHT) and $80) = $80 then
Inc(x);
end;