Log in

View Full Version : Making an input devices manager



Brainer
02-05-2008, 03:05 PM
Hello. :)

I'd like to write an input devices manager, but I have absolutely no idea, how it should look like. Can you give me any tips or maybe source codes? I would like not to use DirectInput.

Thank you in advance.

chronozphere
02-05-2008, 03:47 PM
You could use windows messages to handle any input. I recall that DirectInput is just a winapi wrapper so it should work quite well. You can use MMSystem to handle joystick input.

You can use this link to figure out how to use MMSystem. If i'm not mistakin. http://msdn.microsoft.com/en-us/library/ms709377.aspx


Hope this helps. ;)

Brainer
02-05-2008, 04:35 PM
The idea Cronodragon suggested me was:

unit UBEInput;

interface

uses
Contnrs, UBELogger;

type
{ .: TDeviceType :. }
TDeviceType = (dtKeyboard, dtMouse, dtJoystick);

{ .: TInputDevice :. }
TInputDevice = class(TObject)
private
{ Private declarations }
FDeviceType: TDeviceType;
public
{ Public declarations }
constructor Create(); virtual;

procedure Update(); virtual; abstract;

property DeviceType: TDeviceType read FDeviceType write FDeviceType;
end;

{ .: TKeyboardDevice :. }
TKeyboardDevice = class(TInputDevice)
private
{ Private declarations }
FKeys: array[0..255] of Boolean;
public
{ Public declarations }
constructor Create(); override;

procedure Update(); override;

property DeviceType;
end;

{ .: TDeviceManager :. }
TDeviceManager = class(TObject)
private
{ Private declarations }
FDevices: TObjectList;
public
{ Public declarations }
constructor Create();
destructor Destroy(); override;

procedure AddDevice(const ADevice: TInputDevice);
procedure UpdateDevices();

function IsPressed(ADeviceType: TDeviceType; AButton: Byte): Boolean;
end;

implementation

{ TInputDevice }

constructor TInputDevice.Create;
begin
inherited Create();
end;

{ TKeyboardDevice }

constructor TKeyboardDevice.Create;
begin
inherited Create();

DeviceType := dtKeyboard;
end;

procedure TKeyboardDevice.Update;
begin

end;

{ TDeviceManager }

procedure TDeviceManager.AddDevice(const ADevice: TInputDevice);
begin
FDevices.Add(ADevice);
end;

constructor TDeviceManager.Create;
begin
inherited Create();

FDevices := TObjectList.Create(True);
end;

destructor TDeviceManager.Destroy;
begin
FDevices.Free();

inherited Destroy();
end;

function TDeviceManager.IsPressed(ADeviceType: TDeviceType;
AButton: Byte): Boolean;
var
I: Integer;
begin
for I := 0 to FDevices.Count -1 do
case ADeviceType of
dtKeyboard:
if (FDevices.Items[I] is TKeyboardDevice) then
Result := TKeyboardDevice(FDevices.Items[I]).FKeys[AButton];
end;
end;

procedure TDeviceManager.UpdateDevices;
var
I: Integer;
begin
for I := 0 to FDevices.Count -1 do
TInputDevice(FDevices.Items[I]).Update();
end;

end.


What do you think of it? And how do I read the pressed keys? :?

chronozphere
02-05-2008, 05:31 PM
Take a look at the Omega input unit. It looks very much like the code you posted. One downside (for you) is that it uses Directinput. Do you have reasons to not use that API?? I integrated it into my engine and it works OK. :)

When i'm back from switzerland, i send you that unit over MSN if you like. :)

Brainer
02-05-2008, 08:01 PM
I don't want to use DirectInput, because I don't want to integrate any DX stuff into my engine. But yes, send me the unit when you back - it surely is worth looking at it. ;)

Anyway, I'm waiting for other opinions. :)

chronozphere
03-05-2008, 04:31 PM
Ah i found something you might like. :D

