MonoGame, czyli jak stworzyć grę w C#

Wiele osób chciało kiedyś tworzyć własne gry, oczywiście w tym także ja. Były to co prawda dosyć dawne czasy. Czasy, w których chodziłem do pierwszych klas podstawówki. Nie znałem wtedy nawet podstawowej budowy komputera – nie mówiąc już nic o programowaniu. Jednak lata mijały i pewnego dnia zabrałem się za naukę C++, a następnie C#. Kiedy wiedziałem już o co mniej więcej chodzi w całym tym programowaniu, postanowiłem stworzyć jakąś prostą gierkę. Długo nie zastanawiałem się nad technologią, w której chciałem ją napisać. Wybór padł na MonoGame, głównie dlatego, że używa się w nim (mojego ulubionego) C#.

Łukasz Soroczyński. Naukę programowania rozpoczął około pięciu lat temu, która z mniejszymi lub większymi przerwami trwa do dziś. Uważa, że najlepszym sposobem na zdobywanie wiedzy w tej branży jest samodzielne stawianie sobie zadań i ich realizowanie. Swoją przyszłość wiąże z branżą IT, a w szczególności z programowaniem.


Klika słów o MonoGame

Jak nietrudno domyślić się jest to framework pozwalający pisać gry przy użyciu języka, jakim jest C#. MonoGame jest otwartą implementacją i zarazem następcą Microsoft XNA. Właściwie 100% kodu napisanego kiedyś w XNA powinno z powodzeniem udać się przenieść do MonoGame. Dodatkowym atutem otwartej wersji tego frameworka jest to, że wspiera on prawie wszystkie istniejące systemy i urządzenia (np. Windows, Linux, iOS, Android, Xbox, PS4). Pomimo wielu zalet MonoGame nie jest pozbawione też wad. Jedną z nich jest fakt, że pisanie w nim gier zabiera o wiele więcej czasu niż w przypadku np. Unity, które dostarcza całą masę gotowych komponentów. Z drugiej strony Unity nie jest otwarte… Coś za coś.

MonoGame – jak zbudowany jest kod gry?

Aby zacząć przygodę z tworzeniem gier w tym środowisku wypadałoby stworzyć w Visual Studio nowy projekt. W zależności od platformy, na którą chcemy stworzyć grę, wybieramy interesującą nas opcję. Na potrzeby tego artykułu stworzę MonoGame Windows Project:

W zasadzie nie ma żadnego znaczenia, który projekt zostanie wybrany. Kod wszędzie będzie wyglądał tak samo. Różnica będzie w sposobie generowania przez framework obrazu – w przypadku projektu Windowsowego używany jest DirectX, a w Cross Platform – OpenGL. Po utworzeniu projektu powinien powitać nas mniej więcej taki widok:

Jak widać w projekcie istnieją dwa pliki *.cs oraz folder Content. Plik Program.cs zawiera metodę Main, która tworzy klasę główną gry:

Natomiast plik Game1.cs zawiera klasę główną gry. To tutaj odbywa się rysowanie elementów na ekranie i ich przemieszczanie. Po usunięciu zbędnych komentarzy klasa ta powinna wyglądać tak:

Co do czego służy? Zaczynając od konstruktora – tworzymy w nim nowy obiekt typu GraphicsDeviceManager. Dzięki niemu będziemy mogli w ogóle wyświetlić nasze okno z grą. Klasa ta odpowiada za zarządzanie urządzeniem graficznym – czyli m.in za renderowanie naszego obrazu. Z pomocą jej właściwości możemy ustalić czy gra ma być np. uruchamiana w trybie pełnoekranowym czy nie.

Kolejna linijka wskazuje na miejsce, skąd ładowane będą zasoby do gry (obrazki, dźwięki itd.). Dalej znajduje się metoda Initialize() — jak wskazuje sama nazwa, umieszczamy tu wszystko to co chcemy wykonać przed startem gry. Następna metody to odpowiednio Load i UnloadContent. W nich umieszczany jest kod ładujący/usuwający zasoby używane w grze, czyli na przykład tekstury.

