Czas zacząć pracę na grą Mars-Buggy. Najpierw zobacz mój post Mars-Buggy - Daj Się Poznać 2017. Moje posty będą miały nieco tutorialową formę, aby ułatwić mi samemu pracę z SadConsole w przyszłości.
Instalowanie SadConsole
Na ten moment będę korzystał z dwóch paczek NuGetowych: SadConsole.Core.MonoGameGL i SadConsole.GameHelpers.MonoGameGL. Pierwsza zawiera cały framework do renderowania i emulacji konsoli w oknie OpenGL, a druga zawiera dodatkową logikę, która usprawnia korzystanie z frameworka.
Uruchamianie aplikacji
Utworzyliśmy nowy projekt aplikacji konsolowej w F# i dodaliśmy dwie paczki NuGetowe. W folderze mamy Program.fs
oraz 6 plików - 3 pary - czcionek do SadConsole.
Na Linuxie trzeba ręcznie ustawić CopyToOutputDirectory
dla plików czcionek
Czas dodać kilka linijek do naszej funkcji main
:
open SadConsole.Consoles
open Microsoft.Xna.Framework
[<EntryPoint>]
let main argv =
SadConsole.Engine.Initialize("IBM.font", 80, 25);
SadConsole.Engine.EngineStart.Add (fun _ ->
let console = SadConsole.Engine.ActiveConsole :?> Console
console.Print(1, 1, "Hello World!"))
SadConsole.Engine.Run()
0 // exit code
Najpierw inicjalizujemy nasz silnik konsoli domyślną czcionką (IBM.font) oraz rozmiarem okna 80x25. Następnie subskrubujemy event EngineStart
, podczas którego wypiszemy na konsoli “Hello World!” zaczynając w drugim wierszu i drugiej kolumnie (licząc od 0).
Naszym oczom ukaże się:
Pętla gry
SadConsole jest frameworkiem, co oznacza, że my tylko dostarczamy kod, który aplikacja będzie wykonywać. Dlatego nie my decydujemy o pętli gry, a robi to za nas MonoGame, z którego SadConsole korzysta.
Ponieważ SadConsole było tworzone dla C#, to oczywiście jest zorientowane obiektowo. Pracując dłuższą chwilę w środowisku funkcyjnym musiałem trochę przestawić swój tok myślenia i wrócić do obiektowego paradygmatu. A w zasadzie to je połączyć.
SadConsole pozwala na utworzenie kilku niezależnych konsoli, z których każda będzie na ekranie wyświetlana w innym miejscu. Mi jest to na razie nie potrzebne, więc będę miał tylko jedną konsolę MainConsole
.
type MainConsole(width, height) =
inherit Console(width, height)
override this.Update() = ()
override this.Render() = ()
Dziedziczymy po typie SadConsole.Consoles.Console
i nadpisujemy jego metody Update
i Render
. Są to elementy naszej pętli gry. Jedyne czego brakuje to pobieranie inputu od użytkownika, ale o tym powiemy sobie w przyszłym tygodniu.
Obiekty renderowania
Moim celem na początek jest wypisać na konsolę duży napis “Mars-Buggy”. Chciałbym zawrzeć to w instancji SadConsole.Game.GameObject
, ponieważ pozwala to mi na łatwe renderowanie obiektu na ekranie. Kiedy używamy metody Print
konsoli, to żeby przesunąć napis, trzeba najpierw wymazać stary, bo inaczej będą dwa. Używając metody Render
na obiekcie GameObject
zostanie to zrobione za nas.
Na ten moment tworzenie tych obiektów, a dokładnie animacji jest nieco pracochłonne, więc będę tworzył sobie jakieś metody pomocnicze, ale tego kodu używam w tej chwili:
let Nullable<'a when 'a:(new:unit->'a) and 'a:struct and 'a:>System.ValueType> (x:'a) = System.Nullable x
let editorFill (editor:SurfaceEditor) (fg:Color) (bg:Color) (glyph:int) =
editor.Fill(Nullable fg, Nullable bg, Nullable glyph) |> ignore
let welcomeScreen =
let mainText = GameObject(Engine.DefaultFont)
let textSurface = AnimatedTextSurface("default", 43, 14)
let frame = textSurface.CreateFrame()
let editor = SurfaceEditor(TextSurface(1, 1, Engine.DefaultFont))
editor.TextSurface <- frame
editorFill editor Color.OrangeRed Color.Transparent 0
editor.Print(0, 0, "MM MM A RRRRR SSS ")
editor.Print(0, 1, "M M M M A A R R SS S")
editor.Print(0, 2, "M M M M A A RRRRR SS ")
editor.Print(0, 3, "M M M AAAAA R R SSS ")
editor.Print(0, 4, "M M A A R R S SS")
editor.Print(0, 5, "M M A A R RR SSSS ")
editor.Print(0, 6, " ")
editor.Print(0, 7, "BBBBBB U U GGGGG GGGGG Y Y")
editor.Print(0, 8, "B B U U G G G G Y Y")
editor.Print(0, 9, "BBBBBB U U G G Y Y")
editor.Print(0, 10, "B B U U G GGG G GGG Y")
editor.Print(0, 11, "B B U U G G G G Y")
editor.Print(0, 12, "BBBBBB UUUUU GGGGG GGGGG YY")
mainText.Animation <- textSurface
mainText.Position <- Point(20, 4)
mainText
Po kolei:
- tworzę nowy
GameObject
, - tworzę nową animację
AnimatedTextSurface
, - dodaję do niej pierwszą klatkę
CreateFrame()
, - tworzę edytor
SurfaceEditor
, który ułatwia edytowanie klatek, - ustawiam
editor.TextSurface
na bierzącą klatkę, - ustawiam kolor tekstu (OrangeRed) i tła (Transparent), korzystając z funkcji pomocniczej
editorFill
, - wypisuję to co chcę do edytora, który umieszcza to na klatce
- przypisuję animację do mojego obiektu,
- ustalam jego pozycję,
- przypisuję go do stałej
welcomeScreen
.
Pozycja obiektu jest relatywna do ekranu, a nie do konsoli. Jeśli nasza konsola ma pozycję inną niż (0,0) lub pole renderowania to musimy to przesunięcie uwzględnić.
Teraz w mojej konsoli ustawiam
override this.Render() = welcomeScreen.Render()
Pamiętaj, że w F# ważna jest kolejność deklaracji, więc MainConsole
musi być zdefiniowany poniżej welcomeScreen
.
Na koniec dokładamy to co zrobiliśmy do funkcji main
let main argv =
SadConsole.Engine.Initialize("IBM.font", 80, 25);
SadConsole.Engine.EngineStart.Add (fun _ ->
SadConsole.Engine.ConsoleRenderStack.Clear()
SadConsole.Engine.ConsoleRenderStack.Add(MainConsole(80, 25)))
SadConsole.Engine.Run()
0 // return an integer exit code
Wynik: