Obsługa gamepada - różne maksima dla pozycji osi

0

Załóżmy, że pod komputer mam podłączony gamepad, urządzenie i połączenie są prawidłowe;

W programie, za pomocą funkcji joyGetDevCaps pobieram informacje na temat urządzenia, wypełniając strukturę typu JOYCAPS; W niej znajdują się różne informacje, ale nas interesują pola wXmax i wYmax, czyli maksymalne wartości dla dwóch głównych osi; Na moim gamepadzie, osie X i Y określają pozycję D-Pada (krzyżyka);

Podczas rozruchu aplikacji, rejestruję przechwytywanie kominikatów od gamepada dla głównego okna programu - używam funkcji joySetCapture, podając uchwyt tegoż okna; Natomiast podczas zamykania programu, zwalniam "słuchacza" funkcją joyReleaseCapture, sprzątając po sobie (choć nie jest to konieczne, jeśli robi się to przy zamykaniu całego programu); Taki zabieg pozwoli na obsługę komunikatów przez okno - można sobie napisać metody obsługi np. komunikatów MM_JOY1BUTTONDOWN; Ale nie chcę ich używać, bo są mocno ograniczone (cztery przyciski to mało);

Dlatego też stan gamepada odczytuję w timerze, pobierając dane z pada za pomocą funkcji joyGetPosEx, wypełniając strukturę typu JOYINFOEX, aby mieć komplet danych; I faktycznie tak się dzieje - dane są prawidłowe; Stan osi X i Y (czyli położenia D-Pada) określają trzy możliwe wartości:

  • 0x0000 - minimum (góra i/lub lewo),

  • 0x7FFF - środek,

  • 0xFFFF - maksimum (dół i/lub prawo);
    I teraz tak - teoretycznie, skoro nie obsługuję komunikatów płynących do okna, nie potrzebuję wywoływać funkcji joySetCapture; Stan gamepada sprawdzany jest cyklicznie przez timer; Tutaj pojawia się problem - jeżeli nie wywołam tej funkcji, wartości dla dwóch głównych osi będą jednobajtowe, a dokładniej takie jak poniżej:

  • 0x0000 - minimum (góra i/lub lewo),

  • 0x007F - środek,

  • 0x00FF - maksimum (dół i/lub prawo);
    W dalszym ciągu timer pobiera dane z urządzenia, funkcje nie zwracają błędów, ale wartości są okrojone/mniej precyzyjne; A co jeszcze dziwniejsze, jeżeli mając aktywne główne okno programu trzymam lewy Alt, to na czas trzymania wciśniętego tego klawisza, wartości są dwubajtowe, a jak go puszczę to znów jednobajtowe; Nie wiem dlaczego, ale śmiesznie to działa :]

Podsumowując:

  1. skoro dane z pada pobieram przez timer to dlaczego mimo wszystko muszę wywołać funkcję joySetCapture, aby otrzymywać prawidłowe, dwubajtowe wartości stanu osi?
  2. skoro funkcja joyGetDevCaps wypełnia pola wXmax i wYmax wartościami 0xFFFF (dla maksimum), to dlaczego joyGetPosEx wypełnia te pola wartościami 0x00FF (też dla maksimum)?
    Dzięki za ewentualne zainteresowanie;

Edit: Dodam może, żeby wszystko było jasne; Póki co nie widzę problemu, aby wywoływać funkcje joySetCapture i joyReleaseCapture - ich wywołanie w niczym nie przeszkadza; Chciałbym jedynie wiedzieć, dlaczego bez wywołania tej pierwszej funkcji, otrzymywane wartości dla osi są o połowę mniej precyzyjne.

1

Hmm... coś mi ten poprzedni program nie wyszedł, albo bieżący robi mnie w balona, bo wszystko działa :]

Napisałem sobie jeszcze raz program testujący i jak na złość w nim wszystko działa prawidłowo, nawet jeśli nie zarejestruję okna do odbierania komunikatów od urządzenia; Poniżej znajduje się krótki kod testujący, który na starcie pobiera dane o urządzeniu (głównie o dwóch osiach - na moim gamepadzie jest to D-Pad) i pakuje je do komponentu klasy TValueListEditor po lewej stronie okna; Natomiast stan osi sprawdzany jest w timerze, a pobrane dane wrzucane są do drugiej listy, po prawej stronie;

Kod wykonujący te zadania poniżej:

uses
  MMSystem {..}

type
  TMainForm = class(TForm)
  {..}
  private
    FDevice: TJoyCaps;
    FDeviceTimer: TTimer;
  private
    procedure HandleDevice(Sender: TObject);
  end;

{..}