Kolejne dwie metody są najważniejsze. Obydwie wykonują się w dwóch niezależnych pętlach. W metodzie Update umieszcza się kod odpowiedzialny za logikę gry (czyli np. przemieszczanie postaci), natomiast w metodzie Draw tylko rysuje się elementy na ekranie. Taki podział jest spowodowany tym, aby liczba wyświetlanych klatek na sekundę nie miała wpływu na szybkość wykonywania się logiki gry.

MonoGame – jak stworzyć proste menu gry?

Jak widać gry pisze się w zgoła inny sposób niż standardowe aplikacje. Całość zabawy polega na rozmieszczaniu i rysowaniu jakiś kształtów, wyświetlaniu ich na ekranie oraz obsłudze interakcji między nimi. Oczywiście dochodzi do to tego obsługa urządzeń wejściowych takich jak np. mysz czy klawiatura. Nie ma tutaj mowy o użyciu gotowego przycisku i zdarzenia jego kliknięcia. Aby stworzyć proste klikane menu musimy podejść do tematu w trochę inny sposób.

Najpierw należy stworzyć teksturę naszego przycisku (np. w paincie). Ja na szybko zrobiłem taki:

Tło pozostawiłem białe nie bez powodu, o czym niedługo się przekonasz. Kolejnym krokiem jest dodanie tekstury przycisku do zasobów gry. W tym celu klikamy dwa razy na ten element:

 

W efekcie powinno pojawić się takie oto okienko:

Po wybraniu Add->Existing Item możemy dodać do projektu grafikę naszego przycisku. Zostanie ona umieszczona w zasobach aplikacji. Teraz możemy zająć się naszym kodem.

Na początek odblokujmy sobie możliwość zmiany rozmiaru okna gry w trakcie jej działania oraz pozwolimy na wyświetlanie kursora nad polem gry (będzie nam potrzebny do klikania w przyciski). W tym celu w metodzie metodzie Initialize() umieścimy następujący kod:

Od teraz kursor będzie już widoczny, a okienko gry można dowolnie powiększać i pomniejszać.

Teraz możemy zabrać się za wyświetlenie przycisku. Aby był on w ogóle widoczny, poza dodaniem go do zasobów naszej gry musimy go jeszcze załadować i wyświetlić. Tak, więc zabierzmy się za jego załadowanie do pamięci. W tym celu musimy stworzyć zmienną typu Texture2D, nazwijmy ją PlayButtonTexture – to w niej będzie trzymany nasz obrazek. Kolejną rzeczą jest stworzenie zmiennej typu Rectangle, która będzie określała rozmiar obrazka i jego pozycję. Będzie też potrzebna do wykrywania kolizji. Nazwijmy ją recPlayButton. Dodajmy jeszcze jedną zmienną typu Color, o nazwie PlayButtonColor. Przyda się nam później. Następnie należy przejść do metody LoadContent(), aby załadować plik z obrazkiem do zmiennej typu Texture2D:

Tekstura załadowana. Teraz przydałoby się ją wyświetlić… Skoczmy, więc do metody Draw().

Teraz na chwilę się zatrzymamy, w celu wyjaśnienia co jest od czego. Pierwsza linijka odpowiada za czyszczenie ekranu i wypełnienie go jednolitym tłem. Tutaj jest to jakiś odcień niebieskiego – co oczywiście możemy sobie zmienić. Dalej wywoływana jest metoda spriteBatch.Begin();. Musi być ona wywołana zawsze przed tym, zanim użyjemy metody Draw(). Teraz jesteśmy w miejscu najważniejszym. Ta linijka odpowiada za rysowanie na ekranie naszego przycisku. Argumenty jakie przyjmuje to kolejno: tekstura z naszym obrazkiem, zmienna typu Rectangle, która określa położenie naszego obrazka, oraz jego wielkość. Ostatnim argumentem jest kolor jaki ma przyjąć nasza tekstura – właśnie dlatego pisałem, że najlepiej jak obrazek będzie biały. Kolejna linijka kodu to wywołanie kończące rysowanie klatki.

