Przetwarzanie klawiatury w SadConsole

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.