procedure TMainForm.FormCreate(Sender: TObject);
begin
  joyGetDevCaps(JOYSTICKID1, @FDevice, SizeOf(FDevice));

  vleDevCaps.Strings.ValueFromIndex[0] := '0x' + HexStr(FDevice.wXmin, 4);
  vleDevCaps.Strings.ValueFromIndex[1] := '0x' + HexStr(FDevice.wXmax shr 1, 4);
  vleDevCaps.Strings.ValueFromIndex[2] := '0x' + HexStr(FDevice.wXmax, 4);

  vleDevCaps.Strings.ValueFromIndex[3] := '0x' + HexStr(FDevice.wYmin, 4);
  vleDevCaps.Strings.ValueFromIndex[4] := '0x' + HexStr(FDevice.wYmax shr 1, 4);
  vleDevCaps.Strings.ValueFromIndex[5] := '0x' + HexStr(FDevice.wYmax, 4);

  FDeviceTimer := TTimer.Create(Self);
  FDeviceTimer.Interval := FDevice.wPeriodMin;
  FDeviceTimer.OnTimer := @HandleDevice;
  FDeviceTimer.Enabled := True;
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  FDeviceTimer.Free();
end;

procedure TMainForm.HandleDevice(Sender: TObject);
var
  jieDevice: TJoyInfoEx;
begin
  jieDevice.dwSize := SizeOf(jieDevice);
  jieDevice.dwFlags := JOY_RETURNX or JOY_RETURNY;

  joyGetPosEx(JOYSTICKID1, @jieDevice);

  vlePosEx.Strings.ValueFromIndex[0] := '0x' + HexStr(jieDevice.wXpos, 4);
  vlePosEx.Strings.ValueFromIndex[1] := '0x' + HexStr(jieDevice.wYpos, 4);
end;

No i w liście z prawej strony okna, dane są cały czas prawidłowe - wartości są dwubajtowe:

joysup.png

Tak więc najwyraźniej w tamtym programie coś źle zrobiłem (choć jeszcze nie wiem co), albo gamepad w tamtym czasie źle działał; Albo system robił mnie w konia - nie wiem co się działo i w sumie nieważne; Najważniejsze, że wcale nie trzeba rejestrować okna, jeśli stan urządzenia sprawdzamy sobie w timerze;

W załączniku zostawiam archiwum joysup.zip z plikami tego testowego programu.

0

Może jeszcze jako ciekawostkę dodam, że w systemowym module kalibracji wartości dla osi są jednobajtowe:

calibration.png

Więc coś w tym jest... :D

0

może to (w sensie windowsowe okno) pokazuje tylko starszy bajt?

0

@abrakadaber - tylko jaki jest w tym cel?

Wywołując funkcję joyGetDevCaps, pobieram dane o samym urządzeniu i również o sterowniku (kilka pól struktury zawiera różne maksima, np. maksymalną liczbę obsługiwanych przycisków i osi, u mnie odpowiednio 32 i 8); Zakres wartości dla wszystkich osi (co ląduje w podanej w argumencie strukturze) liczony jest od 0x0000 do 0xFFFF; Jeśli program pobiera dane dotyczące stanu wszystkich istniejących osi, dane te otrzymuje jako dwubajtowe, dokładne liczby; Raz mi się zdarzyło, że dane były jednobajtowe - młodszy bajt był wypełniany, a starszy zawsze 0x00; Dotyczyło to nie tylko osi X i Y dla D-Pada, ale także osi Z, czyli rolki przepustnicy;

Aby prawidłowo określić pozycję osi, korzystam z pól wXmax i wYmax; Problem pojawi się w momencie, gdy coś znów zgłupieje i wartości pozycji osi będą jednobajtowe, zawarte w młodszych bajtach pól struktury; Wtedy nawet jeśli pole wXpos struktury typu JOYINFOEX będzie zawierać maksimum (0x00FF), to i tak będzie różne od wartości pola wXmax (0xFFFF), więc poprawne rozróżnienie pozycji krzyżyka będzie niemożliwe; Jedyne co by się mogło zgadzać to minimum, bo w obu przypadkach będzie to 0x0000;

Więc w jakim celu kreator przetwarza jedynie połowę tych wartości i co mu to daje?

Przeglądnąłem sporo materiałów w sieci, przeanalizowałem także podawane kody (również komponentów z torry.net i nic ciekawego w tych darmowych z otwartym kodem nie znalazłem; Zresztą i tak większość znalezionych kodów wykorzystuje funkcję joyGetPos, czyli tą uboższą (trzy osie i cztery przyciski).

1 użytkowników online, w tym zalogowanych: 0, gości: 1