Pomimo całej naszej pracy, na tym etapie przycisk jeszcze nie będzie widoczny. To dlatego, że nie określiliśmy jego położenia, ani rozmiaru – koloru też nie. Jednak tym zajmiemy się w metodzie Update(), albowiem takie rzeczy właśnie tam się robi.

W metodzie Update(), ustalamy jakie rozmiary i położenie ma mieć nasz element (w tym przypadku przycisk). Robi się to z pomocą właściwości obiektu klasy Rectangle:

Wypada nadmienić, że GraphicsDevice.Viewport.Height/Width zwraca rozmiar istniejącego okna gry.

Po dodaniu tych linijek kodu przycisk jest w końcu wyświetlany. Aktualizuje on swoje rozmiary i położenie przy zmianach rozmiaru okna. Jest jednak nadal martwy. Teraz należy zabrać się za sprawdzanie czy kursor znajduje się nad przyciskiem oraz czy użytkownik w niego klika. W tym celu możemy stworzyć niewidzialny kwadracik o rozmiarach 1x1px, który będziemy przesuwać wraz z kursorem:

Oczywiście powyższą metodę należy wywołać w metodzie Update();
Teraz wystarczy tylko sprawdzać czy nasz kursor-kwadracik styka się z prostokątem reprezentującym przycisk i czy LPM jest wciśnięty:

Wszystkie akcje, które mają zostać wykonane po kliknięciu przycisku umieszczamy pod linijką PlayButtonColor = Color.Red;

MonoGame – podział gry na sceny

Gra posiadająca same menu, to trochę słaby pomysł. Wypadałoby, żeby po kliknięciu przycisku „Graj” jakaś rozgrywka faktycznie się zaczynała. W tym celu należałoby podzielić grę na sceny. W najbardziej łopatologiczny sposób można to zrobić na przykład tak:

1. Tworzymy dwa nowe pliki o nazwach MenuScene.cs oraz GameScene.cs, które będą rozszerzały klasę główną gry (oczywiście nic nie stoi na przeszkodzie, aby były to dwie odrębne klasy, ale dla ułatwienia na potrzeby tego artykułu wszystko wpakuję w jedną – co nie jest zbyt eleganckie przy większych projektach).

2. Tworzymy także nowy plik, w którym umieścimy typ wyliczeniowy:

3. Przenosimy wszystkie elementy związane z menu z pliku Game1.cs do MenuScene.cs:

4. Analogicznie modyfikujemy plik GameScene.cs:

5. W pliku Game1.cs tworzymy nowy obiekt wyliczeniowy i za jego pomocą sterujemy tym, które metody Update() i Draw() będą wykonywane:

6. W pliku MenuScene.cs modyfikujemy metodę ButtonEvents, tak aby po kliknięciu przycisku zmieniły się wykonywane metody UpdateMenu() na UpdateGame() oraz DrawMenu() na DrawGame():

Po tych operacjach po kliknięciu w przycisk ten powinien zniknąć. Dzieje się tak dlatego, że po jego kliknięciu od teraz wywoływane będą metody DrawGame() i UpdateGame():

MonoGame – obsługa klawiatury, poruszanie obiektami

Skoro mamy już menu, to wypadałoby wyświetlić coś po wciśnięciu przycisku „Graj”. Niech będzie to jakiś prosty kwadracik, którym można poruszać za pomocą strzałek na klawiaturze. Kod czegoś takiego może wyglądać np. tak:

Myślę, że kod jest na tyle prosty, że nie wymaga tłumaczenia. Właściwie przy odrobinie kreatywności przy pomocy powyższych instrukcji łatwo można stworzyć prostego Snake’a. Nie będę jednak już pokazywał w tym artykule, jak tego dokonać.

Oczywiście to co udało mi się tutaj przedstawić to same podstawy podstaw MonoGame i tworzenia gier w ogóle. Jeżeli ktoś chce wziąć się za samodzielne tworzenie gier, musi ogarnąć również rzeczy związanie z grafiką, dźwiękiem i… matematyką.


Zdjęcie główne artykułu pochodzi z unsplash.com.

Zapraszamy do dyskusji

Patronujemy

 
 
Polecamy
Od gracza do twórcy gier. Historia Pawła Kubiaka