http://www.delphipraxis.net/topic85712_gamepadjoystick+abfragen.html&highlight=joystick

These sources read joystick input using the MMSystem unit. You might want to integrate this into you input-manager. Use windows messages to capture mouse and keyboard input. ;)

The site is german. If you don't understand something (that's unlikely), you can contact me. :)

Brainer
03-05-2008, 07:14 PM
Thanks a lot, man. :)
Well, I think it might be useful, but right now I'd like to finish keyboard/mouse input classes. My idea was to iterate through the array and check if the key's pressed using GetAsyncKeyState. Do you think it's optimal?

chronozphere
04-05-2008, 09:11 AM
I think using messages is little faster. Calling getASyncKeyState hunderds of times a sec, is likely to be slower than handling only a few messages (or none, when no key is pressed). Moreover, when you use messages, less code is executed when you press a key, because you only have to change one array entry. You only have to respond to some messages which will be send to you anyway. ;)

noeska
04-05-2008, 10:00 AM
Is there a way to know what values can be expected when reading axis?
E.g. i have logitech dual action gamepad.

When reading out the axis the lowest value get is about 480 and the highest is 1000 and 0 for when not touched. Is my gamepad at fault or mmsystem?

In total it has 4 axis (two thumbsticks, or whatever they are called). So i need to read out x and y for the first stick. The second has to be read out as z and r. Is there any logic for that? Or can the axis be numbered as well?

Are there documents on this?

Also how do i now what button is where on the gamepad? Or dont i need to know that? As for my gamepad the numbering seem to be correct, but does that also aply to other gamepads?

JSoftware
04-05-2008, 01:15 PM
noeska, to my knowledge, joystick input in mmsystem uses the 0..65535 where 0 is completely to one side and 65535 is completely to the other side.
Buttons are bitpacked into a byte structure afaik.

Have you calibrated your joystick?

JSoftware
04-05-2008, 01:20 PM
What do you think of it? And how do I read the pressed keys? :?

As someone else wrote, I would largely prefer a solution that used the windows message system. But well, you would probably have to poll some device anyways so the update procedure makes sense.

I like eventhandlers. Have some OnKeyDown, OnKeyUp, OnMouseMove, etc :)

Eventually you could add threading for joystick reading?

Brainer
04-05-2008, 03:14 PM
I went for event handlers, too. :) Here's the code - just in case someone needed it.

unit UBEInput;
{TODO 2 -oPatrick Nusbaum -cInput devices : Add gamepad support.}

interface

uses
Windows;

type
{ .: TKeyEvent :. }
TKeyEvent = (keKeyDown, keKeyUp);

{ .: TMouseEvent :. }
TMouseEvent = (meMove, meLeftButtonDown, meLeftButtonUp, meMiddleButtonDown,
meMiddleButtonUp, meRightButtonDown, meRightButtonUp);

{ .: TInputSystem :. }
TInputSystem = class(TObject)
private
{ Private declarations }
FKeys: array[0..255] of Boolean;
FMouseButtons: array[0..2] of Boolean;
FMouseX, FMouseY: Integer;
public
{ Public declarations }
constructor Create();

procedure ProcessKeyboard(const KeyEvent: TKeyEvent; const TheKey: Word);
procedure ProcessMouse(const MouseEvent: TMouseEvent; const X, Y: Integer);

function IsKeyDown(const AKeyCode: Word): Boolean; overload;
function IsKeyDown(const AKey: Char): Boolean; overload;
end;

implementation

{ TInputSystem }

constructor TInputSystem.Create;
var
I: Integer;
begin
inherited Create();

for I := 0 to 255 do
FKeys[I] := False;
for I := 0 to 2 do
FMouseButtons[I] := False;
FMouseX := 0;
FMouseY := 0;
end;

function TInputSystem.IsKeyDown(const AKeyCode: Word): Boolean;
begin
Result := FKeys[AKeyCode];
end;

