Prawidłowy sposób na wyodrębnienie kontrolera XBox


12

Mam kontroler XBox360, którego chciałbym użyć jako danych wejściowych dla aplikacji.

To, czego nie mogę wypracować, to najlepszy sposób na ujawnienie tego za pośrednictwem interfejsu.

Za kulisami klasa, która obsługuje kontroler (kontrolery), opiera się na stanie przycisku odpytywania.

Początkowo próbowałem coś link:

Event ButtonPressed() as ButtonEnum

gdzie ButtonEnumbył ButtonRed, ButtonStartitd ...

Jest to trochę ograniczone, ponieważ obsługuje tylko naciśnięcia przycisków, a nie blokady / wzory (naciśnij dwukrotnie itp.)

Kolejnym pomysłem było po prostu ujawnienie stanu przycisku aplikacji, np

Property RedPressed as Boolean
Property StartPressed as Boolean
Property Thumb1XAxis as Double

Jest to bardzo elastyczne, ale tak naprawdę wymusza zbyt wiele pracy w aplikacji i wymaga odpytywania aplikacji - wolałbym kierować zdarzeniami, jeśli to możliwe.

Rozważałem dodanie wielu wydarzeń, np .:

Event ButtonPressed(Button as ButtonEnum)
Event ButtonPressedTwice(Button as ButtonEnum)
Event ButtonHeldStart(Button as ButtonEnum)
Event ButtonHeldEnd(Button as ButtonEnum)

ale wydaje się to trochę niezgrabne i było prawdziwym bólem na ekranie „Bind button”.

Czy ktoś może wskazać mi „prawidłowy” sposób obsługi danych wejściowych z kontrolerów.

NB: Używam SlimDX w klasie, która implementuje interfejs. To pozwala mi bardzo łatwo odczytać stan. Doceniamy również wszelkie alternatywy, które mogłyby rozwiązać mój problem

Odpowiedzi:


21

Nie ma jednego doskonałego mapowania, które dałoby abstrakcję specyficzną dla platformy, ponieważ oczywiście większość identyfikatorów, które mają sens dla kontrolera 360, jest niewłaściwa dla kontrolera PlayStation (A zamiast X, B zamiast Circle). Oczywiście kontroler Wii to zupełnie inna sprawa.

Najbardziej skutecznym sposobem, w jaki udało mi się sobie z tym poradzić, jest użycie trzech warstw implementacji. Dolna warstwa jest całkowicie zależna od platformy / kontrolera i wie, ile dostępnych jest przycisków cyfrowych i osi analogowych. To ta warstwa wie, jak sondować stan sprzętu, i ta warstwa wystarczająco dobrze pamięta poprzedni stan, że wie, kiedy przycisk został właśnie naciśnięty, czy był wyłączony przez więcej niż jeden tik, czy też nie został naciśnięty . Poza tym jest głupi - czysta klasa stanu reprezentująca jeden typ kontrolera. Jego prawdziwą wartością jest wyodrębnienie drobiazgowości zapytania o stan kontrolera z dala od warstwy środkowej.

Środkowa warstwa to rzeczywiste odwzorowanie sterowania od prawdziwych przycisków do koncepcji gry (np. A -> Jump). Nazywamy je impulsami zamiast przycisków, ponieważ nie są one już powiązane z określonym typem kontrolera. Na tej warstwie można ponownie mapować elementy sterujące (podczas programowania lub w czasie wykonywania na żądanie użytkownika). Każda platforma ma własne mapowanie elementów sterujących na wirtualne impulsy . Nie możesz i nie powinieneś próbować uciec od tego. Każdy kontroler jest wyjątkowy i wymaga własnego mapowania. Przyciski mogą być mapowane na więcej niż jeden impuls (w zależności od trybu gry), a więcej niż jeden przycisk może mapować na ten sam impuls (np. A i X przyspieszają, B i Y zwalniają). Mapowanie określa to wszystko,

Górna warstwa to warstwa gry. Wymaga impulsów i nie obchodzi ich, jak zostały wygenerowane. Może pochodzą od kontrolera lub nagrania kontrolera, a może pochodzą z AI. Na tym poziomie nie obchodzi cię to. Ważne jest, aby pojawił się nowy impuls skoku, czy impuls przyspieszenia był kontynuowany, lub że impuls nurkowania ma wartość 0,35 tego tyknięcia.

W takim systemie zapisujesz dolną warstwę raz dla każdego kontrolera. Górna warstwa jest niezależna od platformy. Kod warstwy środkowej wymaga tylko jednokrotnego zapisu, ale dane (ponowne mapowanie) należy wykonać ponownie dla każdej platformy / kontrolera.


To wydaje się bardzo czyste i eleganckie podejście. Dziękuję Ci!
Podstawowy

1
Bardzo dobrze. Znacznie lepsze niż moje: P
Jordaan Mylonas,

3
Bardzo ładna abstrakcja. Należy jednak zachować ostrożność podczas wdrażania: nie należy tworzyć i niszczyć nowego obiektu impulsowego dla każdej akcji użytkownika. Użyj puli. Śmieciarz będzie Ci wdzięczny.
grega g

Absolutnie. Układ statyczny zwymiarowany do maksymalnej liczby jednoczesnych impulsów jest prawie zawsze najlepszą opcją; ponieważ prawie zawsze chcesz aktywować tylko jedną instancję każdego impulsu jednocześnie. Najczęściej w tej tablicy jest tylko kilka elementów, więc szybko się iteruje.
MrCranky,

@grega dziękuje wam obojgu - nie myślałem o tym.
Podstawowy

1

Szczerze mówiąc, powiedziałbym, że optymalny interfejs będzie w dużym stopniu zależał od użycia w grze. Jednak w przypadku ogólnego scenariusza użytkowania sugerowałbym architekturę dwuwarstwową, która oddziela podejścia oparte na zdarzeniach i odpytywaniu.

Warstwę niższą można uznać za warstwę „opartą na sondowaniu” i ujawnia bieżący stan przycisku. Jeden z takich interfejsów może po prostu mieć 2 funkcje, GetAnalogState(InputIdentifier)a GetDigitalState(InputIdentifier)gdzie InputIdentifierjest wyliczona wartość reprezentująca, z którym przyciskiem, spustem lub drążkiem sprawdzasz. (Uruchomienie GetAnalogState dla przycisku zwróci 1,0 lub 0,0, a uruchomienie GetDigitalState dla drążka analogowego zwróci wartość true, jeśli przekroczy ustalony próg, lub false w przeciwnym razie).

Drugi poziom używałby następnie niższego poziomu do generowania zdarzeń po zmianie stanu i pozwalał elementom rejestrować się dla wywołań zwrotnych (zdarzenia C # są chwalebne). Te wywołania zwrotne obejmowałyby zdarzenia dla prasy, zwolnienia, dotknięcia, przytrzymania itd. W przypadku drążka analogowego możesz dołączyć gesty, np. QCF do walki. Liczba ujawnionych zdarzeń zależy od stopnia szczegółowości zdarzenia, które chcesz wywołać. Możesz po prostu wystrzelić prosty, ButtonStateChanged(InputIdentifier)jeśli chcesz obsłużyć wszystko inne w osobnej logice.

Jeśli więc chcesz sprawdzić bieżący stan przycisku wprowadzania lub drążka sterującego, sprawdź dolny poziom. Jeśli chcesz po prostu odpalić funkcję po zdarzeniu wejściowym, zarejestruj się, aby uzyskać oddzwonienie z drugiej warstwy.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.