Posts tagged ‘game development’

Wyjście = format + ujście

2007-08-19 11:58

Dla odmiany zająłem się ostatnio czymś nieco prostszym niż GUI, a mianowicie systemem logującym. To zdecydowanie niezbędne narzędzie, które docenia się zwłaszcza wtedy, kiedy coś innego psuje. Jak dotąd aczkolwiek nic poważnego nie zdążyło się jeszcze, ale w programowaniu jest to, jak wiadomo, tylko kwestią czasu ;)

Do logowania istnieje mnóstwo podejść, które różnią się głównie tym, gdzie i jak zapisujemy nasze informacje. Bo jeśli o to, co mamy logować, to odpowiedź jest prosta: wszystko albo jak najwięcej.
Dość obszerny przegląd możliwych wyjść loggera przedstawił TeMPOraL w jednej swoich notek. Jakkolwiek długa ta lista by nie była, pewne jest jedno: na pewno nie może być ona zapisana “na sztywno” w kodzie głównej klasy systemu logującego. Same ‘wyjścia’ należy bowiem opakować w osobne klasy, wywodzące się ze wspólnej bazy i wykorzystać zalety polimorfizmu. W ten sposób można kierować komunikaty zarówno na konsolę, do zwykłego pliku tekstowego, jak i do funkcji OutputDebugString.

Pomyślałem jednak nad rozszerzeniem tego pomysłu, który wyraża się w tytułowym równaniu: wyjście = format + ujście. Postanowiłem oto podzielić wyjście logowania na dwie części:

  • format odpowiadający za wygląd pojedynczego wpisu. W zależności od rodzaju obiekt ten przerabia komunikat logowania na zwykły tekst, XHTML, XML albo inny, dowolnie wybrany format danych.
  • ujście (drain), zajmujące się zapisaniem sformatowanego komunikatu w określonym miejscu. Tym miejscem może być standardowe wyjście diagnostyczne (czyli zwykle konsola), lokalny plik czy nawet plik na zdalnym serwerze.

Schemat systemu logującego

Nie jest to oczywiście żadna rewolucja. Czasami też obie części muszą być do siebie ściśle dopasowane, aby osiągnąć pożądany efekt (np. wypisywanie kolorowych komunikatów w asynchronicznej konsoli Windows). Zwykle jednak można je zestawiać dowolnie i otrzymywać ciekawe rozwiązania. Najważniejsze, że tą dodatkową elastyczność da się osiągnąć małym kosztem.

Tags: , ,
Author: Xion, posted under Programming » Comments Off on Wyjście = format + ujście

Poczucie misji

2007-08-17 20:36

Często na warsztatowym kanale IRC nie dzieje się nic ciekawego, ale czasem można usłyszeć tam coś interesująco. I czasem nawet kontrowersyjnego – jak choćby ta oto opinia:

[19:19:21] przy gamedevie brakuje poczucia misji
[19:19:29] to tak jakbym sprzedwal narkotyki dzieciom

Może jest ona nieco skrajna, ale to nie znaczy, że nie warto z nią polemizować. Jaka jest więc misja przemysłu wytwarzającego gry komputerowe?… W odpowiedzi na podobne wątpliwości lubimy zwykle sięgać do przykładu kinematografii – z tego porównania korzysta się na przykład wtedy, kiedy ktoś (po raz nie wiadomo który) podnosi kwestię nadmiernej brutalności gier.
Nie da się ukryć, że obecnie główną misją obu dziedzin twórczości jest dbanie o odpowiednio przyzwoity stan kont osób, które się nimi parają. W przemyśle filmowym jest domena hollywoodzkich superprodukcji, słynących z wtórności, powtarzalności, słabej strony fabularnej i przeładowaniem efektami specjalnymi. To nie przypadek, że dokładnie te same epitety możemy dopasować do wielu komercyjnych gier.

Jednocześnie mamy też tzw. kino niezależne, które celuje w nieco bardziej wyrafinowane gusta. Być może jego odpowiednikiem w przemyśle gier komputerowych są produkcje realizowane przez małe, czasem nawet nieprofesjonalne zespoły. Ich dzieła zwykle nie są nastawione na stronę techniczną – grafikę zapierającą krew żyłach i mrożącą dech w piersiach (;]) i superrealistyczne efekty dźwiękowe. Zamiast tego posiadają to, co bardzo rzadko przejawia się w komercyjnych grach – oryginalność.

Screen z gry Aquaria, jednego z laureatów IGF 2007

Można jej zasmakować przeglądając chociażby zwycięskie produkcje z dorocznego Independent Games Festival. Mówienie o nich jako o “zwykłych casualach” byłoby według mnie grubym nietaktem ;P

Tags:
Author: Xion, posted under Games, Thoughts » 4 comments

System GUI #3 – Zdarzenia

2007-08-14 11:42

Graficzny interfejs może (i powinien) ładnie wyglądać, ale jego najważniejszą cechą jest oczywiście interaktywność. GUI musi przede wszystkim umożliwiać reakcję na poczynania użytkownika, a to jest możliwe przez odpowiednią obsługę zdarzeń.

Informację o tym, że kliknięto gdzieś myszką lub wciśnięto klawisz, pozyskać jest oczywiście bardzo łatwo. W Windows wystarczy w tym celu obsługiwać komunikaty systemowe przychodzące do okna. O wiele większym wyzwaniem jest przetłumaczenie informacji “kliknięto w punkt (458,89) obszaru klienta” na “kliknięto w obrębie kontrolki TextBox o nazwie Text1“. Komunikat należy bowiem przekazać do odpowiedniej kontrolki (-ek) – tej, której on dotyczy.

Jest pewnie wiele sposób na zrealizowanie takiego przekazywania, jednak najbardziej sensowne wydają mi się dwa. Pierwszym z nich jest propagacja zdarzenia na kolejne poziomy drzewa systemu GUI. Kliknięcie jest więc przekazywane do okna i od tej pory cała odpowiedzialność za jego obsłużenie spada na to właśnie okno. Ono sprawdza, czy myszka trafiła w którąś z kontrolek potomnych; jeśli tak, to znów zdarzenie jest przekazywane właśnie do tej kontrolki i okno się już nim nie zajmuje. Proces ten przebiega aż dojdziemy do najniższego możliwego poziomu, czyli kontrolki nie zawierającej już żadnych innych. Tam następuje właściwa obsługa zdarzenia.

Przepływ zdarzeń w systemie GUI - wariant pierwszy

Drugi sposób zakłada, że zdarzenie nie będzie “rozłazić” się po całym drzewie, tylko od razu trafiać do tej właściwej, docelowej kontrolki. Naturalnie nie zawsze da się tak zrobić. W przypadku wciśnięć klawiszy możemy pamiętać, która kontrolka ma tzw. fokus i do niej kierować komunikaty. Natomiast obsługa myszki wymaga wysłania swego rodzaju sondy wgłąb drzewa kontrolek w celu znalezienie tej najmniejszej, w którą trafił kursor (operacja ta jest znana jako hit test).

Przepływ zdarzeń w systemie GUI - wariant drugi

Generalnie zdarzenia z punktu widzenia ich obsługi można podzielić na dwie grupy: na zdarzenia myszki i na… wszystkie inne :) To właśnie te pierwsze przysparzają najwięcej kłopotów. Nie tylko wymagają rekurencyjnego przeszukiwania drzewa kontrolek, ale też mogą mieć globalne konsekwencje, jak np. zmiana fokusu. Zajmowanie się tymi konsekwencjami jest łatwiejsze, gdy wszystko odbywa się “na górze” – na poziomie głównych klas systemu GUI, a nie pojedynczych kontrolek. Jest to jeden z powodów przemawiających za wyborem drugiego sposobu przekazywania informacji o zdarzeniach.

Tags: , , ,
Author: Xion, posted under Programming » 1 comment

System GUI #2 – Model klas

2007-08-06 11:40

Po kilku dniach niezbyt intensywnego zastanawiania się nad strukturą systemu GUI zdołałem wysmażyć coś, co można nazwać schematycznym modelem klas. Jest on wybitnie poglądowy i przedstawia się następująco:

Schemat modelu klas systemu GUI

Na pierwszy rzut oka może wydawać się nieco pokręcony, ale z odrobiną wysiłku można go odcyfrować :] To co jest w nim chyba najważniejsze, to dość wyraźny podział na część rysującą (lewa strona) i tę zawierającą kontrolki. Łączy je “most” złożony z głównej klasy systemu, nazwanej bardzo oryginalnie: System :) Taki podział zapewni między innymi prostsze rysowanie samych kontrolek, które będą renderowane przy pomocy takich poleceń jak: narysuj ramkę, narysuj wciśnięty przycisk, itp. To rozwiązanie zaczerpnąłem z Windows.Forms, gdzie jest zresztą ono posunięte o wiele dalej.
Lista kontrolek jest, jak widać, skromna i odpowiada tej, którą wcześniej ustaliłem jako minimalną. Oprócz abstrakcyjnej klasy dla wszystkich kontrolek wprowadziłem też osobną dla takich, które mogą zawierać i grupować inne kontrolki. Póki co jedynym obiektem tego rodzaju jest okno, ale GUI zna ich więcej: jak np. panel czy pole grupy (groupbox).