function TInputSystem.IsKeyDown(const AKey: Char): Boolean;
var
KeyCode: Integer;
begin
KeyCode := VkKeyScan(AKey) and $FF;
if (KeyCode = $FF) then
KeyCode := -1;
Result := IsKeyDown(KeyCode);
end;

procedure TInputSystem.ProcessKeyboard(const KeyEvent: TKeyEvent;
const TheKey: Word);
begin
case KeyEvent of
keKeyDown: FKeys[TheKey] := True;
keKeyUp: FKeys[TheKey] := False;
end;
end;

procedure TInputSystem.ProcessMouse(const MouseEvent: TMouseEvent; const X,
Y: Integer);
begin
case MouseEvent of
meMove:
begin
FMouseX := X;
FMouseY := Y;
end;
meLeftButtonDown: FMouseButtons[0] := True;
meLeftButtonUp: FMouseButtons[0] := False;
meMiddleButtonDown: FMouseButtons[1] := True;
meMiddleButtonUp: FMouseButtons[1] := False;
meRightButtonDown: FMouseButtons[2] := True;
meRightButtonUp: FMouseButtons[2] := False;
end;
end;

end.

noeska
04-05-2008, 06:36 PM
noeska, to my knowledge, joystick input in mmsystem uses the 0..65535 where 0 is completely to one side and 65535 is completely to the other side.
Buttons are bitpacked into a byte structure afaik.

Have you calibrated your joystick?

Would be nice if it were so. But it is not. Total left = -1000 and total right = 1000. So: -1000 to -470 then 0 then 470 to 1000. And that goes for all axis on my gamepad.
And yes it is calibrated.

@Brainer: How do i use your source as cannot seem to find anything like onkeypress and the likes.


Oops :oops:
I feel stupid as i found the following lines:

constructor TGamepad.Create;
begin
inherited Create;
DeviceNr := 0;
Range := 1000;
DeadZone := 400;
Calibrate;
end;
I skipped one of my rules as not to use an unit/component that i haven't studied. Now i did and it all makes sense.

Brainer
04-05-2008, 07:21 PM
[quote="JSoftware"]noeska, to my knowledge, joystick
@Brainer: How do i use your source as cannot seem to find anything like onkeypress and the likes.

I use this class with my form. Here's the code:

unit UBEWindow;

interface

uses
Windows, Classes, Controls, Forms, AppEvnts,
// -- Headers --
dglOpenGL,
// -- Engine units --
UBEOpenGLBuffer, UBEInput, UBELogger, UBETimer;

type
{ .: TBrainEngineForm :. }
TBrainEngineForm = class(TForm)
private
{ Private declarations }
FAppEvent: TApplicationEvents;
FOGLBuffer: TOpenGLBuffer;
FInput: TInputSystem;
FPerfTimer: TPerformanceTimer;
procedure MyOnResize(Sender: TObject);
procedure MyOnKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure MyOnKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure MyOnMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure MyOnMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure MyOnMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
protected
{ Protected declarations }
procedure DoRender(); virtual; abstract;
procedure MyOnIdle(Sender: TObject; var Done: Boolean); virtual; abstract;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
destructor Destroy(); override;

property OpenGLBuffer: TOpenGLBuffer read FOGLBuffer;
property InputSystem: TInputSystem read FInput;
property PerformanceTimer: TPerformanceTimer read FPerfTimer;
end;

implementation

{ TBrainEngineForm }

constructor TBrainEngineForm.Create(AOwner: TComponent);
begin
inherited Create(AOwner);

FAppEvent := TApplicationEvents.Create(Self);
FOGLBuffer := TOpenGLBuffer.Create(GetDC(Handle));
FOGLBuffer.InitGL();
FInput := TInputSystem.Create();
FPerfTimer := TPerformanceTimer.Create();

