2004 12 Piszemy grę w stylu Quake'a [Programowanie]


dla programistów
Piszemy grę
w stylu Quake'a
Marek Sawerwain
szyscy znają takie tytuły, Napiszemy program, który pokaże
jak Quake czy Unreal. dwa komunikaty na ekranie. Dodamy
Napisanie takiego rodza- również przycisk Quit. Oczywiście, po
Wju gry to prawdziwe kliknięciu na ten przycisk nasz program
wyzwanie dla całego zespołu progra- zakończy swoje działanie. Rysunek 1
mistów, grafików oraz specjalistów od zawiera zrzut ekranu z naszym progra-
dzwięku. W tym artykule postaram się mem. Aplikacja, jak widać, nie jest zbyt
pokazać, jak zrealizować coś w rodzaju skomplikowana.
prototypu gry typu FPS. Na potrzeby Analizę programu podzielimy na
naszej gry załadujemy poziom w formacie dwie części. Listing 1 zawiera pełną treść
przeznaczonym dla gier Quake 3. Ponad- funkcji main. Tworzymy w niej wszystkie
to, wprowadzimy kolizję pomiędzy ścia- podstawowe obiekty niezbędne do dzia-
nami, aby nie można było niczym duch łania aplikacji. Właściwe przetwarzanie
przechodzić przez ściany. Zobaczymy danych realizujemy w pętli while. Po
również, w jaki sposób załadować bohate- krótkiej analizie Listingu 1 widać, że
rów gry z plików w formacie MD2 i zreali- nigdzie nie przerywamy działania aplika-
zujemy strzelanie do postaci, biegającej po cji, gdyż za przetwarzanie zdarzeń, czyli
naszym poziomie. m.in. za reagowanie na wciskane klawi-
Do tych zadań będziemy stosować sze, jest odpowiedzialny obiekt Simple-
dość młodą, ale łatwą w użyciu bibliotekę EventReceiver. Jego pełen kod prezentuje
o nazwie Irrlicht. Jest ona dostępna dla Listing 2. Na obydwu listingach zostały
systemu Windows oraz, co nas bardziej pomięte tylko dyrektywy preprocesora
interesuje, dla Linuksa. Warto już w tym oraz polecenia using namespace.
miejscu podkreślić, że do tej biblioteki Pierwszą czynnością w funkcji main
polecana jest karta z akceleracją OpenGL, jest utworzenie urządzenia. Realizuje to
DVD
czyli układ NVIDIA bądz ATI. Z tego poniższa linia kodu:
Po uruchomieniu dystrybucji
powodu należy zadbać, aby odpowiednie
Linux+ Live DVD można przete-
S
sterowniki dla systemu X Window zostały device = createDevice(EDT_OPENGL,
stować omawiane programy.
S
poprawnie zainstalowane. dimension2d(resx, resy), 16,
Można zapytać się, dlaczego zamiast false, false, false, 0);
Na płycie CD/DVD
biblioteki Irrlicht nie zastosujemy jednego
Na płycie CD/DVD znajdują
z wielu projektów modyfikujących udo- Parametrów jest wiele, ale tylko trzy począt-
się wykorzystywane biblioteki,
stępniony kod zródłowy gier Quake 1 kowe są dla nas istotne. W pierwszym
kod zródłowy programów oraz
wszystkie listingi z artykułu. i 2. To oczywiście świetny pomysł, ale określamy system wspomagający tworze-
biblioteka Irrlicht jest bardziej przejrzysta nie grafiki 3D. W naszym przypadku ma
O autorze
i łatwiejsza do zastosowania  kod zró- to być OpenGL. W następnym, za pomocą
Autor zajmuje się tworzeniem
dłowy Quake'a jest mało czytelny, skom- wzorca, określamy wymiary okna. Aktual-
oprogramowania dla Windows
plikowany i wymaga głębszych studiów. ne wymiary są zapamiętane w zmiennych
i Linuksa. Zainteresowania: teoria
resx, resy. Wartość szesnaście to ilość
języków programowania oraz
Przykład na rozgrzewkę bitów opisujących kolor. Obecnie dopusz-
dobra literatura.
Zanim zaczniemy tworzyć naszą grę, czamy już tylko dwie wartości: 16 bądz 32.
Kontakt z autorem:
autorzy@lpmagazine.org chciałbym podać przykład, który łagod- Następny parametr pozwala określić, czy
nie wprowadzi nas do biblioteki Irrlicht. ma zostać włączony tryb pełnoekranowy.
66
grudzień 2004
dla programistów
piszemy mini-quake'a
Na początku tworzenia aplikacji warto
korzystać z trybu okna, a na konsolę termi-
nala przesyłać komunikaty o błędach, czy
innego rodzaju informacje.
Następne dwie linie z Listingu 1
tworzą obiekt odbierający zdarzenia oraz
rejestrują ten obiekt za pomocą metody
setEventReceiver. Pózniej metodą setWin-
dowCaption ustalamy tytuł okna. Gdy nie
korzystamy z trybu pełnoekranowego,
pojawi się on na belce okna tytuło-
wego. Kolejne trzy linie realizują dość
istotne zadanie, gdyż odczytują obiekty
reprezentujące pozostałe części systemu
Irrlicht: sterownik obrazu (typ IVideoDri-
ver), menedżer sceny (typ ISceneManager)
oraz obiekt odpowiedzialny za interfejs
użytkownika (będę go nazywał również
Kilka słów o bibliotece
Rysunek 1. Ekran naszego pierwszego programu w Irrlicht
Irrlicht
Irrlicht wyróżnia się na tle innych bibliotek
bardzo przystępnym API w języku C++.
menedżerem interfejsu GUI), czyli za
Kompilacja oraz instala-
Chociaż biblioteka nie jest obszerna
przyciski, okna dialogowe i inne kontrol-
(skompresowany kod zródłowy to ok. 1
cja biblioteki Irrlicht
ki ekranowe (typ IGUIEnvironment).
MB), to oferuje całkiem duże możliwo-
Irrlicht nie korzysta jeszcze (przynajm-
Po wykonaniu wstępnych czynności
ści. Możliwe jest wczytywanie obiektów
niej w trakcie powstawania tego tekstu)
tworzymy na ekranie dwa komunikaty
trójwymiarowych w formatach m.in. MD2,
z systemów automake i autoconf. Kom-
3DS oraz map w formacie bsp z gry oraz przycisk. Dodanie etykiety tekstowej
pilację musimy przeprowadzić samo-
Quake 3. Irrlicht obsługuje także podsta-
realizuje tylko jedna linia kodu, korzysta- dzielnie. Warto zwrócić uwagę na fakt,
wowe formaty plików graficznych, takie
jąca z metody addStaticText, z obiektu że w oryginalnym pakiecie autor zawsze
jak jpg, tga, bmp. Brakuje mu natomiast
przygotowuje bibliotekę statyczną, więc
menedżera GUI:
wsparcia dla plików png.
może się okazać, iż kompilacja nie jest
Wielką zaletą API jest możliwość
potrzebna.
S
guienv->addStaticText(L"Hello World!!!",
tworzenia niejednorodnych scen. Biblio-
Jeśli chcemy dokonać kompilacji, to
rect(10,70,100,82), true);
teka pozwala tworzyć scenę za pomocą
trzeba upewnić się, czy została popraw-
węzłów. Pozwala to na łączenie pod-
nie zainstalowana obsługa OpenGL oraz
W pierwszym argumencie podajemy treść
stawowych dwóch typów scen  indoor
środowisko X Window. Jeśli wszystko jest
komunikatu do wyświetlenia, poprzedzając
(sceny w pomieszczeniach zamkniętych,
w porządku, to wystarczy rozpakować
makrem L ciąg ujęty cudzysłowami  Irr-
czyli główne mapy bsp z gier typu Quake)
zródła biblioteki:
ze scenami outdoor (sceny na terenie licht oczekuje komunikatów w Unikodzie.
otwartym).
W drugim argumencie określamy współ- # unzip source.zip
Jeszcze raz podkreślę czytelność
rzędne prostokąta, w którym ma zostać
najlepiej w tym samym katalogu, w którym
API, ponieważ węzłami zarządzamy
wyświetlony komunikat. Wartość true na
się znajdują. Następnie musimy wydać
podobnie jak typami dynamicznymi.
końcu oznacza, że ma zostać narysowana
polecenie make i po paru chwilach w kata-
Węzeł możemy w dowolnym momencie
ramka otaczająca tekst komunikatu.
logu ~/irrlicht-0.7/lib/linux (jeśli główną dys-
utworzyć, usunąć bądz tylko ukryć.
W niemal identyczny sposób umiesz-
trybucję rozpakowaliśmy w katalogu
Chociaż biblioteka Irrlicht została
czamy na ekranie przycisk. Linia kodu
domowym) zobaczymy nowy plik biblio-
pierwotnie napisana w C++, to są przy-
realizująca tę czynność wygląda nastę-
teczny libIrrlicht.a. Możemy bez zbędnych
gotowane wersje dla innych języków,
pująco:
kłopotów kompilować przykłady dołączone
np. Java. Niestety, oprócz C++, jak na
do biblioteki po wydaniu polecenia make.
razie, oficjalnie wspierana jest tylko
S
Autorzy, choć nie dostarczyli pliku confi-
platforma .NET. Biblioteka jest obecnie guienv->addButton(rect
gure, to zadbali, aby proces budowania
dostępna dla dwóch systemów: Windows
(100,210,200,240), 0, OK_BTN_ID, L"Quit");
i kompilacji przykładów był jak najmniej
oraz Linux. W przypadku Linuksa, jako
skomplikowany.
podstawowy system grafiki 3D jest sto-
Kolejność argumentów, w stosunku do
Jeśli chcemy kompilować przykłady
sowany OpenGL. Dla osób, które mają
metody addStaticText, została niestety
znajdujące się na płycie dołączonej do
dobre karty graficzne, lecz brakuje im
zmieniona. Mamy również nowe argu-
pisma, wystarczy tylko zmienić katalogi,
linuksowych sterowników, dobrą wiado-
menty: w drugim autorzy Irrlicht dają
w których znajdują się biblioteka statycz-
mością jest fakt, iż Irrlicht oferuje tryb
możliwość określenia rodzica kontrolki,
na libIrrlicht.a oraz pliki nagłówkowe.
programowy grafiki 3D.
natomiast w trzecim podajemy wartość
67
www.lpmagazine.org
dla programistów
pierwszym programie mamy tylko jedno
Listing 1. Treść funkcji main pierwszego przykładu
zdarzenie, a mianowicie sytuację, gdy
S
device = createDevice(EDT_OPENGL, dimension2d(resx, resy), 16, false,
użytkownik kliknie na przycisk umiesz-
false, false, 0);
czony w naszej aplikacji. Aby wykryć to
zdarzenie, w metodzie OnEvent spraw-
SimpleEventReceiver rcv;
dzamy, czy typ zdarzenia podanego
device->setEventReceiver( &rcv );
w argumencie event tej metody jest równy
device->setWindowCaption(L"Hello World! - First Example");
wartości EET_GUI_EVENT (inaczej mówiąc,
mamy do czynienia ze zdarzeniem zwią-
IVideoDriver* driver = device->getVideoDriver();
zanym ze środowiskiem użytkownika).
ISceneManager* smgr = device->getSceneManager();
Jeśli wystąpiła taka sytuacja, to musimy
IGUIEnvironment* guienv = device->getGUIEnvironment();
sprawdzić identyfikator klikniętego kom-
guienv->addStaticText(L"Hello World!!!", rect(10,70,100,82), true);
ponentu:
guienv->addStaticText(L"Irrlicht based!!!", rect(10,90,100,102), true);
guienv->addButton(rect(100,210,200,240), 0, OK_BTN_ID, L"Quit");
s32 id = event.GUIEvent.Caller->getID();
while(device->run())
Na Listingu 2 widać również, że odczy-
{
driver->beginScene(true, true, SColor(0, 200, 200, 200));
tujemy obiekt menedżera. Następnie, za
smgr->drawAll();
pomocą switch, sprawdzamy, jaki typ
guienv->drawAll();
zdarzenia pojawił się. Jeśli było to klik-
driver->endScene();
nięcie na przycisk, to analizujemy, czy
}
identyfikator zapisany w zmiennej id jest
device->drop();
równy OK_BTN_ID, a jeśli tak, to wywołu-
jemy metodę close obiektu driver, czyli
Listing 2. Odbieranie zdarzeń w pierwszym przykładzie zamykamy cały program.
W ten sposób uzyskaliśmy dość
class SimpleEventReceiver : public IeventReceiver prosty, ale kompletny program napisany
{
za pomocą biblioteki Irrlicht. Zachęcam
public:
do dodania drugiego przycisku, który np.
virtual bool OnEvent(SEvent event)
wysyła komunikat na konsolę.
{
if (event.EventType == EET_GUI_EVENT) {
s32 id = event.GUIEvent.Caller->getID(); Mini-Quake  obsługa
IGUIEnvironment* env = device->getGUIEnvironment();
zdarzeń
switch(event.GUIEvent.EventType) {
Następny przykład, który chciałbym poka-
case EGET_BUTTON_CLICKED: {
zać, jest znacznie dłuższy, więc w artykule
if (id == OK_BTN_ID) {
będą znajdować się tylko wybrane frag-
device->closeDevice();
return true; menty. Pełny kod zródłowy znajduje się na
}
płytach dołączonych do tego pisma. Rysu-
} break; nek 2 pokazuje ujęcie z naszego projektu.
} Pogląd na budowę programu daje schemat
}
zawarty na Rysunku 3.
return false;
Podobnie jak w poprzednim progra-
}
mie, utworzymy oddzielny obiekt do ana-
};
lizy zdarzeń. Będziemy w nim sprawdzać,
czy został naciśnięty klawisz [Esc] i jeśli
liczbową, za pomocą której w obiekcie tach określają, czy ma być kasowany bufor pojawi się taka sytuacja, podobnie jak to
SimpleEventReceiver będziemy wykry- obrazu oraz bufor głębokości). Następnie było poprzednio, zakończymy działanie
wać, czy kliknięto na ten przycisk. rysujemy wszystkie obiekty z menedżera
W ten sposób praktycznie zrealizowa- sceny oraz wszystkie elementy z mene-
liśmy wszystkie istotne czynności w funk- dżera GUI, czyli dwa komunikaty tek-
cji main. Musimy już tylko uruchomić pętlę stowe oraz przycisk. Koniecznie trzeba
while, w której zacznie poprawnie działać zakończyć rysowanie sceny metodą end-
nasza aplikacja. Treść tej pętli znajduje się Scene. Jeśli wartość końcowa metody run
na Listingu 1. Jak widać, pętla działa tak to logiczna prawda, ponownie powtarza-
długo, jak metoda run zwraca wartość my opisane powyżej czynności.
prawdziwą. W treści pętli wykonujemy
tylko trzy czynności. Rozpoczynamy Przetwarzamy zdarzenia
tworzenie sceny metodą beginScene (dwie Za przetwarzanie zdarzeń jest odpowie-
Rysunek 2. Mini-Quake w Irrlicht
pierwsze wartości logiczne w argumen- dzialna klasa z Listingu 2. W naszym
68
grudzień 2004
dla programistów
piszemy mini-quake'a
S
level_node = smgr->addOctTreeSceneNode
(level->getMesh(0));
Po wykonaniu tej operacji należy wyko-
nać jeszcze kilka ważnych czynności.
Pierwszą z nich jest ustawienie poziomu
pod odpowiednimi współrzędnymi:
S
level_node->setPosition
(core::vector3df(-1370,-130,-1400));
Oczywiście, mogą się one zmieniać.
Ponieważ korzystamy z gotowych ele-
mentów zawartych w bibliotece Irrlicht,
to wykorzystamy współrzędne, jakimi
posługuje się autor tej biblioteki. Drugą
czynnością jest ustalenie identyfikatora
poziomu, gdyż będzie to bardzo przydane
w przypadku wykrywania zdarzeń.
Trzy następne linie realizują bardzo
Rysunek 3. Schemat działania naszego Mini-Quake'a
ważny element, czyli wykrywanie kolizji.
Tworzymy tzw. selektor. W dalszej części
programu. Najważniejszy fragment kodu nie wczytuje jeszcze samego poziomu, ale połączymy ten selektor z kamerą i w ten
metody OnEvent zawiera Listing 3. dodaje do wirtualnego systemu plików sposób nie będzie można, niczym duch,
Nie ma znaczących różnic w obsłu- archiwum z poziomem oraz teksturami przenikać przez ściany. Selektor tworzy-
dze zdarzeń w stosunku do poprzednio (w rzeczywistości pliki pk3 to archiwa my następująco:
omówionego przykładu. Upewniamy się, w formacie zip). Wczytanie danych pozio-
S
czy wystąpiło zdarzenie EET_KEY_INPUT_ mu wykonuje następna linia kodu: selector = smgr->createOctTreeTriangle
S
EVENT (zdarzenia związane z klawiaturą). Selector(level->
S
Wewnątrz instrukcji warunkowej, za scene::IAnimatedMesh* level = getMesh(0), level_node, 128);
pomocą konstrukcji switch, sprawdzamy, smgr->getMesh("20kdm2.bsp");
jakie klawisze zostały naciśnięte. Jeśli jest Zwróćmy uwagę na to, że nazwa funkcji
to [spacja], to na ekran terminala wysy- Trochę mylący jest typ, jakim się posługu- tworząca selektor odwołuje się do drzew
łamy aktualne współrzędne kamery. Jeśli jemy  IAnimetedMesh wskazuje, że mamy ósemkowych. Po utworzeniu selektora,
naciśnięto klawisz [Esc], to postępujemy do czynienia z obiektem animowanym. należy go przekazać do węzła reprezen-
identycznie jak w poprzednim przykła- Biblioteka ujednolica w ten sposób wszyst- tującego cały poziom i to zadanie realizu-
dzie. kie obiekty statyczne oraz animacje, takie je następującą linia:
Możemy teraz zapytać się, jak mamy jak np. obiekty MD2. Wydając poprzednią
poruszać kamerą czy zmieniać kierunek komendę, nie powodujemy jeszcze zała- level_node->setTriangleSelector(selector);
spojrzenia za pomocą myszki. Irrlicht dowania poziomu, gdyż należy utworzyć
realizuje tego typu zadania samodzielnie odpowiedni węzeł sceny. Aby utrzymać To wszystkie operacje, które trzeba
i nie trzeba ich własnoręcznie programo- wysoką wydajność, Irrlicht posługuje wykonać, aby uzyskać poziom przezna-
wać. się drzewami ósemkowymi. Utworzenie czony do zwiedzania. Zwracam uwagę
takiej struktury z wczytanego poziomu na ostatnią linię Listingu 4. Wywołujemy
Aadowanie mapy jest następujące: tam metodę drop na selektorze. Spowo-
w formacie bsp
Przyszedł czas, aby wykonać naj-
Listing 3. Obsługa zdarzeń w Mini-Quake'u
ważniejszą czynność, tzn. załadować
if(event.EventType == irr::EET_KEY_INPUT_EVENT) {
poziom gry w formacie bsp. Irrlicht
switch(event.KeyInput.Key) {
może w tym celu wykorzystać elementy
case KEY_SPACE: {
Quake'a 3. W jaki sposób to realizuje,
core::vector3df v = cameraFPS->getPosition();
przedstawia Listing 4, chociaż wykonu- std::cout << v.X << " " << v.Y << " " << v.Z << "\n";
} break;
jemy tam znacznie więcej czynności niż
case KEY_ESCAPE: {
tylko ładowanie poziomu.
device->closeDevice();
Pierwsza linia z listingu:
return true;
} break;
S
device->getFileSystem()-> }
S }
addZipFileArchive
("../media/map-20kdm2.pk3");
69
www.lpmagazine.org
dla programistów
S
sydney_model = smgr->
Listing 4. Aadowanie poziomu bsp w Mini-Quake'u
addAnimatedMeshSceneNode(sydney_mesh);
device->getFileSystem()->addZipFileArchive("../media/map-20kdm2.pk3");
Warto by było określić plik z teksturą obie-
scene::IAnimatedMesh* level = smgr->getMesh("20kdm2.bsp");
ktu. W naszym przypadku skorzystamy
z tekstury zawartej w pliku sydney.bmp:
if (level) {
level_node = smgr->addOctTreeSceneNode(level->getMesh(0));
} S
sydney_model->setMaterialTexture
(0, driver->getTexture
if (level_node) {
("../media/sydney.bmp"));
level_node->setPosition(core::vector3df(-1370,-130,-1400));
level_node->setID(LEVEL_ID);
Podobnie jak poprzednio, konieczne jest
S
selector = smgr->createOctTreeTriangleSelector(level->
ustalenie wielu parametrów, ale w przy-
getMesh(0), level_node, 128);
padku obiektów MD2 istotnym elemen-
level_node->setTriangleSelector(selector);
tem jest tryb animacji. Załóżmy, że nasza
selector->drop();
postać będzie biegać. Ten typ animacji
}
uruchamiamy w następujący sposób:
S
duje to usunięcie niepotrzebnego egzem- nia  co może wydawać się dość dziwne sydney_model->setMD2Animation
plarza obiektu selektora.  nowego obiektu animacji. W rzeczywi- (scene::EMAT_RUN);
stości, kolizja ma wpływ na zachowanie
Kamera FPS się kamery. Musimy jeszcze dodać dwa punkty,
Po załadowaniu poziomu oraz utwo- Aby połączyć obsługę kolizji kamery pomiędzy którymi postać będzie mogła
rzeniu selektora następnym ważnym z poziomem (inaczej mówiąc, z otocze- się poruszać:
elementem jest kamera. Będzie ona tego niem), wywołujemy metodę createCol-
samego typu, jaką spotyka się w grach lisionResponseAnimator, która, jak widać core::array waypoint;
S
FPS, tzn. za pomocą klawiszy kurso- w kodzie, ma sporą liczbę argumentów. waypoint.push_back(core::vector3df
ra będziemy mogli przemieszczać się, Dwa pierwsze są dość oczywiste. Trzeci (-150, -25, -80));
S
a myszką zmieniać kierunek patrzenia. argument to parametry elipsoidy kolizji, waypoint.push_back(core::vector3df
Kod tworzący kamerę znajduje się na czyli poszczególne jej promienie. Warto (350, -25, -80));
Listingu 5. Tworzenie kamery oraz ustalenie popróbować, jakie wartości musi mieć
jej pozycji w przestrzeni to tylko dwie linie ten wektor, gdyż może zdarzyć się, że Oczywiście, trzeba sprawdzić wartości
kodu. Pierwsza z nich jest następująca: zbyt małe wartości spowodują, iż będzie- współrzędnych (powyższe zostały wcze-
my niemal wchodzić w ściany, natomiast śniej przetestowane). Jeśli tego nie zrobi-
S
cameraFPS = smgr->addCameraSceneNodeFPS zbyt duże spowodują nienaturalne zacho- my, może zdarzyć się, że postać będzie
(0, 100.0f, 300.0f); wanie się kamery. W czwartym argumen- biegać po suficie (w grach to się czasem
cie opisujemy wartości grawitacji dla zdarza).
W pierwszym parametrze możemy poszczególnych osi: x, y, z. Kolejny argu- Gdy punkty zostały wyznaczone,
podać węzeł rodzica, ale nie jest to ment to wartość przyspieszenia. W ostat- tworzymy specjalny obiekt animacyjny:
konieczne. Drugi parametr to prędkość nim parametrze umieszczamy jeszcze
S
obrotu. W trzecim parametrze określamy jeden istotny element  przesunięcie anim = device->getSceneManager()->
S
prędkość poruszania się kamery w prze- elipsoidy, np. przed obiekt kamery, dla createFollowSplineAnimator
strzeni. Druga linia jest odpowiedzialna lepszego (tzn. wcześniejszego) wykry-
za umieszczenie kamery pod podanymi wania kolizji.
Listing 5. Tworzenie kamery w stylu
współrzędnymi. Tutaj ponownie korzy-
FPS
stamy z gotowych danych, więc zachę- Wczytujemy dodatkowy
S
cam do zmiany tych wartości: obiekt cameraFPS = smgr->addCamera
SceneNodeFPS(0, 100.0f, 300.0f);
Dotychczas udało się nam załadować
S
S cameraFPS->setPosition(core::
cameraFPS->setPosition(core::vector3df poziom i włączyć kolizję kamery ze ścia-
vector3df(-100, 25, -150));
(-100, 25, -150)); nami. W tym punkcie dołączymy postać
oraz każemy jej biegać wzdłuż linii.
S
anim = smgr->
Można powiedzieć, że uporaliśmy się Sam proces załadowania postaci z pliku
createCollisionResponseAnimator(
z kamerą i możemy poruszać się, ale MD2 wymaga tylko dwóch linijek kodu. selector, cameraFPS,
core::vector3df(30, 10, 30),
będziemy przenikać przez ściany. Do W pierwszej wczytujemy tzw. obiekt
core::vector3df(0, -100, 0),
usunięcia tej niedogodności potrzebna jest mesh (czyli siatkę obiektu), a w drugim
100.0f,
detekcja kolizji. Za jej realizację jest odpo- tworzymy węzeł sceny:
core::vector3df(0,50,0));
wiedzialny kod podany na Listingu 5.
cameraFPS->addAnimator(anim);
S
Wprowadzenie obsługi kolizji pomię- sydney_mesh = smgr-> anim->drop();
dzy kamerą a poziomem wymaga doda- getMesh("../media/sydney.md2");
70
grudzień 2004
dla programistów
piszemy mini-quake'a
out.setLength(0.03f);
Listing 6. Główna pętla naszego mini-Quake'a
std::cout << "|col with wall|";
while(device->run()) { }
driver->beginScene(true, true, 0);
smgr->drawAll(); guienv->drawAll(); Wykorzystujemy, jak widać, wcześniej
S
selectedSceneNode = smgr->getSceneCollisionManager()->getSceneNodeFromCameraBB utworzony selektor. Interesuje nas teraz
(cameraFPS); sprawdzenie przecięcia z wczytaną przez
if(selectedSceneNode != level_node) { nas postacią. Postępujemy w bardzo
if(selectedSceneNode->getID()==SYDNEY_ID) { podobny sposób jak poprzednio:
std::cout << "|col|";
S
core::vector3df a=cameraFPS->getPosition(); if (sm->getSceneCollisionManager()
S
cameraFPS->setPosition(a); ->getCollisionPoint(line,
} sydney_selector, end, triangle)) {
S
} core::vector3df out =
selectedSceneNode = 0; driver->endScene(); triangle.getNormal();
} out.setLength(0.03f);
S
sydney_model->setMD2Animation
(0, waypoint, 0.25f); mu tę możliwość. Załóżmy, że będziemy ( EMAT_PAIN_A );
S
sydney_model->addAnimator(anim); strzelać poprzez wciśnięcie klawisza pain_time=device->
[spacja]. Wykrycie tej sytuacji to zadanie getTimer()->getTime();
Niestety, powyższe rozwiązanie nie jest dla obiektu przetwarzającego zdarzenia  }
najlepsze, ponieważ postać będzie biegać SimpleEventReceiver. Po wciśnięciu spacji
również tyłem. W Irrlicht, jak na razie, wywołujemy funkcję shoot_test. W drugiej części powyższej instrukcji if
nie zaimplementowano automatycznego Test, czy w coś trafiliśmy, polega na wykonujemy jeszcze jedną ważną czyn-
odwracania się animowanej postaci. tym, że sprawdzamy, co przecina promień ność. Standardowo, nasza postać biega po
Należy to zrobić samodzielnie i jest to poprowadzony od kamery w kierunku planszy. Gdy ją trafimy, chcemy wywołać
zadanie dla Czytelniczki bądz Czytelni- punktu, na który spoglądamy. W tym animację odpowiednią do sytuacji. Wizu-
ka. celu potrzebny będzie menedżer kolizji. alizację otrzymania trafienia uruchamia
W pierwszej kolejności musimy wyzna- polecenie: sydney_model->setMD2Anima-
Wprawiamy system w ruch czyć początek oraz koniec promienia. tion( EMAT_PAIN_A );. Dodatkowo, do
Na Listingu 6 została przedstawiona pętla Przedstawia się to w następujący sposób: zmiennej pain_time zapisujemy aktualny
while, odpowiedzialna za przetwarzanie czas. W głównej pętli, po pewnym czasie,
S
danych. Oprócz wykonywania identycz- core::vector3df start = przywrócimy standardową animację
nych czynności jak w poprzednim progra- camera->getPosition(); biegu. W ten sposób po trafieniu nasza
S
mie, dodatkowo sprawdzamy, czy nastąpiła core::vector3df end = postać zareaguje w odpowiedni sposób.
kolizja pomiędzy nami (tzn. kamerą, którą (camera->getTarget() - start);
sterujemy) a postacią. Ponieważ scena end.normalize(); Podsumowanie
w systemie Irrlicht zawsze jest dzielona na start += end*5.0f; Mam nadzieję, że tą prezentacją biblio-
S
węzły, to oczekujemy, że system wskaże end = start + (end * camera teki Irrlicht udało mi się zachęcić Czy-
nam węzeł, z którym kolidujemy. Tak stanie ->getFarValue()); telników do własnych eksperymentów.
się, jeśli wydamy następujące polecenie: Dobrą lekturą są także dodatkowe
Zanim sprawdzimy, czy w coś trafiliśmy, przykłady dołączone do dystrybucji
S
selectedSceneNode = smgr-> deklarujemy jeszcze dwie zmienne: Irrlicht. Polecam również zapoznanie
S
getSceneCollisionManager()-> się z projektem IrrlichtNX. Jego autorzy
getSceneNodeFromCameraBB(cameraFPS); core::triangle3df triangle; wprowadzają liczne poprawki do kodu
core::line3d line(start, end); Irrlicht oraz nowe możliwości. Choć
Gdy wystąpi kolizja z obiektem kamery, wersja ta jest dostępna tylko przez CVS,
menedżer kolizji przypisze zmiennej W pierwszej znajdzie się trójkąt, który to pozwala na skorzystanie z nowych
selectedSceneNode odpowiednią war- trafiliśmy, a druga zmienna reprezentuje możliwości, zanim zostanie wydana
tość. Następna instrukcja warunkowa linię testową, czyli promień. Test, czy oficjalna wersja biblioteki.
wyklucza kolizję z otoczeniem, a ponie- nasz promień przecina się ze ścianami
waż wczytaliśmy tylko jeden obiekt, to pomieszczenia, sprowadza się do jednej
W Internecie:
wiemy, czy nastąpiła kolizją pomiędzy instrukcji warunkowej:
" Strona domowa biblioteki Irrlicht:
nim a kamerą, którą sterujemy.
http://irrlicht.sourceforge.net/
S
if (sm->getSceneCollisionManager()
" Specjalna wersja Irrlicht, wyposażo-
S
A gdzie strzelanie? ->getCollisionPoint(line, level_
na w wiele poprawek i rozszerzeń
W naszym prototypie możemy już cho- selector, end, triangle)) {
w stosunku do wersji oryginalnej:
S
dzić, ale brakuje jeszcze jednej funkcji core::vector3df out =
http://www.irrlichtnx.mmdevel.de/
 strzelania. Dodajmy do naszego progra- triangle.getNormal();
71
www.lpmagazine.org


Wyszukiwarka

Podobne podstrony:
2004 09 Rozszerzanie możliwości przeglądarek WWW [Programowanie]
2006 04 Piszemy widget zegara w GTK [Programowanie]
2001 12 Geometry Classes Under Qt Programming
2004 07 Konsolowa przeglądarka plików graficznych [Programowanie]
piszemy gre
2004 03 CVS – system zarządzania wersjami [Programowanie]
2004 12 Apoptoza w procesach
2004 08 Flagi sterujące optymalizacją w GCC [Programowanie]
Babyloniam Eclipse Observations 2004 12 01 Goldstein
2009 12 Metaprogramowanie algorytmy wykonywane w czasie kompilacji [Programowanie C C ]
Programowanie notatka 10 09 12
Magazine Analog Science Fiction and Fact 2004 Issue 12 December (v1 0) [txt]
12 Sekretów Błyskawicznego Zarabiania w Programie Partnerskim Chomikuj pl
Alternator 2200SRM0002 (12 2004) US EN
Magazine Asimov s Science Fiction 2004 Issue 12 December (v1 0) [txt]
12 Programowanie 5osi
Programowanie cwiczenia zjazd VII 18 12 2011

więcej podobnych podstron