Tags: , ,
Author: Xion, posted under Programming » 1 comment

System GUI #1 – Założenia

2007-08-02 14:21

Skoro posiadam już w miarę sprawnie działający moduł odpowiedzialny za grafikę 2D, pora zająć się jednym z jego najważniejszych zastosowań. Bo oprócz menu czy ekranu powitalnego, najważniejszymi dwuwymiarowymi elementami w każdej grze są składowe jej interfejsu użytkownika.

Po co jednak pisać własny system GUI?… Cóż, oprócz uniwersalnej odpowiedzi (“bo tak”) można odrzec, że to po prostu fajne :) W bardziej rozwiniętej wersji uzasadnienie brzmi tak, iż struktura wewnętrzna dobrze napisanego systemu UI to jeden z najelegantszych przykładów zastosowania programowania obiektowego w praktyce. Jest w niej aż gęsto od przeróżnych wzorców projektowych i całość aż kipi od kipi od tego, co nazywam estetyką projektowania.
Tej estetyki niestety będzie mi prawdopodobnie brakować, kiedy później zajmę się już całkiem “trójwymiarowymi” aspektami silnika, jak modelami, organizacją sceny, materiałami, oświetleniem, itd. Zbalansowanie wydajności z przejrzystością interfejsu (i kodu) na pewno będzie wymagało wielu nieprzyjemnych wysiłków…

System UI z D3DX

A zatem z pewną przesadą można powiedzieć, że system GUI będę pisał dla… relaksu :) Wobec takiego postawienia sprawy nie ma rzecz jasna potrzeby odpierania argumentów w stylu: “Przecież to nie jest potrzebne!”, “W D3DX jest już system GUI (screen powyżej)”, “Dublujesz kontrolki systemowe”, itd. Dla porządku dam aczkolwiek jedno logiczne uzasadnienie: te gatunki gier, które najbardziej lubię – a więc RPGi, strategie czy gry ekonomiczne – wykorzystują UI w dużych i różnorodnych ilościach. Więc jeśli kiedyś… no ale do tego chyba przejdziemy znacznie później :]

GUI jest jedną z tych rzeczy, które można rozbudowywać właściwie w nieskończoność. Dlatego przydałoby się od razu ustalić założenia, jakie chciałbym spełnić. Przede wszystkim nie chcę mnożyć kontrolek ponad potrzebę, gdyż sądzę, że w dużej części przypadków do szczęścia wystarczą poniższe cztery:

  • etykieta (label) – czyli kontrolka ze statycznym tekstem
  • przycisk (button)
  • pole tekstowe (edit, textbox)
  • pole wyboru (checkbox) – kontrolka umożliwiająca włączanie/wyłączanie opcji

Faktycznie przydałaby się jeszcze jakaś kontrolka listowa, ale na razie dla prostoty chcę ją pominąć.
Oprócz samych kontrolek ważne jest też odpowiednie ich zachowanie się. Przede wszystkim chodzi tu o kwestię fokusu klawiatury czy capture myszki, a także funkcjonalności klawisza Tab. Krótko mówiąc, chciałbym żeby mój system przypominał UI, które na co dzień można spotkać w Windows.

Tyle że moje UI nie musi być wcale takie “piękne” jak Aero z Windows Vista ;P

Tags: ,
Author: Xion, posted under Programming » 1 comment

Jak się liczy wartość FPS

2007-08-01 13:32

Pod koniec notki na temat czcionek i wyświetlania tekstu stwierdziłem, że podstawowym zastosowaniem tej części biblioteki 2D jest wyświetlanie licznika FPS :) No cóż, pewnie wcale nie byłem daleko od prawdy, bo – jak wiadomo – pomiar ilości generowanych na sekundę klatek jest bardzo ważny. Można to jednak robić na kilka sposobów.

Najprościej jest chyba policzyć czas generowania ostatniej klatki w sekundach – i tak jest to potrzebne do wyliczenia nowych pozycji obiektów i uaktualnienia całej reszty symulacji. Odwrotność tego czasu będzie chwilową wartością FPS.
Można też policzyć ilość klatek narysowanych w jakimś krótkim odstępie czasu poprzedzającym chwilę obecną – zwykle w ciągu jednej sekundy. Wtedy podzielenia owego czasu przez zliczoną ilość ramek da nam średnie FPS.
Wreszcie można te obliczenia prowadzić od momentu uruchomienia gry, śledząc liczbę klatek i całkowity czas jej działania. Wówczas iloraz tych wartości da nam całkowite FPS.