FAppEvent.OnIdle := MyOnIdle;
OnResize := MyOnResize;
OnKeyDown := MyOnKeyDown;
OnKeyUp := MyOnKeyUp;
OnMouseDown := MyOnMouseDown;
OnMouseUp := MyOnMouseUp;
OnMouseMove := MyOnMouseMove;

Log.Log('Window created.', 'UBEWindow');

FPerfTimer.StartTiming();
end;

destructor TBrainEngineForm.Destroy;
begin
FAppEvent.Free();
FOGLBuffer.Free();
FInput.Free();
FPerfTimer.Free();

Log.Log('Window freed.', 'UBEWindow');

inherited Destroy();
end;

procedure TBrainEngineForm.MyOnKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
FInput.ProcessKeyboard(keKeyDown, Key);
end;

procedure TBrainEngineForm.MyOnKeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
FInput.ProcessKeyboard(keKeyUp, Key);
end;

procedure TBrainEngineForm.MyOnMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
case Button of
mbLeft: FInput.ProcessMouse(meLeftButtonDown, X, Y);
mbRight: FInput.ProcessMouse(meRightButtonDown, X, Y);
mbMiddle: FInput.ProcessMouse(meMiddleButtonDown, X, Y);
end;
end;

procedure TBrainEngineForm.MyOnMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
FInput.ProcessMouse(meMove, X, Y);
end;

procedure TBrainEngineForm.MyOnMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
case Button of
mbLeft: FInput.ProcessMouse(meLeftButtonUp, X, Y);
mbRight: FInput.ProcessMouse(meRightButtonUp, X, Y);
mbMiddle: FInput.ProcessMouse(meMiddleButtonUp, X, Y);
end;
end;

procedure TBrainEngineForm.MyOnResize(Sender: TObject);
begin
glViewport(0, 0, ClientWidth, ClientHeight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, ClientWidth / ClientHeight, 0.1, 1000.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
end;

end.

noeska
04-05-2008, 07:56 PM
Oh you are using the events from the form and feed them to your class.

Brainer
04-05-2008, 07:59 PM
Yes. I decided to go with this approach, because it's simple to do and I want to keep my engine as simple as possible. :) But I think you can easily change the code that it doesn't use VCL.

noeska
04-05-2008, 08:31 PM
When using a windows form application it is indeed best to use the vcl for getting them.

I want something to work independed of vcl.
I decided to give the following a try:


var
P: TPoint;
Msg: TMsg;
begin
GetCursorPos(P);
FMouseX := P.X;
FMouseY := P.Y;

if getmessage(Msg, 0, 0, 0) then
begin
case Msg.message of
WM_LBUTTONDOWN : FMouseButtons[0] := True;
WM_LBUTTONUP : FMouseButtons[0] := False;
WM_MBUTTONDOWN : FMouseButtons[1] := True;
WM_MBUTTONUP : FMouseButtons[1] := False;
WM_RBUTTONDOWN : FMouseButtons[2] := True;
WM_RBUTTONUP : FMouseButtons[2] := False;
end;

//give message back for further processing
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end;

Unfortunately some events are missed. I gues that is drawback of polling the windows message que.

Is there a way to set up a listener to the windows message que?

Or am i better of integrating this into an (opengl) api template as that already includes handling windows messages.


Just thinking why not make a tthread descendantclass. So the timer can be left out also.

JSoftware
04-05-2008, 09:38 PM
Noeska, shouldn't you be checking the message events in the WndProc instead of in the message pump loop?

I don't think I understood the part about missing events. Do you mean special buttons?

noeska
05-05-2008, 03:47 PM
It tested it with mousebuttons. And sometimes the mouseup event is missed.

Brainer
05-05-2008, 03:53 PM
I want something to work independed of vcl.
[...]
Or am i better of integrating this into an (opengl) api template as that already includes handling windows messages.

This code of mine can be easily used with OpenGL WinAPI template. Just like you did, but I think, just like JSoftware, that it should be inside your window procedure, because this is where the messages come to.