Piszę sobie dalej kod mappera inputu w swojej grze. Dobrze idzie, coraz bliżej ukończenia tego modułu. Rozgryzłem to w jaki sposób stworzyć wygodne API do sterowania ruchem bohatera. Założenie jest takie, że z jednej strony mamy kupkę urządzeń wejściowych (mysz, klawiatura i bug wie jakie kontrolery USB), a z drugiej strony mamy bohatera, który ma się ruszać zgodnie z tym co użytkownik wciśnie w przypisanym do akcji ruchu urządzeniu.
Pierwszy problem — do ruchu można wykorzystać dowolny ”manipulator” na dowolnym urządzeniu. Może to być ruch myszą; mogą to być cztery przyciski, np. WASD
na klawiaturze, D-Pad lub jakiekolwiek inne przyciski na kontrolerze; może to też być gałka analogowa. Tutaj jest pewne podobieństwo — ruch myszy można reprezentować tak jak gałkę analogową. Zmodyfikowałem więc kod wirtualnej gałki analogowej, tak aby użyć jej zarówno do reprezentacji myszy, jak i gałek analogowych dowolnych kontrolerów USB. Oczywiście w przypadku gałek kontrolerów, są one wirtualnymi manipulatorami, bo API systemowe i SDL dostarcza dane poszczególnych osi, a nie ich par (i dobrze).
Jak wyżej pisałem, bez względu na sposób zmapowania inputu przez gracza, jego akcje muszą być odczytane z urządzeń wejścia i przetłumaczone na kilka danych — kąt ruchu (w radianach), dystans (z przedziału <0.0,1.0>
, jako mnożnik) oraz dodatkowo też jeden z ośmiu głównych kierunków (na potrzeby menu). Zaprogramowałem wszystko, dodałem sobie jeszcze możliwość ustawiania rozmiaru martwej strefy — do grania myszą potrzeba dużej martwej strefy, do grania gałką analogową malutkiej. Przyszedł czas na dodanie opcji ustawiania precyzji ruchu kursora na potrzeby grania myszą — i tu problem.
Zmienna precyzja ruchu kursora to nic innego jak mnożenie relatywnego ruchu kursora o jakąś wartość zmiennoprzecinkową. Aby kursor poruszał się wolniej, mnożnik musi być mniejszy od 1.0
, jeśli szybciej, to większy od 1.0
— np. 0.5
da ruch dwa razy wolniejszy, 2.0
da dwa razy szybszy. Nie jest to jakimś wielkim kłopot, bo SDL w zdarzeniach SDL_MOUSEMOTION
dostarcza zarówno nowe współrzędne kursora, jak i relatywne zmiany pozycji kursora, jednak nie da się ich wykorzystać. Wszystko dlatego, że jeśli kursor jest trzymany w obrębie okna dzięki SDL_SetWindowGrab
, to gdy ten dotyka krawędzi okna i rusza się myszą nadal w tym kierunku, to kursor nie zmienia pozycji, a więc zdarzenie SDL_MOUSEMOTION
dostarcza wartość 0
w polu xrel
/yrel
.
Skorzystałem więc z relatywnego trybu myszy — słuzy do tego funkcja SDL_SetRelativeMouseMode
. Jej włączenie powoduje, że kursor jest ukryty i porusza się wyłącznie w obrębie klienta okna (wołanie SDL_SetWindowGrab
i ukrywanie kursora nie są już potrzebne). Istotną różnicą jest to, że bez względu na to gdzie jest kursor w oknie i w którą stronę się go przesuwa, pola xrel
i yrel
w zdarzeniach SDL_MOUSEMOTION
dostarczają niezerowe wartości kroków w sposób ciągły — win. Zmodyfikowałem więc kod wirtualnej gałki, aby jej pozycja była zmieniana o zadany krok i dodałem obsługę zmiany precyzji. Działa elegancko, ale jest nowy problem.
Jeśli włączę relatywny tryb myszy, to przy mnożniku równym 1.0
(a więc precyzja domyślna), kursor zapierdziela wielokrotnie szybciej niż w trybie podstawowym. Nie wiedziałem dlaczego tak się dzieje. Sprawdziłem kod czy może gdzieś obliczenia są popsute, ale nie. W Google też nic na ten temat nie znalazłem. Dodałem więc zgłoszenie w repo SDL-a pytając co jest z tym trybem nie tak — może bug gdzieś się wkradł? Powód okazał się trywialny.
Otóż w podstawowym trybie obsługi myszy, prędkość kursora uzależniona jest od ustawień w OS-ie użytkownika, a w relatywnym trybie myszy, zdarzenia dostarczają dane surowe. W panelu sterowania mam ustawioną bardzo niską prędkość (wartość 2
), co jest około trzy razy wolniejsze od prędkości domyślnej — dlatego mój wirtualny analog tak popierdziela.
Teoretycznie nie jest to bug, ale za to spore ograniczenie, dlatego do SDL-a zostanie dodany nowy hint — SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE
— dzięki któremu będzie można odblokować skalowanie ruchu myszą, zgodne z ustawieniami systemowymi użytkownika. Czyli w tym momencie wszystko gra, prawda? Nie — jest kolejny problem. :D
Jeśli ustawi się niską prędkość w mojej wirtualnej gałce analogowej, to powolny ruch myszą w ogóle nie rejestruje ruchu i kursor się nie rusza. Nie jest to dziwne. Jeśli mnożnik precyzji ma wartość np. 0.2
, to przy powolnym ruchu myszą, zdarzenie dostarcza wartość 1
lub 2
w polach xrel
/yrel
— po przemnożeniu dostajemy wartości mniejsze niż piksel, a po zaokrągleniu wychodzi 0
(brak zmiany położenia kursora). Aby więc nie tracić mikroruchów myszą, dodałem sobie zmiennoprzecinkowe akumulatory do struktury analogu:
type
TGame_Stick = record private
SizeStick: TGame_SInt32;
SizeDeadZone: TGame_Float;
Precision: TGame_Float;
AccumulatorX: TGame_Float; // ło
AccumulatorY: TGame_Float; // tu
RadiusStick: TGame_Float;
RadiusDeadZone: TGame_Float;
RadiusXY: TGame_Float;
X: PGame_State;
Y: PGame_State;
Angle: PGame_State;
Distance: PGame_State;
Direction: PGame_State;
Active: TGame_Bool;
end;
i wykorzystałem do gromadzenia subpikselowych ruchów:
procedure Game_StickMoveBy(AStick: PGame_Stick; AX, AY: TGame_SInt32);
var
NewAccumulatorX: TGame_Float;
NewAccumulatorY: TGame_Float;
NewX: TGame_SInt32;
NewY: TGame_SInt32;
begin
// dolicz kroki w formie subpikselowej do wartości akumulatorów
NewAccumulatorX := AStick^.AccumulatorX + AX * AStick^.Precision;
NewAccumulatorY := AStick^.AccumulatorY + AY * AStick^.Precision;
// użyj części całkowitej kroków do ukreślenia nowej pozycji gałki analogowej
NewX := PGame_SInt32(Game_StateGetCurrent(AStick^.X))^ + Trunc(NewAccumulatorX);
NewY := PGame_SInt32(Game_StateGetCurrent(AStick^.Y))^ + Trunc(NewAccumulatorY);
// zachowaj jedyne część dziesiętną w akumulatorach
AStick^.AccumulatorX := Frac(NewAccumulatorX);
AStick^.AccumulatorY := Frac(NewAccumulatorY);
// ustaw gałkę w zadanej pozycji
Game_StickMoveTo(AStick, NewX, NewY);
end;
I teraz wszystko działa poprawnie.
Skoro mam już zaprogramowaną precyzję na podstawie relatywnego trybu myszy oraz mnożnika precyzji, kod odpowiedzialny za zmianę rozmiaru obszaru analogu mogłem już usunąć. Obszar ten zawsze jest stały (i nieduży), dzięki czemu zmiana rozmiaru okna na malutki nie wpłynie na sterowanie myszą.
Po tym wszystkim, skorzystałem z implementacji wirtualnej gałki do reprezentacji par osi w kontrolerach. Różnica jest taka, że dla myszy rozmiar obszaru wynosi 224px
(wysokość tylnego bufora), natomiast dla gałek kontrolerów jest to rozmiar 0xFFFF
— dlatego, że pozycja osi w kontrolerach to wartość 16-bitowa (co prawda ze znakiem, ale tę mapuję sobie na przedział bez znaku).
Koniec końców, jedna implementacja wirtualnej gałki analogowej została z powodzeniem użyta dla myszy oraz kontrolerów USB i we wszystkich przypadkach zapewnia to czego będę potrzebować do sterowania bohaterem, czyli kąta ruchu, dystansu i opcjonalnie kierunku. Niczego nie będę musiał obliczać ani w mapperze, ani tym bardziej w obsłudze logiki gry, bo wszystkie dane dostarcza strukturka wirtualnej gałki.
O to właśnie chodzi. ;)
Demko najpewniej będzie gotowe pod koniec tego roku — musicie jeszcze poczekać.
@WhiteLightning: chciałbym, ale jeszcze nawet nie mam co pokazać — okno na razie wygląda tak jak pokazałem w poprzednim wpisie. Obecnie programuję podstawy, które nie dotyczą rzeczy normalnie widocznych na ekranie (jak np. mapper inputu), więc i nie mam za bardzo co nagrywać.
Obiecuję, że jak już będzie coś sensownego do pokazania, to zacznę nagrywać i publikować na swoim kanale. Choć zanim powstanie silnik, najpierw będę budował system kontrolek na potrzeby UI. I wtedy nie tylko będę mógł już sobie budować główne menu gry, ale też podłączyć pod nie mapper inputu.
Ale pamiętaj - jak zaczniesz na tym zarabiać miliony, to 5% dla mnie za wsparcie moralne na etapie developmentu ;)
To nagrywaj jak najszybciej, jest sporo gosci na YT ktorzy nagrywaja kady etap robienia gry i jak ja wypuszczaja to maja juz zebrana spolecznosc + kase z wyswietlen. A jak bedziesz mogl rzucic robote i zyc z indie-gamedevu to bedziesz mogl wiecej czasu na tworzenie poswiecic:)
@WhiteLightning: nawet jeśli zacznę nagrywać później, kiedy już będę miał sporo kodu (obecnie mam około 6kLoC), to i tak nie zmienia to faktu, że zacznę od omawiania implementacji od początku i tak sobie będę powoli gonił stan obecny. Tak więc nic nie przepadnie, wszystko zostanie opowiedziane, nie ma się czym martwić na zapas. A w razie gdyby było trochę osób chcących poznać jakiś temat dogłębniej, to zawsze będzie można nagrać dodatkowy odcinek i ten temat zgłębić. :]
obecnie mam około 6kLoC
- a umiesz to oszacować nie w LoC, tylko procentach?
6%? Kod rozwijam od kilku miesięcy, a roboty jest co najmniej na trzy lata.
To fakt, bo ze wstępnych estymacji wyszło mi, że będzie ich 100-150k. Lepiej się nie opierać na liczbie linijek — lepiej liczyć gotowe ficzery i tych póki co jest niewiele, bo to dopiero początek prac.
Napisalem czy jeszcze piszesz ten kod bo to dlugo trwa i nie wiem czemu jakis cerrato mi usunal ten wpis. dlugo bedziesz to jeszcze pisal?
@chomikowski: Usunąłem, bo napisałeś to w agresywny i szyderczy sposób. Jak widzisz - można to samo pytanie zadać w grzeczny i nie chamski sposób.
@cerrato: ale nie jestes od tego by oceniac odczucia postow ktore ktos pisze, post nikogo nie obrazal a ty sie skup na swojej pracy lepiej zamiast wsciubiac nos w nie swoje sprawy
@chomikowski: nle nie jestes od tego by oceniac [...] a ty sie skup na swojej pracy lepiej
- właśnie na tym polega moja praca - jako moderatora, żeby pilnować poziomu wypowiedzi i tępić chamstwo. I jak najbardziej to ja mam oceniać czy coś jest obraźliwe lub w inny sposób niepożądane.
@chomikowski: cieszę się Twoim szczęściem. ;)
Napisalem czy jeszcze piszesz ten kod bo to dlugo trwa i nie wiem czemu […]
Mapper z obsługą wymaganych urzadzeń wejścia, nad którym obecnie pracuję, to dość skomplikowany moduł, którego implementacja wymaga dużo czasu i wysiłku. Pamiętaj, że mapper nie jest jedyną rzeczą, którą teraz programuję, i też o tym, że nie poświęcam na ten projekt 10h dziennie, aby robota szła sprawnie. Wiele rzeczy muszę najpierw rozgryźć i przetestować, zanim napiszę właściwy kod we właściwym projekcie. Szczególnie, że wiele z nich jest rzadko spotykanych w grach, ze względu właśnie na stopień skomplikowania i czasochłonność implementacji.
Programowanie to nie rąbanie drewna — nie wystarczy machać rękami szybciej, aby szybciej skończyć zadanie.
ale nie jestes od tego by oceniac odczucia postow ktore ktos pisze, post nikogo nie obrazal […]
Widząc słowa z tego komentarza — „Jeszcze piszesz ten kod? ja juz zdazylem wypuscic 5 projektow w swiat a ty ciagle te guziki robisz hahahaha? w ogole skonczysz to do 2025?” — spokojnie można uznać, że naśmiewasz się ze mnie i z tempa mojej pracy nad tym projektem. Ja tam się nie obrażam, ale wyluzuj trochę, bo celuję nie w ilość, a w jakość. ;)
@furious programming: prawda, usmiealem sie ze to tyle trwa :) ale jeszcze nie jestes gotowy na moje opinie przyjacielu w Chrystusie
daj wreszcie jakieś działające demo, a nie tylko robisz apetyt takimi wpisami ;)