W ostatnim poście mieliście szansę zobaczyć już jaką formę będzie miała moja funkcja przetwarzająca input z klawiatury. Teraz nieco wgłębimy się w temat.
KeyboardInfo
W klasie KeyboardInfo
znajdziemy informacje o stanie naszej klawiatury. Możemy sprawdzić, które klawisze są:
- wciśnięte -
.IsKeyDown(key)
- niewciśnięte -
.IsKeyUp(key)
- niewciśnięte ale były wciśnięte podczas poprzedniego sprawdzania -
.IsKeyReleased(key)
Metoda KeyboardInfo.ProcessKeys()
ma w sobie trochę logiki, określającej czy dany przycisk jest dopiero naciśnięty teraz, czy już był i jak długo. Korzysta ona z Keyboard.GetState()
z MonoGame, który dostarcza tylko informację jakie klawisze są wciśnięte w tej chwili.
Implementacja obsługi klawiszy
Na wstępie zaznaczę, że to rozwiązanie nie koniecznie będzie finalnym, ponieważ cały czas eksperymentuję szukając najładniejszego rozwiązania.
Pamiętacie rekord Scene
z poprzedniego postu? Teraz obok dodamy kolejny typ
type SceneHandler = {
Update: float -> Scene -> Scene
ProcessKeyboard: KeyboardInfo -> Scene -> Scene
}
Każda scena dostaje swój moduł w którym definiujemy wartość sceny jako takiej, czyli zbioru obiektów na ekranie. Jednak ich zachowanie będzie odrębne, ponieważ mamy do czynienia ze zmianą scen, a oczywiście chcemy uniknąć cyklicznych zależności.
Pójdźmy na moment poziom wyżej do MainConsole
. Wywołuje ona funkcję Scenes.processKeyboard
z argumentem Engine.Keyboard
. Jest to instancja klasy KeyboardInfo
, która jest aktualizowana z każdym obrotem pętli gry.
Teraz zdefiniuję ogólną funkcję, która będzie reagować na wciśnięcie przycisku
let processSingleKey key func (keyInfo:KeyboardInfo) scene =
if keyInfo.IsKeyDown(key) then func() else scene
I funkcja szczegółowa będzie wyglądać np. tak:
let help = processSingleKey Input.Keys.H <| fun () -> HelpScene.scene
Teraz określimy zbiór reakcji na klawisze przez SceneHandler
let startSceneHandler = {
Update = fun _ scene -> scene
ProcessKeyboard = fun kInfo ->
newGame kInfo
>> quit kInfo
>> help kInfo
}
Kiedy aplikujemy dwa argumenty do ProcessKeyboard
to dla każdej funkcji processSingleKey
sprawdzamy czy ustalony w niej przycisk jest naciśnięty i dostajemy odpowiednią scenę.
Ponieważ kolejność sprawdzania jest ważna (ostatnia scena zostanie zwrócona), to ja zakładam, że dwa klawisze nie będą naciśnięte naraz. Jeśli tak się stanie to użytkownikowi wykona się któraś akcja, ale nie obie. W tej grze nie będzie więcej potrzebne.
Ostatecznie funkcja wywoływana przez MainConsole
wygląda tak
let processKeyboard keyboardInfo scene =
match scene.Type with
| Start -> startSceneHandler.ProcessKeyboard keyboardInfo scene
//...
W GameScene będzie nieco więcej logiki, m.in. podskakiwanie naszego łazika. Już niedługo do tego dojdziemy.
Szersze spojrzenie
Ponieważ moje użycie klawiatury jest dość ograniczone, to warto wspomnieć ogólnie jak to wygląda. Nasz silnik SadConsole.Engine
ma własności UseKeyboard
i UseMouse
kontrolujące czy w ogóle używamy klawiatury i myszki. Klasa Console
, po której dziedziczy MainConsole
ma własności CanUseKeyboard
i CanUseMouse
, które są odpowiednikami tamtych w kontekście jednej konsoli.
Metoda ProcessKeyboard
w Console
ma trochę logiki dotyczącej wypisywania wprowadzanych znaków na ekran.
W gruncie rzeczy użycie jest mniej więcej takie jak w czystym MonoGame. W metodzie Update
sprawdzamy jakie klawisze są wciśnięte i wywołujemy odpowiednie zachowanie. Tu dodatkowo klasa Engine
wywołuje metodę Console.ProcessKeyboard
w trakcie update’u. Więc można nieco odseparować logikę interakcji z użytkownikiem od innych aktualizacji. Chociaż tak jak napisałem ostatnio nie działa to w pełni tak jak bym chciał, chociaż nadchodzi ogromny pull request do repo SadConsole, więc zobaczymy jak to będzie w kolejnej wersji.