Która wartość jest najlepsza do pokazania na ekranie?… Wartość chwilową najłatwiej wyliczyć, ale zmienia się ona co klatkę, więc podlega przypadkowym wahaniom, niemającym związku z kodem samej gry. Poza tym tak częsta aktualizacja sprawiałaby, że nie dałoby się tej wartości odczytać. W praktyce trzeba by więc wypisywać ją rzadziej, np. co sekundę, co komplikuje sprawę.
Z kolei całkowite FPS ze względu na swój globalny charakter “spłaszcza” wszelkie wahania. Im dłużej działa gra, tym mniejszy jest związek między jego wartością, a tym co widać na ekranie. Może się więc zdarzyć, że gra będzie strasznie przycinała, podczas gdy całkowite FPS będzie stało na wartościach 60-70 z lekką tylko tendencją spadkową.

Najlepiej jest więc pokazywać średnie FPS wyliczone dla jakiegoś krótkiego odcinka czasu. Ponieważ jego wartość zmienia się często (zazwyczaj co sekundę), dobrze odzwierciedla aktualną szybkość generowania klatek. Zmiana parametrów sceny (ilość wielokątów, światła, cienie, odbicia, cząsteczki, pozycja kamery, itd.) może więc prawie natychmiast przełożyć się na wartość FPS. Negatywnie, rzecz jasna :]
Jednocześnie średnie FPS nie podlega tak bardzo chwilowym i losowym zatorom na linii procesor-RAM-karta graficzna, gdyż jest liczone na podstawie przynajmniej kilkudziesięciu próbek.

I chociaż średnie FPS liczy się w nieco bardziej skomplikowany sposób niż chwilowe, jego wyznaczenie nie jest niczym trudnym ani kosztownym:

  1. UInt Frames = 0;
  2. Time time1 = GetTime(), time2;
  3. Time fpsTime = 0;
  4. while (!bClosing)   // główna pętla
  5. {
  6.   time2 = GetTime();
  7.   Time dt = time2 - time1;     // czas generowania ostatniej klatki
  8.   time1 = time2;
  9.  
  10.   // rysowanie klatki
  11.   Update (dt);
  12.   Render();
  13.  
  14.   ++Frames;
  15.   fpsTime += dt;
  16.   if (fpsTime >= 1.0)     // minęła sekunda
  17.   {
  18.     FPS = fpsTime / Frames;
  19.     fpsTime = 0;
  20.     Frames = 0;
  21.   }
  22. }

Jedno jest pewne: wartość FPS nie powinna spadać od samego jej liczenia ;)

Tags: ,
Author: Xion, posted under Programming » 3 comments

Czcionki i tekst(ury)

2007-07-30 9:08

Żadna porządna biblioteka do grafiki 2D nie może obyć się bez narzędzi służących do wypisywania tekstu. Kiedy jednak mamy na uwadze głównie programowanie gier (lub pokrewnych aplikacji), sprawa wygląda nieco inaczej niż w bardziej “ogólnych” zastosowaniach. Nie trzeba na przykład rozkładać tekstu na czynniki pierwsze:

Linie pisma

Pozwalałoby to oczywiście w razie potrzeby dodać kursywę, pod-, nad- i przekreślenie. Zazwyczaj jednak nie jest to potrzebne.

Tak więc chociaż wygląda to na krok wstecz, stosuje się najczęściej czcionki bitmapowe. Pomysł polega na tym, że cały używany zestaw znaków danego kroju i danej wielkości jest rysowany na jednej teksturze w taki sposób, by łatwo można było obliczyć pozycję każdego znaku:

Tekstura czcionki Arial stworzona programem Bitmap Font Builder Tekstura czcionki Verdana stworzona programem Bitmap Font Generator

Wyświetlanie napisu polega wtedy na renderowaniu oteksturowanych prostokątów – po jednym dla każdego znaku. Jest to bardzo wydajne, bo chociaż trójkątów może być bardzo dużo, to ich tekstura jest zawsze taka sama. Można zatem wyrzucić całe połacie tekstu na ekran tylko jednym wywołaniem Draw[Indexed]Primitive.

Tylko skąd wziąć taką sprytną teksturę? Można rzecz jasna generować ją samemu przy pomocy funkcji GDI; choć jest to wolne, byłoby wykonywane tylko raz, więc nie stanowiłoby problemu. Lepszym rozwiązaniem jest użycie odpowiednich programów, z których najlepszym jest chyba Bitmap Font Generator. Potrafi on w sprytny sposób upakować w jednym obrazku sporą ilość znaków, zaś ich parametry opisuje w łatwym do odczytania formacie tekstowym przypominającym szczątkowy INI.

Obecnie używam więc właśnie jego i dzięki temu mogłem w końcu dodać do swojego silnika podstawowy i zupełnie niezbędny element: licznik FPSów :)

 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.