SDJ_02_2010_PL

84

description

Software Developers Jurnal

Transcript of SDJ_02_2010_PL

Page 1: SDJ_02_2010_PL
Page 2: SDJ_02_2010_PL
Page 3: SDJ_02_2010_PL
Page 4: SDJ_02_2010_PL

4

SPIS TREŚCI

PROGRAMOWANIE C++30 Fabryki obiektówRobert NowakTechniki opisane w tym artykule pozwalają tworzyć obiekty na podstawie identyfikatorów dostarczanych w czasie działania pro-gramu, co jest wygodniejsze niż podawanie typów w formie zro-zumiałej dla kompilatora.

PROGRAMOWANIE GIER34 Jak napisać swoją pierwszą grę komputerowąKonstanty KalickiPrawdopodobnie każdy gracz ma w głowie co najmniej jeden po-mysł na rewelacyjną grę. Niektórzy idą dalej i zakładają zeszyty w kratkę, których strony zapełniają pomysłami i szkicami. Niestety w tym szczególnym przypadku sam pomysł to zbyt mało i – bez konkretnych kroków – jest niewiele wart.

38 Techniki renderingu 2,5D w grachCity-Interactive teamPierwsze gry wykorzystujce technik 2,5 D nazywan równie pseudo 3D powstay w latach 70 XXw. na 8 bitowe komputery Atari oraz C64. Pionierami tej techniki byy gry „Night Driver”, „Outrun”, „Pole Position”. To technika uywana w rónych kategoriach gier, ma za za-danie stworzenie w grze dwuwymiarowej wraenia przestrzennoci.

42 Mapy kafelków w grach 2D – Wstęp i rysowanieJacek ZagrodzkiMarzysz o stworzeniu swojej wymarzonej dwuwymiarowej plat-formówki albo klasycznego RTSa? Od czego zacząć? Oczywiście od map kafelków (ang. tiled maps). Dzięki temu artykułowi po-znasz podstawy tej techniki – dowiesz się, jak mapy kafelków za-programować i jak je rysować.

54 Efektywny blitter – metody optymalizacjiBartosz TaudulJak to jest, że jedne gry posiadają świetnie wyglądającą i płynną gra-fikę, podczas gdy inne, mimo znacznie uboższego wyglądu, ledwo sobie radzą z przerysowywaniem pola gry? Dlaczego u konkuren-cji na ekranie może poruszać się całe mrowie przeciwników, podczas gdy u nas już przy pięciu jest problem? Różnica tkwi w kunszcie pro-gramisty odpowiedzialnego za niskopoziomową obsługę grafiki.

17 Opis DVD

BIBLIOTEKA MIESIĄCA6 Biblioteka cocos2d-iphone – Łatwe programo-wanie gier 2D pod iPhoneJakub WęgrzynBiblioteka cocos2d-iphone zapewnia wygodny i łatwy w uży-ciu zestaw klas, które pozwalają na szybkie tworzenie dwu-wymiarowych gier pod iPhone. Jeśli marzysz o tym, aby napi-sać własną grę na ten właśnie telefon, to koniecznie przeczy-taj ten artykuł.

KLUB TECHNICZNY18 Co nowego we Flex 4Rafał NagrodzkiFlex jest jedną z najbardziej zaawansowanych technologii do budowania aplikacji typu RIA w bezpośrednim tłumaczeniu bo-gatych aplikacji internetowych. Silnikiem wyświetlającym apli-kacje Flex’owe jest technologia Adobe Flash, która pozwala na osiągnięcie jednolitego wyglądu uruchamianej aplikacji, nieza-leżnie od wykorzystywanej przeglądarki czy systemu operacyj-nego.

22 Technologie Progress OpenEdge – Część 6. Obiekty ProDataSet Piotr TucholskiProgressowe obiekty typu Dataset (ProDataSet) rozszerzają moż-liwości definiowania złożonych obiektów biznesowych oraz rela-cji między nimi. Są bardzo ważnym elementem w procesie budo-wania nowoczesnych aplikacji rozproszonych i wymiany danych z innymi aplikacjami lub ich modułami poprzez XML.

CLOUD COMPUTING26 Enterprise Private CloudsMichał KuratczykCloud computing robi od pewnego czasu zawrotną karierę me-dialną. Tematem zajmują się nie tylko tytuły poświęcone bran-ży IT, ale także biznesowe i popularno-naukowe. Nawet Dilbert wspomina już o cloud computing. Warto zatem zastanowić się czym jest, a czym nie jest cloud computing i jak wpłynie na two-rzenie i zarządzanie oprogramowaniem.

02/2010 (182)

4 02/2010

Page 5: SDJ_02_2010_PL

www.sdjournal.org 5www.sdjournal.org

60 Xcode – Xcode – oto czytelnik. Czytelniku –oto Xcode. Poznajcie się. Czyli wprowadzenie do pro-gramowania najpopularniejszego urządzenia mobilnego na świecie. Patryk Bukowiecki, Jarosław WojczakowskiiPhone i App Store to światowy fenomen. Firma Apple dokonała niemożliwego i uwolniła wielką siłę niezależnych developerów mogących od teraz spełniać swoje wizje i tworzyć gry jak daw-niej, w pojedynkę lub w małych zespołach, siedząc nad nimi po pracy, w garażach.

64 Cyfrowa kreacja – Artystyczne projektowania gierArkadiusz WychadaczukNiezalenie od tego, czy tworzymy film, spot reklamowy, efekty specjalne, wizualizacj czy gr, potrzebujemy narzdzi, które pozwol nam w produktywny i elastyczny sposób stworzy to, co widzimy oczami wyobrani. Wtedy pojawia si konieczno wybrania oprogra-mowania do tzw. cyfrowej kreacji.

68 Programowanie wizualne – co to takiego?Arkadiusz BrzegowyNiniejszy tekst to informacje, przykłady i tutorial przybliżające technologię Quest3D. Artykułem tym chcę pokazać, że można tworzyć aplikacje 3D i gry szybko oraz bez znajomości języka pro-gramowania, bibliotek DirectX czy OpenGL. Jeszcze raz – można. Jest do tego gotowe narzędzie. Resztę znajdziecie poniżej.

72 Producent – A kto to jest?Łukasz SzczepańskiRola producenta w procesie budowania gry komputerowej bywa, szczególnie dla osób postronnych, dość niejasna. Czytając poniż-szy artykuł, przekonasz się, kim jest producent i jaka jest jego rola we wspomnianym procesie.

EFEKTYWNOŚĆ PRACY74 Wspinaczka do profesjonalizmu – Modelowa ścieżka rozwoju kompetencji – podejście pragma-tyczneSławomir SobótkaAutor przedstawi jeden z modeli rozwoju kompetencji – Model Braci Dreyfus, który odnosi się nie tylko do umiejętności technicz-nych, ale również do ogólnej aktywności każdego z nas. Model pozwoli Ci uświadomić sobie swój aktualny poziom kompetencji w dowolnej dziedzinie oraz zaplanować dalszą drogę ich rozwo-ju. Spojrzenie przez pryzmat modelu na kolegów z zespołu oraz ew. podwładnych zwiększy efektywność Twej komunikacji i może znacząco wpłynąć na redukcję frustracji.

FELIETON82 GeeCon 2010

Miesięcznik Software Developer’s Journal (12 numerów w roku)jest wydawany przez Software Press Sp. z o.o. SK

Redaktor naczelny: Łukasz Łopuszański [email protected]

Projekt okładki: Agnieszka Marchocka

Skład i łamanie: Monika Grotkowska [email protected]

Kierownik produkcji: Andrzej Kuca [email protected]

Dział produkcji i kolportażu: Alina Stebakow [email protected]

Nakład: 6 000 egz.

Adres korespondencyjny:Software Press Sp. z o.o. SK,

ul. Bokserska 1, 02-682 Warszawa, Polskatel. +48 22 427 36 91, fax +48 22 224 24 59

www.sdjournal.org [email protected]

Dział reklamy: [email protected]

Obsługa prenumeraty: [email protected]

Dołączoną do magazynu płytę CD przetestowano programem AntiVirenKit firmy G DATA Software Sp. z o.o.

Redakcja dokłada wszelkich starań, by publikowane w piśmie i na towarzyszących mu nośnikach informacje i programy były poprawne, jednakże nie bierze odpowiedzialności za efekty

wykorzystania ich; nie gwarantuje także poprawnego działania programów shareware, freeware i public domain.

Uszkodzone podczas wysyłki płyty wymienia redakcja.

Wszystkie znaki firmowe zawarte w piśmie są własności odpowiednich firm.

Zostały użyte wyłącznie w celach informacyjnych.

Redakcja używa systemu automatycznego składu

Osoby zainteresowane współpracą prosimy o kontakt:[email protected]

Druk: Artdruk www.artdruk.com

Wysokość nakładu obejmuje również dodruki. Redakcja nie udziela pomocy technicznej w instalowaniu i użytkowaniu

programów zamieszczonych na płycie CD-ROM dostarczonej razem z pismem.

Sprzedaż aktualnych lub archiwalnych numerów pisma po innej cenie niż wydrukowana na okładce – bez zgody wydawcy – jest działaniem na jego szkodę i skutkuje odpowiedzialnością

sądową.

5

Page 6: SDJ_02_2010_PL

02/20106

Biblioteka MiesiącaProgramowanie gier pod iPhone

www.sdjournal.org 7

Chyba każdy pamięta, jaka rewo-lucja została dokonana przez la-ta królowania odtwarzacza iPod,

którego główną siłą był internetowy sklep z muzyką iTunes. Dziś można już powie-dzieć, że Apple ponownie wyprzedził konkurencję, wprowadzając do sprzedaży telefon iPhone, jednocześnie uruchamia-jąc sklep z aplikacjami AppStore. Telefon mimo wielokrotnie wytykanych wad, bra-ków funkcjonalności czy też wysokiej ceny sprzedaje się doskonale. Ogromną część sprzedawanych aplikacji stanowią gry. Wystarczy wspomnieć, że kilka tytułów sprawiło, iż ich autorzy zarobili miliony dolarów i w tej chwili mają swoje firmy tworzące kolejne gry. Nic nie stoi na prze-szkodzie, aby samemu spróbować swoich sił w tej dziedzinie.

Wielką pomocą w pierwszych próbach tworzenia własnej gry jest biblioteka coco-s2d-iphone. Zapewnia ona wygodny i ła-twy w użyciu zestaw klas, które pozwala-ją na szybkie tworzenie gier 2D dla telefo-nu iPhone. O jej sile świadczyć może mno-

gość tytułów opracowanych za jej pomocą, które skutecznie walczą o swoje miejsce w AppStorze.

Wymagane narzędziaRozpoczęcie prac nad tworzeniem apli-kacji dla iPhone’a wymaga sporych nakła-dów finansowych. Podstawowym wymo-

giem jest posiadanie komputera Mac z pro-cesorem Intela, co wyklucza większość star-szych urządzeń. Ponadto konieczne jest po-siadanie zainstalowanego systemu Mac OS X w wersji 10.5 lub wyższej. Niestety łącz-ny koszt zakupu telefonu i komputera mo-że swobodnie dotrzeć do pułapu 10 tysięcy złotych , co dla wielu osób stanowi barierę nie do przebicia.

Jeżeli jednak jesteśmy szczęśliwymi po-siadaczami odpowiedniego sprzętu, musi-my zaopatrzyć się w iPhone SDK zawierają-ce niezbędne biblioteki oraz edytor XCode. W tym celu należy założyć darmowe konto na portalu iPhone Dev Center. I na tym eta-pie moglibyśmy już zakończyć kompleto-wanie narzędzi pod warunkiem, że wystar-

Biblioteka cocos2d-iphone

Biblioteka cocos2d-iphone zapewnia wygodny i łatwy w użyciu zestaw klas, które pozwalają na szybkie tworzenie dwuwymiarowych gier pod iPhone. Jeśli marzysz o tym, aby napisać własną grę na ten właśnie telefon, to koniecznie przeczytaj poniższy artykuł.

Dowiesz się:• Jakie są podstawowe konstrukcje języka Ob-

jective-C;• Jak przygotować środowisko pozwalające

na tworzenie gier;• Jak korzystać z biblioteki cocos2d-iphone.

Powinieneś wiedzieć:• Jak programować w języku C.

Poziom trudności

Łatwe programowanie gier 2D pod iPhone

Rysunek 1. Tworzenie przykładowego projektu Objective-C

Page 7: SDJ_02_2010_PL

02/20106

Biblioteka MiesiącaProgramowanie gier pod iPhone

www.sdjournal.org 7

czy nam możliwość testowania tworzonych aplikacji za pomocą emulatora, przewrotnie noszący nazwę iPhone Simulator. Niestety, nawet jeżeli jesteśmy posiadaczami telefo-nu wyprodukowanego przez firmię Apple, nie dostąpimy możliwości uruchamiania na nim naszej aplikacji. Po raz kolejny będzie-my musieli sięgnąć do naszej kieszeni i po-święcając sto dolarów amerykańskich, sta-niemy się posiadaczami konta na iPhone Developer Program Portal. Dla pocieszenia dodam tylko, że od tej chwili nie musimy ponosić żadnych dodatkowych kosztów, na-wet jeżeli zdecydujemy się umieścić naszą aplikację w AppStore i zacząć zarabiać na niej pieniądze.

Ostatni element procesu przygotowania do tworzenia gier to zainstalowanie bibliote-ki cocos2d-iphone, która na dzień powstania niniejszego artykułu dostępna jest w wersji 0.8.1. Po jej pobraniu i rozpakowaniu otrzy-manego archiwum warto zainstalować sza-blon projektu, który pozwoli na szybkie two-rzenie gier z poziomu edytora XCode. W tym celu z poziomu terminala systemu Mac OS X należy wykonać operacje przedstawione na Listingu 1.

Od tej chwili moglibyśmy rozpocząć przy-godę z tworzeniem gier dla iPhone’a. Tutaj pojawia się jednak kolejny problem – język Objective-C. Co prawda możliwe jest wyko-rzystanie w tym celu języka C++, jednak i tak pewne fragmenty będą musiały powstać za pomocą Obj-C. Ponadto wiązać się to będzie z utratą wszelkiego wsparcia ze strony biblio-teki cocos2d-iphone.

Podstawy języka Objective-CPoniższy artykuł absolutnie nie pretendu-je do miana kompletnej specyfikacji tego ję-zyka. Zajęłoby to zdecydowanie zbyt dużo miejsca i czasu, jednak aby rozpocząć swoją przygodę z tworzeniem gier, nie potrzebuje-my rozległej wiedzy. Wystarczy kilka wska-zówek i jestem przekonany, że każdy, kto zdążył do tej pory zapoznać się z dobrym, klasycznym językiem C, poradzi sobie z tym zadaniem.

Nie byłby to artykuł wprowadzający w podstawy programowania w dowolnym języku, bez przykładu Hello World. Leni-wych czytelników uprzedzam, że urucho-mienie go nie wymaga przepisania nawet linijki kodu. Otóż twórcy edytora XCode okazali się odpowiednio przewidujący. Aby nie przedłużać oczekiwania, rozpocznijmy od utworzenia nowego projektu – w tym wybieramy z menu aplikacji File>New pro-ject… i w nowo otwartym oknie wybieramy z listy opcję Command Line Utility, a w polu znajdującym się po prawej stronie zaznacza-my ikonę Foundation Tool (Rysunek 1). Po kliknięciu przycisku Choose… pozostaje tyl-

Listing 1. Instalacja szablonu projektu cocos2d-iphone

$ cd cocos2d-iphone/

$ ./install_template.sh

Listing 2. Witaj świecie!

#import <Foundation/Foundation.h>

int main (int argc, const char *argv[])

{

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

NSLog (@"Hello World");

[pool drain];

return 0;

}

Listing 3. Przykładowa deklaracja klasy

#import <Cocoa/Cocoa.h>

@interface Student : NSObject

{

NSString* name;

}

- (void) displayName;

@end

Listing 4. Implementacja przykładowej klasy

#import "Student.h"

@implementation Student

- (void) displayName

{

NSLog( name );

}

@end

Rysunek 2. Tworzenie projektu z wykorzystaniem biblioteki cocos2d

Page 8: SDJ_02_2010_PL

02/20108

Biblioteka MiesiącaProgramowanie gier pod iPhone

www.sdjournal.org 9

ko wprowadzić odpowiednią nazwę projek-tu i wybrać katalog na dysku, w którym ma zostać utworzony. W tym momencie dyspo-nujemy podstawowym projektem wraz z ko-dem przedstawionym na Listingu 2.

Na pierwszy rzut oka kod nie różni się zbytnio od tysięcy podobnych programów pisanych w czystym C. Jednak odrobi-nę dłuższa chwila pozwoli na wychwyce-nie pierwszych różnic. Na początek dyrek-tywa #import – jest ona odpowiednikiem #include, jednak zapewnia ona, iż plik zo-stanie dołączony tylko raz. Kolejna linia ko-du ujawnia korzenie Objective-C sięgają-ce wspomnianego języka C. Mamy więc do czynienia z funkcją main zwracającą wartość typu int i przyjmującą dwa parametry po-zwalające na dostęp do argumentów przeka-zanych z linii poleceń. Kolejna linia jest od-powiedzialna za rezerwację pamięci tworzo-nych od tej pory obiektów. W tym momen-cie dochodzimy do sedna prezentowanej aplikacji, czyli wyświetlenia na wyjściu stan-dardowym komunikatu Hello World. W tym celu użyta została funkcja NSLog. Przyda nam się ona podczas prac nad praktycznie każdą kolejną aplikacją. Kolejne linie są od-powiedzialne za zwolnienie pamięci i zwró-cenie odpowiedniej wartości.

Po tym lekko przydługim wstępie pozo-staje tylko uruchomienie opisanego pro-gramu. Możemy tego dokonać za pomocą przycisku znajdującego się na pasku narzę-dziowym lub wybierając menu Build>Bu-ild and Go (Run). Aby zobaczyć wiado-mość, jaką nasza aplikacja wysłała w świat, należy otworzyć okno konsoli (menu Ru-n>Console).

Wiemy już, że deklarowanie i wywoły-wanie funkcji jest dokładnie takie samo jak mechanizm znany nam z języka C. Jednak pora przesunąć się bliżej w stronę obiekto-wości, która przecież znalazła swoje miej-sce w nazwie omawianego języka progra-mowania. Dodatkowo, warto wyjaśnić, iż podobnie jak w języku C++ nagłówek kla-

sy umieszcza się w pliku z rozszerzeniem .h, jednak pliki zawierające jej implemen-tację mają rozszerzenie .m. Listing 3 przed-stawiający deklarację prostej klasy wyraźnie ujawnia, jak daleko Objective-C odsunął się od stylu języka C++.

Przedstawia on minimalistyczną klasę o nazwie Student, która zawiera jedno po-le typu NSString o nazwie name. Łatwo za-uważyć, iż deklaracja klasy w języku Objec-tive-C rozpoczyna się słowem kluczowym @interface, po którym wpisana została na-zwa klasy. Po dwukropku powinna znaleźć się nazwa klasy bazowej – w tym wypadku NSObject. Nawiasy klamrowe określają ob-szar, który pozwala na deklarację pól kla-sy, które domyślnie są polami chronionymi (atrybut @protected).

Poza nawiasami klamrowymi przewi-dziano miejsce na deklarację metod klasy. Znak - określa iż metoda jest metodą in-stancyjną. Wykorzystanie znaku + pozwo-liłoby oznaczyć metodę jako statyczną. Następnie w nawiasach podany został typ zwracanej wartości. Po tym pozostaje już tylko podać nazwę metody. Warto tutaj do-dać, iż w języku Objective-C wszystkie me-tody są publiczne.

Na zakończenie deklaracji klasy wystar-czy wpisać słowo kluczowe @end. Niestety taka deklaracja klasy mało przypomina to, z czym można się spotkać w innych języ-kach programowania. Jednak zapewniam, że po krótkim czasie stanie się to natural-ne i zrozumienie takiego kodu stanie się proste.

Pora teraz przejść do implementacji kla-sy – Listing 4. Zadeklarowana wcześniej klasa zawiera tylko jedną metodę, więc przedstawiony kod powinien być w mia-rę zrozumiały. W pierwszej kolejności na-leży za pomocą dyrektywy #import dołą-czyć odpowiedni nagłówek. Słowo kluczo-we @implementation jest tutaj naturalnym odzwierciedleniem słowa @implementation z nagłówka klasy. Implementacja kolejnych

metod sprowadza się do podania jej nagłów-ka i wprowadzenia odpowiedniego kodu po-między nawiasami klamrowymi. Zakończe-nie implementacji klasy oznacza się słowem kluczowym @end.

Warto w tym miejscu zaprezentować, w jaki sposób Objective-C pozwala na dekla-rację metod przyjmujących argumenty. Na początek przykład metody jednoargumen-towej:

- (void) displayText: (NSString*)text;

Na powyższym przykładzie widać, iż de-klaracja argumentu składa się z jego typu (zawartego w nawiasach – w przykładzie wskaźnik na typ NSString) oraz jego na-zwy. Sam argument jest oddzielony od na-zwy funkcji dwukropkiem. Pozostaje tyl-ko przedstawić deklarację funkcji wieloar-gumentowej, która powstaje w analogicz-ny sposób:

- (int) sum: (int)a with: (int)b;

Powyższej zadeklarowana metoda zwraca sumę dwóch liczb całkowitych. Warto tu-taj zauważyć, iż język Objective-C stosuje nazwane argumenty. Pozwala to na czytel-niejsze przedstawienie działania metody za pomocą samego nagłówka, jednak spra-wia, iż zagnieżdżone wywołania metod mogą stać się prawdziwym utrapieniem dla programisty chcącego utrzymać swój kod w ryzach.

Wywołanie prostej metody odbywa się w prosty sposób, który niestety ponownie jest unikalny dla języka Objective-C:

[stud displayName];

Pomiędzy nawiasami kwadratowymi na-leży w pierwszej kolejności podać nazwę utworzonego wcześniej obiektu, a następ-nie nazwę wywoływanej metody. Gdy-by displayName była metodą statycz-ną (dla przypomnienia – oznaczona zna-kiem +), jej wywołanie miałoby następu-jącą formę:

[Student displayName];

Jak widać, nazwa obiektu została zastąpiona odpowiednią nazwą klasy. Wywołanie me-tod przyjmujących parametry odbywa się w analogiczny sposób:

[stud displayText:@”Hello World”];

Korzystanie z metod zwracających wartość już nie powinno być zaskoczeniem:

int c = [stud sum:3 with:5];Rysunek 3. Uruchomiona aplikacja cocos2d

Page 9: SDJ_02_2010_PL

02/20108

Biblioteka MiesiącaProgramowanie gier pod iPhone

www.sdjournal.org 9

Powyższy wstęp stanowi tylko niewielki uła-mek wiedzy niezbędnej do świadomego ko-rzystania z właściwości języka Objective-C. Pominięte zostały tutaj tak ważne elemen-ty jak chociażby zarządzanie pamięcią. Oso-by chcące zgłębić tę wiedzę zapraszam do skorzystania z materiałów wymienionych w ramce.

Witaj świecie po raz drugiTym razem pora na pierwszy przykład wy-korzystania biblioteki cocos2d-iphone. Jeże-li instalacja szablonu projektu powiodła się, wystarczy z menu File > New Project… wybrać odpowiednią pozycję (Rysunek 2) i nadać na-zwę CocosGame. W wyniku tych działań po-wstanie kompletny projekt zawierający źró-dła biblioteki oraz prostą aplikację wyświe-tlającą komunikat tekstowy na ekranie tele-fonu (Rysunek 3).

Warto przeanalizować plik CocosGame-AppDelegate.m przedstawiony na Listin-gu 5. Zawiera on automatycznie wyge-nerowany kod odpowiedzialny za inicja-lizację gry. Na początek dokonamy zmia-ny orientacji ekranu z poziomej na piono-wą. W tym celu należy zlokalizować nastę-pującą linię:

[[Director sharedDirector]

setDeviceOrientation:CCDeviceOrientation

LandscapeLeft];

I dokonać jej zamiany na:

[[Director sharedDirector]

setDeviceOrientation:CCDeviceOrientat

ionPortrait];

Kolejna linia, na którą warto zwrócić uwagę, to ta odpowiedzialna za wyświetlanie liczni-ka klatek na sekundę. Jego wyłączenia moż-na dokonać, komentując linię:

[[Director sharedDirector] setDisplayFPS:

YES];

Cocos2d tak jak każda biblioteka tego typu narzuca pewną filozofię podziału aplikacji. W tym przypadku najważniejsze elemen-ty to sceny (klasa Scene) i warstwy (kla-sa Layer), przy czym użytkownik ma peł-ną dowolność w interpretacji zakresu tych bytów. Proponuję scenę rozważać jako poje-dynczy ekran, taki jak menu główne, ekran gry itp. Każda scena może zawierać dowol-ną liczbę warstw. Tworzenie własnych scen i warstw odbywa się poprzez dziedziczenie z odpowiednich klas.

Tworzenie prostego menuJednym z zadań, które można realizować bar-dzo szybko za pomocą biblioteki cocos2d-

iphone, jest tworzenie menu gry. W poniż-szym przykładzie zostanie stworzone menu

składające się z kilku prostych ekranów. Na początek zajmiemy się menu głównym, któ-

Listing 5. Plik CocosGameAppDelegate.m

#import "CocosGameAppDelegate.h"

#import "cocos2d.h"

#import "HelloWorldScene.h"

@implementation CocosGameAppDelegate

@synthesize window;

- (void) applicationDidFinishLaunching:(UIApplication*)application

{

// Init the window

window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

// cocos2d will inherit these values

[window setUserInteractionEnabled:YES];

[window setMultipleTouchEnabled:YES];

// must be called before any othe call to the director

// WARNING: FastDirector doesn't interact well with UIKit controls

// [Director useFastDirector];

// before creating any layer, set the landscape mode

[[Director sharedDirector] setDeviceOrientation: CCDeviceOrientationLandscapeLeft];

[[Director sharedDirector] setAnimationInterval:1.0/60];

[[Director sharedDirector] setDisplayFPS:YES];

// Default texture format for PNG/BMP/TIFF/JPEG/GIF images

// It can be RGBA8888, RGBA4444, RGB5_A1, RGB565

// You can change anytime.

[Texture2D setDefaultAlphaPixelFormat:kTexture2DPixelFormat_RGBA8888];

// create an openGL view inside a window

[[Director sharedDirector] attachInView:window];

[window makeKeyAndVisible];

[[Director sharedDirector] runWithScene: [HelloWorld scene]];

}

- (void)applicationWillResignActive:(UIApplication *)application {

[[Director sharedDirector] pause];

}

- (void)applicationDidBecomeActive:(UIApplication *)application {

[[Director sharedDirector] resume];

}

- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {

[[TextureMgr sharedTextureMgr] removeAllTextures];

}

- (void)applicationWillTerminate:(UIApplication *)application {

[[Director sharedDirector] end];

}

- (void)applicationSignificantTimeChange:(UIApplication *)application {

[[Director sharedDirector] setNextDeltaTimeZero:YES];

}

- (void)dealloc {

[[Director sharedDirector] release];

[window release];

[super dealloc];

}

@end

Page 10: SDJ_02_2010_PL

02/201010

Biblioteka MiesiącaProgramowanie gier pod iPhone

www.sdjournal.org 11

re zawiera trzy przyciski. W tym celu należy utworzyć nową klasę o nazwie MainMenu, któ-

ra dziedziczy z klasy Scene (Listing 6). Składa się ona z czterech metod:

• init – jest odpowiedzialna za inicjaliza-cję sceny, w tym przypadku utworzenie trzech przycisków i ich wyświetlenie na ekranie;

• onPlayItem, onHelpItem, onAboutItem – metody zostaną automatycznie wy-wołane w przypadku wciśnięcia odpo-wiedniego przycisku przez użytkow-nika.

Podczas tworzenia menu wykorzystywa-na jest przede wszystkim klasa Menu odpo-wiedzialna za zarządzanie przyciskami, wy-świetlanie ich w odpowiedniej kolejności oraz automatyczne pozycjonowanie. Na po-czątek jednak należy utworzyć przyciski. W tym celu wykorzystana zostanie klasa MenuItemFont reprezentująca proste przyci-ski tekstowe:

MenuItem* planitem = [MenuItemFont

itemFromString:@”Play” target:

self selector:@selector

(onPlayItem:)];

Pierwszy z argumentów określa etykie-tę przycisku, natomiast drugi określa obiekt, do którego zostanie wysłana in-formacja o jego wciśnięciu. Ostatni argu-ment definiuje metodę obsługi tego zda-rzenia.

Po zdefiniowaniu kilku przycisków, można przystąpić do utworzenia kompletnego me-nu. W tym celu należy wykorzystać metodę menuWithItems:

Menu* menu = [Menu menuWithItems:

playItem, helpItem,

aboutItem, nil];

Klasa Menu pozwala również na auto-matyczne rozmieszczenie przycisków na ekranie. W tym przypadku przyciski zo-staną ułożone w jednej pionowej kolum-nie:

[menu alignItemsVertically];

Kompletna implementacja klasy MainMenu została przedstawiona na Listingu 7. Imple-mentacja metod odpowiedzialnych za obsłu-gę zdarzeń związanych z przyciskami menu zostanie omówiona w dalszej cześci artyku-łu. Aby przetestować nową scenę, należy w pliku CocosGameAppDelegate.m dodać na-główek:

#import "MainMenu.h"

Następnie po zlokalizowaniu linii:

[[Director sharedDirector] runWithScene:

[MainMenu scene]];

Listing 6. MainMenu.h

#import "cocos2d.h"

@interface MainMenu : Scene

{

}

- (id) init;

- (void) onPlayItem: (id)sender;

- (void) onHelpItem: (id)sender;

- (void) onAboutItem: (id)sender;

@end

Listing 7. Plik MainMenu.m

#import "MainMenu.h"

#import "Game.h"

#import "HelpMenu.h"

#import "AboutMenu.h"

@implementation MainMenu

- (id) init

{

self = [super init];

if ( self != nil )

{

MenuItem* playItem = [MenuItemFont itemFromString:@"Play" target:self

selector:@selector(onPlayItem:)];

MenuItem* helpItem = [MenuItemFont itemFromString:@"Help" target:self

selector:@selector(onHelpItem:)];

MenuItem* aboutItem = [MenuItemFont itemFromString:@"About" target:self

selector:@selector(onAboutItem:)];

Menu* menu = [Menu menuWithItems:playItem, helpItem, aboutItem, nil];

[menu alignItemsVertically];

[self addChild:menu];

}

return self;

}

- (void) onPlayItem: (id) sender

{

[[Director sharedDirector] replaceScene:[RotoZoomTransition

transitionWithDuration:1.0 scene:[Game node]]];

}

- (void) onHelpItem: (id) sender

{

[[Director sharedDirector] replaceScene:[RotoZoomTransition

transitionWithDuration:1.0 scene:[HelpMenu node]]];

}

- (void) onAboutItem: (id) sender

{

[[Director sharedDirector] replaceScene:[RotoZoomTransition

transitionWithDuration:1.0 scene:[AboutMenu node]]];

}

@end

Page 11: SDJ_02_2010_PL

02/201010

Biblioteka MiesiącaProgramowanie gier pod iPhone

www.sdjournal.org 11

Należy dokonać jej zmiany na następującą:

[[Director sharedDirector] runWithScene:

[MainMenu node]];

W ten sposób poinformowaliśmy bibliote-kę cocos2d, że pierwszą sceną aplikacji jest menu główne reprezentowane przez klasę MainMenu. Uruchomiona aplikacja zosta-ła przedstawiona na Rysunku 4.

Implementacja kolejnych ekranów została przedstawiona na Listingach 8-11. Ze wzglę-du na ich podobieństwo do menu głównego nie będą one dokładniej omawiane.

Mając trzy ekrany menu, możemy doko-nać ich połączenia. W tym celu należy w me-todach obsługi zdarzeń dla przycisków doko-nać zamiany aktualnie wyświetlanej sceny na tą wybraną przez użytkownika:

[[Director sharedDirector] replaceScene:

[AboutMenu node]];

Powyższa linia wyświetli na ekranie telefo-nu scenę reprezentującą ekran informacji o aplikacji. W tym miejscu warto skorzystać z kolejnej ciekawej możliwości oferowanej przez omawianą bibliotekę. Chodzi o możli-wość użycia efektów przejścia pomiędzy sce-nami. Wszystkie klasy z tym związane moż-na znaleźć w pliku Transitions.h. Poniższy przykład wykorzystuje efekt szybkiego ob-racania ekranu wraz ze skalowaniem, która trwa przez jedną sekundę:

[[Director sharedDirector] replaceScene:

[RotoZoomTransition transitionWithDuration:

1.0 scene:[AboutMenu node]]];

W taki sposób zakończyliśmy proces two-rzenia menu, które mimo swojej prostoty jest w pełni funkcjonalne. Nie pozostaje nic innego jak przejść do sedna artykułu – two-rzenia prostej gry.

Kółko i KrzyżykJak wskazuje tytuł paragrafu, naszym ce-lem będzie stworzenie mobilnej wersji kla-sycznej gry Kółko i Krzyżyk (Rysunek 5). Skupimy się jednak na aspektach związa-

Rysunek 4. Widok prostego menu

Listing 8. Plik AboutMenu.h

#import "cocos2d.h"

@interface AboutMenu : Scene

{

}

- (id) init;

- (void) onBackItem: (id)sender;

@end

Listing 9. Plik AboutMenu.m

#import "AboutMenu.h"

#import "MainMenu.h"

@implementation AboutMenu

- (id) init

{

self = [super init];

if ( self != nil )

{

MenuItem* backItem = [MenuItemFont itemFromString:@"Back" target:self

selector:@selector(onBackItem:)];

Menu* menu = [Menu menuWithItems:backItem, nil];

[menu alignItemsVertically];

[self addChild:menu];

}

return self;

}

- (void) onBackItem: (id) sender

{

[[Director sharedDirector] replaceScene:[RotoZoomTransition

transitionWithDuration:1.0 scene:[MainMenu node]]];

}

@end

Listing 10. Plik HelpMenu.h

#import "cocos2d.h"

@interface HelpMenu : Scene

{

}

- (id) init;

- (void) onBackItem: (id)sender;

@end

Page 12: SDJ_02_2010_PL

02/201012

Biblioteka MiesiącaProgramowanie gier pod iPhone

www.sdjournal.org 13

nych z wykorzystaniem biblioteki cocos2d i w pełni świadomie zostaną pominięte ta-kie elementy jak opracowanie algorytmów sztucznej inteligencji, które mogłyby za-ciemnić główną treść.

Na początek potrzebne są trzy pliki graficz-ne w formacie PNG przedstawiające odpo-wiednio pojedyncze pole gry, kółko oraz krzy-żyk. Aby możliwe było ich wykorzystanie w

aplikacji, należy je umeścić w katalogu Reso-urces widocznego w panelu znajdującego się po lewej stronie okna edytora XCode.

W grze kółko i krzyżyk mamy do czynie-nia z polem gry w rozmiarze 3x3, przy czym każde z nich może być w jednym z trzech sta-nów: puste, oznaczone krzyżykiem lub ozna-czone kółkiem. Dlatego też pierwszym kro-kiem będzie utworzenie typu wyliczeniowe-

go MarkType pozwalającego na określenie każ-dego z tych stanów.

Podstawowym obiektem wykorzysty-wanym w grze będzie pole gry reprezento-wanie przez klasę BoardField. Jej interfejs wraz z definicją opisanego wcześniej wyli-czenia został przedstawiony na Listingu 12. Warto zwrócić uwagę na deklarację interfej-su TargetedTouchDelegate. Jest on dostar-czany przez bibliotekę cocos2d i ułatwia ob-sługiwanie zdarzeń związanych z interfej-sem dotykowym na poziomie pojedyncze-go obiektu graficznego (w naszym przypad-ku pola gry). Klasa BoardField będzie im-plementować dwie z czterech udostępnia-nych metod.

Pierwsza metoda ccTouchBegan jest wy-woływana za każdym razem, gdy aplikacja otrzyma informacje o nowym dotknięciu ekranu urządzenia przez użytkownika. Ko-rzystając z otrzymanych danych na temat pozycji, należy dokonać decyzji, czy zda-rzenie to powinno zostać przechwycone i zwrócić wartość logiczną YES lub NO. Po-zwala to na uniknięcie konieczności imple-mentacji często skomplikowanych i podat-nych na błędy, algorytmów śledzących po-szczególne ruchy. Interfejs multitouch do-stępny w urządzeniu jest bardzo wygodny dla użytkownika, ale przeciętnego progra-mistę może przyprawić o ból głowy. Dlate-go też warto korzystać z ułatwień , jakie w tej dziedzinie zostały udostępnione przez bibliotekę cocos2d.

Drugą metodą implementowaną przez opisywaną klasę jest ccTouchEnded, której nazwa jednoznacznie wskazuje, iż jest wy-woływana w momencie zakończenia poje-dynczego ruchu na ekranie. Jednak w tym

Rysunek 5. Widok gry

Listing 11. Plik HelpMenu.m

#import "HelpMenu.h"

#import "MainMenu.h"

@implementation HelpMenu

- (id) init

{

self = [super init];

if ( self != nil )

{

MenuItem* backItem = [MenuItemFont itemFromString:@"Back" target:self

selector:@selector(onBackItem:)];

Menu* menu = [Menu menuWithItems:backItem, nil];

[menu alignItemsVertically];

[self addChild:menu];

}

return self;

}

- (void) onBackItem: (id) sender

{

[[Director sharedDirector] replaceScene:[RotoZoomTransition

transitionWithDuration:1.0 scene:[MainMenu node]]];

}

@end

Listing 12. Plik BoardField.h

#import "cocos2d.h"

typedef enum

{

MarkTypeNone,

MarkTypeCross,

MarkTypeCircle

} MarkType;

@interface BoardField : TextureNode <TargetedTouchDelegate>

{

MarkType mark;

}

@property (assign) MarkType mark;

+ (id)itemWithTexture:(Texture2D *)texture;

- (id)initWithTexture:(Texture2D *)texture;

@end

Page 13: SDJ_02_2010_PL

02/201012

Biblioteka MiesiącaProgramowanie gier pod iPhone

www.sdjournal.org 13

wypadku nie jest konieczne sprawdzanie, czy otrzymaliśmy zdarzenie związane z do-tykiem, który jest powiązany z obiektem. Otrzymamy informację o takim zdarzeniu tylko wtedy, gdy metoda ccTouchBegan zwróci wartość YES. Na początku może się to wydawać nieco zagmatwane, jednak kompletny kod źródłowy opisywanej klasy przedstawiony na Listingach 12 i 13 powi-nien wszystko wyjaśnić.

Warto przy tym zwrócić uwagę na meto-dy onEnter i onExit. Są one wywoływane w momencie, gdy obiekt klasy dziedziczącej po klasie CocosNode (lub jej pochodnej) zo-stanie dodany lub usunięty ze sceny. W na-szym przypadku zostały one wykorzystane w celu uruchomienia mechanizmu śledze-nia zdarzeń związanych z interfejsem doty-kowym.

Krótkiego wyjaśnienia może wymagać implementacja opisanej wcześniej meto-dy ccTouchBegan. W pierwszej kolejności sprawdza ona, czy pole nie zostało już wcze-śniej zaznaczone. Jeżeli jest ono puste, na-leży na podstawie współrzędnych określić, czy właśnie to pole zostało dotknięte przez użytkownika. Jeżeli oba powyższe warun-ki są spełnione, wystarczy zwrócić wartość YES, aby poinformować, iż zdarzenie zwią-zane z tym konkretnym dotknięciem zo-stało na stałe powiązane z rozpatrywanym obiektem.

Dziedziczenie z klasy TextureNode zwal-nia programistę z konieczności ręcznego renderowania obiektu na ekranie. Należy je-dynie dostarczyć teksturę, która ma poja-wić się na ekranie urządzenia. Zajmuje się tym metoda iniWithTexture, która jest od-powiedzialna za inicjalizację obiektu kla-sy BoardField. Bliźniacza metoda statyczna itemWithTexture upraszcza proces tworze-nia obiektu.

Klasa BoardField wykorzystuje prosty, ale ogromnie przydatny mechanizm powia-domień, który jest udostępniany przez bi-bliotekę Cocoa. Jego sercem, a jednocześnie samowystarczalnym elementem jest klasa NSNotificationCenter udostępniana w for-mie singletonu. W celu wysłania do wszyst-kich nasłuchujących obiektów, informacji o zaistnieniu jakiegoś zdarzenia (identyfikowa-nego za pomocą nazwy) wystarczy wywołać metodę postNotificationName z odpowied-nimi parametrami. Przykład jej użycia przed-stawia metoda ccTouchEnded, która infor-muje o zaznaczeniu danego pola przez użyt-kownika.

Silnik gryMożliwe, że w przypadku gry w kółko i krzyżyk określenie silnik gry jest lekką przesadą, jednak posłużymy się nim z bra-ku lepszej alternatywy. Zostanie on zawar-

Listing 13. Plik BoardField.m

#import "BoardField.h"

@implementation BoardField

@synthesize mark;

+ (id)itemWithTexture:(Texture2D *)texture

{

return [[[self alloc] initWithTexture:texture] autorelease];

}

- (id)initWithTexture:(Texture2D *)texture

{

self = [super init];

if ( self )

{

self.texture = texture;

mark = MarkTypeNone;

}

return self;

}

- (void)onEnter

{

[[TouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0

swallowsTouches:YES];

[super onEnter];

}

- (void)onExit

{

[[TouchDispatcher sharedDispatcher] removeDelegate:self];

[super onExit];

}

- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event

{

if ( mark != MarkTypeNone )

{

return NO;

}

CGSize size = [self.texture contentSize];

CGRect rect = CGRectMake( -size.width/2, -size.height/2, size.width, size.height );

if ( !CGRectContainsPoint( rect, [self convertTouchToNodeSpaceAR:touch] ) )

{

return NO;

}

return YES;

}

- (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event

{

[[NSNotificationCenter defaultCenter] postNotificationName:@"BoardFieldTouch"

object:self];

}

@end

Page 14: SDJ_02_2010_PL

02/201014

Biblioteka MiesiącaProgramowanie gier pod iPhone

www.sdjournal.org 15

Listing 14. Plik Game.h

Listing 15. Plik Game.m

#import "Game.h"

#import "MainMenu.h"

#include <stdlib.h>

@implementation Game

-(id) init

{

self = [super init];

if( self )

{

// Reset marked fields counter

fieldsCounter = 0;

// Load board field texture

Texture2D* fieldTexture = [[TextureMgr

sharedTextureMgr] addImage:@"ttt-

field.png"];

// Calculate position of top-left board field

CGSize displaySize = [[Director sharedDirector]

winSize];

float x = ( displaySize.width - 2*fieldTexture.contentS

ize.width )/2.0f;

float y = displaySize.height - ( displaySize.height

- 2*fieldTexture.contentSize.height

)/2.0f;

// Create game board

fields = [[NSMutableArray alloc] initWithCapacity:3];

for ( int i = 0; i < 3; ++i )

{

NSMutableArray* column = [NSMutableArray

arrayWithCapacity:3];

[fields addObject:column];

for ( int j = 0; j < 3; ++j )

{

BoardField* field = [BoardField itemWithTexture:

fieldTexture];

field.position = CGPointMake(

x + i*fieldTexture.contentSize.width,

y - j*fieldTexture.contentSize.height

);

[self addChild: field];

[column addObject: field];

}

}

// Listen for board fields touch notifications

[[NSNotificationCenter defaultCenter] addObserver:

self selector:@selector(fieldTouched:)

name:@"BoardFieldTouch" object:nil];

}

return self;

}

- (void) dealloc

{

[fields release];

[super dealloc];

}

-(void) fieldTouched:(NSNotification*)notification

{

fieldsCounter++;

// Mark touched board field

BoardField* field = [notification object];

[self markField:field mark:MarkTypeCross];

// Check if player has won

if ( [self checkBoard:MarkTypeCross] )

{

[self displayMessage:@"You won the game!"];

}

else if ( fieldsCounter == 3*3 )

{

[self displayMessage:@"Tie!"];

}

else

{

id delay = [DelayTime actionWithDuration:0.5];

id call = [CallFunc actionWithTarget:self selector:

@selector(opponentTurn)];

id sequence = [Sequence actions:delay, call, nil];

[self runAction:sequence];

}

}

-(void) opponentTurn

{

fieldsCounter++;

// Mark random field

[self markRandomField:MarkTypeCircle];

// Check if player has lost the game

if ( [self checkBoard:MarkTypeCircle] )

{

[self displayMessage:@"You have lost!"];

}

}

- (BOOL) checkBoard:(MarkType)mark

{

int i, j;

// Check columns

for ( i = 0; i < 3; ++i )

{

for ( j = 0; j < 3; j++ )

{

BoardField* field = [[fields objectAtIndex:i]

objectAtIndex:j];

if ( field.mark != mark )

#import "cocos2d.h"

#import "BoardField.h"

@interface Game : Scene <UIAlertViewDelegate>

{

NSMutableArray* fields;

int fieldsCounter;

}

-(void) fieldTouched:(NSNotification*)notification;

-(void) opponentTurn;

-(BOOL) checkBoard:(MarkType)mark;

-(void) markField:(BoardField*)field mark:(MarkType)mark;

-(void) markRandomField:(MarkType)mark;

-(void) displayMessage:(NSString*)message;

@end

Page 15: SDJ_02_2010_PL

02/201014

Biblioteka MiesiącaProgramowanie gier pod iPhone

www.sdjournal.org 15

Listing 15. Plik Game.m

{

break;

}

}

if ( j == 3 )

{

return YES;

}

}

// Check rows

for ( i = 0; i < 3; ++i )

{

for ( j = 0; j < 3; j++ )

{

BoardField* field = [[fields objectAtIndex:j]

objectAtIndex:i];

if ( field.mark != mark )

{

break;

}

}

if ( j == 3 )

{

return YES;

}

}

// Check diagonally from top-left to bottom-right corner

for ( i = 0; i < 3; i++ )

{

BoardField* field = [[fields objectAtIndex:i]

objectAtIndex:i];

if ( field.mark != mark )

{

break;

}

}

if ( i == 3 )

{

return YES;

}

// Check diagonally from bottom-left to top-right corner

for ( i = 0; i < 3; i++ )

{

BoardField* field = [[fields objectAtIndex:2-i]

objectAtIndex:i];

if ( field.mark != mark )

{

break;

}

}

if ( i == 3 )

{

return YES;

}

return NO;

}

-(void) markField:(BoardField*)field mark:(MarkType)mark

{

field.mark = mark;

Texture2D* texture = NULL;

if ( mark == MarkTypeCircle )

{

texture = [[TextureMgr sharedTextureMgr] addImage:

@"ttt-circle.png"];

}

else if ( mark == MarkTypeCross )

{

texture = [[TextureMgr sharedTextureMgr] addImage:

@"ttt-cross.png"];

}

else

{

return;

}

TextureNode* node = [TextureNode node];

node.texture = texture;

CGSize size = [field contentSize];

node.position = CGPointMake( size.width/2, size.height/2 );

[field addChild:node];

id fadeIn = [FadeIn actionWithDuration:0.75];

[node runAction:fadeIn];

}

-(void) markRandomField:(MarkType)mark

{

BoardField* field = NULL;

while ( !field || field.mark != MarkTypeNone )

{

int index = arc4random()%(3*3);

int column = index/3;

int row = index%3;

field = [[fields objectAtIndex:column] objectAtIndex:row];

}

[self markField:field mark:mark];

}

-(void) displayMessage:(NSString*)message

{

[[Director sharedDirector] pause];

UIAlertView* dialog = [[UIAlertView alloc] init];

[dialog setDelegate:self];

[dialog setTitle:@"Game Over"];

[dialog setMessage:message];

[dialog addButtonWithTitle:@"OK"];

[dialog show];

[dialog release];

}

-(void) alertView:(UIAlertView *)alert clickedButtonAtIndex:

(NSInteger)buttonIndex

{

[[Director sharedDirector] resume];

// Return to main menu

[[Director sharedDirector] replaceScene:

[RotoZoomTransition

transitionWithDuration:1.0 scene:

[MainMenu node]]];

}

@end

Page 16: SDJ_02_2010_PL

02/201016

Biblioteka Miesiąca

ty w klasie GameScene reprezentującej po-jedynczą rozgrywkę przedstawionej na Li-stingach 14 i 15. W związku z prostotą opi-sywanej gry nie jest ona zbyt złożona, jed-nak warto opisać kilka kluczowych ele-mentów. .

Klasa GameScene zawiera tylko dwa po-la. Pierwsze z nich opatrzone nazwą fields przechowuje w tablicy wszystkie obiek-ty reprezentujące pojedyncze pola gry. NSMutableArray jest kontenerem udostęp-nianym przez bibliotekę Cocoa i pozwala na przechowywanie obiektów dowolnego typu. Drugie pole będzie wykorzystywane w celu śledzenia liczby zaznaczonych pól. Informa-cja ta pozwoli na wykrycie zakończenia gry w przypadku remisu.

Przegląd silnika gry rozpoczniemy od jego inicjalizacji, która odbywa się w meto-dzie init. Pierwszą czynnością, jaką należy wykonać, jest załadowanie tekstury pustego pola gry. W dalszej części metody wykorzy-stana ona jest podczas tworzenia obiektów klasy BoardField i ich rozmieszczania na

ekranie. W metodzie tej kolejny raz pojawia się klasa NSNotificationCenter. Do tej po-ry wykorzystaliśmy ją tylko w celu wysyła-nia informacji o zdarzeniach. W tym miej-scu rejestrujemy metodę fieldTouched, która posłuży do ich obsługi. Jej działanie jest dosyć oczywiste - dokonuje ona za-znaczenia dotkniętego pola, a następnie sprawdza, czy użytkownik zapewnił sobie tym ruchem zwycięstwo lub remis. Jeżeli nie, wykonywany jest ruch przeciwnika. Jak wspomniałem we wstępie, nie będzie-my zajmować się opracowaniem algorytmu sztucznej inteligencji. Dlatego też przeciw-nik gracza zachowuje się dosyć nieprzewi-dywanie, a wynika to z prostego faktu za-znaczania losowego pola gry.

Przy okazji metody markField odpowie-dzialnej za załadowanie odpowiedniej tek-stury i zaznaczenie pola kółkiem lub krzy-żykiem, warto zwrócić uwagę na wykorzy-stanie mechanizmu akcji. Jest on udostęp-niany przez bibliotekę cocos2d i pozwa-la na odtwarzanie prostych animacji. Aby zapewnić płynne pojawienie się zaznacze-nia na dotkniętym polu, użyta została ak-cja FadeIn. Jednak możliwości udostępnia-ne przez ten mechanizm są znacznie więk-sze. Warto spojrzeć do dokumentacji, aby zapoznać się ze wszystkimi dostępnymi ak-cjami. Zapewniam, że samo eksperymento-wanie z nimi może przynieść dużo satys-fakcji, a otrzymane efekty często przerasta-ją oczekiwania.

Na koniec każdej gry należy poinformować gracza o jego rezultatach. W przypadku na-szej gry może to być zwycięstwo, przegrana lub remis. W tym celu wykorzystany zosta-nie natywny interfejs użytkownika systemu, a dokładniej okienko UIAlertView (Rysunek 6). Metoda displayMessage prezentuje spo-sób jego inicjalizacji w kilku linijkach kodu, które można streścić jako utwórz, nazwij, do-daj przyciski i pokaż.

Na tym kończy się proces tworzenia gry. Zainteresowane osoby odsyłam do płyty dołączonej do numeru, na której znajdu-ją się kompletne źródła gry wraz z plikiem

projektu. Wystarczy otworzyć go za pomo-cą edytora XCode i uruchomić w symula-torze. Warto bliżej przyjrzeć się samej bi-bliotece cocos2d-iphone i poeksperymen-tować z utworzoną aplikacją. A jeżeli uda się stworzyć coś ciekawego, nie pozosta-je nic innego jak pochwalenie się naszymi umiejętnościami poprzez umieszczenie gry w AppStore.

PodsumowanieNiestety zarówno sam temat tworzenia gier, jak i zakres możliwości oferowanych przez bibliotekę cocos2d stanowczo wy-kracza poza zakres jednego artykułu. Mam jednak nadzieję, że udało mi się przybli-żyć podstawowe zagadnienia związane z tym jakże emocjonującym zajęciem. Osoby chcące rozwinąć swoją wiedzę odsyłam do ramki zawierającej adresy najważniejszych stron internetowych.

Na sam koniec warto wspomnieć, że bi-blioteka cocos2d-iphone rozpowszechniana jest na stosunkowo liberalnej licencji GNU Lesser General Public License, co pozwala na bezpieczne wykorzystywanie jej w komercyj-nych projektach.

W Sieci

• http://www.cocos2d-iphone.org/ – cocos2d for iPhone;• http://lethain.com/entry/2008/oct/03/notes-on-cocos2d-iphone-development/ – Notes on Cocos2d iPhone Development;• http://www.cocoadevcentral.com/d/learn_objectivec/ – Cocoa Dev Central: Learn Objective-C;• http://developer.apple.com/iphone/ – iPhone Dev Center.

Literatura

• Programming in Objective-C 2.0, Stephen G. Kochan, Addison Wesley;• iPhone Games Projects, PJ Cabrera, Apress;• Beginning iPhone Development: Exploring the iPhone SDK, Dave Mark, Jeff LaMarche, Apress.

Rysunek 6. Widok gry

JAKUB WĘGRZYNPracuje na stanowisku Starszego Programi-sty Gier Natywnych w firmie Gamelion, wcho-dzącej w skład Grupy BLStream. Jakub specja-lizuje się w technologiach związanych z pro-dukcją oprogramowania na platformy mobil-ne, ze szczególnym naciskiem na tworzenie gier. Grupa BLStream powstała, by efektyw-niej wykorzystywać potencjał dwóch szybko rozwijających się producentów oprogramowa-nia – BLStream i Gamelion. Firmy wchodzące w skład grupy specjalizują się w wytwarzaniu oprogramowania dla klientów korporacyjnych, w rozwiązaniach mobilnych oraz produkcji i te-stowaniu gier.Kontakt z autorem: [email protected]

Page 17: SDJ_02_2010_PL

17

Opis DVD

Jeśli nie możesz odczytać zawartości płyty DVD, a nie jest ona uszkodzona mechanicznie, sprawdź ją na co najmniej dwóch napędach DVD. W razie problemów z płytą, prosimy pisać pod adres: [email protected]

Redakcja nie udziela pomocy

technicznej w instalowaniu i użytkowaniuprogramów

zamieszczonych na płytach DVD-ROM dostarczonych razem z pismem.

Kurs Video Programowanie w języku Java

Od Witaj świecie do aplikacji korporacyjnych. Cz.VAplikacje internetowe - Serwlety

Piąty odcinek serii to krok w kierunku tworzenia aplikacji kor-poracyjnych. Tym razem zajmować się będziemy podstawowym mechanizmem, stojącym u podstaw współczesnych narzędzie do tworzenia aplikacji internetowych na platformie Java – serw-letami.Na początku dowiemy się jak wygląda typowa komunikacja z użyciem protokołu HTTP oraz w jaki sposób serwery serwletów czyli kontenery serwletów wpasowują się w ten model.W kolejnych krokach dowiemy się jak przygotować środowisko, które umożliwi stworzenie pierwszego serwletu. Pobierzemy i przygotujemy jeden z najpopularniejszych kontenerów – Apache Tomcat oraz zainstalujemy odpowiednią wersję środowiska Eclip-se, przeznaczonego do tworzenia aplikacji korporacyjnych.Następnie stworzymy pierwszy serwlet, który dynamicznie stworzy zawartość strony internetowej, po to by w kolejnej od-

słonie zademonstrować przekazywanie parametrów i generowanie odpowiedzi z ich użyciem.Jednym z podstawowych mechanizmów, który umożliwia prze-chowywanie stanu w aplikacjach internetowych jest sesja, która jest dostępna w serwletach. Dowiemy się, jak z użyciem Java EE zarządzać sesją w środowisku serwletów i jak generować odpowie-dzi posługując się nią.Ostatni element, który poznamy w tym odcinku to będzie przetwa-rzanie stron JSP. Dowiemy się, czemu służą strony JSP i w jakiej są relacji do serwletów oraz poznamy podstawy używania stron JSP.

Kurs Video Flex – Papervision3DOdc. 7 - Przez dwa odcinki zajmiemy się grafiką 3D oraz biblioteką Papervision3D. Na początku stworzymy pierwszą scenę wraz z jej elementami oraz poznamy podstawy pv3D.

Odc. 8 - Kontynuując eksperymenty z Papervision3D, zacznie-my ładować bardziej skomplikowane modele oraz dodamy interak-tywność do naszej aplikacji.

Video Kurs Quest3D - Prosta gra FPP

Page 18: SDJ_02_2010_PL

02/201018

Klub techniczny Progress SoftwareTechnologie Progress OpenEdge

www.sdjournal.org 19

W niniejszym odcinku zapoznamy się z zagadnieniami definiowa-nia, zapełniania danymi, prze-

prowadzania operacji na danych dla obiek-tów ProDataSet oraz ich wymianą z innymi aplikacjami.

ProDataSet (PDS) jest obiektem utwo-rzonym w pamięci, złożonym z tablic tym-czasowych. Dla tablic tych można opcjonal-nie zdefiniować relacje. Ponadto PDS mo-że być podłączony do zdefiniowanych źró-deł danych, które odpowiadają za zapełnie-nie obiektów informacjami. Zmiany danych w ProDataSetach mogą być przesyłane z po-wrotem celem zapisania w źródłach danych. Inaczej mówiąc, ProDataSet realizuje mapo-wanie pomiędzy zbiorem tablic w bazie da-nych (lub innym typie źródeł danych) i ich re-prezentacją w pamięci.

Obiekty ProDataSet są definiowane nieza-leżnie od struktury danych, którymi są zapeł-nione. Z tego powodu przy ich pomocy moż-na w łatwy i pewny sposób odseparować da-ne aplikacji od specyfikacji oryginalnego źró-dła danych.

Korzyści:PDS pozwalają na zdefiniowanie struktury da-nych, reprezentującą wybrane tablice, niezależnie od struktury zewnętrznych danych w bazie. Pod-stawową korzyścią jest tutaj możliwość tworzenia logiki biznesowej i logiki interfejsu odnoszących się do formatu danych zawartych w strukturze PDS, dopasowanych do specyfiki aplikacji.

Inne korzyści to:

• Dane pochodzące z różnych źródeł mogą być traktowane jako jeden obiekt.

• Obiekt ten może być automatycznie za-pełniany danymi, przy zachowaniu relacji między nimi.

• Wszelkie modyfikacje danych w PDS, do-dawanie i usuwanie rekordów są automa-tycznie wychwytywane.

• PDS może być przekazany jako pojedynczy parametr między procedurami wewnątrz sesji lub pomiędzy sesjami.

• Obsługa zdarzeń przez trygery (Callback Procedures). Można dopasować obsługę standardowych zdarzeń lub napisać wła-sną w języku OpenEdge ABL.

• Obsługa mapowania danych PDS do/z pli-ków XML.

Spośród wielu sposobów zastosowania PDS w aplikacjach, najczęściej stosowane to:

• Zastosowanie PDS do reprezentacji złożo-nych danych.

Technologie Progress OpenEdgeProgressowe obiekty typu Dataset (ProDataSet) rozszerzają możliwości definiowania złożonych obiektów biznesowych oraz relacji między nimi. Są bardzo ważnym elementem w procesie budowania nowoczesnych aplikacji rozproszonych i wymiany danych z innymi aplikacjami lub ich modułami poprzez XML.

Dowiesz się:• Jakie są zastosowania obiektów ProDataSet;• Jak się je tworzy i nimi zarządza;• O obsłudze zdarzeń dedykowanych dla Pro-

DataSetu.

Powinieneś wiedzieć:• Ogólne zasady manipulacji danymi. Znajo-

mość obiektów DataSet jest dodatkowym atutem.

Poziom trudności

Część 6. Obiekty ProDataSet

Rysunek 1. Dołączenie źródła danych do PDS

����������������������������������������������������������

��������

����������������

�������

���������������������������

������������

Page 19: SDJ_02_2010_PL

02/201018

Klub techniczny Progress SoftwareTechnologie Progress OpenEdge

www.sdjournal.org 19

• Tam i z powrotem, czyli przetwarzanie da-nych pomiędzy sesją klienta i serwera.

• Wstawienie tablic tymczasowych do PDS w celu wykorzystania jego zaimplemento-wanych cech (np. obsługi zdarzeń).

Tworzenie ProDataSetówDefinicja ProDataSetu (DATASET) musi być poprzedzona zdefiniowaniem jednej lub kil-ku tablic tymczasowych (TEMP-TABLE). Na-stępnym krokiem jest zdefiniowanie i podłą-czenie obiektów źródeł danych (DATA-SOURCE – patrz Rysunek 1). Listing 1 pokazuje całą aplikację, służącą do zapełnienia PDS (dsOr-derOrderline) złożonego z dwóch tablic tym-czasowych (ttOrder, ttOrderLine) i relacji między nimi (drOrderOrderLine), do którego przyłączone są dwa źródła danych (srcOrder, srcOrderLine). Każdy bufor tablicy tymcza-sowej może być podłączony do innego źródła danych. Podczas ładowania danych brane są pod uwagę pola o identycznych nazwach (ta-blica tymczasowa –> źródło danych) , a pozo-stałe ignorowane. Można określić jednak wła-sny sposób mapowania , jeśli pola mają różne nazwy, lecz odpowiadający typ i charakter da-nych (np.: BUFFER ttOrder:ATTACH-DATA-

SOURCE(DATA-SOURCE srcOrder:HANDLE,”Cu

stomer.Name,CustName”)). Do zapełniania PDS służy metoda FILL, przy

czym istnieje pięć trybów zapełniania. Domyśl-nym MERGE polega na dodaniu rekordów do ist-niejącego zestawu z pominięciem duplikatów. Co ciekawe, można zapełnić nie cały PDS, lecz wybrane tablice tymczasowe. Dopuszczalne jest również zapełnianie z pominięciem relacji mię-dzy tablicami.

W języku Progress ABL można utworzyć Pro-DataSety także jako obiekty dynamiczne. Jest to użyteczna technika, gdy struktura obiektu nie jest znana podczas kompilacji. Obiekty takie wy-korzystuje się np. przy obsłudze WebSerwisów lub przy realizacji zapisu zmian do bazy. Przy-kład ilustrujący utworzenie dynamicznego PDS załączony jest na płycie CD.

EdycjaProces edytowania danych w PDS (patrz Rysu-nek 2) rozpoczyna się od dodania w definicji tablicy tymczasowej elementu BEFORE-TABLE (patrz Listing 1), który służy do przechowy-wania wartości przed edycją (oryginalna tabli-ca określana jest jako AFTER-TABLE). Każda ta-blica tymczasowa posiada logiczny atrybut TRACKING-CHANGES, którym włącza się proces śledzenia zmian w tablicy BEFORE i AFTER.

W większości przypadków edytowane są tyl-ko wybrane rekordy. Aby nie obciążać niepo-trzebnie sieci, powinno się przesyłać z powro-tem do serwera bazy tylko te zmodyfikowane dane. W tym celu tworzy się dynamiczny PDS o strukturze identycznej jak oryginalny obiekt (metoda CREATE -LIKE) i zapełnia się go zmo-

dyfikowanymi danymi (metoda GET-CHANGES). Zapis do bazy realizowany jest za pomocą me-tody SAVE-ROW-CHANGES. Technologia zapisu

PDS umożliwia obsługę konfliktów, które mogą wystąpić podczas zapisu realizowanego według strategii Optimistic Locking.

Listing 1. Kompletny przykład aplikacji. Pliki z definicjami

/* Plik ttDefs.i, zawierający definicję tablic tymczasowych */

DEFINE TEMP-TABLE ttOrder NO-UNDO

FIELD OrderNum AS INTEGER FORMAT "zzzzzzzzz9"

FIELD OrderDate AS DATE FORMAT "99/99/99"

FIELD ShipDate AS DATE FORMAT "99/99/99"

FIELD PromiseDate AS DATE FORMAT "99/99/99"

FIELD OrderTotal AS DECIMAL FORMAT "->,>>>,>>9.99"

INDEX OrderNum IS UNIQUE PRIMARY OrderNum.

DEFINE TEMP-TABLE ttOrderLine NO-UNDO BEFORE-TABLE ttOrderLineBefore

FIELD OrderNum AS INTEGER FORMAT "zzzzzzzzz9"

FIELD LineNum AS INTEGER FORMAT ">>9"

FIELD ItemNum AS INTEGER FORMAT "zzzzzzzzz9"

FIELD Price AS DECIMAL FORMAT "->,>>>,>>9.99"

FIELD Qty AS INTEGER FORMAT "->>>>9"

FIELD Discount AS INTEGER FORMAT ">>9%"

FIELD ExtendedPrice AS DECIMAL FORMAT "->>>,>>9.99"

INDEX OrderNum_LineNum IS UNIQUE PRIMARY OrderNum LineNum.

/* Plik dsDefs.i, zawierający definicję PDS */

DEFINE DATASET dsOrderOrderLine FOR ttOrder, ttOrderLine

DATA-RELATION drOrderOrderLine FOR ttOrder, ttOrderLine

RELATION-FIELDS (OrderNum, OrderNum).

/* Plik srcDefs.i, zawierający definicje źródeł danych*/

DEFINE QUERY qOrder FOR Order.

DEFINE DATA-SOURCE srcOrder FOR Order.

DEFINE DATA-SOURCE srcOrderLine FOR OrderLine.

W Sieci

• http://communities.progress.com/pcom/community/psdn/openedge – Progress Software Deve-lopers Network, część ukierunkowana na zagadnienia techniczne związane z OpenEdge®;

• http://web.progress.com – strona Progress Software Corporation;• http://www.progress.com/pl – strona Progress Software sp z o.o.

Rysunek 2. Proces edycji danych

�������������������������

�����������������������������

�����������������������

����������������������������������

��������������

�������������

�����������������������

�����������

�� � � ��

� �� � � ��

��

��

��

��

��

��

��

��

�������������

Page 20: SDJ_02_2010_PL

02/201020

Klub techniczny Progress Software

Obsługa zdarzeńDo obsługi zdarzeń służą procedury typu Cal-lback Procedure, uruchamiane automatycznie, gdy zachodzi zdarzenie na wybranym obiekcie. Niektóre zdarzenia mają zdefiniowaną domyśl-ną obsługę, jednakże deweloper aplikacji ma możliwość zdefiniowania własnej obsługi. Po-niżej przedstawiam typy obiektów i zdarzenia, które można dla nich zdefiniować:

• PDS, tablica tymczasowa, bufor: BEFORE-FILL, AFTER-FILL

• tablica tymczasowa: ROW-CREATE, ROW-

DELETE, ROW-UPDATE

• zapytanie: QUERY-OFF-END

Aby zdefiniować obsługę, należy (patrz Li-sting 2):

• napisać kod procedury dla obsługi zdarzenia • skojarzyć procedurę przy pomocy metody

SET-CALLBACK-PROCEDURE.

Dla osób, które chciałyby pogłębić wiedzę z te-go tematu o np. obsługę błędów i transakcji, mapowanie do/z plików XML (ten temat był poruszany w poprzednim odcinku), polecam skorzystać chociażby z informacji, które można

znaleźć pod podanymi poniżej odnośnikami. Niniejszym odcinkiem kończę serię sześciu ar-tykułów poświęconych technologiom Progress OpenEdge. Ciekawych tematów z tego zakresu nie brakuje, toteż mam nadzieję, że w niedale-kiej przyszłości cykl ten będzie kontynuowany. Dziękuję i do zobaczenia.

Listing 1. Kompletny przykład aplikacji c.d. Procedura AllInOne.p

Listing 2. Obsługa zdarzenia

/* Definicje zmiennych */

DEFINE VARIABLE iCustNum AS INTEGER FORMAT ">>>>9"

LABEL "Customer Number".

DEFINE VARIABLE cCustName AS CHARACTER FORMAT

"x(30)" LABEL "Name".

DEFINE VARIABLE hChangeDataSet AS HANDLE NO-UNDO.

DEFINE VARIABLE hdsOrderOrderLine AS HANDLE NO-UNDO.

/* Pliki include z definicjami */

{ttDefs.i}

{dsDefs.i}

UPDATE iCustNum WITH FRAME CustFrame TITLE "Customer Lookup".

{srcDefs.i}

/* Przyłączenie źródeł danych */

BUFFER ttOrder:ATTACH-DATA-SOURCE(DATA-SOURCE srcOrder:

HANDLE,?,?).

BUFFER ttOrderLine:ATTACH-DATA-SOURCE(DATA-SOURCE

srcOrderLine:HANDLE,?,?).

/* Przygotowanie zapytania */

QUERY qOrder:QUERY-PREPARE("FOR EACH Order WHERE

Order.CustNum = " + STRING(iCustNum)).

/* Zapełnienie ProDataSetu danymi */

DATASET dsOrderOrderLine:FILL().

/* Wyszukanie rekordu i wyświetlenie danych */

FIND Customer WHERE Customer.Custnum = iCustNum NO-LOCK.

ASSIGN cCustName = Customer.NAME.

DISPLAY cCustName WITH FRAME CustFrame.

/* Włączenie śledzenia zmian dla ttOrderLine */

TEMP-TABLE ttOrderLine:TRACKING-CHANGES = TRUE.

/* Użytkownik może zmienić wartości Qty i Discount */

FOR EACH ttOrder:

DISPLAY ttOrder.OrderNum

ttOrder.OrderDate

ttOrder.ShipDate

ttOrder.PromiseDate

ttOrder.OrderTotal WITH FRAME ttOrderFrame

1 COL TITLE "Order".

FOR EACH ttOrderLine OF ttOrder:

DISPLAY ttOrderLine.OrderNum

ttOrderLine.LineNum

ttOrderLine.ItemNum

ttOrderLine.Price

ttOrderLine.Qty

ttOrderLine.Discount

ttOrderLine.ExtendedPrice WITH FRAME

ttOrderLineFrame

1 COL

TITLE "Order Lines".

UPDATE

ttOrderLine.Qty

ttOrderLine.Discount

WITH FRAME ttOrderLineFrame 1 COL.

END.

END.

/* Utworzenie dynamicznego PDS: hChangeDataSet o takiej

samej strukturze jak

dsOrderOrderLine */

hdsOrderOrderLine = DATASET dsOrderOrderLine:HANDLE.

CREATE DATASET hChangeDataSet.

hChangeDataSet:CREATE-LIKE( hdsOrderOrderLine , "cds" ).

/* Process Get-Changes to extract Changes at DataSet Level */

hChangeDataSet:GET-CHANGES(hdsOrderOrderLine).

/* Wyłączenie śledzenia zmian */

TEMP-TABLE ttOrderLine:TRACKING-CHANGES = FALSE.

/* Zapis zmian do bazy */

FOR EACH ttOrderLineBefore TRANSACTION:

BUFFER ttOrderLineBEfore:SAVE-ROW-CHANGES().

END.

/* Przypisanie procedury Callback do zdarzenia After-Fill

obiektu OrderLine */

BUFFER ttOrderLine:SET-CALLBACK-PROCEDURE

("AFTER-FILL", "postOrderLineFill", THIS-PROCEDURE).

/* Kod procedury */

PROCEDURE postOrderLineFill:

DEFINE INPUT PARAMETER DATASET FOR dsOrderOrderLine.

PIOTR TUCHOLSKI Autor jest od 12 lat związany z technologiami Pro-gress Software. Wieloletni konsultant i Kierownik Działu Szkoleń Progress Software sp. z o.o., specja-lizujący się w technologii OpenEdge. Kontakt z autorem: [email protected]

DEFINE VARIABLE dOrderTotal AS DECIMAL INITIAL 0 NO-UNDO.

FOR EACH ttOrderLine WHERE ttOrderline.OrderNum =

ttOrder.OrderNum:

dOrderTotal = dOrderTotal + ttOrderLine.ExtendedPrice.

END.

ttOrder.OrderTotal = dOrderTotal.

END.

Page 21: SDJ_02_2010_PL
Page 22: SDJ_02_2010_PL

02/201022

Klub techniczny AdobeCo nowego w Flex 4

www.sdjournal.org 23

W niniejszym artykule postaram się wyjaśnić najważniejsze zmiany, ja-kie zaszły w najnowszej wersji Fle-

x'a. Przesiadając się z Flex'a 3 na Flex'a 4 , mu-simy pamiętać przede wszystkim, że nowa bi-blioteka wymaga wsparcia FlashPlayer'a 10, tu-dzież wszystkie aplikacje powinny być kompi-lowane pod najnowszą wersję FlashPlayer'a. Osobom , które znają poprzednią wersję Flex'a, nie powinna sprawić trudności migracja na 4-tą wersję biblioteki. Doświadczymy co prawda mnogości zmian architektonicznych, pojawiają się nowe przestrzenie nazw i odmłodzono dużą część komponentów, jednak w większości przy-padków ich interfejs udostępniony programi-ście nie odbiega znacznie od interfejsu znane-go z architektury Halo.

Przestrzenie nazw i pakietyFlex 3 wszystkie klasy biblioteki trzymał w jednym pakiecie mx.*, natomiast we Flex 4 wprowadzono nowy pakiet spark.* zawiera-jący komponenty, klasy bazowe, efekty, filtry, układy widoku, podstawowe kształty geome-tryczne, skórki i narzędzia.

Wiele klas zawartych w pakiecie spark.* współdzieli swoje nazwy z klasami zawar-

tymi w pakiecie mx.*. Aby uniknąć konflik-tów pomiędzy tymi klasami, rozszerzono przestrzenie nazw do: MXML 2006, MXML 2009, Spark, Halo.

• MXML 2006: Przestrzeń nazw używa-na w poprzedniej wersji. • URI: http://www.adobe.com/2006/mxml• Domyślny prefix: mx

• MXML 2009: Nowa przestrzeń nazw MXML-a. Nie zawiera tagów kompo-nentów.• URI: http://ns.adobe.com/mxml/2009 • Domyślny prefix: fx

• Spark:• URI: library://ns.adobe.com/flex/spark • Domyślny prefix: s

• Halo:• URI: library://ns.adobe.com/flex/halo • Domyślny prefix: mx

Dodano również wsparcie przestrzeni nazw dla selektorów CSS (przykład na Listingu 2).

SparkFlex 4 został zaprojektowany w oparciu o ideę „Design in Mind”, której celem jest zapewnienie szybkiej i bezproblemowej współpracy progra-misty i grafika. Aby umożliwić realizację tego pomysłu , wprowadzono całkiem nową archi-tekturę komponentów Spark, bazującą na do-brze nam znanej z Flex'a 3 architekturze Halo. Oddzielono w przejrzysty sposób wygląd kom-ponentów od ich logiki, gdzie w starej architek-turze granica ta była często mało czytelna. Za-dbano również o lekkość komponentów, któ-re aktualnie nie zawierają już wszystkich zbęd-nych elementów jak suwaki czy wirtualizację. Przyśpieszyło to w dużym stopniu inicjalizację aplikacji, jak i zmniejszyło jej rozmiar.

LayoutDużym usprawnieniem w stosunku do Ha-lo jest nowy sposób definiowania układu roz-mieszczenia komponentów. Dla programi-stów piszących w MXML-u niewiele uległo zmianie. Podstawowe właściwości takie jak width, height, minWidth, explicitWidth, percentWidth dalej mają to samo znacze-nie. Działanie LayoutManagera, jak i pod-stawowy cykl życia komponentu także nie uległ zmianie. Metody commitProperties(), measure() i updateDisplayList() są wy-woływane przez LayoutManagera w tej sa-mej kolejności i reguły unieważniania pozo-stały te same.

Co nowego w Flex 4Flex jest jedną z najbardziej zaawansowanych technologii do budowania aplikacji typu RIA w bezpośrednim tłumaczeniu bogatych aplikacji internetowych. Silnikiem wyświetlającym aplikacje Flex’owe jest technologia Adobe Flash, która pozwala na osiągnięcie jednolitego wyglądu uruchamianej aplikacji, niezależnie od wykorzystywanej przeglądarki czy systemu operacyjnego.

Dowiesz się:• Jakie są różnice pomiędzy Flex 3 a Flex 4;• Co to FXG;• Co to Spark.

Powinieneś wiedzieć:• Co to Flex;• Co to architektura Halo.

Poziom trudności

Tabela 1. Kombinacje komponentów Spark i odpowiadające im klasy z Halo

Kontenery Halo Kombinacje układów z kontenerami Spark

Canvas Group + BasicLayout

HBox Group + HorizontalLayout

VBox Group + VerticalLayout

Tile Group + TileLayout

List List + VerticalLayout

TileList List + TileLayout

Page 23: SDJ_02_2010_PL

02/201022

Klub techniczny AdobeCo nowego w Flex 4

www.sdjournal.org 23

Zasadniczymi zmianami w stosunku do Flex 3 są:

• Odseparowanie układu od kontenera po-zwoliło na zmianę rozmieszczenia kompo-nentów w trakcie działania aplikacji. Zmi-nimalizowano tym samym liczbę kontene-rów wymaganych do stworzenia zaawanso-wanego układu rozmieszczenia i pozwoliło na wielokrotne użycie tego samego kodu.

• Znacznie przyśpieszono i uproszczono tworzenie własnych układów rozmiesz-czenia, odseparowując je od logiki kon-tenerów.

• Definiowania arbitralnych transformacji 2D.

• Przewijanie zawartości kontenera w do-wolnym kierunku piksel po pikselu.

• Transformacje 3D udostępnione przez Flash Playera.

• Definiowanie głębokości każdego potomka kontenera, do którego przypisano układ.

• Przypisywanie transformacji bez wpływu na układ komponentów. Możemy dzięki temu animować dowolny obiekt, nie wy-wołując zmiany układu komponentów.

• Wszystkie właściwości określające wy-miary nie zmieniają się w trakcie cy-klu życia komponentu. Wyeliminowa-no tym samym wprawiające w zakłopo-tanie reprezentowanie wymiarów nie-przeskalowanych w trakcie measure(), natomiast już przeskalowanych w trak-cie etapu updateDisplayList();

Ponieważ układ rozmieszczenia odseparowano od kontenera, został zmieniony sposób pracy z tym elementem. Przykładowo możemy użyć HorizontalLayout w połączeniu z komponen-tem List. Ustawimy również odstępy pomię-dzy elementami na 0px i wyjustujemy. Przykła-dowy kod przedstawiono na Listingu 3.

Tworząc kombinację grup i układów, w łatwy sposób możemy uzyskać zachowa-nia kontenerów znanych z architektury Ha-lo. Przykładowe kombinacje umieszczono w Tabeli 1.

ScrollbarsJak już wspomniałem, architektura Spark zo-stała mocno odchudzona. Oznacza to, że su-waki nie są domyślnie dołączone do kontene-rów, jak to miało miejsce w Halo. Programista musi sam zadecydować, czy będą one wyma-gane dla danej grupy obiektów, czy też kon-tenera. Aby zapewnić wyświetlenie suwaków dla kontenera, gdy będą potrzebne, należy opakować daną grupę komponentów tagiem Scroller, co przedstawiono na Listingu 4.

StatesFlex 4 oferuje całkiem nowe zarządzanie stana-mi aplikacji. Dzięki nowej składni zapisu wszyst-

kie stany w odróżnieniu od poprzedniej wersji są umieszczone w tablicy. Każdy komponent defi-niuje własne zachowanie na zmianę stanu przy pomocy metod includeIn oraz excludeFrom. Dla przypomnienia sposób deklaracji stanu we Flex 3 został przestawiony na Listingu 5, nato-miast nowy zapis przedstawiony na Listingu 6.

Component skinningWe Flex 4 zostało mocno uproszczone tworze-nie własnych skórek komponentów. Na Listin-gu 7 przedstawiono sposób tworzenia przykła-dowej skórki dla przycisku. Przede wszystkim musimy zwrócić uwagę na deklarację czterech stanów przycisku: up, over, down, disabled.

Listing 1. Przykład użycia przestrzeni nazw w aplikacji

<s:Application

xmlns:fx="http://ns.adobe.com/mxml/2009"

xmlns:s="library://ns.adobe.com/flex/spark"

xmlns:mx="library://ns.adobe.com/flex/halo">

<mx:DateChooser id="main_calendar" x="20" y="20"/>

<s:Button label="wyślij" x="220" y="20"/>

</s:Application>

Listing 2. Przykład użycia przestrzeni nazw z selektorami CSS

<fx:Style>

@namespace s "library://ns.adobe.com/flex/spark";

@namespace mx "library://ns.adobe.com/flex/halo";

s|Button {

color: #FF0000;

}

mx|DateChooser {

color: #FF0000;

}

</fx:Style>

Listing 3. Przykład użycia HorizontalLayout

<s:List id="list">

<s:layout>

<s:HorizontalLayout gap="0" verticalAlign="justify" />

</s:layout>

...

</s:List>

Listing 4. Dodanie suwaków do grupy komponentów

<s:Scroller width="200">

<s:Group>

<s:layout>

<s:HorizontalLayout gap="0" verticalAlign="justify" />

</s:layout>

<s:Button label="jeden" />

<s:Button label="dwa" />

<s:Button label="trzy" />

<s:Button label="cztery" />

</s:Group>

</s:Scroller>

Listing 5. Stany we Flex 3

<mx:states>

<mx:State name="submitState" basedOn="">

<mx:AddChild relativeTo="{loginForm}" >

<mx:Button label="Wyślij" bottom="10" right="10"/>

</mx:AddChild>

<mx:RemoveChild target="{firstTextInput}"/>

</mx:State>

</mx:states>

<mx:TextInput id="firstTextInput" />

<mx:Canvas id="loginForm" />

Page 24: SDJ_02_2010_PL

02/201024

Klub techniczny Adobe

Przykładowo zapis alpha.disabled=".5" oznacza, że gdy przycisk będzie w stanie disa-bled, jego przezroczystość zostanie ustawiona na 50%. Natomiast color.over=”0x33CC22” definiuje zmianę koloru wypełnienia tła po

przejściu w stan over. Zapis jest intuicyjny. Analogicznie możemy zdefiniować odrębne właściwości dla każdego z atrybutów w zależ-ności od wybranego stanu. Tak przygotowaną skórkę podpinamy pod komponent Button,

używając atrybut skinClass, co zaprezento-wano na Listningu 8.

Flash XML Graphics (FXG)Ponieważ było duże zapotrzebowanie na przy-jazny i wydajny system zapisu obiektów graficz-nych, postanowiono wykorzystać format SVG. Istnieją jednak pewne różnice pomiędzy możli-wościami graficznymi Flash Playera a formatem SVG, co nie pozwoliło na bezpośrednią imple-mentację we Flash Playerze. Starano się utrzy-mać jak najwięcej zgodności ze specyfikacją SVG, czego wynikiem jest jego niestandardowa implementacja w postaci formatu FXG.

FXG jest mocno zoptymalizowany pod kątem Flash Playera. Umożliwia z poziomu xml-a za pomocą następujących tagów two-rzyć złożone obiekty graficzne:

• BitmapFill;• BitmapGraphic;• Ellipse;• GradientEntry;• Graphic;• Group;• Library;• Line;• LinearGradient;• LinearGradientStroke;• Matrix;• Path;• RadialGradient;• RadialGradientStroke;• Rect;• SolidColor;• SolidColorStroke;• TextGraphic;• Transform.

Wiązanie obustronneWiązanie obustronne (ang. Two Way Binding) pozwala na stworzenie wiązania znanego z Flex 3, jednak działającego w obie strony. Na Listin-gu 9 przedstawiono implementację wiązania obustronnego we Flex 3, natomiast na Listingu 10 jego nową, skróconą wersję udostępnioną we Flex 4. W obu wersjach aplikacji wprowadzając zmiany do jednego z pól tekstowych, zmiany bę-dą widoczne również w 2 z pól tekstowych.

Listing 6. Stany we Flex 4

<s:states>

<s:State name="submitState" />

</s:states>

<s:TextInput id="firstTextInput" excludeFrom="submitState" />

<s:Group id="loginForm" >

<s:Button label="Wyślij" bottom="10" right="10" includeIn="submitState"/>

</s:Group>

Listing 7. Skórka przycisku zadeklarowana w pliku MySkin.mxml

<?xml version="1.0" encoding="utf-8"?>

<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/

flex/spark" alpha.disabled=".5">

<!-- stany -->

<s:states>

<s:State name="up" />

<s:State name="over" />

<s:State name="down" />

<s:State name="disabled" />

</s:states>

<!-- obramowanie i wypełnienie -->

<s:Rect id="rect" radiusX="4" radiusY="4" top="0" right="0" bottom="0" left="0">

<s:fill>

<s:SolidColor color="0x77CC22" color.over="0x33CC22" color.down="0xBB9911" />

</s:fill>

<s:stroke>

<s:SolidColorStroke color="0x131313" weight="2"/>

</s:stroke>

</s:Rect>

<!-- tekst -->

<s:Label text="Button!" color="0x131313"

textAlign="center" verticalAlign="middle"

horizontalCenter="0" verticalCenter="1"

left="12" right="12" top="6" bottom="6"

/>

</s:Skin>

Listing 8. Wiązanie obustronne w Flex 3

<?xml version="1.0" encoding="utf-8"?>

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://

ns.adobe.com/flex/spark">

<s:Button verticalCenter="0" horizontalCenter="0" skinClass="MySkin" />

</s:Application>

Listing 9. Wiązanie obustronne we Flex 3

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">

<mx:TextInput id="t1" text="{t2.text}"/>

<mx:TextInput id="t2" text="{t1.text}"/>

</mx:Application>

Listing 10. Wiązanie obustronne we Flex 4

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">

<mx:TextInput id="t1" text="@{t2.text}"/>

<mx:TextInput id="t2"/>

</mx:Application>

RAFAŁ NAGRODZKIFreelancer i student Katolickiego Uniwersytetu Lubelskiego. Z technologią Flash/Flex zaprzyjaź-niony od 2004r.Kontakt z autorem: [email protected]

W Sieci

• http://www.adobe.com/devnet/flex/articles/flex3and4_differences.html;

• http://www.adobe.com/devnet/flex/videotraining/flex4beta/.

Page 25: SDJ_02_2010_PL
Page 26: SDJ_02_2010_PL

02/201026

Cloud computingEnterprise Private Clouds

www.sdjournal.org 27

Definicja dopiero się krystalizuje, ale najważniejszym jej wyróżnikiem jest udostępnianie zasobów IT za

pośrednictwem sieci i pobieranie opłat za stopień ich wykorzystania. Klient nie pono-si nakładów inwestycyjnych z góry i nie musi obawiać się niedoszacowania ani przeszaco-wania swoich potrzeb. Jednocześnie specja-lizacja usługodawcy oraz efekt skali powodu-je, że usługi takie powinny być wyższej jako-ści, a koszty dla klienta niższe, niż gdyby sam utrzymywał środowisko IT. Czy cloud com-puting jest zatem czymś nowym? I tak i nie. Tak, ponieważ do niedawna nie było takich platform jak Google App Engine czy Ama-zon EC2. Są to bez wątpienia rozwiązania innowacyjne, dające nowe możliwości. Czy jednak stary, poczciwy, hosting stron WWW nie pasuje do podanej definicji? Pasuje. Po-dobnie jak odpłatne konta pocztowe w ser-wisach internetowych, czy aplikacje bizne-sowe dostępne przez internet. Cloud com-puting nie jest zatem koncepcją rewolucyjną, a jedynie kolejnym krokiem w ewolucji bran-ży IT. Usługi IT stają się dobrem powszech-nym, co pociąga za sobą zmiany w sposobie ich świadczenia.

Różne rodzaje cloud computinguZe względu na olbrzymią pojemność poję-cia cloud computingu, należy wyróżnić kil-ka jego rodzajów. Po pierwsze, usługodaw-cą może być firma zewnętrzna lub dział IT klienta. Mówimy wówczas o chmurze pu-blicznej (public cloud) lub prywatnej (pri-vate cloud).

Po drugie, usługi świadczone w tym mo-delu mogą dotyczyć jednej z trzech warstw – infrastruktury, platformy programi-stycznej lub aplikacji biznesowych. Nazy-wamy je odpowiednio Infrastructure as a Service (IaaS), Platform as a Service (Pa-aS) lub Software as a Service (SaaS). Przy-kładem IaaS jest Amazon EC2 – możemy tu wykupić potrzebny nam czas proceso-rów, miejsce na dyskach i przepustowość sieci. Usługodawca nie wnika w jaki spo-sób korzystamy z tej infrastruktury, ale od-powiada za jej gotowość do pracy. Google App Engine jest już przykładem usługi Pa-aS, gdyż usługodawca daje nam konkretne narzędzia, z których możemy korzystać do stworzenia potrzebnej nam aplikacji. Co prawda w dalszym ciągu płacimy za zaso-by fizyczne (procesor/pamięć/sieć/dysk), ale jednak dostajemy już nie tylko infra-strukturę, ale kompletne, ustandaryzowa-ne, środowisko do rozwoju i działania apli-kacji – możemy co prawda wybrać jeden z dwóch języków (Java, Python), ale nie mo-żemy zastosować dowolnej dostępnej na

rynku technologii. O Software as a Service, ostatnim wariancie cloud computing, mó-wi się dużo już od dłuższego czasu. Jest to po prostu udostępnianie aplikacji bizneso-wych w formie usługi, a nie licencji. Dzię-ki temu klient nie ponosi kosztów począt-kowych, nie musi utrzymywać swojego śro-dowiska, ani zatrudniać specjalistów do za-rządzania nim. W tym wypadku podsta-wą rozliczenia jest zazwyczaj ogólna ilość użytkowników lub ilość równolegle otwar-tych sesji.

Przykładem oprogramowania dostarcza-nego wyłącznie w modelu SaaS jest Google Apps, zaś dostępnego zarówno w modelu tra-dycyjnym (licencyjnym), jak i w modelu Sa-aS jest Oracle CRM i jego wersja SaaS – CRM On Demand.

Enterprise private cloudsEnterprise private cloud, to nic innego jak prywatny cloud danego przedsiębiorstwa, udostępniający usługi IT działom bizneso-wym i partnerom (spółkom zależnym, itp). Z punktu widzenia programistów, najciekaw-szym wariantem takiej chmury, jest Platform as a Service.

Korzystanie z prywatnego PaaS oznacza dla programistów kilka zmian, w porówna-niu do tradycyjnego modelu. Wspomnia-łem już, że w modelu PaaS programista jest ograniczony do narzędzi i technologii do-stępnych na platformie. Może to budzić niechęć ze względu na osobiste preferen-cje, ale daje wymierne korzyści biznesowe firmie, dla której przecież pracujemy. Stan-daryzacja i konsolidacja środowisk pozwala wyraźnie obniżyć koszty sprzętu, licencji, pomocy technicznej oraz personelu. Oczy-wiście powinna istnieć możliwość dodania określonej technologii do zestawu narzę-dzi dostępnych na platformie, ale nie ma co liczyć na pełną dowolność, jak w przy-

Enterprise Private CloudsCloud computing robi od pewnego czasu zawrotną karierę medialną. Tematem zajmują się nie tylko tytuły poświęcone branży IT, ale także biznesowe i popularno-naukowe. Nawet Dilbert wspomina już o cloud computing. Warto zatem zastanowić się czym jest, a czym nie jest cloud computing i jak wpłynie na tworzenie i zarządzanie oprogramowaniem.

Dowiesz się:• Idea i zastosowanie cloud computing• Czym jest Platform as a Service• Wirtualizacja zasobów IT

Powinieneś wiedzieć:• Podstawowa znajomość procesu produkcji

oprogramowania

Poziom trudności

Page 27: SDJ_02_2010_PL

02/201026

Cloud computingEnterprise Private Clouds

www.sdjournal.org 27

padku zupełnie niezależnych projektów i środowisk. W zamian programista otrzy-muje jednak wiele korzyści. Po pierwsze dostaje możliwość prostego tworzenia do-datkowych środowisk na żądanie. Z zało-żenia chmura powinna być łatwa w rozbu-dowie i powinna pomieścić dodatkowe in-stancje, jeśli ich potrzebujemy. Co więcej, tworzenie środowisk powinno być zauto-matyzowane, a zarządzanie nimi oddane w ręce użytkowników. Programista może za-tem zażądać utworzenia nowego środowi-ska, czy to z jakiegoś wzorca (np. czyste śro-dowisko z aplikacją w wersji X), czy też po-przez skopiowanie innego środowiska (po-trzebuję identycznego środowiska jak Y, żeby zdiagnozować problem, który występuje tyl-ko tam). Środowisko takie powinno zostać utworzone w czasie minut lub godzin, a nie dni czy tygodni, jak to często bywa, gdy utworzenie środowiska wymaga akceptacji przełożonych, zamówienia sprzętu i ręcz-nej pracy. Idąc tym tropem, możemy tak-że przenieść środowiska testowe ze stacji roboczych na platformę PaaS, dzięki cze-mu nie musimy sami nimi zarządzać, mo-żemy przywracać je do poprawnego stanu, gdy coś pójdzie nie tak, a wszystkie zmiany konfiguracyjne (np. podłączenia do baz da-nych, adresy serwerów aplikacji czy web se-rvices) są centralnie zarządzane i nie musi-my się tym zajmować. Warto także zazna-czyć, że prywatny PaaS nie ogranicza moż-liwości debugowania systemu – o ile pu-bliczny dostawca niekoniecznie udostępni nam wszelkie możliwości diagnostyczne, o tyle prywatny PaaS daje możliwość włącze-nia odpowiednich opcji i podłączenia się z narzędziami diagnostycznymi.

Zmiany organizacyjneCloud computing, zwłaszcza prywatny, wymaga przede wszystkim zmiany sposo-bu myślenia o zasobach IT. Tak jak SOA nie jest technologią opartą na web services, tyl-ko architekturą wymagającą zmiany sposo-bu i szerszego spojrzenia na tworzenie i po-nowne wykorzystanie aplikacji, tak cloud computing nie wymaga żadnej szczególnej technologii, ale przede wszystkim zmiany w procesie zarządzania zasobami. Techno-logia jest rzeczą wtórną, która usprawnia ten proces i umożliwia zapewnienie do-stępności, wydajności i bezpieczeństwa w tych nowych warunkach. O ile korzystanie z usług publicznych dostawców cloud com-puting może być (i często jest) decyzją od-dolną, o tyle projekt budowy wewnętrznej platformy PaaS w firmie, wymaga konkret-nych nakładów początkowych, zmiany pro-cesu zakupów sprzętu i licencji oraz roz-liczania innych działów za korzystanie z nich. Należy zwrócić uwagę, że wiele apli-

kacji, ze względu na poufność przetwa-rzanych danych lub inne uwarunkowania, nie może być uruchomiona na publicz-nych środowiskach, dlatego korzystanie z nich decyzją oddolną powinno być zgod-ne z polityką bezpieczeństwa firmy, a bu-dowa prywatnej platformy może być nie-zbędna. Na czas budowy takiego środowi-ska można z kolei skorzystać z publiczne-go IaaS, w którym od ręki będziemy mo-gli wynająć sprzęt potrzebny do rozpoczę-cia projektu.

Techniczna realizacja PaaSUdostępnianie platformy aplikacyjnej ja-ko usługi oznacza przede wszystkim zmia-nę modelu sprzedaży/współpracy między działem utrzymania systemów, a działami rozwoju i utrzymania aplikacji. Niemniej jednak, aby zapewnić sprawne działanie ta-kiej platformy, co jest warunkiem koniecz-nym osiągnięcia sukcesu, przydatne są od-powiednie rozwiązania techniczne. Jeśli tworzenie środowisk potrzebnych do roz-woju aplikacji będzie trwało zbyt długo lub utworzone środowiska będą wymagały ręcznych poprawek, programiści powrócą do rozwijania kodu na własnych kompute-rach. Jeśli wzrost obciążenia w jednej apli-kacji będzie powodował spadek wydajno-ści całej platformy, menedżerowie poszcze-gólnych aplikacji będą żądać separacji śro-

dowisk, co zniweczy korzyści wynikające z konsolidacji.

WirtualizacjaMożna stworzyć środowisko bez problemu spełniające definicję PaaS nie korzystając z żadnego rozwiązania wirtualizacyjnego, ale zastosowanie takiej technologii pozwala osiągnąć znacznie większe korzyści z wdro-żenia platformy PaaS. Wirtualizacja pozwala zwiększyć utylizację zasobów sprzętowych, zachować ciągłość pracy w czasie prac kon-serwacyjnych (np. wymiana sprzętu) oraz pomaga zarządzać środowiskami. Pozwala tworzyć wzorcowe instalacje dostępnych na platformie komponentów, np. kompo-nent baza danych zawierający odpowiednio skonfigurowany system operacyjny z podłą-czeniem do przestrzeni dyskowych, bazę da-nych w odpowiedniej wersji i ze wszystkimi poprawkami, narzędzia diagnostyczne i tak dalej. Po przygotowaniu odpowiednich kom-ponentów, gdy będziemy zaczynać nowy projekt, programista będzie mógł zażądać nowego środowiska ze wszystkimi potrzeb-nymi komponentami, a po wprowadzeniu niezbędnych zmian wymaganych dla danej aplikacji (np. schematy bazy danych, połą-czenie z odpowiednim modułem repozy-torium kodu), zachować obraz jako wzor-cowy dla innych osób pracujących nad tym projektem.

Rysunek 1. Schemat koncepcyjny architektury IaaS/PaaS

����������� ����������� �����������

���������������������������� �����������

���������������

�������������������

�����������������������������������

������������������

��������������

����������������������������������

�����������������

�������������

�������

�������

������

���������������

�����������

��������

����������

����������

����������

�����������

�����������

�������������

��������

Page 28: SDJ_02_2010_PL

02/201028

Cloud computing

Sprawnie działająca platforma PaaS po-winna pozwolić na stworzenie środowisk dla wszystkich zainteresowanych w ciągu kilku godzin.

Warstwa middlewareW warstwie oprogramowania middlewa-re, przydatnych może być kilka funkcji. Po pierwsze klastrowanie z synchronizacją se-sji użytkowników, jako podstawowy me-chanizm skalowania i zapewniania ciągło-ści pracy tej warstwy. Po drugie, możliwość jednoczesnego korzystania z różnych wer-sji tych samych bibliotek, bowiem nasza aplikacja niekoniecznie będzie działać na zupełnie niezależnym środowisku. Insta-lacja nowej aplikacji wraz z zależnościami nie może powodować zmiany wersji biblio-teki używanej przez już działającą aplika-cję. Kolejna sprawa, to możliwość testowe-go uruchomienia aplikacji na platformie. Dopóki całe środowisko nowej aplikacji jest niezależne – możemy testować aplika-cję do woli bez udostępniania jej użytkow-nikom. Jeśli jednak chcemy przeprowadzić ostateczne testy przed upublicznieniem aplikacji na platformie PaaS, musimy mieć możliwość uruchomienia nowej aplikacji tylko dla wybranych użytkowników (te-sterów). Kolejna kwestia bardzo ułatwia-jąca zarządzanie tak dynamiczną platfor-mą, z wieloma środowiskami i aplikacja-mi, to mechanizm skryptowy pozwalający zautomatyzować czynności administracyj-ne. Nawet jeśli korzystamy z wirtualizacji i możemy tworzyć obrazy wzorcowe, to po każdym skorzystaniu z takiego wzorca, po-trzebujemy zmienić kilka parametrów (ad-resy, porty i tym podobne). Do tego należy doliczyć późniejsze zmiany, które trzeba w powtarzalny sposób wykonywać na wszyst-kich środowiskach (np. dodanie nowego data source). Ręczne wykonywanie zmian mogłoby spowodować niespójność środo-wisk i tym samym zniwelować korzyści wdrożenia PaaS.

Bardzo ciekawym rozwiązaniem, dopie-ro wchodzącym na rynek, są serwery apli-kacji działające bezpośrednio na hyperviso-rze – bez klasycznego systemu operacyjne-go. Takie podejście pozwala nie tylko pod-nieść wydajność, dzięki lepszemu zarzą-dzania pamięcią (normalnie system opera-cyjny i JVM częściowo pokrywają się funk-cjonalnością w tym zakresie), ale również pozwala wyraźnie uprościć całe środowisko (skoro nie ma systemu operacyjnego, to nie trzeba go konfigurować, optymalizować, ani aktualizować). Dodatkowo uzyskuje-my mniejsze obrazy maszyn wirtualnych, gdyż nie zawierają one binariów systemu operacyjnego. Pozwala to obniżyć koszty przestrzeni dyskowych, zaoszczędzić miej-

sce na urządzeniach backupowych i skró-cić czas migracji maszyny wirtualnej mię-dzy fizycznymi serwerami.

Poza samym kontenerem, warto rów-nież zastosować technologię in-memory da-ta grid. Pozwala ona przechowywać duże ilo-ści danych obiektowych (w tym obiekty se-sji HTTP) w pamięci operacyjnej, dzięki cze-mu odciąża kontener JEE. Rozwiązania wy-raźnie przyspieszają działanie aplikacji i po-zwalają skalować aplikacje praktycznie w nie-skończoność.

Warstwa bazy danychPodobnie jak w przypadku serwerów apli-kacji, w warstwie baz danych zastosowanie klastrów również pozwala zachować cią-głość pracy i umożliwia skalowanie syste-mów w razie takiej potrzeby. Kluczowe sta-je się także zarządzanie przestrzenią dys-kową. Zastosowana technologia powinna nie tylko umożliwiać łatwą rozbudowę, ale również równomiernie rozkładać obciąże-nie między dostępne dyski. W przeciw-nym wypadku, w krótkim czasie mieliby-śmy analogiczną sytuację jak z serwerami bez wirtualizacji – gdy do aplikacji przy-pisane są na stałe określone zasoby sprzę-towe, to prawie zawsze część z nich jest przeciążona, a część niedociążona. Podob-nie jak w przypadku serwerów, aby w pełni czerpać korzyści z konsolidacji, potrzebu-jemy zatem warstwy wirtualizacji dla zaso-bów dyskowych.

Zarządzanie zasobamiW praktycznie każdym systemie, także nie korzystającym z platformy PaaS, warto ko-rzystać z mechanizmów ograniczania do-stępu do zasobów pojedynczym użytkow-nikom, czy aplikacjom. Użytkownicy nie powinni odczuwać wyraźnego spadku wy-dajności w sytuacji, gdy jeden z nich ge-neruje skomplikowany raport. W tym wy-padku baza danych powinna odpowied-nio ograniczyć zasoby dostępne dla sesji tego użytkownika, umożliwiając normal-ną pracę pozostałym. Zarządzanie zasoba-mi staje się jeszcze ważniejsze, gdy na jed-nym fizycznym komputerze mogą działać na przykład dwie maszyny wirtualne, każ-da ze swoim systemem operacyjnym i ba-zą danych, a każda z baz danych obsługuje kilka różnych aplikacji. Jeśli nie zdefiniuje-my odpowiedniej polityki przydziału zaso-bów, nie będziemy w stanie zapewnić wy-maganego poziomu świadczenia usług dla poszczególnych aplikacji.

Cykl życia “chmury”Jednym z głównych założeń cloud compu-ting jest samoobsługa użytkowników. To oni tworzą kolejne środowiska, gdy ich po-

trzebują, są odpowiedzialni za odpowied-nie ich kategoryzowanie (test/produkcja), żądają dodatkowych zasobów, jeśli wiedzą, że będą potrzebne (w sytuacjach nieprze-widzianych za taką rozbudowę odpowiada już dostawca platformy) i wyłączają instan-cje, których dłużej nie potrzebują. Dostawca chmury PaaS, także prywatnej, musi zatem zapewnić interfejs do zarządzania środowi-skami klienta.

Rozliczanie wykorzystania zasobówZgodnie z definicją, rozliczanie klientów jest oparte o zużycie określonych zasobów. Czynnikami wpływającymi na koszt korzy-stania z clouda mogą być zarówno warto-ści techniczne (czas procesora, zużycie sie-ci, etc), jak i biznesowe – na przykład ilość kampanii marketingowych przeprowadzo-nych przy użyciu danej aplikacji. Moż-na sobie łatwo wyobrazić sytuację, w któ-rej dostępne są również różne taryfy, po-dobnie jak to jest u operatorów telefonicz-nych. Opłata składa się zwykle z części sta-łej (abonamentu) oraz zmiennej – zależnej od wykorzystanych zasobów. W celu mi-nimalizacji kosztów zmiennych, koniecz-na może się okazać optymalizacja aplikacji – czynność, o której niestety wiele osób za-pomniało w dobie wydajnych i często prze-szacowanych serwerów (analizy pokazu-ją, że średnie obciążenie serwerów rzadko przekracza 20%).

PodsumowanieBudowa prywatnej chmury Platform as a Service pozwala osiągnąć cele, do których dąży praktycznie każde przedsiębiorstwo – skrócenie czasu od pomysłu do realizacji (time to market), a co za tym idzie wzrost przychodów. Konsolidacja sprzętu i licen-cji oraz łatwa dynamiczna rozbudowa za-pewnia jednocześnie niższe koszty utrzy-mania, oraz brak konieczności ponoszenia dużych kosztów na początku każdego pro-jektu. Z punktu widzenia programisty, to przede wszystkim likwidacja barier orga-nizacyjnych i większa swoboda w korzysta-niu z zasobów firmy, a jednocześnie mniej czasu straconego na zarządzaniu środowi-skami i mniej problemów wynikających z różnic między nimi. Dobrze zrealizowana platforma powinna zatem przynieść korzy-ści wszystkim zainteresowanym.

MICHAŁ KURATCZYKPrincipal solution architect, Oracle Polska

Page 29: SDJ_02_2010_PL
Page 30: SDJ_02_2010_PL

02/201030

Programowanie C++Fabryki obiektów

www.sdjournal.org 31

Fabryka obiektów jest klasą, której obiekty pośredniczą przy tworze-niu innych obiektów. Dostarcza-

na informacja jednoznacznie identyfiku-je konkretny typ, znany w momencie kom-pilacji, ale nie jest to literał, więc informa-cja o typie jest nieodpowiednia dla kom-pilatora, na przykład jest to napis lub inny identyfikator. Fabryka ukrywa przed użyt-kownikiem mechanizm zamiany identyfi-katora na literał dostarczany do operato-ra new, upraszczając tworzenie obiektów (patrz Rysunek 1).

W języku C++ podczas tworzenia obiektu należy podać konkretny typ w formie zrozu-miałej dla kompilatora (patrz Listing 1). Nie możemy posłużyć się mechanizmem funkcji wirtualnych, nie możemy także przekazać identyfikatora typu w czasie działania, argu-mentem operatora new może być tylko literał oznaczający typ znany w momencie kompila-cji. Po utworzeniu obiektu można się do nie-go odwoływać poprzez wskaźnik lub referen-cję na klasę bazową, ale przy tworzeniu nale-ży podać rzeczywisty typ obiektu, nie można użyć mechanizmu późnego wiązania (funk-cji wirtualnych).

Funkcje fabrycznePrzykład wykorzystania fabryki obiektów został pokazany na Listingu 2, gdzie poka-zano funkcję create tworzącą obiekty klas na podstawie danych zapisanych w strumie-niu wejściowym, na przykład w pliku. Funk-cja ta dostarcza obiekt odpowiedniego typu konkretnego dla hierarchii Figure. Metody zapisu (write) oraz odczytu (read) są do-starczane przez każdą z klas konkretnych. Jeżeli chcemy obiekt utworzyć (odczytać), to typ obiektu jest dostarczany w czasie dzia-łania, jest on wyznaczany przez identyfika-tor dostarczany przez strumień. Ponieważ tak dostarczany identyfikator nie jest ak-ceptowany jako argument dla operacji new, należy wykorzystać fabrykę, która pozwoli

tworzyć obiekty na podstawie identyfikato-ra, rolę tę pełni funkcja createObj. Po utwo-rzeniu obiektu możemy wczytać składowe, wykorzystując mechanizm funkcji wirtual-nych, wołając metodę read dla utworzone-go obiektu.

Przy zapisie obiektu do strumienia wyj-ściowego (na przykład do pliku) nie potrze-bujemy dodatkowych mechanizmów, któ-re dostarczą identyfikator dla danego typu, ponieważ możemy wykorzystać mechanizm funkcji wirtualnych. Posiadając wskaźnik lub referencję na klasę bazową, wołamy meto-dę write, która zapisuje identyfikator klasy konkretnej oraz jej składowe. Odczyt obiek-tu jest o wiele bardziej złożony niż zapis ze względu na to, że zapis posługuje się istnie-jącymi obiektami, natomiast odczyt musi je tworzyć.

Fabryka skalowalnaFunkcja createObj jest prostą fabryką obiek-tów, pozwala ona tworzyć obiekty na podsta-wie informacji, która jest dostarczana w cza-sie działania, ale ma wiele wad: mapowanie identyfikatora na typ za pomocą instrukcji

Fabryki obiektów

Techniki opisane w tym artykule pozwalają tworzyć obiekty na podstawie identyfikatorów dostarczanych w czasie działania programu, co jest wygodniejsze niż podawanie typów w formie zrozumiałej dla kompilatora.

Dowiesz się:• Jak tworzyć obiekty w C++;• Co to jest wzorzec fabryki.

Powinieneś wiedzieć:• Jak pisać proste programy w C++;• Co to jest dziedziczenie i funkcje wirtualne.

Poziom trudności

Szybki startAby uruchomić przedstawione przykłady, należy mieć dostęp do kompilatora C++ oraz edytora tekstu. Niektóre przykłady korzystają z udogodnień dostarczanych przez bi-bliotekę boost::mpl, warunkiem ich uruchomienia jest instalacja bibliotek boost (w wer-sji 1.36 lub nowszej) Na wydrukach pominięto dołączanie odpowiednich nagłówków oraz udostępnianie przestrzeni nazw, pełne źródła dołączono jako materiały pomoc-nicze.

Listing 1. Podczas tworzenia obiektu należy podać typ w odpowiedniej formie

class Bazowa { /* ... */ }; //przykładowa definicja typu

class KonkretnaA : public Bazowa { /* ... */ };

Bazowa* p = new KonkretnaA; //przy tworzeniu trzeba podać konkretny typ

//nazwa typu musi być zrozumiała dla kompilatora

Page 31: SDJ_02_2010_PL

02/201030

Programowanie C++Fabryki obiektów

www.sdjournal.org 31

switch sprawia, że funkcja ta jest zależna od wszystkich klas w hierarchii, jeżeli będzie dodawana lub usuwana jakaś klasa, to mody-fikacji będzie musiał podlegać także kod fa-bryki; brak kontroli przy wiązaniu identyfi-katora z typem sprawia, że musimy zapew-nić, aby przy odczycie obiektu korzystać z tego samego identyfikatora co przy zapisie. Poza tym, zestaw identyfikatorów jest zaso-bem globalnym, przy modyfikowaniu zbio-ru klas w danej hierarchii musi on podlegać modyfikacjom.

Fabryka skalowalna, przedstawiona na Listingu 3, umożliwia tworzenie obiektów na podstawie identyfikatorów, wprowadza mniejszą liczbę zależności w porównaniu z poprzednio omówionym rozwiązaniem, ponieważ jest ona zależna tylko od klasy bazowej, a nie od wszystkich klas konkret-nych. Mniejsza liczba zależności wynika z zastosowania wskaźnika na funkcję two-rzącą obiekty danej klasy konkretnej oraz przez użycie dynamicznej struktury prze-chowującej mapowanie pomiędzy identy-fikatorem a typem.

Klasa konkretna woła metodę registerFig, przekazując swój identyfikator oraz funkcję tworzącą obiekty danej klasy. Metoda ta doda-je element do kolekcji, można więc elastycznie modyfikować zestaw klas, których obiekty bę-dą tworzone przez fabrykę. Jeżeli chcemy usu-wać wpisy, to należy zaimplementować meto-dę unregister, która będzie usuwała elemen-ty z kolekcji.

Tworzenie obiektów odbywa się w meto-dzie create, która wyszukuje funkcję two-rzącą dla danego identyfikatora. Jeżeli taka funkcja zostanie znaleziona, to jest ona woła-na (patrz Listing 3), a obiekt utworzonej kla-sy jest zwracany.

Klasa konkretna musi dostarczyć funkcję tworzącą obiekty, funkcja ta (patrz Listing 4) może być umieszczona w module zawierają-cym implementację klasy konkretnej, podob-

nie moduł ten może zawierać kod rejestrują-cy dany typ w fabryce, tak jak pokazano na Listingu 4.

Fabryka skalowalna jest bardziej elastycz-na, ale też bardziej kosztowna niż rozwią-

zanie bezpośrednie (wybór typu w zależno-ści od identyfikatora za pomocą switch lub łańcucha if ... else), ponieważ wymaga prze-chowywania kolekcji wiążącej identyfikator z funkcją tworzącą. Obiekt jest tworzony za po-

Rysunek 1. Tworzenie obiektów przez fabrykę

������ �������

���������������������

������

Listing 2. Przykład, w którym uzasadnione jest wykorzystanie fabryki

class Figure { //klasa bazowa

public:

enum Type { SQUARE, CIRCLE, /* ... */ };//identyfikatory klas konkretnych

virtual bool write(ostream& os) const = 0;//zapisuje obiekt

virtual bool read(istream& is) = 0;//odczytuje obiekt

};

class Square : public Figure {//jedna z klas konkretnych

public:

bool zapisz(ostream& os) {//zapisuje kwadraty

os << KWADRAT;//zapisuje identyfikator typu

//zapisuje poszczególne składowe

}

bool read(istream& is) {//odczytuje obiekt, zakładając, że jest to kwadrat

//odczytuje poszczególne składowe

}

};

//pozostałe klasy konkretne także dostarczają metody odczytu i zapisu

//...

Figure* createObj(istream& is) {//funkcja pełni rolę fabryki

Figure::Type type;

is >> type; //odczytuje identyfikator typu

Figure* obj;

switch(type) { //zapewnia mapowanie pomiędzy identyfikatorem a typem

case SQUARE://w formie zrozumiałej dla kompilatora

return new Square(); //tworzy odpowiedni obiekt

case CIRCLE: /* ... */

}

}

//tworzy obiekt na podstawie identyfikatora i odczytuje jego składowe

Figure* create(istream& is) {

Figure* obj = createObj(is); //tworzy obiekt odpowiedniego typu

obj->read(is);//obiekt istnieje, może wykorzystać funkcje wirtualne

}

Listing 3. Fabryka skalowalna

class FigFactory {

public:

typedef Figure* (*CreateFig)(); //wskaźnik na funkcję tworzącą obiekt

//rejestruje nowy typ

void registerFig(int id, CreateFig fun) {

creators_.insert( value_type(id, fun) ); //dodaje do kolekcji

}

//tworzy obiekt na podstawie identyfikatora

Figure* create(int id) { //tworzy obiekt danego typu

Creators::const_iterator i = creators_.find(id);

if(i ! = creators_.end() ) //jeżeli znalazł odpowiedni wpis

return (i->second)(); //woła metodę fabryczną

return 0L; //zwraca pusty wskaźnik, gdy nieznany identyfikator

}

private:

typedef std::map<int, CreateFig> Creators;

Creators creators_;//przechowuje powiązania pomiędzy identyfikatorem a funkcją

tworzącą

};

Page 32: SDJ_02_2010_PL

02/201032

Programowanie C++

średnictwem tej funkcji, więc tworzenie trwa dłużej (jeden skok więcej w porównaniu z metodą bezpośrednią).

Aby zaimplementować w pełni funkcjo-nalną fabrykę obiektów, należy uwzględ-nić dodatkowe zagadnienia. Po pierw-

sze, problem dostarczania odpowiedniego obiektu fabryki wymaganego przy rejestra-cji klas (na wydruku 4 została użyta funk-cja getFactory). Często stosowanym roz-wiązaniem jest singleton. Po drugie, należy zarządzać czasem życia powołanych obiek-tów, fabryka tworzy obiekty na stercie, ale kto ma je zwalniać? W tym celu warto po-służyć się sprytnymi wskaźnikami (patrz SDJ 11/2009). Kolejnym zadaniem jest wiązanie identyfikatora z typem, aby wy-kluczyć możliwości pomyłek. Można od-powiedzialność dostarczania identyfika-torów przenieść na fabrykę, obiekt ten ma może tworzyć unikalne identyfikatory, zaś klasy, które są rejestrowane w fabryce, mo-gą ten identyfikator przechowywać w skła-dowej statycznej (patrz Listing 5). Obiekty klas będą wykorzystywały ten identyfika-tor podczas zapisu, natomiast fabryka wy-korzystuje go podczas odczytu.

Inną możliwością jest generowanie identy-fikatora przez mechanizmy kompilatora, na przykład używając struktury type_id, wtedy nie trzeba zarządzać nim w fabryce.

Inicjacja fabryki skalowalnej (rejestracja typów) może być uproszczona, jeżeli będzie wykorzystywana kolekcja typów z biblioteki boost::mpl (patrz SDJ 12/2009) oraz algoryt-my, które na kolekcji operują( patrz Listing 6). Funkcja tworząca może być metodą sta-tyczną klasy konkretnej, wtedy nie musi za-wierać nazwy tworzonej klasy. Takie rozwią-zanie prezentuje Listing 6.

PodsumowanieOpisany sposób tworzenia obiektów na pod-stawie identyfikatora dostarczanego pod-czas działania programu jest jednym z wzor-ców kreacyjnych, termin został zapropono-wany przez bandę czworga (Gamma, Helm, Johnson, Vlissides) w książce Wzorce projek-towe. Inne udogodnienia dotyczące tworze-nia obiektów, takie jak fabryki prototypów i fabryki abstrakcyjne, są tematem jednego z kolejnych artykułów.

Listing 4. Przykład funkcji tworzącej i rejestracji typu w fabryce

Figure* CreateSquare() { //funkcja tworząca dla typu konkretnego

return new Square();

};

FigFactory& factory = getFactory();//pobiera obiekt fabryki

factory.registerFig(SQUARE, CreateSquare); //rejestruje się w fabryce

Listing 5. Fabryka skalowalna zarządzająca identyfikatorami

typedef shared_ptr<Figure> PFigure; //sprytny wskaźnik

class FigFactory {

public:

typedef PFigure (*CreateFig)(); //wskaźnik na funkcję tworzącą obiekt

int registerFig(CreateFig fun) {//zwraca id zarejestrowanego typu

creators_.insert( value_type( currentId_, fun) );

return currentId_++; //zwraca identyfikator

}

PFigure create(int id); //tworzy obiekt danego typu (patrz Listing 3)

private:

int currentId_; //kolejny wolny identyfikator

//składowe związane z funkcjami tworzącymi

};

FigFactory& factory = FigFactory::getInstance();//singleton

Square::id_ = factory.registerFig(CreateSquare); //ustawia identyfikator

Listing 6. Rejestracja klas w fabryce skalowalnej przez algorytm biblioteki boost::mpl

class Square : public Figure {

public:

//każda klasa konkretna dostarcza metodę statyczną create

static Figure* create() { return new Square; }

};

struct RegisterFigure { //szablon użyty do rejestracji

template<typename T> void operator()(T) {

T::id_ = Factory::getInstance().registerFig( T::create );

}

};

typedef mpl::vector<Square, Circle> Types; //kolekcja typów dla klas konkretnych

mpl::for_each< Types > ( RegisterFigure() ); //woła w czasie wykonania operację dla

każdego typu

W Sieci

• http://www.boost.org – dokumentacja bibliotek boost;• http://www.open-std.org – dokumenty opisujące nowy standard C++.

Więcej w książceZagadnienia dotyczące współcześnie stosowanych technik w języku C++, wzorce projekto-we, programowanie generyczne, prawidłowe zarządzanie zasobami przy stosowaniu wy-jątków, programowanie wielowątkowe, ilustrowane przykładami stosowanymi w bibliotece standardowej i bibliotekach boost, zostały opisane w książce Średnio zaawansowane progra-mowanie w C++, która ukaże się niebawem.

ROBERT NOWAKAdiunkt w Zakładzie Sztucznej Inteligencji Insty-tutu Systemów Elektronicznych Politechniki War-szawskiej, zainteresowany tworzeniem aplika-cji dla biologii i medycyny, programuje w C++ od ponad 10 lat.Kontakt z autorem:[email protected]

Page 33: SDJ_02_2010_PL
Page 34: SDJ_02_2010_PL

02/201034

Programowanie gierJak napisać swoją pierwszą grę komputerową

www.sdjournal.org 35

Trudno jest zainteresować poważną firmę inwestycją w grę , przysyła-jąc im zeszyt z pomysłem. Na pa-

pierze każda gra – choćby była oryginalna i rewolucyjna – wygląda podobnie i, nie-stety, zazwyczaj nieciekawie. Dzisiaj pro-dukcja gry komputerowej to duży budżet i wiele zaangażowanych osób. Mówimy tu o co najmniej dziesiątkach tysięcy złotych w przypadku małych gier, a milionach do-larów w przypadku produkcji większych, nie ma więc co się dziwić, że mało kto wy-łoży pieniądze na realizację pomysłu jakie-goś gościa z zeszytem. Zwłaszcza pamięta-jąc, że gry to kapryśne produkty i czasem nawet bardzo dobra produkcja może prze-paść na rynku w wyniku niesprzyjających okoliczności lub pecha (przykładowo Vam-pire: The Masquerade – Bloodlines czy choć-by Psychonauts).

Skoro widoki na to, że dyrektor znanej fir-my podsłucha nasz pomysł w autobusie i od ręki nas zatrudni, są marne, trzeba przejść do planu B.

Na potrzeby niniejszego artykułu założy-my, że nasz pomysł na grę to gra platformo-wa, której bohaterem jest wściekła małpa. Małpa zbiegła z miejskiego ZOO i teraz ści-ga po mieście biznesmanów w garniturach, aby zatłuc ich wielkim bananem. Grę robo-czo nazywamy Monkey Business

Plan B, czyli „Wystarczy chcieć”Plan B sprowadza się do tego, że musimy grę wykonać sami. Pierwszym krokiem powin-na być ocena naszych umiejętności i moż-liwości. Jeśli jesteśmy akurat John’em Car-mack’iem lub mamy szafę pełną pieniędzy, to sprawa jest dosyć prosta. W innym przy-padku musimy usiąść i zastanowić się, co właściwie potrafimy.

Spisujemy wszystkie nasze umiejętno-ści takie jak rysowanie, modelowanie gra-fiki trójwymiarowej, projektowanie baz da-nych, interfejsów baz danych, projektowanie stron WWW, programowanie (w czymkol-wiek) lub dowolną inną umiejętność powią-zaną z szeroko pojętą informatyką. Nawet je-śli lista jest bardzo krótka, nadal jest nadzie-ja, nie poddajemy się. Kolejny krok to próba dopasowania naszych umiejętności do kon-kretnego projektu.

Jeśli planujemy grę handlową lub nawet MMORPG (Massive Multiplayer Online Ro-le Playing Game), a potrafimy tylko tworzyć

bazy danych, to możemy przyciąć nasz pro-jekt tak, by dało się go zrealizować na bazie danych lub tylko formularzu. Przykładem bardzo wciągającej gry wykonanej napraw-dę prostą metodą jest Drug Wars lub jej pol-ski odpowiednik MaXDila 2000. Kilka pól tekstowych i zabawa murowana na wiele go-dzin.

Co możemy zrobić, gdy nie mamy żadne-go doświadczenia w tworzeniu graficznych interfejsów użytkownika i jedyne, co potra-fimy, to wyświetlanie znaków ASCII? Może-my zrobić grę oczywiście. Jest cały gatunek gier Roguelike, czyli tak zwanych rogalików. Są to gry naśladujące kultową grę Rogue (stąd ich nazwa). Cała grafika reprezentowana jest tu za pomocą znaków ASCII. Prostota repre-zentacji wizualnej może być myląca – gry te są często przerażająco złożone. Przykładem może być ADOM lub Dwarf Fortress. Stwo-rzenie gry w tak ubogiej oprawie graficznej może przy okazji ujawnić słabe punkty sce-nariusza lub mechaniki. Gdy nie ma nic, co by nas rozpraszało, to właśnie fabuła i game-play trzymają nas przy klawiaturze (lub kon-trolerze).

W takim razie co robić, jeśli umiemy tyl-ko rysować lub tworzyć modele trójwymia-rowe? Paradoksalnie możemy się odprę-żyć, jesteśmy w dosyć komfortowej sytu-acji. Sieć jest pełna zdesperowanych progra-mistów szukających grafików, którzy po-mogliby skończyć ich projekt. Pozostaje tyl-ko przekonać ich, że to właśnie nasz projekt jest ciekawszy.

Oczywiście może się też zdarzyć, że kart-ka pozostaje pusta, ponieważ nie posiada-my żadnych umiejętności, które pomogły-by nam w stworzeniu gry. W takiej sytu-acji nadal nie wszystko jest stracone – wy-

Jak napisać pierwszą grę komputerową

Prawdopodobnie każdy gracz ma w głowie co najmniej jeden pomysł na rewelacyjną grę. Niektórzy idą dalej i zakładają zeszyty w kratkę, których strony zapełniają pomysłami i szkicami. Niestety w tym szczególnym przypadku sam pomysł to zbyt mało i – bez konkretnych kroków – jest niewiele wart.

Dowiesz się:• Z jakich technologii możesz korzystać two-

rząc pierwszą grę;• Czym jest prototyp;• Jak zorganizować sobie pracę.

Powinieneś wiedzieć:• co to gra komputerowa;• … i jaką grę chcesz zrobić.

Poziom trudności

Czyli najtrudniejszy pierwszy krok

Page 35: SDJ_02_2010_PL

02/201034

Programowanie gierJak napisać swoją pierwszą grę komputerową

www.sdjournal.org 35

starczy bliżej zainteresować się narzędzia-mi do tworzenia gier. Nie zawsze oferują swobodę, jakiej oczekujemy. Nie zawsze są wydajne. Mają jednak jedną ogromną zale-tę, a mianowicie są proste w obsłudze i czę-sto darmowe lub bardzo tanie. Dzięki temu skupiają wokół siebie spore społeczności. To z kolei oznacza, że dostępne jest bardzo du-żo gotowych przykładów i poradników, jak stworzyć własne gry, bazując na danym na-rzędziu. Przykładem takiego narzędzia mo-że być Game Maker.

Gdy umiemy trochę programować, po-winniśmy rozważyć wykorzystanie istnie-jącego silnika lub framework’a. Zawsze pa-miętajcie, że gra to olbrzymie przedsięwzię-cie i trzeba ułatwiać sobie życie jak się tyl-ko da. Częstym błędem jest zabieranie się do pisania własnego silnika, żeby potem na nim stworzyć grę. Przypomina to trochę wywa-żanie drzwi otwartych na oścież – jest wie-le darmowych rozwiązań, które zaoszczędzą nam miesięcy żmudnej dłubaniny. No chy-ba że właśnie ta dłubanina wciąga was naj-bardziej – wtedy twórzcie silniki i dzielcie się nimi z innymi. Kto wie, może zostanie-cie zauważeni.

W naszym przypadku lista jest krótka – trochę doświadczenia z C++ i PHP. Po-trafimy coś narysować w Gimp’ie, ale na tym nasze zdolności artystyczne się, nieste-ty, kończą.

Na tym etapie trzeba zdecydować, czy pi-szemy grę dwuwymiarową, czy trójwymia-rową. Większość z nas w pierwszym odru-chu powie, że oczywiście trójwymiarową. W tym przypadku jednak więcej nie za-wsze znaczy lepiej. Jak powiedział Anto-ine de Saint-Exupéry „wiesz, że osiągnąłeś perfekcję w projekcie, nie, gdy nie masz już nic do dodania, lecz gdy nie możesz już nic uprościć”. Zastanówmy się , czy ten trzeci wymiar jest rzeczywiście konieczny. Czy bez niego gra nie będzie ciekawa? Czy me-chanika nie może działać w dwóch wymia-rach? Jeśli zdecydujemy się na realizowa-nie gry w trzech wymiarach, musimy li-czyć się ze znaczącym wzrostem nakładu pracy i złożoności zadania. W przypadku gry dwuwymiarowej do stworzenia grafi-ki wystarczy usiąść do przysłowiowego „pa-inta” na kilka minut i naszkicować sprite’a. Może być nawet paskudnie brzydki – pro-grammers art’y też mają swój urok i przede wszystkim pozwalają zaprezentować pro-jekt innym osobom. Oczywiście może-my także skorzystać z ogromnej ilości gra-fik dostępnych za darmo w sieci. Przykła-dem może być Reiner`s Tilesets – zbiór do-stępnych za darmo obiektów i tile’i do gier w rzucie izometrycznym. Pewnym proble-mem jest tworzenie przyzwoicie wyglądają-cych animacji dwuwymiarowych. Koniecz-

ne jest ręczne rysowanie kolejnych klatek, co jest dosyć uciążliwe.

Pewnym ułatwieniem może być tworze-nie grafiki wektorowej – rozwiązuje to pro-blem niewprawnej drżącej ręki, pozwala wy-pełniać kształty gradientami i ułatwia ani-mowanie. Co prawda powstaje grafika fla-szowa, ale lepszy rydz niż nic.

Taka grafika nie musi być grafiką docelo-wą – może służyć tylko do przygotowania prototypu gry, który będziemy chcieli – ce-lem zachęcenia do współpracy – pokazać wydawcy lub kolegom potrafiącym rysować.

Jednak w przypadku trzech wymiarów sprawa się komplikuje. Żeby nasza gra za-częła dobrze wyglądać, musimy stworzyć model w jakimś programie do modelowa-nia, takim jak darmowy Blender. Jednak sam model to nie wszystko – raczej dopie-ro początek. Jeśli będzie to obiekt animo-wany, musimy przygotować animację. Ten krok jest łatwiejszy niż animowanie ra-strowych obiektów dwuwymiarowych ze względu na wektorowy charakter modeli trójwymiarowych (zakładając, że nie po-rywamy się na nic niestandardowego jak voxele).

Kolejna sprawa to pokrycie naszego obiektu barwą, czyli stworzenie tekstu-ry. Musimy namalować obraz dwuwymia-rowy, który zostanie zmapowany na po-wierzchnię obiektu trójwymiarowego. Można więc powiedzieć, że i tak musi-my wykonać pracę, której wymagałaby na-sza gra, gdybyśmy zdecydowali się na gra-fikę dwuwymiarową. W zależności od te-go, na jak złożone efekty specjalne i oświe-tlenie się decydujemy na tym etapie, może-my skończyć prace nad modelem lub jesz-cze wygenerować mapy normalnych, ma-py nierówności, mapy odbić i co tylko nam przyjdzie do głowy.

Oczywiście możemy znaleźć darmowe modele trójwymiarowe w sieci. Jednak doświadczenie uczy, że dużo trudniej jest znaleźć odpowiadający nam model trójwy-miarowy niż bitmapę dwuwymiarową. Na-wet gdy znajdziemy odpowiedni obiekt, może okazać się, że nie posiada on wszyst-kich map, których potrzebujemy lub jest wykonany w sposób uniemożliwiający wy-korzystanie z naszymi metodami wyświe-tlania.

W naszym konkretnym przykładzie naj-rozsądniejszym rozwiązaniem wydaje się być grafika dwuwymiarowa. Gry platfor-mowe jako gatunek kojarzone są z dwuwy-miarowymi planszami. Dzięki uproszcze-niu świata gry będziemy mogli szybciej za-implementować zasady rozgrywki i skupić się na dodawaniu ekstra funkcjonalności takiej, jak rzucanie bananem w odległych przeciwników.

Podstawowymi elementami, znajdujący-mi się w większości gier, są:

• silnik graficzny odpowiedzialny za wy-świetlanie gry;

• biblioteki odpowiedzialne za odgry-wanie efektów dźwiękowych oraz mu-zyki;

• silnik symulacji fizycznej;• skrypty;

Jeśli planujemy stworzenie gry dwuwy-miarowej , warto zainteresować się PopCap Games Framework. Framework ten jest bardzo kiepsko udokumentowany (garść dokumentów opisujących ogólne zasady działania), jednak jego prostota rekompen-suje wszelkie wady. Nawet niewprawny programista po przejrzeniu przykładów dostarczonych wraz z kodem źródłowym może napisać prototyp gry platformowej (lub dowolnej innej) w dosłownie kilka go-dzin. Framework zapewnia wszystko, cze-go potrzeba do stworzenia gry dwuwymia-rowej. Znajdziemy tu parser XML’a, obsłu-gę rejestru, dźwięku oraz animacji. Two-rzenie interfejsu użytkownika jest stosun-kowo proste i już na starcie dysponujemy praktycznie wszystkimi potrzebnymi kon-trolkami. Nic, tylko siadać i pisać. Oczy-wiście im głębiej w las, tym więcej drzew – framework nie jest pozbawiony wad. Nie powinny one jednak przeszkadzać w two-rzeniu pierwszej gry. Nie musimy ograni-czać się do PopCap’a – jest wiele innych bardzo dobrych bibliotek i framework’ów wspomagających tworzenie gier dwuwy-miarowych – chociażby SDL, Allegro czy Blitz2D.

W przypadku gdy decydujemy się reali-zować nasz projekt w trzech wymiarach, wybór jest jeszcze większy. Możemy zdecy-dować się na silniki graficzne takie jak Irr-licht lub Ogre. Jako że są to silniki graficz-ne same w sobie nie oferują żadnej funkcjo-nalności poza wyświetlaniem sceny. Tu z po-mocą przychodzą społeczności powstałe wo-kół tych projektów. Z łatwością znajdziemy dodatkowe narzędzia ułatwiające budowa-nie silnika gry.

Ponieważ zdecydowaliśmy się na reali-zację naszej gry w dwóch wymiarach, wy-bieramy framework PopCap. Pozwoli to nam bardzo szybko stworzyć pierwszą wer-sję gry.

Do obsługi dźwięku możemy wykorzy-stać gotowe biblioteki, takie jak FMOD lub OpenAL (Open Audio Library). Pierwsza z nich jest biblioteką komercyjną. Jeśli bę-dziemy chcieli sprzedawać naszą grę, musi-my liczyć się z kupnem kosztownej licen-cji – nawet do kilku tysięcy dolarów. Jeśli jednak nasza gra ma być produktem dar-

Page 36: SDJ_02_2010_PL

02/201036

Programowanie gier

mowym, możemy korzystać z FMOD bez-płatnie.

Tym, którzy od komercyjnych bibliotek dostają wysypki, na pewno przypadnie do gustu OpenAL. Jest to biblioteka w peł-ni open source, rozwijana przez społecz-ność na wzór OpenGL (choć nie w tak sko-ordynowany sposób). Dzięki temu nazew-nictwo i konwencje nazw przypominają OpenGL.

Wybrany przez nas framework PopCap posiada moduły odgrywające dźwięk, jed-nak decydujemy się na zaimplementowa-nie własnego modułu wykorzystującego bi-bliotekę OpenAL. Jest darmowa i oferu-je dźwięk przestrzenny, co jest dla nas bar-dzo ważne. Przecież chcemy, żeby odgłos banana uderzającego w biznesmana dobie-gał dokładnie z miejsca, w którym znajdu-je się małpa i brzmiał inaczej w zależności od otoczenia.

Kolejnym ważnym elementem naszej gry mogą, lecz nie muszą, być skrypty. Zaimplementowanie silnika skryptowego może nam pozwolić na przeniesienie czę-ści logiki poza kod. W szczególności po-zwoli to na modyfikowanie parametrów i zachowań obiektów w świecie gry bez ko-nieczności rekompilacji silnika. Nie każda gra wymaga implementowania skryptów – na pewno nie będą potrzebne w prostej platformówce czy grze logicznej. General-nie wszędzie tam, gdzie mechanika gry jest powtarzalna i nie występują nieprzewidzia-ne zdarzenia, skrypty są zbędne. Będą za to niezastąpione w grach role-playing lub przygodówkach.

Dwa języki, które można polecić, to z pewnością Lua oraz Python. Prawdopodob-nie lepszym rozwiązaniem (przynajmniej na początek) będzie wykorzystanie Lua. Jest to język skryptowy projektowany z myślą o in-tegracji z C lub C++. Jest bardzo lekka, szyb-ka i prosta do opanowania. Te cechy spra-wiają , że jest bardzo popularna wśród twór-ców gier.

Nasz projekt nie wymaga silnika skrypto-wego. Co prawda rozważaliśmy dodanie nie-standardowych zachowań planszy takich jak walące się budynki czy załamujące się pod małpą kładki dla pieszych. Jednak po do-kładnej analizie doszliśmy do wniosku, że takich zdarzeń będzie niewiele i szybciej bę-dzie zaprogramować je na twardo niż imple-mentować skrypty. Nie ma sensu wytaczać artylerii na muchę.

Ostatnim wartym rozważenia elemen-tem jest wykorzystanie biblioteki symula-cji fizycznej. Nie powinniśmy dać się po-nieść modzie na symulowanie wszystkie-go. Musimy się zastanowić , czy nasza gra potrzebuje symulacji fizycznej. Jeśli to przygodówka, to pewnie nie. Gry, w któ-

rych często dochodzi do interakcji z oto-czeniem w trudny do przewidzenia spo-sób, takie jak różnego rodzaju strzelanki lub symulatory wyścigów, prawdopodob-nie skorzystają na zaimplementowaniu symulacji fizycznej. Do wyboru jest bar-dzo dużo zarówno komercyjnych, jak i dar-mowych rozwiązań. Popularniejsze biblio-teki open source to Open Dynamics Engi-ne (ODE), Bullet, Tokamak. Spośród po-zostałych rozwiązań warto wymienić Ha-vok, PhysX oraz Newton Game Dynamics. Praktycznie wszystkie te silniki pozwolą nam zaimplementować realistycznie za-chowujące się bryły sztywne, efekty czą-steczkowe oraz efekt rag-doll (wykorzysty-wany do symulowania realistycznie padają-cych bezwładnych ciał).

W naszej grze zdecydowaliśmy się wyko-rzystać silnik ODE. Użyjemy go, by zasymu-lować realistyczne upadki postaci trafionych bananem oraz różne przedmioty, które mał-pa może przewrócić. Takie smaczki nie bę-dą miały wpływu na rozgrywkę, jednak bę-dą dawały graczowi dużo satysfakcji z poko-nania wroga i sprawią, że plansza będzie bar-dziej interaktywna.

Możemy także zdecydować się na kom-pleksowe rozwiązanie i wykorzystać gotowy silnik gry. Tu wybór jest bardzo duży. Więk-szość silników dostarczana jest wraz z narzę-dziami i edytorami pozwalającymi w wy-godny sposób projektować gry. Minusem za-stosowania gotowego silnika będzie oczywi-ście utrata elastyczności , jaką daje nam na-pisanie własnego silnika dopasowanego do naszych potrzeb. Może się jednak okazać, że znajdziemy silnik doskonale pasujący do wizji naszej gry. Warte uwagi pozycje to To-rque Game Engine, Blitz3D, Unity i id Tech od 1 do 3 (silniki , na których powstał Do-om, Quake 2 oraz 3), Virtools czy Blender Game Engine. Niektóre z wymienionych silników pozwalają na tworzenie gier prak-tycznie bez pisania kodu – w VirTools mo-żemy dosłownie składać grę z klocków meto-dą drag and drop.

PrototypBez względu na to, jaki silnik zdecyduje-my się wykorzystać, pierwszą rzeczą, któ-rą stworzymy, powinien być prototyp na-szej gry. Prototyp jest to bardzo prosta wer-sja gry pozwalająca zapoznać się z mecha-niką rozgrywki i ją przetestować. Najczę-ściej opiera się na bardzo prostych elemen-tach graficznych i powinien pozwalać gra-czowi na zagranie w grę i jej wygranie (lub przegranie). Jeśli piszemy grę przygodową, prototyp powinien być jedynym zadaniem, które postać musi wykonać – na przykład wydostać się z celi, wykorzystując znale-ziony pod pryczą widelec. Jeśli nasza gra

będzie strzelanką, w prototypie powinni-śmy móc strzelać i niszczyć wrogów. Ce-lem może być dotarcie żywym do określo-nych drzwi.

Generalnie prototyp powinien powstać w kilka dni, a w idealnej sytuacji godzin. Jeśli w ciągu tygodnia nie jesteśmy w sta-nie ukończyć prototypu ,może być to ważna wskazówka, że nasz projekt jest zbyt obszer-ny jak na nasze możliwości i trzeba zrewido-wać plany. Może na początek warto stwo-rzyć prostszą grę, którą będziemy potem rozszerzać o nową funkcjonalność? Tak czy inaczej bardzo ważne jest , abyśmy jak naj-szybciej mogli zobaczyć efekty swojej pracy w postaci działającej gry. Da nam to siłę i mo-tywację do dalszej pracy!

Dzięki prototypowi stosunkowo wcze-śnie możemy stwierdzić , czy nasza gra jest interesująca i czy nasze pomysły faktycznie sprawdzają się. Jeśli nie – oszczędziliśmy właśnie sporo czasu. Jeśli tak – możemy od razu sprawdzić, czy wymyślone sterowa-nie sprawdza się, czy interfejs jest czytelny i tak dalej. Możemy też pokazywać proto-typ ewentualnemu inwestorowi lub współ-pracownikom (na przykład grafikowi ,któ-ry zachwycony wciągającą rozgrywką przy-gotuje nam profesjonalną oprawę wizual-ną). Taki prototyp możemy pokazać w sieci i próbować ewentualnie zwerbować pomoc do naszego projektu. Jeśli nie możemy po-chwalić się działającą wersją gry, zostanie-my potraktowani w najlepszym razie jak marzyciele i zachęceni do dalszych prac. Takimi miejscami w sieci ,gdzie możemy podzielić się naszymi doświadczeniami lub skorzystać z wiedzy i doświadczenia innych przy pisaniu gier, jest na przykład GameDev.net lub nasz rodzimy Warsztat (www.gamedev.pl).

W naszym idealnym przypadku wokół naszego projektu powstaje prężna społecz-ność zdolnych ludzi nienawidzących biz-nesmenów w garniturach, ale lubiących rzucać bananami. Szybko powstaje fanta-styczna komiksowa oprawa graficzna i gra podbija serca graczy. A druga część także portfele.

Pozostaje życzyć powodzenia w zmaga-niach z pierwszą grą!

KONSTANTY KALICKIOpiekun specjalizacji Programowanie Gier Kom-puterowych na [email protected]

Page 37: SDJ_02_2010_PL
Page 38: SDJ_02_2010_PL

���������

������������������

���� ������� ����� ��� ������ �������

������������� �������� ������ ������

������ ������ ������������� �����

����������������������������������������������

������������������������������������������

��������������������������������������������

������������ ��� ��� ��� ���������� �������� ����

����� ������� �� ����� ����� ����������� ���� �����

�������������� ��������� �������� ��������

�����������������������������������������

�������������������������������������������

�������������������������������������������

��������� ��� �� ���� ������� ��� ���������

���������� ����� ������������ �����������

����� ������� ����� ������������ ��� ����������

�����������������������������������������������

����������������������������������������

��������������������������������

��������� ����������� ����������� �����

���� ���������� �� ������� ������������ �������

��� ���������� ������ ���������� ������ ������

������ ����������� ��� ������� ������� �������

����� ��� �������� ��� ������ ����� �����������

����� ����� ���������� �� ������� ���������

��� �������� ����������� �� ������� ��� �������

����������������������������������������

��� ��������� ��� �������� ��� ��������������

������������������������������������������

��������������������������������������������

����������������������������������������

��������������������

��� ������ �������� ������ ����� ��������� ��� ��

����������������������������������������

��� ������ �������� ������ ����� ������������ ��� �����

������� ���������� �������� ���� ������ ���

���������������� �������� �� ��������� ������

�����������������������������������������

��������������������������������������������

�������������������������������������

������������������������������������������

������������������������������������������

�����������������������������������������������

����������� �� ����������� ��������������� ����

����� ������� ��� �������� ������� ��������� ���

����� ���� ������������ �� ���������� ��������

���������������������������������������������

�� ������� ����������� ������ ��������� ���������

��������� ��� �� ��������� ��� ������� ������

�����������������������������������������

������� ����������� ����� �� ������� �������

�����������������

������������ ������ ��������� ��� ��������

�������������������������������������������

�������������������������������������������

���� ��������� ���������� �� ����������� �� ��

���� ��������������� ����������� ���� ���������

�����������������������������

��������� �������������� ��������� ��� ���

����������� ���� ����� ���������� ������ ������

�������� ���� ������������� �������� �� �����

���� ���������������������������������������

���������� ��������� ��� ��� ���������������

��������� ����� ������ ��������� �����������

��������������������������������������

��������������������������������������������

������ ������� ��� �� ��������� ����������

�������� ���������� ������������ ���� ������

�������������������������������������������

��� ���������� ���������� �� ���� ������� ���� �����

��������� ���� �������� ���������� �� ����������

�������������� �������� ������ ���������� ���

���� ������� ��� ����������� ��� ����������� ����

�����������������������������������

��������� ������������� ��������� ��� ������

�����������������������������������������������

�������������������������������������������

����� �������� �������� �� ��������� �����������

��������������������������������������������

���������� �� ������� ������� �� ����������������

������������������������������������������

��������������������������������������������

��� ������� �� �������������� ���� ���������

��������������������������������������������

���������������������������������������������

��������� ���������������������� �� ������������

�������������������������������������������

������������������������������������������

��������������������������������������������

���������������������������������������������

���������������������������������������������

����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� ��������� �������� �� �������� ������������ ������ ��� ��� ������������������������������������������������������������������

��������������� ������������������������������������ �����������������������������������������

�������� ��������������������������

����������������������� ����������������������������������������

�����������������������

����������������

�������������������������������������

����������������������������� ��������������������

Page 39: SDJ_02_2010_PL

����������������� ��

����������������������������������������������

���������������������������������������������

�����������������������������������������������

�������������������������������������������

��� ������ ��������� ��� ����������� �� ������ ��

���������������������������������������������

������ ���������� ������������������������������

������������ ��������������������� ���������

������������ ���������� ��������� �������������

���������������������������������

���������������������������������������

�������������������������������������������

�������������������������������������������

������������������������������������������

�����������������������������������������������

��������������������������������������������

���������������������������������������

�����������������

������� ������ ��������� ����� ���������

������������

���������������

������

����������������������������������������

�������������

��������������������������������������

���

������������������������������������������������������������������������������������������

���������������������������������������������

�������������������������������������������

�������������������������������������������

���������������������������������������������

�������������������������

��� ������ ��������� ������������ �������� �����

������� ���������� ����� ���������������

��������� ��� ������� ������� ��� ������ �����

���������������������������������������

��������������������������������������

����

��� ����������������������������������������������

�������������� ����������� �� �����������

�������������������������������������

��� ������� ����� �������������������

���������������������������������������

�����

�������������������������������������������

����������� ������� ��� ������� �������� ���

���������� ����� ���������� �������� �������

������ ������ ������� ����������� �� ������

����� �� ���������� ���� �������� ������������

���������������������

�������� ��������������������� �������������

��� ����� �� ������������������ ��

�����������������������������

�� ������������� �� ������ ����

�������������������������

����

���������������������������� �������������

���

���

���

���������������������

�������� ������������������������������������ �������������

��� ���� ������������� �� ���������� ��

�� ������������� �� ������� ����

����������������������������������

���

���

�������� �������� �� ������������ ��

��� ����� �� ������������������ ��

�����������������������������

�� ������������� �� ����������� ������������������������������

�������������� ����

����������������������������������������

�����

���

���

���

���

����������������������������� ��������������������

Page 40: SDJ_02_2010_PL

���������

������������������

���������������������������������������������

��������� ������ ��������� ������ �������� ����

�����������������������������������������

��� ��� ��������� ����������� ������������ �����

�����������������������������������������

��������������������������������������������

����� �� �������� ������� ����� �������� ������

�������������������������������������������

��������������������������������������������

������������������������������������������

������ ���������������������������� ���������

�����������������������������������������

���������������������������������� ���������

�����������������������������������

�� ����������� ����� ����� ��� ����������

���������������������������������������������

����������������������������������������������

����� ��������� ������ ��� ������� ������ ��������

��������������������������������������������

��� ������������ ��������� ���� ��������� �����

���������������������������������������������

�������������������������������������������

��������������������������������������������

���������������������������������������������

����������� ������������� ������� ���������

��������������������������������������������

���� ��������� �������� ����������� ��� ��������

������������ ���������� ���� ���������� ��������

���������������������������������������������

���������������������������������������������

������������������������

��� ������ ������������ ��� �������� ��� ������

������������ �������� ����� ����������� ����

�������� ������ ������������������� �����

�������� ����� ���������� �������������

�����������������������������������������

��������� �� ���������� ���� ��������� ������

������������������������������������

��� �����������������������������������������

����������������������������������������

����������� ��������� ��� ���������� ����

����������������������������������������

���������� ��� ������������ ��������� ��

�����������������������������������������

���������� ���� ��������� ���� �������� �����

�������������������������������

������������� ���� ������ ����� ��������� ���

�������� ��������� ���� ����� ������� �����������

��������� ����� ������ ����������� ���������

����������������������������������������

��� ��������� �� ����� ������� ����� �� ����� ���

����������� ���������� ������������ �� ����

���� ����� ���� ������ ��������� �������� �� ���

�������������������������������������

��������������������������

��� ������ �� ������ ������� ��� �������� �����

�������� �������� ������ ����� �� ������� ���

������� ���������������������������������

�� ��������� �������� ��������� ��������

���������������������������������

�����������������������������������������

�������������������������������������������

��� ������ �������� ��������� ����� ����������

����� ������������ ��������� ���������������

�����������������������������������������

�����������������������������

��������������������������������������������������������������������

���������������������������������������������

�������� �� �������� ��������� ����� ����������

���� �� ������ ��������� ���� ������ �����������

��������������������

��������������

������������������������������

���������� ������ ��������� ��� �������� ������

��������������

����������������

���������

���������������������������������������

��� ��������� �� ������� ������� �����������

��������������

��� ��������� �� ������� ������� �����������

���������������

��� �������� �� ��������� ������� ���������

���������������� ����������������������

��� �������� �������� ����� ���������� �����

������������������������������� ���������

����������������������������

�������������������������� ������������� ��������������� ������������� ����������� ������� ���������� ����� ������������������ ���������� ��������� �� ����������� ����������������������������������������������������� ����������� ������������ �� ������ ����������������������������������������������������������� ��������������� ���������� ���������� ������������������ ������� ������ ����� ����������������������������������������������������������������������������������������������������������������������������� �������������������������� �������� �������������������������������������������

���������������������������������������������������������

��� ������� �� ������� ������� �����������

�������� �� ���� ����������� ��� ��������� ����

�������� ������� �������� ������ ����������

��������������

������������ ���� ���������� ���������� �����

�������������������������������������������

����� ����� ����� �������� ��� ��������� ����� ����������

����������������������������������������������

�����������������������������������������������

������������������������������������

�� ���������� ���������� �������� ��������

������� ���������� ����� ������� ������������ ���

��������������������������������������������

��������������������������������������������������

��� �������� ��� ������ ��������� ��� �����������

����������������������������������������������

����������������������������������������������

������������������ �������� ��������� �����

�������������������������������������������������

����������������������������������������

�������������������������������������

�������������������������������������������

����������������������������������������������

������������������������������������������������

������������������������������������������������

������������������������������������������������

��������������������������������������������

�������������������������������������������������

�����������������������������

��������������������������������������������

������������ �������� ��������� ��������� ���

������ ������ �������� �� ����������� �����������

������������������������������������������������

��������� ������� ���� ���������� ��������������

��� ���� ��� ��������� �������������� �������

����� ����� ������� �������� ���� ��� �������������

���� ������������� ������� ��� �� �������� ����������

��������� ������������� ����� �� ������������ ���

�������� ���� ���� �� ���������� ����������� ���

������������������������������������

����� �� ��������� ��������� ����������

���������� ���� ������� ��� ��������� ������ ���

������������������������������������� ������

��������������������������������������������

���� �������� ������� ����� ��������������� ����

���������� ������������ �� �������� ��������

��������������

�������������������������������������

����������������������������� ��������������������

Page 41: SDJ_02_2010_PL

������������������������

Page 42: SDJ_02_2010_PL

02/201042

Programowanie gier Mapy kafelków

www.sdjournal.org 43

Artykuł ten poświęcony jest jednej z podstawowych technik używa-nych do tworzenia dwuwymia-

rowych gier – mapom kafelków (ang. tiled maps). Metoda ta używana jest z powodze-niem od bardzo dawna, a z jej użyciem po-wstało dużo świetnych gier. Mimo iż w dzi-siejszych czasach może wydawać się trochę archaiczna, to stanowi świetny punkt wyjścia w nauce programowania gier, a także ciągle doskonałą i stosowaną technikę w programo-waniu na urządzenia o ograniczonych zaso-bach, np. telefony komórkowe.

Artykuł wprowadza w świat map kafel-ków, a następnie przedstawia sposoby ich implementacji. Kolejnym zagadnieniem bę-dą układy współrzędnych używane w ma-pach oraz prosta metoda ich rysowania. Temat rysowania zostanie następnie roz-szerzony o rysowanie z buforem. W artyku-le znajdzie się też kilka sztuczek optymali-zacyjnych.

Tekstowi będą towarzyszyć Rysunki i Listingi. Listingi pełnią rolę koncepcyj-ną – będą to fragmenty pseudokodu ma-

jące zobrazować pewne zagadnienia. Jako że kompletny, przykładowy kod też jest do-brym źródłem informacji, do artykułu do-łączona jest prosta implementacja opisywa-nego materiału. Jest ona stworzona w Javie (Micro Edition, projekt w NetBeans) oraz C++ (SDL, projekt w Visual C++ Express 2008). Wszystkie narzędzia potrzebne do uruchomienia dołączonego kodu dostępne są za darmo.

Wykafelkuj sobie światWyobraźmy sobie, że chcemy stworzyć pro-gram, powiedzmy grę, w którym będzie wy-stępował pewien dwuwymiarowy świat. Chcemy, by po tym świecie mogła poruszać się postać sterowana przez gracza. Oczy-wiście świat musi być widoczny, więc bę-dziemy go rysować na ekranie. Świat powi-nien być dość duży, na pewno większy niż ekran. Oglądać go będziemy z boku (al'a Ma-rio Bros) bądź z góry (al'a klasyczne RTSy). Chcemy, żeby różne fragmenty świata mia-ły różny wygląd i różne właściwości (np. że coś jest z cegieł albo z trawy, że po czymś gracz może się poruszać, a po czymś nie itp.) A do tego chcemy takich światów mieć kil-kanaście, np. jako różne plansze w grze. Jak to zrobić?

Istnieje wiele różnych technik. Podzielo-ny jest ogólnie na dwie grupy – rastrowe i wektorowe (pikselowe i geometryczne).

W skrajnej formie wersji rastrowej może-my narysować cały świat jako jeden ogrom-ny obrazek. W wersji wektorowej cały świat reprezentowany jest przez np. zbiór wielo-kątów. Oba podejścia różnią się m.in. zuży-ciem pamięci, mocą obliczeniową potrzeb-ną do rysowania, złożonością algorytmów, dokładnością, z jaką możemy tworzyć ele-menty świata, czy łatwością ich tworzenia i modyfikowania.

Podejście typu wielki obrazek pociąga za so-bą ogromne zużycie pamięci, ale za to ryso-wanie w tym przypadku jest szybkie i pro-ste. Wystarczy narysować na ekranie widocz-ny fragment obrazu-świata. W każdej chwili możemy dodać (dorysować) nowy element lub szczegół. Z drugiej strony, jeśli naraz chce-my zmienić wygląd dużej części świata, trze-ba go mozolnie przerysować. Trzeba także wy-myślić sposób przypisania różnych cech róż-nym obszarom świata.

Wersja geometryczna to najczęściej mniej-sze zużycie pamięci, ale wymaga znacznie większej mocy obliczeniowej przy rysowa-niu, a samo rysowanie trudniej jest zaim-plementować. Dodanie nowych elementów świata wymaga dodania nowych wielokątów lub edycji istniejących. Wraz ze wzrostem złożoności i ilości geometrii coraz trudniej połapać się w edycji świata. Plusem tego po-dejścia jest łatwość nadawania cech obsza-rom świata – możemy na przykład przypi-sać cechy wielokątom lub stworzyć specjal-ne kształty, które nie będą rysowane, a bę-dą służyć jedynie do opisywania cech wybra-nych obszarów.

Istnieje jednak technika, która wpasowu-je się gdzieś pomiędzy te skrajne podejścia. Z jednej strony opiera się na rysowaniu przy użyciu obrazków, ale jednocześnie pozwala na wyodrębnienie poszczególnych fragmen-

Mapy kafelków w grach 2D

Marzysz o stworzeniu swojej wymarzonej dwuwymiarowej platformówki albo klasycznego RTSa? Od czego zacząć? Oczywiście od map kafelków (ang. tiled maps). Dzięki temu artykułowi poznasz podstawy tej techniki – dowiesz się, jak mapy kafelków zaprogramować i jak je rysować.

Dowiesz się:• W jaki sposób działają mapy kafelków (ang.

tiled maps);• Jak rysować mapy kafelków;• Jak zastosować kilka sztuczek optymaliza-

cyjnych.

Powinieneś wiedzieć:• Podstawowa znajomość C++ i biblioteki SDL

lub podstawowa znajomość Javy.

Poziom trudności

Wstęp i rysowanie

Page 43: SDJ_02_2010_PL

02/201042

Programowanie gier Mapy kafelków

www.sdjournal.org 43

tów świata, operowania na nich i nadawania im cech. Do tego dochodzi małe zużycie pa-mięci oraz łatwość i szybkość rysowania. Z drugiej jednak strony odbywa się to kosztem dokładności i szczegółowości, z jaką może-my tworzyć świat. Technika ta opiera się na przedstawieniu świata za pomocą mapy kafel-ków (ang. tiled maps).

Idea jest prosta – nakładamy na świat siatkę (jak w zeszycie w kratkę) i dokonu-jemy podziału na prostokątne lub kwadra-towe fragmenty, zgodnie z liniami siatki. Fragmenty będące wynikiem podziału bę-dziemy nazywać kafelkami. Wszystkie ka-felki mają te same rozmiary i sąsiadując ze sobą, pokrywają całą powierzchnię świa-ta. Każdy kafelek ma przypisany obrazek (wszystkie kafelki używają obrazków o ta-kich samych rozmiarach) oraz dowolne pa-rametry opisujące obszar zajmowany przez tafelkę. Nadając wygląd i cechy fragmen-tom świata, możemy zmieniać tylko po-szczególne kafelki.

Rysunek 1 pokazuje dwuwymiarowy bar-dzo szczegółowy świat oraz jego wersję ska-felkowaną.

Informacje umieszczone w kafelkuKafelek stanowi podstawowy element ma-py. Jest on prostokątem (często kwadratem, ale nie jest to reguła) zawierającym informa-cje na temat fragmentu powierzchni świata, który pokrywa. Są to całkowicie dowolne in-formacje, sami musimy określić, co jest nam potrzebne. Można wyróżnić kilka grup ta-kich informacji: wizualne, fizyczne, rozgryw-kowe, obiektowe.

Informacje wizualne opisują wygląd ka-felka i są używane przez algorytmy rysujące. Może to być obrazek do wyświetlenia w miej-scu, gdzie znajduje się kafelek, może to być informacja, że kafelek jest niewidoczny, mo-że to być kolor (np. jeśli chcemy, aby kafelek był jednolicie wypełniony kolorem).

Informacje fizyczne zawierają cechy uży-wane w procesie symulacji świata gry. Na przykład, czy gracz może przejść przez kafel-ka, czy może go zniszczyć, jego wytrzymałość na uderzenia, śliskość jego powierzchni (może wpływać na ruch gracza), wartość i kierunek siły grawitacji itp.

Informacje rozgrywkowe to wszelkie in-formacje specyficzne dla mechaniki i zasad naszej gry. Na przykład, ilość życia, jaką tra-ci gracz wchodząc na kafelka, dozwolony czas przebywania na kafelku (powiedzmy, że po tym czasie gracz ginie), czy kafelek został od-wiedzony (np. jeśli chcemy, żeby gracz musiał dotrzeć do pewnych wyznaczonych miejsc w świecie).

Informacje obiektowe najczęściej dotyczą występowania w miejscu położenia kafelka

pewnych obiektów. Mogą to być przeciwni-cy czy przedmioty, które gracz może zbie-rać. Mogą to też być obiekty niewidoczne, np. punkt startowy planszy czy punkt na-wigacyjny dla algorytmów sztucznej inteli-gencji.

Tak naprawdę, przedstawiony powyżej po-dział jest dość umowny, niektóre informacje można zaliczyć do kilku grup. Najważniej-sze jest, aby pamiętać, że kafelek może prze-chowywać dowolne informacje dotyczące je-go obszaru.

Rysunek 1. Dwuwymiarowy świat i jego reprezentacja z użyciem kafelków. Zaledwie dziesięć kafelków pozwoliło na dość wierne odwzorowanie

Rysunek 2 . Mapa (z lewej) i ekran (z prawej). Na czerwono zaznaczony jest viewport; znajduje się on zarówno w mapie, jak i na ekranie

Page 44: SDJ_02_2010_PL

02/201044

Programowanie gier Mapy kafelków

www.sdjournal.org 45

Warto też zauważyć, że cechy kafelka nie muszą być liczbami. Na przykład zamiast trzymać ogólną informację, czy przez kafel-ka można przejść, można przechowywać ob-szar kolizyjny (np. w postaci wielokąta). W przykład kierunku i siły grawitacji w kafel-ku przechowywać będziemy odpowiedni wektor. Może to nawet być tekst, np. wiado-mość wyświetlana na ekranie, gdy gracz wej-dzie na kafelka.

Kafelek w pamięciW tym momencie mamy w głowie ideę map kafelków i tworzenia świata za ich pomocą. Kolejnym krokiem jest przeniesienie idei do programu – do naszej gry.

Zaczniemy od stworzenia reprezentacji ka-felka. Wiemy, że kafelki są kontenerami zawie-rającymi różne informacje na temat pewnego miejsca w świecie. Potrzebujemy zatem struk-tury danych, która będzie te różnorodne in-formacje przechowywać. Mamy co najmniej dwie opcje:

• obiekty – informacje zapisane są jako po-la klasy;

• liczby całkowite traktowane jak pola bi-towe – informacje zapisane są na kolej-nych bitach liczby całkowitej.

Jak to wygląda w praktyce? Powiedzmy, że chcemy, aby nasze kafelki przechowywały następujące informacje:

• identyfikator obrazka używanego do ry-sowania kafelek;

• flagę mówiącą o tym, czy kafelek jest pu-sty (gracz może po nim przejść), czy nie (gracz koliduje z kafelkiem);

• ilość życia, którą gracz straci (wartości ujemne) lub zyska (wartości dodatnie), stykając się z kafelkiem.

Listingi 1 i 2 zawierają przykładowe spo-soby implementacji takiego kafelka wraz z prostym przykładem użycia – stworzenie nowego kafelka oraz odczyt i zmiana jed-nej z informacji przez niego przechowy-wanych. Listingi są napisane w pseudo ko-dzie, ale można je łatwo dostosować do C++ lub Javy.

Listingi 1 i 2 zawierają przykładowe spo-soby implementacji takiego kafelka wraz z prostym przykładem użycia – stworze-nie nowego kafelka oraz odczyt i zmiana jednej z informacji przez niego przecho-wywanych. Listingi są napisane w pseudo kodzie, ale można je łatwo dostosować do C++ lub Javy.

Listing 1 to wersja, w której używamy kla-sy. Zaletą tego rozwiązania jest łatwość i oczy-wistość kodu – po prostu odnosimy się do po-la klasy.

Listing 1. Reprezentacja przykładowego kafelka przez klasę; pseudokod

class Tile

{

short imageId; // id of an image used to draw a tile

boolean isSolid; // if true – tile collides with a player

byte lifeMod; // life modifier

}

void foo()

{

Tile myTile = new Tile(); // create new tile

myTile.lifeMod = SOME_VALUE; // change tile's property

short lifeMod = myTile.lifeMod; // read tile's property

}

Listing 2. Reprezentacja przykładowego kafelka przez pole bitowe wraz z funkcjami dostępu do informacji; pseudokod

int TILE_SHIFT_imageId = 0;

int TILE_MASK_imageId = 0xffff;

int TILE_SHIFT_lifeMod = 16;

int TILE_MASK_lifeMod = 0xff;

int TILE_SHIFT_solid = 24;

int TILE_MASK_solid = 0x1;

void set_tile_lifeMod( int tile, short value )

{

value =<< TILE_SHIFT_lifeMod;

tile &= value;

return tile;

}

short get_tile_lifeMod( int tile )

{

short lifeMod = tile >> TILE_SHIFT_lifeMod;

lifeMod &= TILE_MASK_lifeMod;

return lifeMod;

}

void foo()

{

int myTile = 0; // create new tile

myTile = set_tile_lifeMod( myTile, SOME_VALUE ); // change tile's property

short lifeMod = get_tile_lifeMod( myTile ); // read tile's property

ASSERT( lifeMod == SOME_VALUE ); // we've set tile's life modifier

// to SOME_VALE, so getting it back

// from tile must give the same value

Listing 3. Kafelek jako mapa cech: implementacja w języku C++

// empty tile

std::map< std::string, boost::any > m_tile;

// let's add gravity vector property to the tile

m_tile["gravity"] = Vector2( 0, -3 );

// read the gravity vector property

Vector2f& gravity = boost::any_cast<T&>( m_tile[„gravity"] );

Page 45: SDJ_02_2010_PL

02/201044

Programowanie gier Mapy kafelków

www.sdjournal.org 45

Listing 2 to wersja, w której informacje o kafelku są zapisane na bitach liczby całkowi-tej. Ta wersja jest trochę bardziej zagmatwa-na od wersji z klasą. Nie mamy bezpośred-niego dostępu do składowych kafelka – mu-simy stworzyć sobie pewien mechanizm do-stępu do nich. W tym przykładzie są to funk-cje set_tile_lifeMod i get_tile_lifeMod. Zaletą tego podejścia jest łatwość kopiowania kafelków oraz łatwość zapisu i odczytu mapy do/z pliku.

Kafelek jako mapa cechIstnieje jeszcze inna, ciekawa, aczkolwiek rzadko stosowana metoda – możemy kafel-ka zaimplementować jako prawdziwy kon-tener na różnorodne informacje. Wyko-rzystamy do tego asocjacyjne struktury da-nych przechowujące elementy dowolnego typu – czyli zadajemy pewien klucz i otrzy-mujemy pewną wartość dowolnego typu. W naszym przypadku klucz niech będzie tek-stem (string). Takie rozwiązanie często na-zywa się property map, czyli mapa cech lub właściwości.

Przykładowa implementacja tej techni-ki w języku C++ (zakładam, że mamy swo-ją klasę Vector2) przedstawiona jest na Li-stingu 3.

Podobną strukturę możemy zaimplemen-tować w Javie ME (patrz Listing 4).

Jest to podejście bardzo elastyczne i szcze-gólnie przydatne, gdy tworzymy grę stero-waną danymi. Możemy określać i dodawać dowolne cechy kafelków w locie – nie musi-my określać na sztywno w kodzie, jakie po-la będzie miała klasa, a możemy np. wczy-tać definicję kafelka z pliku (czyli możemy tworzyć różne kafelki bez ponownej kompi-lacji programu). Ponadto, kafelki mogą się różnić między sobą cechami, jakie posiada-ją, np. niektóre kafelki mogą mieć wartość dla gravity, a niektóre nie. W podejściu kla-sycznym wszystkie kafelki mają ten sam ze-staw cech.

W praktyce jednak, podejście to nie jest często stosowane ze względu na duży narzut pamięciowy (trzeba stworzyć obiekt konte-nera, który będzie trzymał informacje o sta-nie zawartości, do tego dużo pamięci zajmu-ją klucze w postaci tekstu) i wydajnościowy (odwołanie się do elementu za pomocą klu-cza trwa dłużej niż zwykłe odwołanie się do pola klasy).

Jeśli jednak narzuty nie stanowią dla nas problemu, a chcemy stworzyć uniwersalny i elastyczny system, jest to rozwiązanie god-ne polecenia.

Mapa w pamięciTeraz musimy zająć się przeniesieniem do programu całej mapy. Mapa to dwuwymia-rowy uporządkowany zbiór kafelków. Za-

tem pierwszym rozwiązaniem, jakie nasu-wa się na myśl, jest użycie dwuwymiarowej tablicy zawierającej kafelki, obojętnie, czy są one reprezentowane jako obiekty, czy pola bitowe. Jest to rozwiązanie proste i in-tuicyjne, gdyż bardzo łatwo i bezpośrednio możemy określić rozmiar mapy i odwołać się do interesującego nas kafelka.

Listingi 5 i 6 przedstawiają przykłady w Javie i C++. Jest tam reprezentacja ma-py, jej tworzenie (o zadanym rozmiarze) i odwoływanie się do konkretnego kafelka (znajdującego się w czwartej kolumnie i trzecim wierszu). Na listingach widać, jak łatwo wykonuje się te czynności; po pro-stu podajemy ilość kafelków w poziomie (szerokość mapy) i pionie (wysokość ma-py) oraz rząd i kolumnę kafelka, do którego chcemy się odwołać (3 i 2). Należy pamię-

tać, że tablice w C++ i Javie są indeksowa-ne od zero, czyli pierwsza kolumna i pierw-szy rząd to 0,0.

Mapa w pamięci – tablica jednowymiarowaNa niektórych platformach występują różne problemy z tablicami wielowymiarowymi. Czasami rozwiązanie takie może być niedo-stępne, czasami może działać wolno.

Z takich czy innych powodów zamiast tablic dwuwymiarowych można użyć ta-blic jednowymiarowych. W tym przypad-ku mapa w pamięci stanowi długi pasek ka-felków, a kolejne rzędy są doklejane do sie-bie z boku.

Jednak pojawia się tutaj pytanie – jak okre-ślić rozmiar mapy i jak odnieść się do kon-kretnego kafelka? Tablica musi przechowy-

Listing 4. Kafelek jako mapa cech: implementacja w języku Java

// empty tile

Hashtable m_tile = new Hashtable();

// let's add gravity vector property to the tile

m_tile.put( "gravity", new Vector2( 0, -3 ) );

// read the gravity vector property

Vector2 gravity = (Vector2)m_tile.get( "gravity" );

Listing 5. Mapa jako tablica dwuwymiarowa; Java

Tile[][] tiledMap;

public void CreateMap( int width, int height )

{

tiledMap = new Tile[width][height]; // create empty map

for( int u=0; u<width; ++u ) { // create tiles

for( int v=0; v<height; ++v ) {

tiledMap[u][v] = new Tile();

}

}

// make tile in 4th column and 3rd row solid

tiledMap[3][2].isSolid = true;

}

Listing 6. Mapa jako tablica dwuwymiarowa; C++

Tile** tiledMap;

void CreateMap( int width, int height )

{

tiledMap = new Tile*[width];

for( int i=0; i<width; ++i ) {

tiledMap[i] = new Tile[height];

}

// make tile in 4th column and 3rd row solid

tiledMap[3][2].isSolid = true;

}

Page 46: SDJ_02_2010_PL

02/201046

Programowanie gier Mapy kafelków

www.sdjournal.org 47

wać wszystkie kafelki, więc jej rozmiar jest iloczynem rozmiaru mapy (w kafelkach) w pionie i w poziomie. Natomiast aby odnieść się do konkretnego kafelka, musimy prze-liczyć wiersz i kolumnę na indeks w tabli-cy: indeks = wiersz * szerokość mapy w kafelkach + kolumna.

Listingi 7 i 8 przedstawiają przykłady ana-logiczne do tych, które były pokazane na Li-stingach 5 i 6, ale z użyciem tablicy jednowy-miarowej.

Czego używać?Suma summarum, sposób reprezentacji ma-py nie jest aż tak ważny, jak to, żeby mieć

mechanizmy łatwego tworzenia map o za-danym rozmiarze i łatwego dostępu do ka-felka za pomocą jego położenia w siatce ma-py (kolumna i wiersz). Jeśli nasz kod będzie używał tych mechanizmów, a nie odnosił się bezpośrednio do tablicy z kafelkami, jeste-śmy zawsze w stanie zmienić reprezentację mapy na inną, bez konieczności zmiany ko-du odwołującego się do niej.

Jeśli wygodnie jest Ci z tablicami dwu-wymiarowymi, to używaj właśnie ich. Jeśli wolisz mieć pełniejszą kontrolę nad spo-sobem odwoływania się do konkretnego kafelka lub na platformie, na którą progra-mujesz tablice wielowymiarowe sprawiają

pewne problemy, używaj tablic jednowy-miarowych.

Przyspieszenie – tablica indeksów początków wierszyJeśli zdecydujemy się na użycie tablicy jed-nowymiarowej do przechowywania mapy, bardzo często będziemy obliczać indeks kafelka na podstawie jego wiersza i kolum-ny w mapie. Dla przypomnienia: indeks = wiersz * szerokość mapy w kafelkach

+ kolumna. Zatem przy każdym odwoła-niu się do kafelka wykonywane jest mno-żenie. Istnieje sposób na pozbycie się tego mnożenia.

Zauważmy, że działanie: wiersz * szerokość mapy w kafelkach daje nam w wyniku indeks pierwszego kafelka w zadanym wierszu. Może-my zatem raz obliczyć indeksy pierwszych ka-felków dla wszystkich wierszy i przechowywać je w tablicy. Wtedy operacja obliczania indek-su wygląda następująco: indeks = pierwsze_kafelki[wiersz] + kolumna. Pozbyliśmy się mnożenia przy odwołaniu do kafelka.

Wartości tablicy pierwsze_kafelki łatwo obliczyć w pętli:

for( int i=0; i < ilość_wierszy; ++i )

pierwsze_kafelki[i] = i * szerokość_

mapy;

Zmniejszenie zużycia pamięci – mapa indeksów do kafelkówJeśli nie potrzebujemy możliwości modyfika-cji właściwości pojedynczych kafelków w ma-pie, to można zmodyfikować jej implementa-cję tak, by zamiast trzymać kafelki trzymała tylko typ kafelka, a same kafelki przechowy-wać gdzieś z boku. W praktyce z boku może oznaczać tablicę (taka jakby biblioteka kafel-ków), a mapa może zawierać indeksy do tej tablicy.

Jeśli kafelki zawierają dużo informa-cji, można w ten sposób zyskać na pamię-ci. Ale nie można zmieniać właściwości po-jedynczych kafelków. Można jedynie global-nie zmieniać właściwości wszystkich kafel-ków danego typu (przez zmianę kafelka w bibliotece).

Przykład: mapę indeksów to kafelków możemy trzymać jako tablicę short'ów (2 bajty na kafelek), daje to nam możliwość posiadania kilkudziesięciu tysięcy różnych kafelków (prawie na pewno wystarczy), a ilość zużytej pamięci = szerokość

mapy * wysokość mapy * 2 + n*ilość

typów kafelków (gdzie n to rozmiar ka-felka w pamięci). W wersji klasycznej zużycie = szerokość mapy * wysokość

mapy * n.Dla mapy, powiedzmy, 60x30 kafelków z

kafelkiem zajmującym 10 bajtów i 100 róż-nymi kafelkami wygląda to następująco:

Listing 7. Mapa jako tablica jednowymiarowa; Java

Tile[] tiledMap;

public Tile GetTile( int u, int v)

{

return tiledMap[v * mapWidth + u];

}

public void CreateMap( int width, int height )

{

tiledMap = new Tile[width * height]; // create empty map

for( int i=0; i < width*height; ++i )

{

tiledMap[i] = new Tile();

}

// make tile in 4th column and 3rd row solid

tiledMap[2*mapWidth + 3].isSolid = true;

// the same as above but using convenience function:

GetTile(3,2).isSolid = true;

}

Listing 8. Mapa jako tablica jednowymiarowa; C++

Tile* tiledMap;

int mapWidth;

Tile& GetTile( int u, int v )

{

return tiledMap[v * mapWidth + u];

}

void CreateMap( int width, int height )

{

mapWidth = width;

tiledMap = new Tile[width * height];

// make tile in 3rd column and 2nd row solid

tiledMap[2*mapWidth + 3].isSolid = true;

// the same as above but using convenience function:

GetTile(3,2).isSolid = true;

}

Page 47: SDJ_02_2010_PL

02/201046

Programowanie gier Mapy kafelków

www.sdjournal.org 47

• wersja klasyczna = 60*30*10 = 18000• wersja indeksowa = 60*30*2 + 100*10 =

3600+1000 = 4600

Im większa mapa, tym większy zysk na pa-mięci w wersji indeksowej. Technika ta mo-że być bardzo przydatna na platformach z ograniczoną ilością pamięci, np. na telefo-nach komórkowych.

Okno na światMamy przygotowane struktury danych przechowujące kafelki i mapę. Powoli zbli-żamy się w kierunku rysowania. Zanim jed-nak tego dokonamy, musimy określić dwie rzeczy:

• co chcemy rysować – jaki wycinek mapy;• gdzie chcemy rysować – w którym miej-

scu ekranu.

Mapy są najczęściej większe od obszaru, w którym będą wyświetlane. Z tego powodu musimy określić, jaki wycinek mapy chcemy narysować. Sam obszar wyświetlania też nie jest rzeczą oczywistą – najczęściej będzie to cały ekran, ale tak być nie musi – prostokąt-ny wycinek świata możemy narysować w do-wolnym miejscu ekranu i nadać mu dowol-ny rozmiar.

To swoiste okno na świat nazywać będzie-my dalej viewport – czyli prostokątna widocz-na część mapy plus informacja, gdzie ma być umieszczona na ekranie. Listing 9 zawiera przykład klasy Viewport, a Rysunek 2 poka-zuje wzajemne relacje między mapą, view-portem a ekranem.

W niektórych systemach graficznych view-port zawiera dodatkowo informacje na temat skalowania, tzn. jeśli widoczny wycinek świa-ta, mimo iż jest to tylko wycinek, jest więk-szy od obszaru, na którym ma być narysowa-ny, można go pomniejszyć, aby go dopasować. Jednak w przypadku naszej prostej mapy ka-felków skala równa się jeden – czyli wycinek mapy ma ten sam rozmiar co obszar, na któ-rym go rysujemy.

Układy współrzędnychW tym miejscu, gdy mówimy o świecie, view-porcie i ekranie, pojawia się zagadnienie ukła-dów współrzędnych. W naszym zagadnie-niu występują układy: świata, viewportu i ekranu.

Układy te mają te same jednostki – pikse-le – oraz kierunki i zwroty osi (przyjmijmy, że oś X rośnie w prawo, Y w dół). Jedyna róż-nica to inne początki – układy są przesunięte względem siebie.

Układ świata ma swój początek w górnym lewym rogu pierwszego kafelka mapy. Układ viewportu zaczyna się w górnym lewym ro-gu okna na świat. Układ ekranu to po prostu

układ ekranu, ma swój początek w górnym le-wym rogu ekranu.

Występuje jeszcze jeden układ współrzęd-nych, który ma inne jednostki. Nazwijmy go kaflowy układ współrzędnych – jest to układ, w którym współrzędne to numer kolumny i

wiersza w mapie. Służy on do wskazywania konkretnego kafelka, np. (1, 4) oznacza kafe-lek w drugiej kolumnie i piątym wierszu ma-py (pamiętajmy o numerowaniu od zera).

Rysunek 3 pokazuje wzajemne zależności między układami.

Listing 9. Klasa Viewport; pseudokod

public class Viewport

{

/* size of viewport in pixels */

int width;

int height;

/* position of top-left corner of viewport in the world */

int worldX;

int worldY;

/* position of top-left corner of viewport in the screen */

int screenX;

int screenY;

}

Listing 10. Prosty algorytm rysowania mapy; pseudokod

void DrawMap()

{

// determine visible tiles bounds:

int left = viewport.worldX / tileWidth;

int right = viewport.worldY / tileHeight;

int top = left + (viewport.width / tileWidth) + 1;

int bottom = top + (viewport.height / tileHeight) + 1;

// make sure we draw only within viewport, so set clipping:

setClip( viewport.screenX, viewport.screenY, viewport.width, viewport.height );

// draw tiles:

for( int v=top; v <= bottom; ++v )

{

for( int u=left; u <= right; ++u )

{

// calculate tile's position on screen:

int x = u * tileWidth - viewport.worldX + viewport.screenX;

int y = v * tileHeight - viewport.worldY + viewport.screenY;

// get tile and draw it:

Tile tile = map.getTile( u, v );

DrawTile( tile, x, y );

}

}

}

Listing 11. Fragment prostej klasy TileSet; Java

public class ImagesTileSet

{

private Image[] tileImages; // array of tile-images

public void DrawTile(Graphics g, int tileIdx, int x, int y)

{

g.drawImage( tileImages[tileIdx], x, y, Graphics.LEFT | Graphics.TOP );

}

}

Page 48: SDJ_02_2010_PL

02/201048

Programowanie gier Mapy kafelków

www.sdjournal.org 49

Żeby nie pogubić się we współrzędnych i układach, w dalszej części artykułu będę uży-wał następujących oznaczeń:

• układ świata: (world x,y);• układ viewportu: (view x,y);• układ ekranu: (screen x,y);• układ kaflowy: (u,v).

Przechodzenie między układamiWszystkie funkcje rysujące obrazy operują w układzie ekranu. Natomiast kafelki ułożo-ne są w mapie i znamy ich współrzędne ka-flowe. Dlatego żeby narysować mapę, będzie-my musieli przenosić współrzędne pomiędzy układami.

Pierwszym krokiem jest obliczenie poło-żenie kafelka w świecie (tzn. współrzędnych górnego lewego rogu kafelka). Aby to zrobić, wykonujemy następujące działania:

(u,v) → (world x,y)

world_x = u * tile_width

world_y = v * tile_width

Czasami potrzebna jest operacja odwrot-na, tzn. stwierdzenie, w jakim kafelku znaj-

duje się punkt (x,y). Działania wyglądają na-stępująco:

(world x,y) → (u,v)

u = world_x / tile_width

v = world_y / tile_height

Znając położenie kafelka w świecie, musimy przenieść je do współrzędnych viewportu, a następnie do współrzędnych ekranu.

(world x,y) → (view x,y) → (screen x,y)

screen_x = world_x - viewport_world_x +

viewport_screen_x;

screen_y = world_y - viewport_world_y +

viewport_screen_y;

Na koniec możemy złożyć w całość oblicza-nie położenia kafelka na ekranie.

(u,v) → (screen x,y)

screen_x = u * tile_width - viewport_

world_x + viewport_

screen_x;

screen_y = v * tile_height - viewport_

world_y + viewport_

screen_y;

Przyspieszenie – rozmiar kafelka wielokrotnością liczby 2Operacje przechodzenia między układami to ciągłe mnożenie lub dzielenie przez rozmiar kafelka. Na niektórych platformach operacje te (zwłaszcza dzielenie) są powolne. Na po-moc przychodzą jednak operacje bitowe na liczbach całkowitych.

Mnożenie i dzielenie przez liczbę, która jest potęgą liczby 2, można zastąpić przesu-nięciami bitowymi. Przesuwamy o tyle bi-tów, do której potęgi jest podniesiona liczba 2. Jeśli chcemy pomnożyć, przesuwamy bity w lewo, jeśli podzielić – w prawo.

Przykładowo, jeśli chcemy pomnożyć przez 8 (2 do potęgi 3), możemy liczbę przesunąć o 3 bity w lewo. Analogicznie, jeśli chcemy po-dzielić przez 64 (2 do potęgi 5), możemy licz-bę przesunąć o 5 bitów w prawo.

Zatem jeżeli znamy rozmiar kafelka w na-szym programie i wiemy, że nie będzie się zmieniał, możemy przyspieszyć operacje na kafelkach, używając przesunięć bitowych. Należy jednak uważać, gdyż w czasie trwania projektu założenia mogą się zmienić. Może się okazać, że jednak musimy użyć rozmiaru kafelka innego niż potęga dwójki. Lub może się okazać, że musimy obsługiwać kilka roz-miarów kafelków (w czasie działania aplika-cji, np. różne mapy o różnych rozmiarach kafelka; bądź w czasie kompilacji, np. gdy tworzymy różne wersje gry z różnymi roz-miarami kafelków w zależności od rozmiaru ekranu urządzenia docelowego, często dzieje się tak przy tworzeniu gier na telefony).

Dobrze jest posiadać mechanizm pozwa-lający na łatwe przełączanie implementacji między wersjami obsługującymi mnożenie/dzielenie i przesunięcia bitowe.

Proste rysowanieWreszcie jesteśmy gotowi, aby narysować ma-pę. Najprostszy sposób rysowania mapy ka-felków, w dużym uproszczeniu, jest nastę-pujący:

• określamy, które kafelki są widoczne w viewporcie – musimy wyznaczyć ko-

Rysunek 3. Układy współrzędnych. Niebieski – układ ekranu. Czerwony – układ viewportu. Zielony – układ świata i układ kafelkowy (mają te same osie i początek, ale różne jednostki; ich jednostki to odpowiednio piksele i „kratki”). Czarne strzałki pokazują pozycję viewportu w świecie i na ekranie

Rysunek 4. Macierz kafelków. Zamiast trzymać obrazki kafelków w oddzielnych plikach można je umieścić w jednym dużym obrazie

Listing 12. Fragment prostej klasy TileSet; C++/SDLclass TileSet

{

private:

SDL_Surface** m_tileImages; // array of pointers to tile-images

SDL_Rect m_tileRect; // (0, 0, tile width, tile height)

public:

void DrawTile( SDL_Surface* target, int tileIdx, int x, int y )

{

SDL_Rect dstRect;

dstRect.x = x;

dstRect.y = y;

SDL_BlitSurface( m_tileImages[tileIdx], &m_tileRect, target, &dstRect );

}

};

Page 49: SDJ_02_2010_PL

02/201048

Programowanie gier Mapy kafelków

www.sdjournal.org 49

lumny najbardziej z lewej i z prawej stro-ny viewportu, oraz rzędy u góry i u dołu viewportu.

• następnie rysujemy kafelki po kolei, rzędami lub kolumnami – dla każde-go kafelka obliczamy jego położenie we współrzędnych ekranu; transformacja (u,v) –> (screen x, screen y); a następnie rysujemy obrazek odpowiadający kafel-kowi

Spójrzmy na na Listing 10 i prześledźmy, co się tam dzieje.

Najpierw obliczamy skrajne wiersze i ko-lumny widoczne w viewporcie. Aby wyzna-czyć lewą kolumnę i górny wiersz, używa-my położenia viewportu w świecie i przeno-simy go do współrzędnych kaflowych (dzie-lenie przez rozmiar kafelka). Prawą kolum-nę i dolny wiersz wyznaczamy przez dodat-nie do lewej kolumny/górnego wiersza roz-miaru viewportu w kafelkach (zaokrąglając do góry).

Następnie należy ustawić przycinanie (clip-ping), żeby rysowane kafelki nie wyszły poza obszar viewportu. Jeśli rysujemy na całym ekranie, operację tę można pominąć.

Kolejnym krokiem jest samo rysowanie – w podwójnej pętli przechodzimy po kolej-nych kafelkach (poruszamy się w układzie ka-flowym, zmienne u,v). Dla każdego kafelka obliczamy jego współrzędne na ekranie, po-bieramy go z mapy i rysujemy go.

Rysowanie pojedynczego kafelka, „tilesety”Wszystko niby już jasne, wiemy jak ryso-wać mapę, ale w Listingu 10 zamiast ko-du, który rysuje konkretne kafelki, jest ma-giczna funkcja DrawTile. Specjalnie jej nie opisywałem, gdyż można ją zrealizować na różne sposoby. Przyjrzyjmy się, jak można to zrobić.

Do tej pory zakładaliśmy, że kafelek za-wiera w sobie jakąś informację na temat tego, jak ma wyglądać. W przykładach by-ło to pole imageId określające, jaki obrazek ma być użyty. Równie dobrze może to być wskaźnik lub referencja do konkretnego obrazka, a samo rysowanie w najprostszej wersji może być odwołaniem do funkcji w stylu drawImage/SDL_BlitSurface, której przekazujemy obrazek i współrzędne. W takim wypadku musimy wcześniej wczy-tać interesujące nas obrazki i umieścić re-ferencje do nich w kafelkach. Możemy rów-nież wczytać obrazki, umieścić je w tablicy, a w kafelkach trzymać indeksy, np. w polu imageId.

Bardzo często postępuje się właśnie w ta-ki sposób. Określamy pewien zbiór obraz-ków, które będziemy używać jako reprezen-tacje graficzne kafelków. Taki zbiór obrazków

określa się mianem tile set. Stwórzmy więc klasę TileSet.

Klasa TileSet musi być w stanie zwrócić lub narysować żądany obrazek. Obrazki bę-dziemy identyfikować przez ich numer w zbiorze (przez indeks, numerujemy od ze-ra). Przyjmijmy zatem, że obrazki kafelków będą przechowywane w klasie TileSet w tablicy. Listingi 11 i 12 przedstawiają frag-menty przykładów takiej klasy wraz z kon-kretnym kodem rysującym pojedynczego kafelka.

Czasami tileset składa się z kilkudziesię-ciu lub nawet kilkuset kafelków. Przechowy-wanie tak dużej ilości obrazków w oddziel-nych plikach może być niewygodne. Dlate-go, wykorzystując fakt, że wszystkie kafelki mają takie same rozmiary, można umieścić obrazki w jednym dużym obrazie, obok sie-bie, w rzędach, tworząc macierz kafelków. Rysunek 4 przedstawia przykład takiej ma-cierzy.

Następnie przy ładowaniu można kafel-ki wyciąć z macierzy i umieścić w oddziel-nych obrazkach, a te umieścić w tablicy (jak na Listingach 11 i 12). Można też trzymać w pamięci cały duży obrazek z macierzą kafel-ków, a rysując pojedynczego kafelka, użyć od-powiedniej funkcji rysującej tylko fragment obrazu.

Przyspieszenie – pozbycie się mnożeńW tym miejscu chciałbym jeszcze wrócić do algorytmu rysowania kafelków. Kod z Listin-gu 10 rysuje kolejne wiersze mapy, zaczynając od góry viewportu, schodząc w dół. W każ-dym wierszu kafelki rysowane są od lewej do prawej. Dla każdego kafelka obliczane są jego położenie na ekranie i indeks w mapie.

Można zauważyć, że w obrębie wiersza po-zycje kolejnych kafelków zwiększają się za-wsze o szerokość kafelka. Można więc, za-miast obliczać współrzędną x dla każde-go kafelka, obliczyć ją dla pierwszego kafel-ka w rzędzie, a następnie zwiększać o szero-kość kafelka.

Analogicznie można postąpić z wiersza-mi. Współrzędna y kolejnych wierszy zwięk-sza się zawsze o wysokość kafelka. Można więc obliczyć współrzędną y tylko pierwsze-go wiersza, a dla kolejnych zwiększać ją o wy-sokość kafelka.

Składając to razem, okazuje się, że wystar-czy obliczyć położenie tylko jednego kafel-ka – położonego najbardziej u góry z lewej w viewporcie – a następnie odpowiednio zwiększać współrzędne x i y rysowania.

Podobnie można postąpić z indeksem ka-felka w mapie. W danym wierszu, indek-sy kolejnych kafelków zwiększają się o 1, a z

Rysunek 5. Rysowanie mapy z buforem – na ekran rysujemy fragment bufora zawarty wewnątrz viewportu. Pierwszy rysunek od lewej przedstawia stan początkowy – mapa narysowana na buforze, viewport umieszczony pośrodku bufora. Jeśli viewport porusza się po mapie, ale mieści się w buforze (rysunek drugi od lewej), nie trzeba przerysować bufora. Natomiast jeśli viewport wyjdzie poza bufor (rysunek trzeci), należy wycentrować viewport w buforze i przerysować bufor kafelkami (rysunek czwarty)

Rysunek 6. Dorysowywanie kafelków na cyklicznym buforze. Kratkowany prostokąt to bufor. Na początku rysujemy kafelki, aby wypełnić obszar viewportu. W drugim kroku viewport przesunął się w prawo i należało dorysować dwie kolumny kafelków (zaznaczone na żółto). W trzecim kroku viewport przesunął się jeszcze bardziej w prawo – należy dorysować kolumnę z prawej strony. Jednak bufor skończył się, więc przenosimy dorysowywaną kolumnę na drugą stronę (na żółto zaznaczono miejsce, gdzie ostatecznie dorysowano kolumnę), a viewport zostaje „zawinięty”

Page 50: SDJ_02_2010_PL

02/201050

Programowanie gier Mapy kafelków

www.sdjournal.org 51

każdym kolejnym wierszem indeks wraca do skrajnie lewej kolumny widocznej w viewpor-cie, a następnie zwiększa się o szerokość całej mapy w kafelkach.

Używając opisanych operacji, pozbywa-my się kilku mnożeń dla każdego kafelka. Na

niektórych platformach (np. powolne telefo-ny komórkowe) może to owocować pewnym przyspieszeniem, zwłaszcza gdy rysujemy du-żo kafelków.

Implementację tej optymalizacji moż-na znaleźć w załączonym do artykułu ko-

dzie źródłowym (funkcja drawMapOpti-mized).

Rysowanie z buforemW prostym rysowaniu mapy kafelków wy-wołujemy funkcję rysującą pojedynczy kafe-lek (a co za tym idzie rysującą obrazek) tyle razy, ile jest kafelków widocznych na ekranie. Ilość ta zależy od wielkości kafelka i ekranu i czasem może być całkiem spora (mały kafe-lek, duży ekran).

Różne platformy bardzo różnie realizują ry-sowanie pojedynczych obrazków. Dla niektó-rych obojętne jest, czy rysujemy dużo małych obrazków, czy mało dużych, ale dla niektórych ma to istotne znaczenie. Przykładem mogą być Javowe telefony komórkowe – w wielu z nich narzut wywołania funkcji rysujących obrazek jest znaczny i trzeba się z nim liczyć, i warto rysować mniej, ale większych obrazków. Z te-go powodu powstała koncepcja rysowania ma-py kafelków z buforem.

Idea jest następująca. Zamiast na ekran ry-sujemy mapę na buforze. Bufor jest większy od rozmiaru ekranu. Na początku umieszcza się viewport w środku bufora i rysuje kafel-ki na całej powierzchni bufora. Na ekran ry-sujemy tylko część bufora zawartą wewnątrz viewportu.

Jeśli vieport nie przemieszcza się po mapie, to nie ma potrzeby przerysowywania kafel-ków w buforze, wystarczy tylko rysować bu-for na ekran. Dzięki temu w każdej klatce gry rysujemy tylko jeden duży obraz.

Jeśli viewport porusza się po mapie, to prze-suwamy go także w buforze i sprawdzamy, czy ciągle się w nim mieści. Jeśli tak, to nie musimy przerysowywać kafelków i rysujemy bufor na ekran. Pamiętajmy, że rysujemy tyl-ko część bufora zawartą wewnątrz viewpor-tu – dzięki przemieszczaniu viewportu we-wnątrz bufora na ekranie mamy wrażenie po-ruszania się po mapie.

Przerysowanie kafelków odbywa się dopie-ro w momencie, gdy viewport wyszedł poza obszar bufora. Należy wtedy ponownie na-rysować kafelki na całej powierzchni bufora i wycentrować w nim viewport.

Rysunek 5 przedstawia sposób działania rysowania z buforem.

Nie jest to rozwiązanie idealne – ciągle musimy co jakiś czas przerysować wszyst-kie widoczne kafelki, przez co co pewien czas mogą występować zwolnienia. Zda-rza się to, gdy viewport dochodzi do krawę-dzi bufora. Jednak pomiędzy przerysowa-niami rysowanie na ogół odbywa się szyb-ciej. Warto więc technikę tę przetestować w swojej aplikacji.

Rysowanie – cykliczny buforRysowanie do bufora cyklicznego to modyfi-kacja i rozwinięcie techniki rysowania do bu-

Listing 13. Rysowanie zawiniętego viewportu

// get viewport bounds (left, right, top, bottom) in pixels

int left = viewport.bufferX;

int right = viewport.bufferX + viewport.width;

if( right > bufferWidth ) // wrap

{

right -= bufferWidth;

}

int top = viewport.bufferY;

int bottom = viewport.bufferY + viewport.height;

if( bottom > bufferHeight ) { // wrap

{

bottom -= bufferHeight;

}

// determine case

if( left < right && top < bottom ) {

// case #1: whole viewport fits inside buffer

drawBufferRegionToScreen( ... );

}

else if( left > right && top < bottom ) {

// case #2: y in bounds, split x

// rectangle 1

drawBufferRegionToScreen( ... );

// rectangle 2

drawBufferRegionToScreen( ... );

}

else if( left < right && top > bottom ) {

// case #3: x in bounds, split y

// rectangle 1

drawBufferRegionToScreen( ... );

// rectangle 2

drawBufferRegionToScreen( ... );

}

else {

// case #4: x split, y split

// rectangle 1

drawBufferRegionToScreen( ... );

// rectangle 2

drawBufferRegionToScreen( ... );

// rectangle 3

drawBufferRegionToScreen( ... );

// rectangle 4

drawBufferRegionToScreen( ... );

}

Page 51: SDJ_02_2010_PL

02/201050

Programowanie gier Mapy kafelków

www.sdjournal.org 51

fora. Metoda ta rozwiązuje problem przery-sowywania wszystkich widocznych kafelków co pewien czas – dorysowujemy tylko kafel-ki, które znalazły się w viewporcie, a nie było ich tam wcześniej.

Na początku rysujemy kafelki tak, żeby zapełnić obszar viewportu. Jeśli viewport przesuwa się wewnątrz bufora, dorysowu-jemy tylko brakujące kafelki (np. jeśli bufor porusza się w prawo, będziemy dorysowy-wać kolejne kolumny z prawej strony). Te-raz bardzo ważna informacja – dokładnie tak samo postępujemy (tzn. rysujemy bra-kujące kafelki), gdy bufor przekroczy kra-wędź ekranu (w tym momencie w meto-dzie z buforem trzeba było przerysować ca-ły bufor). Jednakże pojawia się pytanie – gdzie narysować brakujące kafelki, jeśli viewport wychodzi poza bufor? Odpowiedź znajduje się na Rysunku 6 – dorysowujemy z drugiej strony bufora, a viewport zawija-my. Dzięki zawijaniu viewportu i rysowa-niu kafelków cyklicznie na buforze nie mu-simy dokonywać całkowitego przerysowa-nia bufora (chyba że viewport w całości wyj-dzie poza bufor).

Zawijany viewportCo to w praktyce znaczy, że viewport jest za-winięty? Jak narysować na ekranie obszar bufora zawarty w viewporcie, jeśli nie jest on prostokątem, tylko dwoma prostokąta-mi przyległymi do prawej i lewej krawędzi ekranu? A jeśli bufor zamiast prawo-lewo będzie poruszał się góra-dół? Wtedy view-port po zawinięciu stanie się dwoma prosto-kątami przyległymi do górnej i dolnej krawę-dzi bufora. A co, jeśli viewport będzie poru-szał się jednocześnie w prawo-lewo i w gó-rę-dół? Wtedy po zawinięciu stanie się czte-rema prostokątami.

Jak to wszystko narysować na ekranie? Każdy prostokąt składowy viewportu rysu-jemy oddzielnie, ale wszystkie prostokąty umieszczamy na ekranie sąsiadująco wzglę-dem siebie, tak aby składały się w jednoli-tą całość.

Rysunek 7 przedstawia ideę zawijania view-portu i rysowania go na ekranie. Przeanalizuj-my go dokładnie. Mamy cztery przypadki. Przypadek (1) jest najprostszy – viewport w całości mieści się w buforze; wystarczy nary-sować prostokąt na ekranie. W przypadku (2) musimy narysować dwa prostokąty – prosto-kąt 1 rysujemy na ekranie we współrzędnych viewportu, a prostokąt 2 sąsiadująco z prawej. Analogicznie postępujemy w przypadku (3), z tą różnicą, że prostokąt 2 umieszczamy są-siadująco od dołu. W przypadku (4) musimy narysować aż cztery prostokąty; prostokąt 1 umieszczamy we współrzędnych viewportu, a pozostałe trzy sąsiadują z prawej, z dołu i z prawej-dołu. Listing 13 pokazuje zarys przy-

Rysunek 7. Rysowanie zawiniętego viewportu na ekranie. U góry znajdują się cztery możliwe przypadki ułożenia viewportu w buforze. Na dole przedstawiony jest, dla każdego przypadku, sposób rysowania viewportu na ekranie. Niebieska kropka to współrzędne viewportu na ekranie

Listing 14. Sprowadzanie viewportu do obszaru bufora

void wrapToBuffer()

{

// bufferX/Y is current position of viewport within buffer

int newX = m_bufferX;

int newY = m_bufferY;

// keep X in bounds:

if( m_bufferX > m_bufferWidth ) {

newX = m_bufferX % m_bufferWidth;

}

else if( m_bufferX < 0 ) {

// keep in mind that modulo of negative value gives negative result

// so actually newX is calculated by substracting from bufferWidth

newX = m_bufferWidth + ( m_bufferX % m_bufferWidth );

}

// keep Y in bounds:

if( m_bufferY > m_bufferHeight ) {

newY = m_bufferY % m_bufferHeight;

}

else if( m_bufferY < 0 ) {

newY = m_bufferHeight + ( m_bufferY % m_bufferHeight );

}

// set new position within buffer:

if( newX != m_bufferX || newY != m_bufferY ) {

setPositionInBuffer( newX, newY );

}

}

Listing 15. Rozszerzenie klasy Vieport na potrzeby implementacji rysowania mapy kafelków z buforem cyklicznym

class CyclicViewport extends Viewport

{

int previousWorldLeft;

int previousWorldRight;

int previousWorldTop;

int previousWorldBottom;

}

Page 52: SDJ_02_2010_PL

02/201052

Programowanie gier

kładowego kodu realizującego rysowanie za-winiętego viewportu. Konkretną implemen-tację można znaleźć w dołączonym do arty-kułu kodzie źródłowym.

Istnieje jeszcze jeden szczególny przypa-dek: viewport może w całości znaleźć się poza buforem. Musimy temu zapobiegać – jeśli tylko viewport w całości wyskoczy po-za bufor, musimy sprawić, że od razu poja-wi się z jego drugiej strony – w ten sposób zawsze będzie znajdował się wewnątrz bu-

fora. W praktyce osiąga się to przez umiesz-czenie viewportu w buforze we współrzęd-nych równych reszcie z dzielenia (modulo) położenia viewportu w świecie przez jego rozmiar. Listing 14 pokazuje to działanie w praktyce.

Dorysowywanie brakujących kafelkówJeśli wiemy już, jak narysować na ekranie zawijany viewport (lub jak kto woli cyklicz-

ny bufor), wystarczy opracować sposób do-rysowywania kafelków, które pojawiły się w viewporcie.

Wydaje się to oczywiste – dorysowujemy kolumny lub wiersze – z prawej, lewej, góry lub dołu. Jednak jeśli viewport przesunął się zarówno w poziomie, jak i w pionie, sytuacja komplikuje się.

Rysunek 8 przedstawia możliwe scena-riusze. Musimy wziąć pod uwagę wszystkie przypadki. Żeby to zrobić, sprawdzimy kolej-no wszystkie kafelki widoczne w aktualnym położeniu viewportu – jeśli kafelek jest obec-nie widoczny, a nie był widoczny w poprzed-nim położeniu viewportu, oznacza to, że jest o nowy i należy go dorysować do bufora.

Zatem pierwszym krokiem jest określe-nie, które kafelki pojawiły się, a które były już widoczne. W tym celu rozszerzymy kla-sę Viewport, dodając informacje o skrajnych wierszach i kolumnach ostatnio widocznych w viewporcie (tzn. podczas poprzedniego rysowania). Na Listingu 15 pokazane jest, jak można to zrobić.

Następnie musimy przejść w pętli po wszystkich kafelkach widocznych w nowym viewporcie (iterujemy po współrzędnych u,v) i dla każdego sprawdzić, czy znajdował się w obszarze poprzednio rysowanych kafelków – sprawdzamy, czy (u,v) kafelki znajdują się wewnątrz poprzednich granic viewportu. Je-śli nie, tzn. kafelek jest nowy, musimy go na-rysować.

Powstaje jednak pytanie – w jakim miejscu go narysować? Rysując kafelka na buforze cy-klicznym, musimy jego położenie w świecie (world u,v) sprowadzić do położenia w bu-forze (buffer u,v) i dopiero wtedy obliczyć współrzędne (buffer x,y) rysowania. Pomocą służy tu operacja reszty z dzielenia (modulo). Dla każdego rysowanego kafelka wykonu-jemy obliczenia przedstawione na Listingu 16, przy czym (u,v) to współrzędne kaflowe w świecie, (bufferU, bufferV) to współrzęd-ne kaflowe w buforze, a (x,y) to współrzędne używane do rysowania na buforze.

Listing 17 przedstawia sposób dorysowy-wania kafelków. Przedstawiony kod zawiera operacje modulo dla każdego kafelka, moż-na je jednak zoptymalizować, aby wykony-wać to działanie tylko raz. Optymalizacja ta jest użyta w dołączonym do artykułu kodzie źródłowym.

Rozmiar buforaWarto zauważyć, że rozmiar bufora musi być co najmniej o jeden kafelek większy niż rozmiar viewportu. Jeśli nie spełnimy tego warunku, viewport zawinie się na samego siebie. W efek-cie, po narysowaniu, część wspólna nałożonych na siebie części viewportu znajdzie się na ekra-nie dwa razy. Do tego dorysowywane kafelki narysują się na kafelkach obecnie widocznych

Rysunek 8. Możliwe przypadki dorysowywania nowych kafelków. Kartkowany prostokąt to wycinek świata. Prostokąt zielony to kafelki widoczne w poprzednim położeniu viewportu. Prostokąt niebieski to kafelki obecnie widoczne w viewporcie. Na różowo zaznaczono kafelki, które zostaną dorysowane

Listing 16. Obliczanie współrzędnych kafelka na buforze cyklicznym

int bufferU = u % bufferWidthInTiles;

int bufferV = v % bufferHeightInTiles;

int x = buffU * tileWidth;

int y = buffV * tileHeight;

Listing 17. Dorysowywanie kafelków do bufora cyklicznego

int bufferWidthInTiles = m_bufferWidth / tileWidth;

int bufferHeightInTiles = m_bufferHeight / tileHeight;

// paint new tiles, but only ones that appeared in new viewport:

for( int v = currTop; v <= currBottom; ++v )

{

for( int u = currLeft; u <= currRight; ++u )

{

int buffU = u % bufferWidthInTiles;

int buffV = v % bufferHeightInTiles;

// calculate (buffer x,y)

int x = buffU * tileWidth;

int y = buffV * tileHeight;

if (u >= 0 && v >= 0 && u < mapWidth && v < mapHeight)

{

// get tile and draw it:

int tileIdx = v * mapWidth + u; // (!!) using (world u,v) here

int imageId = map.m_tiles[tileIdx].m_imageIdx;

if (imageId >= 0)

{

tileSet.DrawTile(g, imageId, x, y);

}

}

}

}

Page 53: SDJ_02_2010_PL

www.sdjournal.org 53

w viewporcie, zakrywając je. Ogólnie rzecz uj-mując – mogą dziać się dziwne rzeczy.

Ponadto rozmiar bufora musi być wielo-krotnością rozmiaru kafelka. Jest to bardzo ważne – bez spełnienia tego warunku ka-felki dorysowywane w ostatnim wierszu/kolumnie będą przycięte.

Podsumowanie rysowania „z buforem cyklicznym”Dla czytelności przedstawię teraz wszystkie kroki rysowania „z buforem” w formie listy:

• przy inicjalizacji umieszczamy viewport w początku bufora (górny-lewy róg) i wypełniamy obszar viewportu kafelka-mi;

• jeśli viewport w całości znalazł się poza buforem, sprowadzamy go do wnętrza bufora przez zawinięcie pozycji (pozycja viewportu w świecie modulo rozmiar bufora);

• iterujemy po wszystkich kafelkach aktu-alnie widocznych w viewporcie;

• jeśli kafelek nie był widoczny w po-przednim viewporcie, rysujemy go na buforze;

• aby narysować kafelka, obliczamy jego współrzędne (u,v) w buforze (pozycja kafelka w mapie modulo rozmiar bu-fora w kafelkach), a następnie oblicza-my współrzędne rysowania (x,y) i rysu-jemy;

• gdy wszystkie nowe kafelki zostaną nary-sowane, na buforze rysujemy obszar bu-

fora widoczny w viewporcie na ekran (jeden z czterech przypadków).

Już wiemy, jak rysować z buforem cyklicz-nym. A jak ta metoda ma się do wcześniej omówionych metod rysowania mapy?

W metodzie prostej rysujemy wszystkie kafelki co klatkę (chcemy tego uniknąć, wolimy rysować jeden duży obraz). W po-dejściu z buforem nie przerysowujemy ka-felków wcale (o to chodziło, rysujemy jeden duży obraz co klatka), robimy to dopiero w momencie, gdy trzeba przerysować cały bufor. Jednak może wystąpić wtedy chwi-lowy zauważalny spadek płynności (musi-my przerysować cały bufor kafelkami + na-rysować bufor na ekran).

W rysowaniu z buforem cyklicznym jeste-śmy pomiędzy dwoma poprzednimi meto-dami – w przeciętnym scenariuszu musi-my dorysowywać pewną ilość kafelek co klatkę (tylko nowe kafelki), ale za to nie mamy sytuacji, gdy trzeba przerysować wszystko w jednym momencie (znikają spadki płynności).

Rozważmy jeszcze skrajne przypadki. Jeśli viewport nie porusza się, nie musimy prze-rysowywać kafelków wcale. Jeśli viewport co klatka zmienia diametralnie swoje poło-żenie, może zaistnieć potrzeba przerysowa-nia całego viewportu. Jednak statystycznie, w zwykłej grze, rzadko kiedy w viewporcie pojawia się w kolejnej klatce więcej niż je-den wiersz/kolumna nowych kafelków. W tym właśnie tkwi zaleta tej metody, w prze-

ciętnym przypadku daje dobre wyniki, mi-mo iż w skrajnych sytuacjach trzeba przery-sować cały bufor i do tego narysować go na ekranie.

Na dzisiaj koniecArtykuł przedstawił podstawy zagadnienia map kafelków. Droga do stworzenia swojej wymarzonej gry na pewno się skróciła. Jed-nakże był to zaledwie wstęp – w tej chwili możemy stworzyć mapę i ją narysować. Żeby stworzyć grę, trzeba zapoznać się z kolejnymi zagadnieniami, takimi jak rysowanie obiek-tów na mapie (np. postać gracza), wykrywa-nie kolizji z kafelkami, tworzenie map w edy-torach, ładowanie i zapisywanie ich z/do pli-ków etc.

Mam nadzieję, że lektura artykułu była do-brym punktem wyjścia do dalszego poszerza-nia swojej wiedzy.

JACEK ZAGRODZKIPracuje na stanowisku Kierownika Działu Tech-nologicznego w firmie Gamelion, wchodzącej w skład Grupy BLStream. Jacek specjalizuje się w programowaniu gier w oparciu o technolo-gię JME. Grupa BLStream powstała, by efektyw-niej wykorzystywać potencjał dwóch szybko roz-wijających się producentów oprogramowania – BLStream i Gamelion. Firmy wchodzące w skład grupy specjalizują się w wytwarzaniu oprogra-mowania dla klientów korporacyjnych, w roz-wiązaniach mobilnych oraz produkcji i testowa-niu gier. Kontakt z autorem: [email protected]

Mapy kafelków

R E K L A M A

Page 54: SDJ_02_2010_PL

02/201054

Programowanie gierEfektywny blitter

www.sdjournal.org 55

Jak to? – ktoś może się oburzyć po prze-czytaniu takiego wstępu, przecież od cza-sów Windowsów 3.0 mamy akcelerację

operacji graficznych!. Już Amiga miała sprzęto-wy blitter! – krzyknie ktoś inny. Jest to, oczy-wiście, prawda, niemniej trzeba wziąć pod uwagę fakt, że nie wszystkie dzisiejsze plat-formy posiadają takie udogodnienia. Takie na przykład telefony komórkowe. Widoczny jest, co prawda, mniej lub bardziej raczkujący trend wprowadzania pełnoprawnych akcele-ratorów grafiki trójwymiarowej, które można wykorzystać również do przyspieszania grafi-ki 2D, ale po pierwsze, sprzęt ten jest dostęp-ny tylko w modelach z najwyższej półki, a po drugie, korzystanie z niego wiąże się z pew-nymi ograniczeniami. Hardware'owych roz-wiązań przyspieszających tylko i wyłącznie grafikę dwuwymiarową na urządzeniach przenośnych nie stwierdzono. No, może po-za obecnymi na Nintendo DS wynalazkami do obsługi duszków rodem z Atarynki.

Problem może być dla nas mało widocz-ny, w sytuacji gdy używamy w naszej grze zewnętrznych bilbliotek. Taki na przykład SDL posiada implementację funkcji blitują-cych jeden obraz na drugi. Sęk w tym, że taka

funkcja prawie na pewno pozbawiona będzie większości możliwych do zastosowania opty-malizacji. Optymalizacji, które wiążą się z po-gorszeniem jakości wynikowej grafiki, a więc czegoś, co jest niedopuszczalne w uogólnio-nym algorytmie blittera. Ale nie uprzedzaj-my faktów.

Blitter?Blitter jest to specjalizowany sprzętowy układ, mający za zadanie przenosić duże blo-ki danych z jednego obszaru pamięci do dru-giego. Jego nazwa bierze się od skrótu BLIT – BLock Image Transfer, co można tłuma-czyć jako blokowy transfer obrazu. Jak zo-stało wcześniej wyjaśnione, nas interesować będzie programowa implementacja algoryt-mów wykonywanych przez tenże układ.

Funkcja realizująca zadanie blittera zawsze musi przyjmować obraz źródłowy i docelowy. W dobrym tonie byłoby również umożliwienie wskazania miejsca, w którym na obrazie doce-lowym ma się znaleźć obraz źródłowy, a zatem dodatkowo należy przekazać parę współrzęd-nych, które będą specyfikowały punkt zaczepie-nia. Użyteczna jest również możliwość wyspe-cyfikowania obszaru obrazu źródłowego, który chcemy narysować. Dzięki temu będziemy mo-gli przechowywać, przykładowo, 10 klatek ani-macji na jednym obrazie, a podczas blitowania podamy, która klatka (tj. obszar obrazu, który ją zawiera) nas interesuje.

Rysunek 1 przedstawia niektóre możliwe kombinacje wzajemnych położeń obrazów

źródłowego i docelowego. Pierwszym zada-niem funkcji blitującej jest wyznaczenie prze-cięcia interesujących nas obszarów obu ob-razów, będzie to prostokąt, na którym prze-prowadzane będą zmiany. Czasami (przy-padek numer 6) może się okazać, że część wspólna nie występuje – wtedy nie pozostaje nam nic innego jak wyjść z funkcji. W pozo-stałych wypadkach musimy wyznaczyć sze-reg zmiennych, które umożliwią nam potem szybkie przetwarzanie danych. Będą to na przykład szerokość i wysokość obszaru prze-cięcia – używane jako liczniki wewnętrznej i zewnętrznej pętli rysowania, wskaźniki na le-we górne (początkowe) piksele części wspól-nej obrazów, mapowane na ich współrzęd-ne. Potrzebować będziemy również informa-cji o tym, ile bajtów należy przeskoczyć, aby z ostatniego piksela w linii przenieść się do pierwszego piksela w następnej linii, w obu obrazach, ale ta wartość obliczona zostanie później, zależy bowiem od szerokości piksela. Wszystkie te zmienne zostały przedstawione na Rysunku 2.

Moje piksele mają szerokość równą jeden!Na ekranie – prawdopodobnie tak, ale nas, jako programistów, bardziej interesuje szero-kość bitowa. Czy też może – w szerszym zna-czeniu – format piksela. Czym się różni jed-no od drugiego? Mówiąc o szerokości bitowej , myślimy o kolorze ośmio-, szesnasto-, dwu-dziestocztero- czy trzydziestodwu-bitowym. Jednak powiedzenie, że jeden piksel zajmu-je 32 bity, jest dla nas niewystarczające. Ja-kie jest ułożenie kanałów w tych 32 bitach? RGBA? ARGB? ABGR? Każda z kombina-cji jest poprawna, co więcej, na każdą z nich można się natknąć przy różnych okazjach. Dlatego dopiero powiedzenie, że format pik-sela to, od najmłodszych bitów, 8 bitów kana-łu alfa, 8 bitów kanału niebieskiego, osiem bi-

Efektywny blitterJak to jest, że jedne gry posiadają świetnie wyglądającą i płynną grafikę, podczas gdy inne, mimo znacznie uboższego wyglądu, ledwo sobie radzą z przerysowywaniem pola gry? Dlaczego u konkurencji na ekranie może poruszać się całe mrowie przeciwników, podczas gdy u nas już przy pięciu jest problem? Różnica tkwi w kunszcie programisty odpowiedzialnego za niskopoziomową obsługę grafiki.

Dowiesz się:• Jak znacznie przyspieszyć rysowanie grafiki;• Jak użytecznie posługiwać się kodem asem-

blerowym;• Że należy wierzyć pomiarom, a nie przeczu-

ciom.

Powinieneś wiedzieć:• Jak się robi gry.

Poziom trudności

Metody optymalizacji

Page 55: SDJ_02_2010_PL

02/201054

Programowanie gierEfektywny blitter

www.sdjournal.org 55

tów kanału zielonego i osiem bitów kanału czerwonego, jest dla nas satysfakcjonujące.

Mogłoby się wydawać, że im więcej bitów mamy na każdy kanał, tym lepiej będzie wy-glądała finalna grafika, prawda? W teorii tak, w praktyce nie bardzo. Przykładowo, Nokia szczyci się, że niektóre jej telefony mają ekra-ny zdolne wyświetlić 16 milionów kolorów (kolor 24-bitowy). Dobry dowcip. Na 24 bi-tach (a raczej na 32, żeby dane były wyrów-nane do szerokości słowa) kolory, owszem, są trzymane. Ale podczas wyświetlania z każdej składowej obcinane są dwa najmłodsze bity, przez co tak naprawdę otrzymujemy kolor 18-bitowy (po 6 bitów na składową). Należy sobie zadać pytanie – czy dla marnych dwóch bitów opłaca się implementować wszystkie operacje graficzne tak, by działały z kolorem 32-bito-wym zamiast użyć po prostu koloru 16-bito-wego? Jakby nie patrzeć, oznacza to dwukrot-ne zwiększenie liczby danych przesyłanych z/do pamięci, czego zignorować nie możemy. Udało nam się zatem dokonać pierwszej opty-malizacji blittera – zakładamy, że powierzch-nia ekranowa (a więc ta, na której będziemy w większości przypadków rysowali) zawsze bę-dzie miała głębokość szesnastu bitów.

Bardzo ważną, a zarazem niezwykle ba-nalną optymalizacją działania blittera jest zapewnienie odpowiedniego formatu pikse-la obrazów źródłowych. Przykładowo, jeżeli chcemy rysować na obrazie, który ma format 565 (po 5 bitów na składowe czerwoną i nie-bieską, 6 na składową zieloną), to należy spra-wić, oczywiście w miarę możliwości, by ob-raz źródłowy również miał format 565. Dzię-

ki temu blitowanie można zaimplementować za pomocą prostej pętli kopiującej. Natomiast gdyby obraz źródłowy miał format 888, dla każdego piksela należałoby przeprowadzać konwersję do formatu obrazu docelowego. Po wykonaniu testów okazało się, że blitowanie 565 –> 565 trwa 1,23 milisekund, a blito-wanie 888 –> 565 trwa 5 ms. Różnica chyba całkiem spora. Tutaj coś, na co warto zwrócić uwagę: graficy, jak to artyści, zbyt dużej uwa-gi do zagadnień technicznych nie przykłada-ją. Lepiej im patrzeć na ręce, żeby się potem nie okazało, że całoekranowa bitmapa tła po-siada kanał alfa, przez co przelatuje niezmie-niona przez nasz sprytny automatyczny kon-werter zamieniający wszystkie ładowane ob-razy mające format 888 (bez kanału alfa) na format 565. W efekcie, zamiast prostego ko-piowania pamięci musimy przeprowadzić operację blendingu.

Optymalizacja kopiowaniaImplementacją funkcji blitujących jako ta-kich zajmować się tu nie będziemy. Dość po-wiedzieć, że są to pętle podobne do tej z Li-stingu 1. Na podstawie tej pętli, realizującej kopiowanie 565 –> 565, można stworzyć wszystkie inne, dodając tylko odpowiednie konwersje formatów piksela.

Czy taką, optymalnie napisaną wydawało-by się pętlę można jeszcze przyspieszyć? Oka-zuje się, że zastąpienie wewnętrznej pętli sys-temową funkcją memcpy przyspiesza wykona-nie funkcji prawie trzykrotnie. Jest to rezultat zdecydowanie nieintuicyjny, w końcu zastępu-jemy banalne przepisywanie w pętli wielokrot-

nym wywoływaniem funkcji, co nie może być szybkie, w końcu muszą zostać przekazane pa-rametry (przez stos), funkcja zwraca też pew-ną wartość, która nie jest nam potrzebna, ale zwrócona zostać musi... A jednak. Tylko i wy-łącznie zbadanie rzeczywistej szybkości wy-konywania kodu na urządzeniu jest w stanie stwierdzić, czy zastosowana przez nas opty-malizacja nie jest tylko optymalizacją. Przykła-dowo, podczas prac nad przyspieszaniem pew-nego kawałka kodu udało się zastąpić opera-cję mnożenia prostymi bitowymi operacjami logicznymi. Mogłoby się wydawać, że prze-sunięcia bitowe są szybsze od powolnego ob-liczania iloczynu, że wyeliminowanie mno-żenia z pewnością przyspieszy działania algo-rytmu. Po przeprowadzeniu testów wydajno-ściowych okazało się, że wersja z mnożeniem jest szybsza. Morał jest prosty: nigdy nie należy ufać swojemu przeczuciu, zawsze należy opie-rać się na twardych pomiarach.

Załóżmy jednak, niech będzie to ekspery-ment myślowy, że funkcja memcpy nie istnieje, zmuszeni jesteśmy używać własnoręcznie na-pisanej pętli kopiującej. Czy w takiej sytuacji

Rysunek 1. Niektóre z możliwych wzajemnych konfiguracji obrazów podczas blitowania

�� �� ��

�� �� ��

��������������

��������������

Rysunek 2. Zmienne używane podczas blitowania

���������

��������

�������

���������

�������������������

�������������������

��������������������

�����������������

����������������������������

�����������������������������

����������������������������

��������������������������

����������������������

Listing 1. Pętla blitująca

void Blit565To565( … )

{

do

{

int w = width;

do

{

*dst++ = *src++;

}

while( --w );

dst += dstSkip;

src += srcSkip;

}

while( --height );

}

Listing 2. Wewnętrzna pętla programu z Listingu 1

.L5:

ldrh r5, [r0], #2

subs ip, ip, #1

strh r5, [r1], #2 @ movhi

bne .L5

Listing 3. Odwinięcie pętli

UInt32 w = width / 4;

do

{

*dst++ = *src++;

*dst++ = *src++;

*dst++ = *src++;

*dst++ = *src++;

}

while( --w );

Page 56: SDJ_02_2010_PL

02/201056

Programowanie gierEfektywny blitter

www.sdjournal.org 57

można w jakiś sposób przyspieszyć program z Listingu 1? Spójrzmy, jak wygląda wewnętrz-na pętla po skompilowaniu (Listing 2). Mne-moniki ldrh i strh odpowiednio ładują i zapisują 16-bitową wartość z/do pamięci, zwiększając jednocześnie wskaźnik o 2 bajty. Instrukcja subs zmniejsza licznik pętli, a bne wykonuje skok (następną iterację pętli), jeżeli wynik odejmowania jest różny od zera. Widać tu jasno, że tylko 50% wykonywanych przez procesor instrukcji odnosi się do kopiowania pamięci, a więc tego, co nas najbardziej inte-resuje. Oczywiście, ocenić relatywnego czasu wykonywania poszczególnych instrukcji nie sposób, przynajmniej bez posiadania symu-latora procesora na poziomie bramek logicz-nych. Mamy tu wszak instrukcje zapisu i od-czytu z pamięci (duże opóźnienie!), co praw-da dostęp jest sekwencyjny, więc cache znacz-nie przyspiesza działanie, ale konkretnie, o ile procent? Modyfikacje licznika pętli są na po-ziomie rejestrów procesora, ale operacja sko-ku czeka, aż ALU obliczy wynik odejmowa-nia... Na szczęście kompilator sprawia się nad-zwyczaj dobrze, przeplatając obsługę licznika pętli z działaniami operującymi na pamięci, co minimalizuje czas oczekiwania na zakoń-czenie operacji zależnej.

Dygresje na bok, chcemy zmaksymalizo-wać liczbę operacji na pamięci, minimalizu-jąc jednocześnie liczbę operacji potrzebnych do obsługi pętli. Listing 3 przedstawia pętlę odwiniętą 4 razy. Nie da się tu oczywiście nie zauważyć poważnego problemu: pętla dzia-ła tylko dla szerokości będących wielokrot-nościami czwórki. Aby kod działał popraw-nie, należy wpierw obsłużyć ułamkową część szerokości. Można też zintegrować odwinię-cie pętli z obsługą części ułamkowej w całość. Jak? Za pomocą mechanizmu Duffa (Duff's device). Przykład wykorzystania tej techniki przedstawiony został na Listingu 4. To dzi-

waczne przeplecenie instrukcji switch z pę-tlą do .. while jest jak najbardziej legalną kon-strukcją w języku C, co więcej, realizuje do-kładnie taką funkcjonalność, jaką właśnie te-raz potrzebujemy. Spójrzmy na Listing 5, któ-ry przedstawia zapis asemblerowy powyższej pętli. 80% instrukcji dotyczy odczytu/zapisu pamięci, a tylko 20% zajmuje się sprawdza-niem warunku końca pętli! Jednocześnie wi-dzimy, że w środku pętli obecne są punkty wejścia dla różnych wartości części ułamko-wej szerokości.

Nie należy się zbytnio przejmować tym, że w przykładzie pętla została odwinięta cztery razy. W rzeczywistości może zostać wykorzy-stana dowolna liczba, chociaż dobrze byłoby się wpierw zastanowić, czy nie należałoby użyć którejś z potęg dwójki. Wszak żeby ob-liczyć wynik i resztę z dzielenia przez takie liczby, wystarczy wykonać proste operacje bi-towe. Koniecznie należy za to przeprowadzić testy szybkości dla każdej sprawdzanej licz-by odwinięć. Przykładowo, wykorzystanie mechanizmu Duffa w kodzie zmieniającym format dźwięku, przy ośmiokrotnym odwi-nięciu pętli, przyspieszyło wykonanie pętli z 4,60 ms do 4,16 ms. Szesnastokrotne odwi-nięcie pętli zmieniającej głośność dźwięku spowodowało spadek czasu potrzebnego na wykonanie pętli z 13,25 ms do 10,26 ms.

BlendingCzyli innymi słowy mieszanie. Złożenie czę-ści jednego koloru z proporcjonalnie odwrot-ną częścią innego koloru. Występuje wszę-dzie tam, gdzie wykorzystywana jest wartość alfa. Należy tu zwrócić uwagę, że podczas bli-towania wartość alfa piksela pochodzi tak na-prawdę z dwóch źródeł. Pierwszym jest, o ile występuje, kanał alfa obrazu źródłowego. Drugim źródłem jest globalna wartość alfa, opcjonalnie podawana do funkcji blitującej. Dopiero połączenie globalnej wartości z war-tością z kanału alfa, inną dla każdego piksela, daje nam finalną wartość alfa, która powinna zostać użyta podczas rysowania.

Do rzeczy jednak. Listing 6 przedstawia naiwną implementację blittera 8888 –> 565. Kod, rzecz oczywista, działa zgodnie z ocze-kiwaniami, jednak prędkość jest zdecydowa-nie niesatysfakcjonująca. Wystarczy spojrzeć na liczbę operacji wykonywanych dla każde-go piksela, aby wiedzieć czemu (Listing 7). 30 instrukcji asemblera, 6 mnożeń, na szczę-ście tylko 2 odczyty z pamięci i jeden zapis. No, mogłoby być lepiej. Optymalizację mo-żemy zacząć od wyrzucenia z kodu niepo-trzebnych operacji, które co prawda mają ja-kiś wpływ na czytelność, ale nas interesuje jak największa wydajność. Aby pobrać ze źródło-wego piksela wartość alfa, nie ma sensu usu-wanie kanałów R, G i B, ponieważ przesunię-cie w prawo o 24 bity automatycznie pozba-

wi nas tych wartości. Podobnie, podczas skła-dania finalnej wartości piksela nie ma sensu obcinanie najmłodszych trzech bitów ze skła-dowej niebieskiej, ponieważ i tak za chwi-lę wykonywane jest przesunięcie bitowe. Po wprowadzeniu modyfikacji wynikowy kod asemblerowy zmniejszył się o jedną instruk-cję. Nie jest to może jakaś ogromna zmiana, ale na początek wystarczy.

Czym by się można teraz zająć? Może spró-bujmy popatrzeć na matematykę, w końcu sześć mnożeń musi nieźle spowalniać nasz kod. Spójrzmy na to, w jaki sposób wykony-wana jest interpolacja koloru źródłowego z docelowym (tutaj wartość alfa będzie w za-kresie 0 – 1, a nie 0 – 255):

d = s * a + d * ia

Rozwińmy ia :

d = s * a + d * ( 1 – a )

O, jakoś to teraz wygląda. Rozwijajmy dalej:

d = s * a + d – d * a

Rysunek 3. Jedna z grafik wykorzystanych w grze Boom Letters firmy Gamelion

Listing 4. Mechanizm Duffa

UInt32 w = ( width + 3 ) / 4;

switch( width % 4 )

{

case 0:

do

{

*dst++ = *src++;

case 3:

*dst++ = *src++;

case 2:

*dst++ = *src++;

case 1:

*dst++ = *src++;

}

while( --w );

}

Listing 5. Skompilowany program z Listingu 4

.L7:

ldrh r6, [r0], #2

strh r6, [lr], #2 @ movhi

.L10:

ldrh r6, [r0], #2

strh r6, [lr], #2 @ movhi

.L11:

ldrh r6, [r0], #2

strh r6, [lr], #2 @ movhi

.L12:

ldrh r6, [r0], #2

subs ip, ip, #1

strh r6, [lr], #2 @ movhi

bne .L7

Page 57: SDJ_02_2010_PL

02/201056

Programowanie gierEfektywny blitter

www.sdjournal.org 57

A teraz wyciągnijmy wartość alfa przed na-wias:

d = d + a * ( s – d )

Sukces! Udało nam się wyeliminować jedno mnożenie! A ponieważ przetwarzamy trzy kanały, rzeczywista liczba mnożeń spada z sześciu do trzech. Liczba instrukcji asemble-ra pozostała niezmieniona, ale czy to ozna-cza, że kod będzie się wykonywał tyle sa-mo czasu? Należy wykonać pomiary i wy-ciągnąć wnioski, co niech zostanie zadaniem dla czytelnika.

Trzy mnożenia na piksel to całkiem do-bry wynik, liczby kanałów zmienić się nie da, a mnożenia przez wartość alfa też raczej nie wyeliminujemy. Czy spoczniemy zatem na laurach? Nie, zmniejszymy liczbę mno-żeń z trzech do jednego. Niemożliwe? Ha! W chwili obecnej przetwarzamy 8-bitowe war-tości na 32-bitowych rejestrach. Przy mnoże-niu przez 8-bitową alfę wykorzystane jest tyl-

ko 16 bitów. Co by było, gdyby zostawić star-sze 8 bitów w młodszym 16-bitowym słowie na wynik mnożenia, a w górnym 16-bitowym słowie wstawić drugi kanał? Popatrzmy:

0606 * 5 / 10 = 3030 / 10 = 0303

Działa! Na obsługę trzech kanałów zabrak-nie nam jednak miejsca w rejestrze. Zauważ-my jednak, że docelowy format piksela to i tak 565. Rozpiszmy to sobie:

RRRRRGGGGGGBBBBB

Hmm... A gdyby tak...

00000GGGGGG00000RRRRR000000BBBBB

Udało nam się zmieścić trzy kanały w 32 bi-tach, zostawiając miejsce na wynik mnoże-nia, dzięki czemu możemy obliczyć wartości wszystkich kanałów, wykonując tylko jedno mnożenie. Oczywiście należy zauważyć kil-

ka rzeczy. Kolory z obrazu źródłowego muszą zostać stratnie przekształcone z formatu pik-sela 888 na 565. Musimy również użyć 5-bi-tową alfę, żeby zmieścić się w buforach. Stra-ta jakości, może się wydawać, dosyć znaczna, jednak w rzeczywistości jest praktycznie nie-zauważalna. Implementacja tej optymalizacji przedstawiona jest na Listingu 8. To, co się dzieje w środku pętli, jest czarną magią w po-równaniu z poprzednią wersją, ale spójrzmy na Listing 9. Liczbę mnożeń udało się zredu-kować do jednego, a liczba wykonywanych in-strukcji asemblera spadła do 21! Dobrze rów-nież zauważyć, iż mimo wielu operacji odczy-tu z pamięci (z tego samego adresu) w kodzie wysokopoziomowym, kompilator był w sta-nie wygenerować tylko jeden rzeczywisty do-stęp do pamięci.

Może się wydawać, że tego to już się na pew-no bardziej nie da zoptymalizować. Ale proszę się zastanowić, czy 21 instrukcji asemblera to nie jest troszkę za dużo, w przypadku gdy war-tość alfa jest równa 0, a więc piksel powinien

Rysunek 4. Animacja postaci w grze Tiger Woods PGA Tour

Listing 6. Naiwny blitter 8888 –> 565

UInt32* src;

UInt16* dst;

do

{

int w = width;

do

{

UInt32 a, ia, sr, sg, sb, dr, dg, db;

a = ( *src & 0xFF000000 ) >> 24;

ia = 255 – a;

sr = ( *src & 0x00FF0000 ) >> 16;

sg = ( *src & 0x0000FF00 ) >> 8;

sb = ( *src & 0x000000FF );

dr = ( *dst & 0xF800 ) >> 8;

dg = ( *dst & 0x07E0 ) >> 3;

db = ( *dst & 0x001F ) << 3;

dr = ( sr * a + dr * ia ) >> 8;

dg = ( sg * a + dg * ia ) >> 8;

db = ( sb * a + db * ia ) >> 8;

*dst = ( ( dr & 0xF8 ) << 8 ) | ( ( dg & 0xFC ) << 3 ) | ( ( db & 0xF8 ) >>

3 );

src++;

dst++;

}

while( --w );

}

while( --height );

Listing 7. Skompilowany program z Listingu 6

.L5:

ldrh r4, [r8, #0]

ldr ip, [sl], #4

and r2, r4, #2016

mov lr, ip, lsr #24

rsb r0, lr, #255

mov r1, r2, asr #3

and r3, r4, #63488

mov r6, r3, asr #8

and r5, r4, #31

mul r4, r0, r1

mov r2, r5, asl #3

mul r5, r0, r6

and r1, ip, #65280

mul r6, r0, r2

mov r3, r1, lsr #8

and r0, ip, #16711680

mla r1, lr, r3, r4

mov r2, r0, lsr #16

mla r3, lr, r2, r5

and ip, ip, #255

mla r2, lr, ip, r6

mov r0, r1, lsr #5

and ip, r3, #63488

and r1, r0, #2016

orr r0, ip, r1

mov r2, r2, asl #16

orr r3, r0, r2, lsr #27

subs r7, r7, #1

strh r3, [r8], #2 @ movhi

bne .L5

Page 58: SDJ_02_2010_PL

02/201058

Programowanie gier

być po prostu przeskoczony bez zmian? A je-żeli alfa jest równa 255 – nie ma potrzeby wy-konywania blendingu, można po prostu sko-piować (po zmianie formatu piksela) wartość z obrazu źródłowego do docelowego. Są to rze-czy, wydaje się, oczywiste, ale kto o tym wcze-śniej pomyślał? Koszt związany z obsługą róż-nych zachowań w zależności od alfy nie jest oczywiście zerowy, ale spójrzmy na pomiary szybkości wykonywania. Blitowanie obrazu, w którym wszystkie piksele mają wartość kanału alfa równą 0, zajęło 4,34 ms. Dla wartości alfa równych 255 było to 7,43 ms. Dla innych war-tości, czyli wtedy gdy musiał być wykonywany blending, rysowanie trwało 15,57 ms. Różni-ca dosyć znaczna! Szczególnie gdy w progra-mie wykorzystujemy grafiki takie jak przedsta-wiona na Rysunku 3. Aby uzyskać zadowalają-cą liczbę klatek na sekundę przed wprowadze-niem optymalizacji polegającej na zmianie za-chowania w zależności od wartości alfa, grafi-ka musiała zostać pocięta na kilka mniejszych. Zauważenie problemu umożliwiło użycie nie-pociętej grafiki bez spadku wydajności, równo-cześnie zmniejszając nakład pracy grafików.

O takich rzeczach, jak wczesne wyjście z funkcji, w przypadku gdy użytkownik podał globalną wartość alfa równą 0, chyba nie trze-ba mówić.

Problemy z pamięciąPodczas prac nad grą Tiger Woods PGA Tour dla firmy Electronic Arts pojawił się dość nie-oczekiwany problem. Blitter co prawda był szybki, ale zostały zupełnie zaniedbane wy-magania pamięciowe. Przedstawiona na Ry-sunku 4 animacja ma rozmiar 2987x103 piksele i, przy 32-bitowym kolorze, zajmu-je w pamięci 1,17 MB. Co gorsza, w pamię-

ci musiały się jednocześnie znajdować jesz-cze 3 inne animacje o podobnych rozmia-rach. Przy ograniczeniu możliwej do użycia pamięci do 10 MB (wymaganie platformy) stało się to znacznym problemem. Pewnym rozwiązaniem byłoby użycie obrazu z paletą, co zmniejszyłoby zużycie pamięci czterokrot-nie, jednak oznaczałoby to znaczną degrada-cję jakości obrazu, chociażby przez utratę an-tyaliasingu czy przezroczystości cieni.

Problem został usunięty przez zastosowanie kompresji RLE (Run Length Encoding). Polega ona na ustaleniu kodów operacji, które potem są odpowiednio interpretowane przez blitter. Przykładowo, kod 0 niech odpowiada ciągowi pikseli przezroczystych (w ciągu danych zapisa-na jest długość ciągu obejmowanego przez kod), kod 1 może odpowiadać pikselom nieprzezro-czystym (zapisujemy dodatkowo długość cią-gu i wartości pikseli, dla szybkości można użyć formatu docelowego, e.g. 565), piksele półprze-zroczyste mogą być opisane kodem 2 (plus dłu-gość i wartości pikseli razem z kanałem alfa). To oczywiście tylko przykład. Analizę obrazów podczas kompresji można rozszerzyć o wykry-wanie obszarów takiego samego koloru, obsza-rów ze stałą wartością alfa, gradientów itp. Blit-ter interpretujący kod 0 może w tym momen-cie przeskoczyć o zapisaną liczbę pikseli, w ogó-le ich nie przetwarzając. Interpretacja kodu 1 sprowadza się do wywołania funkcji memcpy – odpada konieczność konwersji pikseli z for-matu 888 na 565 przy każdym rysowaniu.

Po zastosowaniu powyższej metody obszar pamięci zajmowany przez animację z Rysun-ku 4 został zmniejszony do 122 KB. Dziesię-ciokrotne zmniejszenie, chyba całkiem nie-źle? Operacja kompresji może być na tyle szybka, aby dało się ją wykonywać podczas ła-

dowania obrazów z dysku, ale dużo lepszym rozwiązaniem jest wykonanie jej tylko raz – podczas tworzenia assetów programu. Wy-nikowy rozmiar na dysku może być co praw-da większy – PNG z animacją zajmował 99,6 KB, a po kompresji RLE 121 KB, ale należy pamiętać, że obraz w formacie RLE lepiej się kompresuje. Po spakowaniu obrazu PNG programem 7zip rozmiar wynikowego archi-wum wyniósł 99,2 KB, a obraz RLE skompre-sował się do 45 KB. Nie dość, że zmniejsza-ją się wymagania pamięciowe, zmniejsza się więc również zajętość dysku przez aplikację.

Co dalej?Przedstawione techniki w żadnym razie nie wyczerpują możliwych do zastosowania roz-wiązań. Z całą pewnością da się wymyślić jeszcze wiele innych sposobów na przyspie-szenie programowego rysowania obrazów, czego czytelnikom życzę. Pewną pomocą mo-że być książka Graphics Programming Black Book autorstwa Michaela Abrasha, traktu-jąca co prawda o sprzęcie trochę przestarza-łym, ale pełna wielu świetnych pomysłów i wskazówek. Książkę można ściągnąć za dar-mo pod adresem http://nondot.org/~sabre/Mirrored/GraphicsProgrammingBlackBook/.

Listing 8. Jednoczesne przetwarzanie wszystkich kanałów

do

{

int w = width;

do

{

UInt32 a, tmp;

a = *src >> 24;

tmp = ( ( *dst | ( ( *dst & 0x07E0 ) << 16 ) ) & 0xFFFFF81F ) + ( ( ( ( ( (

*src & 0xF80000 ) >> 8 ) | ( ( *src & 0x0000F8 ) >> 3 ) | (

( *src & 0x00FC00 ) << 11 ) ) - ( ( *dst | ( ( *dst & 0x07E0

) << 16 ) ) & 0xFFFFF81F ) ) * a ) >> 5 );

*dst = ( tmp & 0xF81F ) | ( ( tmp & 0x07E00000 ) >> 16 );

src++;

dst++;

}

while( --w );

}

while( --height );

Listing 9. Skompilowany program z Listingu 8

.L5:

ldr r0, [r5], #4

ldrh r2, [r4, #0]

and r3, r0, #248

and ip, r0, #16252928

and r1, r2, #2016

mov r3, r3, lsr #3

orr r2, r2, r1, asl #16

orr r3, r3, ip, lsr #8

and r1, r0, #64512

orr ip, r3, r1, asl #11

bic r2, r2, #2016

rsb r3, r2, ip

mov r0, r0, lsr #24

mul r1, r0, r3

add ip, r2, r1, lsr #5

and r3, ip, #132120576

bic r0, ip, #2016

orr r2, r0, r3, lsr #16

subs lr, lr, #1

strh r2, [r4], #2 @ movhi

bne .L5

BARTOSZ TAUDULBartosz Taudul zajmuje się, pośród wielu innych rzeczy, rozwojem i optymalizacją niskopoziomo-wych procedur graficznych w wieloplatformo-wej bibliotece opracowywanej w firmie Gamelion wchodzącej w skład Grupy BLStream.Kontakt z autorem: [email protected]

Page 59: SDJ_02_2010_PL
Page 60: SDJ_02_2010_PL

02/201060

Programowanie gierXcode

www.sdjournal.org 61

Do napisania gry wymagany jest komputer Mac, co może wiązać się z wydatkiem zaczynającym się

w okolicach dwóch tysięcy złotych. Do testo-wania dzieła na pewno też dobrze mieć do-celowe urządzenie – iPhone’a lub iPoda To-uch. Gdy już te absolutnie podstawowe wa-runki zostają spełnione, nic (lub prawie nic) nie stoi na przeszkodzie, by rozpocząć pracę nad grą marzeń. Z tym, co stanąć może, po-staramy się rozprawić w poniższym artykule. Zapraszamy do lektury.

Krok pierwszy – rejestracja na portaluCzas zacząć. Zdecydowanie pierwszym kro-kiem powinno być zarejestrowanie na oficjal-nym portalu developerskim Apple dostęp-nym pod adresem:http://developer.apple.com/iphone/

Dzięki temu developer będzie mógł korzy-stać ze wszystkich zasobów i informacji poma-gających w programowaniu na iPhone OS. Sa-ma rejestracja podzielona jest na dwie części. Pierwsza darmowa umożliwia ściąganie SDK i uruchamianie aplikacji na symulatorze. Dru-

ga płatna w wysokości $99 (lub 299 dla du-żych firm) pozwala na uruchamianie aplikacji na sprzęcie i późniejszą dystrybucję aplikacji na App Store. Prócz tego płatna rejestracja da-je większy dostęp do bazy wiedzy i przykładów przygotowanych przez Apple. Niezbędne będą takie dane, jak numery konta, informacje o ban-ku. Należy przy tym pamiętać, że wszelkie przy-chody z App Store należy rozliczyć z Urzędem Skarbowym. Tak głosi prawo.

Krok drugi – ściągnięcie i zainstalowanie odpowiednich materiałówPo pomyślnej rejestracji na portalu pracę czas zacząć! W zależności od zainstalowanego OS-u na Macu należy ściągnąć i zainstalować SDK w wersji Leopard bądź Snow Leopard. SDK zawiera:

• Xcode – środowisko pozwalające na programowanie/kompilowanie/uruchamianie stworzonej aplikacji,

• iPhone simulator – symulator iPhone po-zwalający na uruchomienie stworzonej aplikacji, jej testowanie i debugowanie,

• Tools – zestaw dodatkowych narzędzi pozwalających na łatwiejsze i sprawniej-sze programowanie i tworzenie aplika-cji. Jednym z ciekawszych narzędzi jest Instruments pozwalający na dokładne i

Xcode

iPhone i App Store to światowy fenomen. Firma Apple dokonała niemożliwego i uwolniła wielką siłę niezależnych developerów mogących od teraz spełniać swoje wizje i tworzyć gry jak dawniej, w pojedynkę lub w małych zespołach, siedząc nad nimi po pracy, w garażach.

Dowiesz się:• Co potrzebne jest do programowania gier

na iPhone’a;• Jak zacząć pracę nad grą i jakie są etapy jej

produkcji;• Jak warto zorganizować sobie strukturę pro-

jektu.

Powinieneś wiedzieć:• Podstawy obsługi Xcode;• Znać podstawy C++ i Objective-C;• Przeczytać artykuł dotyczący programowa-

nia na iPhone;• Podstawowe informacje na temat develop-

mentu gier.

Poziom trudności

Xcode – oto czytelnik. Czytelniku – oto Xcode. Poznajcie się. Wprowadzenie do programowania najpopularniejszego urządzenia mobilnego na świecie.

Rysunek 1. Ekran powitalny Xcode IDE

Page 61: SDJ_02_2010_PL

02/201060

Programowanie gierXcode

www.sdjournal.org 61

szybkie namierzenie problemów związa-nych z zarządzaniem pamięcią.

Krok trzeci – odpalenie środowiska i stwo-rzenie przykładowej aplikacjiDo tego celu służy środowisko Xcode. Jest to główne narzędzie SDK pozwalające na tworze-nie aplikacji, testowanie, uruchamianie i przy-gotowywanie wersji na urządzenie. Sercem ze-stawu narzędzi Xcode jest Xcode IDE – graficz-ny kombajn zawierający profesjonalny text editor, system tworzenia aplikacji, debugger oraz kom-pilator. Więcej o Xcode można przeczytać pod adresem: http://developer.apple.com/tools/Xcode/.

Stwórzmy grę!Wiele gier powstaje, bazując na już istnieją-cych produktach. Warto jednak sięgnąć po własny produkt, którym zawojujemy rynek.

Pomysł – skąd go wziąć?• burza mózgów.

Twórcze głowy zasiadają do stołu (na łące, w pubie itd.) i snują pomysły, dzieląc się swo-imi przemyśleniami. Wyznaczona osoba no-tuje. Zakaz krytykowania i wyśmiewania pod groźbą stawiania kolejki.

• analiza rynku (co na rynku robi konku-rencja, na co jest popyt, czego brakuje?);

• przypadkowe olśnienie. Czasami uderza znienacka, czasami pod wpływem jakiejś innej gry. App Store pełen jest takich gier, wiele z nich odnosi sukces właśnie dzięki nieprzewidywalności i oryginalności.

Strona organizacyjna produkcji gryCzasy, w których każda gra powstawała w zaci-szu domowym i była tworzona przez jedną lub dwie osoby, dawno odeszły do lamusa. Aktual-nie w tworzenie gry bardzo często zaangażowa-nych jest wiele osób. W zależności od wielkości projektu może to być 4 lub nawet 400 osób. Aby poprawnie zarządzać taką ekipą, trzeba

nie lada doświadczenia i wiedzy. Profesjonalne i zdroworozsądkowe podejście do sprawy wy-maga stworzenia planu produkcji gry. Dzieje się to w pierwszym etapie zwanym zazwyczaj pre-produkcją. Podczas tego etapu liderzy projektu wraz z producentem, bazując na GDD i TDD, gry przygotowują zadania dla poszczególnych członków zespołu.

Niezbędne narzędzia w procesie developmentu gryDo sprawnej organizacji całego przedsięwzię-cia przydadzą się różne narzędzia. Pozwolą one na łatwiejszą, sprawniejszą i szybszą pra-cę całego zespołu. W dzisiejszych dniach two-rzenie gry wymaga takich narzędzi jak:

• Managing tools – pozwalają na stworze-nie planu, przydzielenia zadań członkom zespołu i koordynację całego procesu pro-dukcji. Najbardziej znanym komercyjnym rozwązaniem jest Microsoft Project. Ist-nieją także alternatywne darmowe roz-wiązania takie jak np. dotProject.;

• Bugtracking – dzięki niemu zarządzanie błędami, które mogą wystąpić w trakcie trwania projektu staje się dużo prostsze, przejrzystsze i szybsze. Do najpopular-niejszych bugtrackerów należą: Mantis, Bugzilla czy Track Studio;

Rysunek 2. Ekran powitalny symulatora iPhone

Rysunek 3. Boozle – gra firmy Vivid Games inspirowana pomysłami z firmowej imprezy

Na Skróty

• SDK (ang. Software Development Kit) – Zestaw narzędzi dla programistów niezbędny do stworzenia aplikacji korzystającej z określonych funkcji pod określonym systemem operacyjnym.

• App Store – Sklep online firmy Apple pozwalający na zakup cyfrowych produktów, między innymi gier na iPhone’a.• GDD (ang. Game Design Document) – Dokument zawierający wszystkie wymagane informacje pozwalające na stworzenie gry. Tworzony jest

przez “Game Designera”• TDD (ang. Technical Design Document) – Techniczna wersja GDD opisująca wszelkie zagadnienia dotyczące gry od strony technicznej.• API (ang. Application Programming Interface) – Interfejs programowania aplikacji umożliwiający komunikację z systemem operacyjnym, bi-

blioteką bądź innym zewnętrznym systemem.• Cocoa – Jedno z pięciu głównych API systemu operacyjnego Mac OS X firmy Apple,• Xcode – Zintegrowane środowisko programistyczne firmy Apple. Służy do tworzenia aplikacji i innego oprogramowania przeznaczonego

m. in. na system Mac OS X.• UML (ang. Unified Modeling Language) – Zunifikowany Język Modelowania służący do opisu świata obiektów w analizie obiektowej oraz

programowaniu obiektowym.

Rysunek 4. Przykładowy szkielet głównej klasy w grze zaprojektowany za pomocą UML

����

����������

�����������������

�����������

����������������

�����������������

��������������

������������������������������

����������������������������

�������������������������

��������������������������������������������������������������������

������������������������������

���������������������������

����������������������������������

Page 62: SDJ_02_2010_PL

02/201062

Programowanie gierXcode

www.sdjournal.org 63

• Time manager – zestaw narzędzi pozwa-lający na organizację czasu pracowni-ków, ich czasu pracy przy projekcie. Bar-

dzo często zintegrowany jest on bezpo-średnio z narzędziami służącymi do za-rządzania projektem.

Tworzenie gry krok po krokuEtapy produkcji gry dzieli się na następujące fazy produkcji:

• Preproduction – Etap planowania gry. W trakcie jego trwania powstaje cały plan produkcji i lista zadań do wyko-nania przez poszczególnych członków zespołu.

• First playable – wynikiem tego etapu jest pierwsza wersja gry pozwalająca na sprawdzenie, czy założenia dotyczą-ce grywalności ustalone w czasie pre-produkcji sprawdzają się w rzeczywi-stości.

• Alpha – Na zakończenie tej części pro-jektu powstaje pełna wersja gry, mo-że jednak ona zawierać różnego rodzaju błędy w funkcjonalności. Według najpo-pularniejszych metod prowadzenia pro-jektów, wersja Alpha powinna jednak być już całkowicie wyposażona w tryby gry, teksty, grafiki itd.

• Beta – Etap projektu, w którym głów-ną uwagę spędza się na naprawianiu błę-dów i szlifowaniu finalnego produktu.

• Release Candidate – Po zakończeniu te-go etapu powstaje pierwsza wersja będąca kandydatem do wydania gry. Jest to w peł-ni stabilna, niezawierająca błędów gra.

• Gold Master – Powstający w tym czasie produkt jest finalną wersją gry, która do-stępna jest dla graczy.

• Localization – Etap, w którym gra jest lo-kalizowana na różne języki. W tym cza-sie powstają alternatywne wersje języko-we gry bądź jedna wersja z możliwością wyboru języków.

• Marketing – Po zakończonych etapach ukończony produkt jest gotowy do wyda-nia, aby jednak osiągnął sukces, muszą po-wstać odpowiednie materiały marketin-gowe służące do reklamowania i promocji gry. Bardzo często zdarza się, że materia-ły marketingowe powstają dużo wcześniej lub równolegle z procesem produkcyjnym gry. Kontakt z mediami i fanami powinien zostać zawiązany znacznie wcześniej, by o grze było wiadomo cokolwiek, zanim po-jawi się ona na rynku.

Etapy tworzenia kodu gry

Podział prac pomiędzy programistamiLider programistów wraz z project manage-rem w czasie planowania projektu rozdzie-lają poszczególnym członkom zespołu za-dania. Dobry podział zadań to połowa suk-cesu. Ważne jest to, aby każdy z programi-stów miał swoje pole działania, a współpra-ca pomiędzy kodem poszczególnych progra-mistów odbywała się za pomocą określone-go interfejsu.

Rysunek 5. Przykładowy szkielet głównej klasy w grze w języku Java

Debugowanie i naprawianie błędówPakiet Xcode zawiera w swoim zestawie bardzo ciekawą i pomocną aplikację o nazwie In-struments. Dzięki niej jesteśmy w stanie podglądać wiele rzeczy podczas pracy aplikacji na symulatorze bądź na iPhonie. Program ten pozwala między innymi na:

• przeglądanie stanu pamięci

Możesz śledzić zmiany alokacji pamięci dla wybranej aplikacji. Dzięki temu łatwo i przyjemnie można namierzyć największe „pochłaniacze” pamięci. Jest to dość istotne, gdyż należy pamię-tać, że iPhone to tylko telefon i jego zasoby są ograniczone. Najbezpieczniejszą granicą, której nie warto przekraczać, to zużycie 30MB realnej pamięci.

• znajdowanie wycieków pamięci

Opcja bardzo przydatna dla mniej wprawionych programistów. Bardzo często zapomina się o tak ważnej rzeczy jak zwalnianie zaalokowanej pamięci. Szczególnie zarażeni są tym progra-miści Java przesiadający się na C++. W Javie menadżer pamięci czyści ją za nich, tutaj trzeba pamiętać o tym samemu. Skutkiem tego bardzo często zdarzają się wycieki pamięci, które w większych projektach jest bardzo ciężko namierzyć. Tutaj z pomocą przychodzi nam opcja Ac-tivity Monitora. Dzięki niej dokładnie możemy zlokalizować wyciek cennych bajtów. Program wskaże nam dokładną linijkę w kodzie, gdzie wyciek nastąpił. Co ciekawe, wycieki pamięci zdarzają się też w samym OS-ie.

• analizę zużycia czasu procesora

Jest to narzędzie podobne do Task Managera znanego z Windowsa. Dzięki niemu może-my sprawdzić poziom zużycia pamięci realnej i wirtualnej, a także czas zużycia procesora dla wszystkich aplikacji uruchomionych na iPhonie.

• obserwowanie file systemu i dostępu do plików

Pozwala na przejrzenie wszystkich operacji dyskowych/plikowych, jakie występują od urucho-mienia aplikacji na iPhonie. Jest to rzadziej używana opcja, jednakże pozwala ona programi-ście na obserwację zachodzących procesów związanych z plikami.

Page 63: SDJ_02_2010_PL

02/201062

Programowanie gierXcode

www.sdjournal.org 63

Tworzenie szkieletu klas w UML bazując na TDD przez programistówNajwygodniejszym i najszybszym sposo-bem na sprawne zaplanowanie rozłożenia klas pomiędzy programistami jest stwo-rzenie szkieletu całego projektu. Czy wy-obrażacie sobie tworzenie go bezpośrednio w kodzie? Byłoby to bardzo czasochłonne i trudne. W tym momencie z pomocą przy-chodzi nam UML. Dzięki niemu tworzenie zarysu kodu staje się dużo wygodniejsze, a dowolną zmianę można zrobić dosłownie w chwilę.

Recenzja, ewentualne zmiany i finalizowanie klas w UML przez lideraW tworzeniu kodu uczestniczą wszyscy pro-gramiści. Po podzieleniu poszczególnych za-dań przez lidera każdy z nich tworzy szkie-

let klas mu przydzielonych. Na końcu ca-łość zostaje złożona i dopracowania pod ką-tem interfejsów i komunikacji. Bazując na do-kumentacji programiści są w stanie szybko wprowadzić zmiany w kodzie, tak aby speł-niał on wymagania projektu. Na tym pozio-mie projektowania powstają także komen-tarze do klas, metod i argumentów, które w późniejszej produkcji znacznie usprawnią pracę z kodem.

Generowanie kodu wraz z komentarzami z UMLDodatkową zaletą UML-a jest możliwość wyge-nerowania kodu w prawie dowolnym języku pro-gramowania. Dzięki temu po fazie projektowej programiści mają gotowy kod, który muszą tylko uzupełnić. Wszelkie hierarchie, interfejsy itp. są już określone i praca jest dużo wygodniejsza.

Programowanie poszczególnych metod i funkcjiBazując na specyfikacji przygotowanej podczas preprodukcji oraz na strukturze kodu zapro-jektowanej w UML, dalsza praca to już „buł-ka z masłem”. Niestety, jednak podczas two-rzenia gry bardzo często zmieniają się części projektu, wprowadzane są udoskonalenia i po-prawiona zostaje grywalność. Dlatego właśnie pierwszym ważnym etapem programowania jest przygotowanie grywalnej wersji gry. To ona pozwoli ocenić całemu zespołowi, czy gra jest fajna, czy może coś będzie trzeba zmieniać i dopracowywać. To w tym etapie programista powinien jak najwięcej czasu poświęcić na gry-walność, a mniej przejmować się błędami.

Gdy okaże się, że pierwsza wersja to praw-dziwy hit, pozostaje nic innego, jak zakasać rękawy i przejść do programowania. Tutaj praca jest już bardziej nudna. Bazując na spe-cyfikacji, należy przygotować wszystkie me-chanizmy pozwalające obsłużyć całą grę.

Warto pomyśleć także o nowinkach do gry, które popularne są na iPhonie. Są to między innymi:

• Facebook – obsługa konta facebook, ofi-cjalna strona, zapisywanie wyników na facebook itp.,

• iTunes library – funkcjonalność w aplikacji pozwalająca na słuchanie plików muzycz-nych zapisanych na swoim iPhone podczas gry (zamiast oryginalnej muzyki z gry),

• More games – opcja pozwalająca graczowi na zapoznanie się z ofertą gier producenta.

Zakończenie

• Testy i polishing gry.• Stworzenie finalnej wersji gry.• Wydanie gry na AppStore.

Bez wątpienia tworzenie gier na iPhone’a to do-skonała zabawa, przygoda i szansa dla nieza-leżnych developerów. Dziesiątki tysięcy (dane wskazują, że już ponad 100!) aplikacji nie wzię-ły się znikąd. Każdy szuka swojej niszy i prze-błysku geniuszu. Powodzenia z projektami i do zobaczenia na szczycie listy Top 10!

PATRYK BUKOWIECKISpecjalista ds. PR i marketingu, manager, czujny obserwator branży gier video, socjolog, redaktor i wieloletni współpracownik Neo Plus, gracz, pił-karz. W Vivid Games na stanowiskach producent i PR and Marketing Manager.

JAROSŁAW WOJCZAKOWSKICTO w firmie Vivid Games, programista/project manager z kilkunastoletnim doświadczeniem, współwłaściciel firmy Vivid Games, wesoły i po-godny człowiek, otwarty na nowe rozwiązania.

Rysunek 6. Ekran powitalny aplikacji Instruments

Rysunek 7. Przykładowy wyciek pamięci w OS

Page 64: SDJ_02_2010_PL

02/201064

Programowanie gierCyfrowa kreacja

www.sdjournal.org 65

Aktualnie największym producen-tem tego typu software'u jest fir-ma Autodesk, dlatego skupię się

na jej produktach i funkcjonalności jaką oferują w zakresie animacji oraz wspoma-gania procesu tworzenia gier. Oczywiście lwią część pracy wykonuje zespół progra-mistów, ale nie byłoby czego oprogramo-wać, gdyby nie treść dostarczona przez artystów tworzących animacje, postacie, lokacje czy oprawę interfejsu. Autodesk pokrywa wszystkie te dziedziny dzięki ca-łej linii produktów ukierunkowanych na wykorzystanie w danej dziedzinie kreacji. Celowo nie wspominam tu o teksturach i obróbce finalnej obrazu ruchomego, po-nieważ z tego można byłoby napisać ko-lejny artykuł, a są to rzeczy, które są tylko częścią procesu. Skupię się na krótkich opisach najważniejszych w dziedzinie gier aplikacji z całego portfolio Autodesk. Nie-które z poniższych produktów występują jako samodzielne aplikacje, niektóre są czę-ścią pakietów, bądź mają charakter uzupeł-niający, niemniej jednak całość produkcji gier jest procesem ciągłym i wymagającym kompletnego systemu, a tym samym uzu-

pełniających się rozwiązań, dlatego podział ma znaczenie dopiero po określeniu swoich potrzeb.

Autodesk 3ds MaxNajpopularniejszy program służący do ge-nerowania i obróbki grafiki trójwymiaro-wej. Nie bez powodu nazywany kombaj-nem – modułowa budowa pozwala na do-wolne konfigurowanie oraz dodawanie i odejmowanie funkcji przez system wty-czek. Rozbudowany język skryptowy czę-sto wykorzystywany jest do automatyzacji zadań, a całkowicie konfigurowalny inter-fejs ułatwia dostosowanie narzędzi do wła-snych potrzeb.

Dla szefa studia graficznego na pewno ma to duże znaczenie. Łatwo znaleźć wy-kwalifikowanego człowieka, który z miej-sca siada i zaczyna pracować. Ma to duże znaczenie przy projektach, w których za-soby ludzkie wykorzystuje się w zależno-ści od potrzeb i obciążenia pracą na da-nym etapie.

Graphite modeling tool - nowe narzędzia do modelowania poligonowego zostały ukie-runkowane na szybkie manipulowanie bryła-mi i siatką – zwiększono ich ilość do niemal stu nowych funkcji służących tylko do ope-racji na obiekcie. Dodano długo oczekiwane rzeźbienie oraz malowanie tekstur. Pozwala to w krótkim czasie nanieść poprawki na mo-del. Oczywiście aby szybko zobaczyć poten-cjalny efekt końcowy okna podglądu, działają

w trybie DirectX z wyświetlaniem shaderów, a xView Mesh Analyzer pozwoli po wszyst-kim sprawdzić poprawność siatki wykona-nego modelu.

Okno renderingu zostało zmodyfikowa-ne tak, by można było dokonywać korekty jakości i parametrów bezpośrednio w nim bez każdorazowego otwierania dodatkowych funkcji, pozwalając na szybkie podejrzenie efektu wypalania tekstur przy różnych para-metrach wejściowych.

Narzędzia UV w 3ds Max zostały spe-cjalnie opracowane pod kątem tworzenia gier i zawierają szereg elementów wskaza-nych przez użytkowników w trakcie wie-loletniego rozwoju aplikacji, wzbogacając 3ds Max o Material Explorer, dzięki któ-remu skończyło się przebijanie przez kil-kanaście poziomów materiału, na które na-rzekali do tej pory użytkownicy. Wisien-ką na torcie jest mental mill, zdobywają-cy popularność moduł pochodzący z men-tal images, a pozwalający tworzyć skompli-kowane shadery z podglądem w czasie rze-czywistym, co niesamowicie wpływa na szybkość generowania tekstur i materiałów obiektów w grach.

Wprowadzone kontenery ułatwiają pra-cę z dużymi scenami, gdzie na jednym kom-puterze można tworzyć postacie, na drugim lokacje, na trzecim mapę, a na kolejnym łą-czyć to wszystko razem, nie obciążając ma-szyn, a tym samym uprzyjemniając i przy-spieszając pracę.

MayaBędący standardem w produkcji filmów pa-kiet coraz częściej jest zauważany przez twór-ców gier. Powodem jest fakt zbliżenia się tych dwóch dziedzin rozrywki do tego stopnia, że kino myśli o interaktywnych filmach, a prze-mysł gier myśli o kinowej jakości obrazie i ani-macji w grach.

Cyfrowa kreacja

Niezależnie od tego, czy tworzymy film, spot reklamowy, efekty specjalne, wizualizację czy grę, potrzebujemy narzędzi, które pozwolą nam w produktywny i elastyczny sposób stworzyć to, co widzimy oczami wyobraźni. Wtedy pojawia się konieczność wybrania oprogramowania do tzw. cyfrowej kreacji.

Dowiesz się:• Jakie są trendy w aplikacjach trójwymiaro-

wych• Co najnoweszego znajdziesz na rynku• Jak przyspieszyć proces tworzenia przy uży-

ciu nowoczesnych narzędzi 3D

Powinieneś wiedzieć:• Jakie są podstawy terminologi 3D

Poziom trudności

Artystyczne projektowania gier

Page 65: SDJ_02_2010_PL

02/201064

Programowanie gierCyfrowa kreacja

www.sdjournal.org 65

Maya ze względu na swoja przeszłość łą-czy doskonale oba te światy, zapewniając po jednej i drugiej stronie wszechstronne i do-skonałe narzędzia i często jest wykorzysty-wana jako środowisko systemowe do dal-szego rozwoju projektu. Pozwala na to dzię-ki obsłudze języków C++ i Python oraz wła-snego MEL.

W Polsce ma to zapewne mniejsze znacze-nie, ale może być istotne przy pracy w obrę-bie jednego podmiotu na różnych platfor-mach. O ile 3ds Max jest przeznaczony tyl-ko dla MS Windows, o tyle Maya działa rów-nież na MacOS X oraz Linux. Jest to jedyny komercyjny produkt zapewniający taką ela-styczność.

Tak samo jak w przypadku 3ds Max spo-ro materiałów w Internecie pomaga rozwijać umiejętności, chociaż w Polsce jest mniej lu-dzi znających na poziomie zaawansowanym tę linię produktów, ale z reguły jak już ktoś tą wiedzę posiada, to jest profesjonalistą.

SoftimageZnany głównie z nazwy, ale rzadko od stro-ny użytkowej program, który ma najdłuższą tradycję ze wszystkich produktów gamy Au-todesk. Uznany za standard w dziedzinie fil-mowej animacji postaci, a w grach coraz czę-ściej z tego powodu wykorzystywany do two-rzenia modeli.

Posiada zintegrowaną platformę ICE (Interactive Creative Environment), będącą zbiorem narzędzi pozwalającym składać z klocków efekty wizualne. Nowej generacji silnik GigaCore zapobiega zawieszaniu się programu przy obiektach składających się z wielu milionów punktów, a przy okazji znacznie przyspiesza obróbkę dużych scen i plików.

Jedno z najciekawszych narzędzi to Face Robot, będący do tej pory osobnym produk-tem, teraz zintegrowany z Softimage, służą-cy tworzeniu w pełni animowalnych siatek, mimiki i realistycznie wyglądających twa-rzy – szczególnie użyteczna funkcja, gdy w grze trzeba stworzyć wielu różnych bohate-rów, a na modelowanie każdego osobno nie ma czasu.

Całość w pełni współpracuje z pozostałymi produktami Autodesk – w Polsce jeden z naj-większych producentów gier wykorzystuje właśnie równolegle Softimage i 3ds Max.

MotionBuilderZaawansowana animacja postaci (nie tylko humanoidów) wymaga często wielu godzin spędzonych przed komputerem. Naprzeciw oczekiwaniom animatorów wychodzą mo-duły (Character Animation Toolkit i Character Studio) do wspomagania tego procesu od two-rzenia szkieletów, przez łączenie ich z siatka-mi, aż po końcową sekwencję animacji ru-

chu. Możliwość pobrania danych z systemów Motion Capture i późniejsza ich obróbka da-

je spore możliwości, jednak na pewnym eta-pie precyzji zaczyna brakować dbałości o de-

Rysunek 1. Autodesk 3ds Max 2010, przygotowanie skóry postaci

Rysunek 2. Autodesk Maya 2010, malowanie trójwymiarowej trawy bez modelowania

Rysunek 3. Autodesk Softimage 2010, miksowanie animacji ruchu

Page 66: SDJ_02_2010_PL

02/201066

Programowanie gier

tale animacyjne w dopasowaniu do konkret-nej sytuacji.

Dlaczego warto zainwestować w dodat-kowy program tylko do generowania ani-

macji ruchu? Jak zwykle odpowiedzią bę-dzie to, czego oczekujemy od produktu fi-nalnego i czasu (a tym samym kosztów), ja-kie możemy poświęcić na proces produk-cyjny.

Jeżeli wierzyć twórcom MotionBuilde-ra – pozwala on skrócić czas przygotowa-nia animacji o przeszło 250%! To wszyst-ko dzięki prewizualizacji, automatycznym modyfikacjom skali, układu kończyn, nie-destrukcyjnej edycji kluczy i dopasowaniu do modelu. Większość dużych game deve-loperów na świecie używa właśnie Motion-Buildera do animacji postaci – można by rzec, że jest to industry standard.

MudboxW wirtualnym rzeźbieniu konkurencja jest silna, a Autodesk nieśmiało od kilku lat sta-wia w tej dziedzinie pierwsze kroki. Nie-mniej jednak ma jedną niesamowitą zaletę – pełna integracja z pozostałymi produkta-mi tej marki. Oczywiście kwestią gustu jest przyzwyczajenie do interfejsu, ale funk-cjonalność podstawowa narzędzi wszyst-kich niezależnych producentów pozostaje zbliżona. Narzędzia rzeźbiarskie, warstwy, wypalanie tekstur – to cecha wspólna, ale jednak jest coś ciekawego w Mudbox, co, poza wspomnianą integracją, podnosi je-go użyteczność (i to wysoko) ponad stan-dard. Niespotykana gdzie indziej precyzja podglądu czasu rzeczywistego tego, co two-rzymy, dzięki której efekty naszej pracy wi-dać od razu. Teraz już nie trzeba poprawio-nego modelu, materiałów czy shaderów im-portować do aplikacji 3D, żeby zobaczyć fi-nalny efekt swojej pracy – wszystko widać w MudBox i to bez renderingu!

Maya Composite i MatchMoverJak już stworzymy wspaniałą grę o nietu-zinkowej fabule, zaczynamy się zastanawiać nad opakowaniem jej w przepiękną anima-cję intro. Wtedy zaczynają się schody. Są fir-my, które zlecają takie rzeczy na zewnątrz, ale są też takie, które mają tak dobry zespół ludzi, że grzechem byłoby pominąć ich ta-lent w tej części produkcji. Autodesk włą-czył te dwie aplikacje w skład pakietu Maya i dzięki temu stworzył kompleksowe rozwią-zanie filmowe.

MatchMover jako narzędzie śledzące ruch pikseli obrazu wideo potrafi stwo-rzyć pełny ruch kamer i matrycę punktów referencyjnych pozwalających skompono-wać obraz wideo z animacją. Pomimo oczy-wistych zalet w tworzeniu efektów filmo-wych należy pamiętać, że coraz częściej w grach kamery prowadzi się jak w hollywo-odzkiej krainie snów, a MatchMover jest najprostszym sposobem na uzyskanie efek-tu ujęcia kinowego.

Rysunek 4. Autodesk MotionBuilder 2010, przygotowanie kinematyki człowieka

Rysunek 5. Autodesk Mudbox 2010, rzeźbienie trójwymiarowe

Rysunek 6. Autodesk Maya 2010, MatchMover, śledzenie ruchu kamery w przestrzeni trójwymiarowej

Page 67: SDJ_02_2010_PL

www.sdjournal.org

Maya Composite (znany wcześniej jako Toxik) jako system kompozycyjny pozwoli wykończyć animację i finalnie połączyć jej elementy w jedną całość jak Photoshop – tyl-ko w ruchu. Włączenie Maya Composite do pakietu pozwoli nam zaoszczędzić kilka ty-sięcy na dodatkowym oprogramowaniu przy zachowaniu zgodności formatu. Całą ani-mację można podzielić na „pasy” renderingu i po złożeniu w Maya Composite uzyskać wy-sokiej jakości końcowy efekt bez mozolnych prerenderingów. Efekty specjalne, korekty kolorystyczne, głębia ostrości i inne procesy zajmujące wiele godzin obliczeń w tej aplika-cji robione są niemal natychmiast.

Poza powyższymi, znanymi produktami, należy zwrócić uwagę na kilka mniejszych elementów układanki (co nie znaczy mniej po-trzebnych), które w procesie tworzenia mogą zapewnić zwiększenie wydajności, a z których istnienia niewiele osób zdaje sobie sprawę.

• ImageModeler – na podstawie już kilku zdjęć pozwala na wymodelowanie rze-czywistych obiektów trójwymiarowych. Im więcej zdjęć, tym dokładniej zostanie wykonane modelowanie (w procesie au-tomatycznym lub manualnym), a do ca-łości zostanie dołączona tekstura z koor-dynatami mapowania.

• Stitcher – skleja dowolną ilość zdjęć w panoramy, sfery i hemisfery.

• Human IK – zaawansowane rozwiąza-nie do tworzenia w czasie rzeczywistym animacji humanoidów na potrzeby gier z uwzględnieniem interakcji z otoczeniem.

• Kynapse – sztuczna inteligencja środo-wiskowa pozwalająca między innymi na generowanie zachowań postaci i tłumu w zastanych warunkach lokacji gry.

• FBX – międzyplatformowy format wy-miany danych, pozwala w jednym stu-diu wykorzystywać całą gamę aplikacji (nie tylko Autodesk!) i wymieniać mię-dzy nimi (w zakresie funkcjonalności) bezstratnie dane.

Oczywiście o każdym z programów moż-na napisać osobny artykuł, a nawet książ-kę, ale zebranie wszystkiego w jednym miejscu z krótkim opisem pozwoli zo-rientować się w sytuacji i wybrać wła-ściwy zestaw dla siebie. Nie bez znacze-nia jest również fakt popularności Auto-desk w branży kreatywnej ze względu na to, że nawet brak wiedzy na danym etapie projektu pozwala na szybkie rozwiązanie problemu dzięki tysiącom informacji za-wartych na forach internetowych, stro-nach pasjonatów 3D czy w tutorialach. Popularność świadczy o sile oprogramo-wania, ponieważ pozwala szybko rozwią-zać skomplikowane problemy i nigdy nie pozostaniemy bez pomocy, a to chyba jed-na z najistotniejszych funkcji, jakiej po-trzebujemy!

Rysunek 7. Autodesk Maya 2010, Composite, kompozycja obrazu

ARKADIUSZ WYCHADAŃCZUKPasjonat filmowych efektów specjalnych oraz animacji 3D. Zajmuje się obrabianiem oraz two-rzeniem grafiki i obrazu ruchomego od niemal piętnastu lat. Zdobył doświadczenie zawodowe współpracując ze większością polskich (i niektó-rych europejskich) studiów postprodukcyjnych oraz stacji telewizyjnych, zarówno jako twórca wizualny jak i doradca oraz specjalista technicz-ny. Pozwoliło mu to uzyskać wszechstronną wie-dzę w każdej gałęzi twórczej związanej z nowy-mi mediami.

Page 68: SDJ_02_2010_PL

02/201068

Programowanie gier

Zapewne wiele razy spotkałeś się z listin-gami programów pisanych w takich ję-zykach jak C++, Java, C# czy innych.

Pojedyncze linie tworzą funkcje lub procedury, z których składa się program. Jeżeli poznałeś je-den z takich języków, na pewno wiesz, że dużo pracy kosztuje poznanie nazw funkcji, składni , a potem pilnowanie, aby podczas pisania progra-mu nie pojawiła się literówka czy też czeski błąd albo parametry funkcji podane w odwrotnej ko-lejności, co może spowodować, że programu nie uda sie skompilować.

Z drugiej strony może niekoniecznie chcesz być programistą. Ja np. nigdy nie chciałem nim być. Chciałem po prostu móc wprowadzić inte-rakcję do wykonywanych przez siebie scen i mo-deli 3D. Z pomocą przyszło właśnie programo-wanie wizualne.

Charakteryzuje się ono tym, że twój program w wersji źródłowej nie jest już wieloma liniami kodu. W programowaniu wizualnym źródło twojego programu stanowią bloki (channels) i połączenia między nimi, wizualizowane jako graf. Głównym narzędziem nie jest klawiatura, tylko myszka. Poszczególne klocki grafu możesz

łączyć ze sobą, po prostu przeciągając linię od jednego do drugiego. Na liniach łączących widać czarną strzałkę informującą, w którym kierun-ku danego połączenia przekazywane są informa-cje. Większe fragmenty grafu możesz zwinąć do postaci folderu (patrz Rysunek 1), aby struktura twojego programu była przejrzysta i czytelna dla innych pracujących nad tym samym projektem. W ten sposób programujesz wizualnie.

Metoda tworzenia w pełni funkcjonalnych aplikacji Quest3D sprawia, że jest on narzę-dziem dla wszystkich. Pracują w nim graficy-3D, projektanci, inżynierowie i ludzie ze świata rozrywki, bo każdy może po prostu usiąść i za-cząć prace – bez wielomiesięcznych kursów na-uki języka. To jest właśnie serce Quest3D.

Aplikacje 3D programowane wizualnieQuest3D jest jednocześnie wizualnym środo-wiskiem programistycznym oraz wydajnym sil-nikiem renderującym 3D. Służy do tworzenia aplikacji 3D. Zarówno tych małych wirtual-nych spacerów, prostych gier 3D, jak i większych i sieciowych produkcji widzianych z różnych ka-mer. Jednocześnie tworzymy w nim duże sy-mulatory obliczane przez kilkanaście maszyn. Jest zatem środowiskiem bardzo skalowalnym. Wszystkie wymienione rodzaje aplikacji stwo-rzysz bez wpisania choćby jednej linii kodu.

Wyobrażam sobie, że wielu rdzennych progra-mistów może poczytać to jako minus aplikacji.

Kiedy prezentuję obraz znajomym firmom czy też na targach, bardzo często słyszę ...acha, czyli nie mogę programować tradycyjnie? I poprawiam ich Nie w tym rzecz, że nie możesz, ale że nie musisz, bo nie ma takiej potrzeby.

Mamy do dyspozycji channnele, które może-my wypełnić za pomocą LUA Script i tam stwo-rzyć swój mały kawałek kodu. Ponadto kiedy ktoś się bardzo uprze, zawsze ma do dyspozycji darmowe biblioteki SDK i może pisać w C++ swoje własne autorskie channele, a następnie używać ich w środowisku Quest3D na takich sa-mych zasadach jak wszystkie inne.

Dzięki temu możliwości nigdy się nie koń-czą, a program rozwija się zarówno w pracowni producenta, jak i w domach i firmach jego użyt-kowników.

Programowanie wizualne 3D na przykładzie Quest3DWydajność wewnętrznego silnika 3D nie ugina się pod sceną zawierającą wiele milionów trójką-tów (face'ów). Najwiekszą jednak zaletą progra-mowania wizualnego w Quest3D jest szybkość budowania aplikacji. Ja nazywam to wydajnością programowania, która zestawiona z mocnym sil-nikiem renderującym pozwala na naprawdę wie-

Programowanie wizualneNiniejszy tekst to informacje, przykłady i tutorial przybliżające technologię Quest3D. Artykułem tym chcę pokazać, że można tworzyć aplikacje 3D i gry szybko oraz bez znajomości języka programowania, bibliotek DirectX czy OpenGL. Jeszcze raz – można. Jest do tego gotowe narzędzie. Resztę znajdziecie poniżej.

Dowiesz się:• Co to jest programowanie wizualne;• Czym Jest Quest3D;• Czym nie jest :);• Jak szybko tworzyć aplikacje 3D bez znajo-

mości języka programowania.

Powinieneś wiedzieć:• Co to jest aplikacja 3D.

Poziom trudności

Co to takiego?

Tutorial na płycieAby ułatwić początkującym i tym po prostu ciekawym zaznajomienie się z podstawa-mi Quest3D, przygotowałem videotutorial, który przekazuję wraz z niniejszym nume-rem SDJ. Jest to krótka lekcja ukazująca krok po kroku stworzenie nieskomplikowanej gry dla jednego gracza. Gatunek: western. Każdy jest w stanie wykonać taką aplikację , postępując za tutorialem w przeciągu kilku godzin. Później pozostaje kwestia wymiany obiektów na swoje. Zapraszam serdecznie do zapoznania się z tutorialem.

Page 69: SDJ_02_2010_PL

Programowanie wizualne

le. Dla przykładu dość statyczna scena ukazana na pierwszych dwóch obrazkach powstała po-przez jedno przeciągnięcie zbioru Quest'owych klocków zapisanych na liście Templates. Są to go-towe do użycia większe i mniejsze grafy fragmen-tów aplikacji. Wystarczy je przeciągnąć do grafu i po prostu połączyć z resztą naszych klocków. To jest ogromnie przyspieszenie i uproszczenie dla programistów. Aby dodać do naszej sceny kame-rę orbitującą wokół obiektu, wystarczy usunąć z grafu nasze Basic Camera i zamiast tego przecią-

Rysunek 1. Przykład grafu - prosta aplikacja 3D

Rysunek 2. Efekt grafu powyżej – scena 3D w czasie rzeczywistym

Rysunek 3. Fragment zbioru szablonów gotowych do użycia – Templates

R E K L A M A

Page 70: SDJ_02_2010_PL

02/201070

Programowanie gier

gnąć z listy Templates nowy zestaw klocków zapi-sany pod nazwą Object Inspection Camera.

W Quest3D w bardzo prosty sposób możesz oprogramować urządzenia do sterowania takie jak klawiatura, mysz, joystick i inne, włączając w to wirtualne rękawice czy chełmy. Co więcej, to środowisko integruje w sobie elementy niezbęd-ne w wielu grach (silnik fizyczny, generowanie tłumu, szukanie drogi). Pisząc grę metodą tra-dycyjną, sam musiałbyś je zintegrować. Modele używane w tym środowisku można wykonać za pomoćą dowolnego pakietu graficznego 3D, jak chociażby 3ds max, maya, blender, cinema, a na-stępnie zaimportować do Quest3D poprzez je-den z uniwersalnych formatów: COLLADA, X, a wkrótce również FBX.

Na Rysunku 1 widać graf przedstawiający pro-stą scenę 3D. Każdy, kto spotkał się już z grafiką 3D i zasadami tworzenia takich obrazów, wie, ze każda taka scena składa się z 3 grup elemen-tów: obiekty 3D – w tym przypadku napis trój-wymiarowy oraz podłoże, światło, które owe obiekty oświetla abyśmy mogli je zobaczyć – w tym przypadku zamknięte w folderze o nazwie

Directional Light, oraz kamera, która ową scenę obserwuje z zadanego punktu widzenia – u nas Basic Camera. Wszystko to podłączamy do kloc-ka Render odpowiedzialnego za wyświetlenie na-szego grafu jako obraz 3D w aplikacji. Na Rysun-ku 2 widać efekt tej najprostszej aplikacji 3D.

Przykłady aplikacji wykonanych w Quest3DJak pisałem wcześniej , środowisko to jest bardzo skalowalne. Na kilku przykladach postaram się przybliżyć najbardziej znanych albo zasługują-cych na uznanie aplikacje wykonane przez róż-ne firmy z bardzo rozbieżnych branż. Wiele firm używa tego środowiska do celów promocyjno-re-klamowych. Przykładowo, w branży dewelope-rów mieszkań, domów i innych nieruchomości jest to idealne rozwiązanie. Firma nie musi na-wet zatrudniać programisty.

Możliwości silnika Quest3DŚrodowisko Quest3D jest silnikiem komplet-nym. Znaczy to, że zawarte w nim moduły i pod-silniki wystarczają do zrobienia sporych rozmia-

rów gry. Do najważniejszych należy zaliczyć Sil-niki fizyczne: ODE, Newton, jest też płatny do-datek: PhysX. Możemy wybrać ten, który będzie spełniał nasze oczekiwania. Są zintegrowane i go-towe do użycia. Czeka na nas także mechanizm oświetlenia słonecznego ustawianego za pomo-cą godziny, pory roku i położenia geograficznego. Mamy do dyspozycji również takie parametry jak wiatr, deszcz czy zamglenie - słowem, kompletny system pogodowy (Weather System).

W Quest3D można również tworzyć aplika-cje sieciowe, i to na dwa sposoby. Pierwszy to ne-tworking budowany na bibliotekach DirectPlay wchodzących w skład DirectX. Drugi to tzw. soc-kety. Z kolei do optymalizacji większych map te-renów służy moduł Nature Painting, który po-trafi doskonale zoptymalizować tereny miejskie oraz wielkie zalesione połacie. Producent w ko-lejnej wersji Quest3D zapowiada także rewolu-cyjny edytor terenu. Wewnątrz programu Qu-est3D mamy możliwość tworzenia własnych shaderów w jęz. HLSL lub wczytywania plików .fx. Dostępny jest również global shader.

Z działki animacji warto wspomnieć o ani-macji szkieletowej, mikserze sekwencji aminacji (tzw. motion blending), potrzebny może okazać się wbudowany system tkanin (cloth rendering). Jeżeli postaci będzie więcej, z łatwością będziesz mógł skorzystać z generowania tłumu za pomo-cą crowd rendering.

Do wyliczonego zbioru należy doliczyć jeszcze gotową realistyczną wodę, zaawansowany system cząsteczek, płynną zmianę ruchu (np. ze space-ru w bieg), ekspozycję światła HDR, obsługę baz danych (mySQL i ODBC), obsługę wielu forma-tów dźwiękowych, oczywiście z dźwiękiem 3D, renderowanie na wielu komputerach tej samej sceny. Na koniec to wszystko możemy wyren-derować jako aplikację 3D (stereorendering lub anaglif). Pełne porównanie z opisem modułów znaleźć można na stronie www.quest3D.pl.

Jak widzisz, jest w czym wybierać. To wszyst-ko jest już w środku i nie wymaga dogrywania ja-kichkolwiek pluginów czy innego rodzaju roz-szerzeń. Zainstaluj i używaj.

Rysunek 4. Agencje PR tworzą za pomoca Quest3D małe i sympatyczne gry reklamowe oraz prezentacje, które całkowicie mieszczą się w jednym pliku EXE i można je łatwo przesyłać Internetem, a uruchomienie aplikacji nie wiąże się z instalacją czegokolwiek. Po prostu jeden plik exe działający na zasadzie Click&Run

Rysunek 5. Pełnowymiarowe gry 3D tworzą zarówno duże studia, jak i małe grupy. W tych drugich często nawet nie ma programisty, tylko kilku grafików 3D i animatorów

Rysunek 6. Ponadto jednymi z najczęściej wykonywanych aplikacji w Quest3D są konfiguratory, aplikacje szkoleniowe 3D (tzw. trenażery), symulatory i różnej maści aplikacje do wirtualnej rzeczywistości (m.in. projekcja na 4 ściany, obsługa rękawic i hełmów wirtualnych)

ARKADIUSZ BRZEGOWYEkspert i certyfikowany trener Quest3D. Partner holenderskiej firmy Act3D, producenta technolo-gii. Właściciel firmy oferującej oprogramowanie Quest3D oraz wsparcie techniczne dla twórców. Założyciel i opiekun Quest3d.pl.Kontakt z autorem: [email protected]

W Sieci

• quest3d.pl – strona główna i podstawowe informacje o technologii i zastosowaniu;

• forum.quest3d.pl – podstawowe źró-dło pomocy, jeżeli zaczniesz używać Quest3D, również linki do tutoriali;

• support.quest3d.com – aktualna doku-mentacja online w j. Angielskim.

Page 71: SDJ_02_2010_PL
Page 72: SDJ_02_2010_PL

02/201072

Programowanie gierProducent

www.sdjournal.org 73

Gry komputerowe, czyli przemysł fil-mowy XXI wieku. Coraz więcej z nas gra w gry, coraz więcej z nas uczestni-

czy w ich tworzeniu. Gry przedostają się powo-li do środków masowego przekazu, otrzymują nagrody i oklaski za całokształt, doświadczenia gracza oraz dziesiątki innych kategorii. Przemysł filmowy ma swoje gwiazdy, są nimi aktorzy, re-żyserowie, scenarzyści, okazjonalny operator. Wszyscy znają ich nazwiska i prace.

Tak jak i w filmach, przemysł gier kompute-rowych ma swoje gwiazdy. Tymi gwiazdami są designerzy, artyści, programiści – ludzie, któ-rzy tworzą rzeczy widoczne, rzeczy, do których można się odnieść, tworzą elementy w pew-nym stopniu namacalne. Tu przechodzimy do pozycji producenta, która jest zazwyczaj moc-no w cieniu.

Obiegowa opinia wśród zespołów projekto-wych jest taka, że producent tak naprawdę nic nie robi. Producent, wbrew nazwie, sam ni-czego tak naprawdę nie produkuje, nie tworzy grafiki, nie tworzy kodu, czasem przyjdzie po-przeszkadzać designerowi, ale zazwyczaj się go nie widzi. Siedzi przy arkuszu i tworzy jakieś ta-belki, więc czemu miałby być pretendentem do gwiazdy przemysłu gier?

Zastanówmy się więc: kim właściwie jest pro-ducent? W dużym uogólnieniu, producent jest odpowiednikiem Project Managera (czyli Kie-

rownika Projektu) w biznesie gier komputero-wych. Kim zatem jest w szczególe? W swoim biznesie producent jest Project Managerem, pro-gramistą, jasnowidzem, grafikiem, designerem, testerem, głosem rozsądku, planistą, strażakiem, facetem od pizzy, salesmanem, najlepszym kole-gą i bezlitosnym szefem.

Przedstawmy pokrótce każdą z tych ról, co by słów na wiatr nie rzucać.

• Project manager – zdawało by się, że głów-na rola producenta. Pilnowanie harmo-nogramu, regulacja zasobów, rozmowa z klientem, czytanie poczty i dokumentacji. Pozycje takie jak rozmowa z klientem po-trafią urosnąć do rozmiarów, gdzie wyma-gałyby osobnego etatu.

• Programista – nie każdy producent ma wiedzę techniczną, w większości wypad-ków nawet nie powinien, bo zarządzanie projektem to nie jest dobre miejsce dla do-brego programisty. Wiedza programistycz-na jednak się przydaje, po pierwsze, poma-ga producentowi zrozumieć, o czym pro-gramiści do niego mówią. Po drugie, po-zwala łatwiej ocenić, czy rzeczywiście na-pisanie Hello World w OpenGL zajmie do-świadczonemu programiście trzy dni ro-bocze. Po trzecie, daje możliwość dialogu z programistami w momentach impasu i nieurodzaju intelektualnego.

• Jasnowidz – drugi, pełny etat producen-ta. Trzeba umieć przewidzieć wszystko, co może się stać w najbliższej przyszłości, i za-cząć się do tego przygotowywać. Problemy z oprogramowaniem, narzędziami, ciąża, grypa, kreatywny klient, skrócenie deadli-

ne’u o połowę. Na to wszystko trzeba mieć odpowiedź, im pełniejszą, tym lepiej.

• Grafik – Graficy dostają od designera gene-ralne wytyczne co do tego, co mają wyko-nać, a reszta jest tak naprawdę przejawem ich własnej inwencji twórczej. Tutaj właśnie wkracza producent, bo taka inwencja musi podlegać kontroli. Często trzeba kontrolo-wać, czy inwencje dwóch grafików, pracu-jących równolegle nad projektem są zbież-ne, bo nie chcemy skończyć z budowlami rodem z Art Deco w środku wioski elfów. Należy też zwracać uwagę na sposób wyko-nania poszczególnych elementów, bo kulka złożona z 10 tysięcy wielokątów nie poto-czy się zbyt płynnie na urządzeniu, które-go wydajność można w skrócie ocenić jako dwa kalkulatory sklejone taśmą klejącą.

• Designer – producent, zwłaszcza przy ma-łych projektach, pełni też rolę designera, aby oszczędzić na kosztach, nigdy nie jest to dobry pomysł. Przy większych projek-tach zawsze deleguje się osobnego designe-ra, ale jako że jest to etat posiadający licen-cję na inwencję twórczą, należy go kontro-lować. Designer, jak każdy artysta, chce być unikalny i jedyny w swoim rodzaju. Dzięki przypływowi takiej weny jest w stanie wy-myślić takie elementy gry, o jakich nie bę-dzie się śniło naszym dzieciom ani nasze-mu budżetowi. Trzeba więc filtrować ta-kie pomysły, aby na następnym spotkaniu projektowym nie zorientować się, że zespół przez ostatnie 2 tygodnie pracował nad czymś, co zupełnie nie nadaje się do naszej gry.

• Tester – mimo tego iż każdy projekt ma swój zespół QA, świeże spojrzenie produ-centa, nie tylko na błędy w grze, ale także na grę jako całość ma zbawienny wpływ na jakość końcowego produktu. Poszczególne zespoły są tak zajęte swoimi zadaniami, że nie są w stanie zauważyć faktu, iż niektóre elementy, nawet sąsiadujące, nie współgrają ze sobą.

• Planista i głos rozsądku – często zespół chciałby coś dodać, odjąć czy zmienić, i za-

ProducentRola producenta w procesie budowania gry komputerowej bywa, szczególnie dla osób postronnych, dość niejasna. Czytając poniższy artykuł, przekonasz się, kim jest producent i jaka jest jego rola we wspomnianym procesie.

Dowiesz się:• Jakie są codzienne obowiązki producenta;• Z jakimi problemami producent boryka się

na co dzień;• Jak wiele ról pełni producent;• Jak producent postrzegany jest przez resztę

zespołu.

Powinieneś wiedzieć:• Co to jest gra komputerowa.

Poziom trudności

A kto to jest?

Page 73: SDJ_02_2010_PL

02/201072

Programowanie gierProducent

www.sdjournal.org 73

daniem producenta jest ocena, czy dana funkcjonalność wpłynie rzeczywiście do-datnio na jakość produktu i czy trzy-tygo-dniowy poślizg związany z implementacją tej funkcjonalności jest dopuszczalny. Nie mówiąc o tym, że na trzech tygodniach może się to nie skończyć. Czasem trzeba podjąć kilka niepopularnych decyzji, aby projekt jednak miał szanse sukcesu.

• Strażak – jak sama nazwa wskazuje, stra-żak gasi pożary. Pożarami nazywamy wszystkie nagłe wypadki w projekcie, któ-re w pośredni lub bezpośredni sposób wpływają na wykonalność projektu w za-mierzonym czasie, lub w ogóle. Dobrymi przykładami jest odejście głównego deve-lopera, zmiana terminu oddania projektu lub odnalezienie krytycznej luki w zaku-pionej technologii. W takich wypadkach producent zaczyna robić wszystko, co tyl-ko możliwe, aby załagodzić następstwa ta-kich wydarzeń. Ekspresowa rekrutacja, długie negocjacje z klientem lub telefony co godzinę do dostawcy technologii, aby do-wiedzieć się, czy błąd już został naprawio-ny. Wszystko to zazwyczaj poza zasięgiem wzroku całego zespołu.

• Facet od pizzy – najłatwiejszy z obowiąz-ków. Jeśli już cały zespół musi siedzieć w nocy, tak aby wyrobić się na określony ter-min, to trzeba go nakarmić. Zamówić piz-ze, podać, opłacić i uśmiechać się ładnie. Im mniej developerzy muszą nad tym myśleć, tym lepiej.

• Sprzedawca – tutaj producent współpracu-je z innymi sprzedawcami w czasie wstęp-nych negocjacji z klientem, aby ustalić listę funkcjonalności, która jest wykonalna w zamierzonym czasie i budżecie. Sprzedaw-cy często mają problemy z oceną możliwo-ści wykonania projektu, co najczęściej koń-czy się ujemnymi budżetami oraz setkami nadgodzin w projekcie, dlatego też należy przypilnować tego, aby sprzedać klientowi coś, co rzeczywiście jest wykonalne przez nasz zespół. Koniec końców, jeśli coś nie wyjdzie, to oczywiście jest to wina produ-centa. Naturalnie.

• Najlepszy kolega – to właśnie producent. Zawsze wysłucha swoich ludzi, poradzi im, co mogą robić w krytycznych sytuacjach, i da urlop przy nagłych wypadkach. Zrozu-mie, jeśli ktoś przyjdzie później po bardzo wesołej nocy.

• Bezlitosny szef – to także producent. Taki tryb włącza się zazwyczaj pod koniec pro-jektu. Godziny pracy są stałe i często bar-barzyńsko wczesne. Urlopy zostały prze-łożone na następny rok, a każde spóźnie-nie kończy się wizytą na dywaniku i poga-danką o tym, jak ważny jest projekt i jak za-wiedliśmy wszystkich swoim nieodpowie-dzialnym zachowaniem.

Mimo tego, że pozornie obowiązków tych wy-daje się być dużo, wśród developerów panuje obiegowa opinia, że producent to taki człowiek, który nic tak naprawdę nie robi. Widzi się go z rana na spotkaniu, pogada coś, a potem przy-chodzi, tylko jeśli coś idzie nie tak. Podobnie jak z administratorami sieci, praca producenta jest najbardziej widoczna, kiedy on sam nie ma co robić. Jeśli wszystko jest dopięte na ostatni gu-zik i współgra ze sobą jak trybiki w zegarku, to producent nie ma już czego poprawiać. Oczy-wiście do czasu, kiedy wybuchnie następny po-żar. Wtedy nasz bohater musi założyć swój ska-fander, wziąć gaśnicę pod pachę i ratować swój (a także nasz i wasz) projekt przed totalną ka-tastrofą.

Katastrof jest co nie miara, uwierzcie mi. Na każdym kroku w projekcie czyhają tylko, aby za-atakować zespół i puścić projekt z dymem. Wte-dy właśnie producent wkracza do akcji, aby wy-konać swoją niewdzięczną akcję ratunkową, o której mało kto tak naprawdę się dowie. O pro-blemach nie mówi się w zespole, problemy dzia-łają bardzo demotywująco. Programiści, graficy i cała reszta ferajny nie musi wiedzieć o tym, że klient miał kolejny genialny pomysł i chciał z na-szej gry akcji zrobić puzzle. Takie informacje wy-wołują nieprzyjemne pomruki dezaprobaty na sali, a niektórym developerom podnoszą w bar-dzo widoczny sposób ciśnienie. Takie skoki ci-śnienia i pomruki są wrogiem najlepszej przy-jaciółki producenta – produktywności, a bez niej problemy zaczynają się mnożyć jak gremli-ny karmione po północy.

Głównym problemem, wynikającym z braku produktywności, są oczywiście opóźnienia. Jed-nak zakres tych opóźnień często przybiera for-mę wykładniczą. Z podstaw informatyki wie-my, iż cały system działa tak szybko jak najwol-niejsza jego część. Opóźnienie jednego zespo-łu często oznacza opóźnienie innych zespołów, przestoje i puste przebiegi. Dla przykładu, jeśli zespół grafików nie dostarczy na czas potrzeb-nych elementów, programiści nie będą w stanie ich poprawnie zaimplementować, a zespół testo-wy nie będzie w stanie zweryfikować poprawno-ści tej implementacji. Cały moduł, a wraz z nim cała gra zaczyna się obsuwać, a zespół generuje puste godziny, na których cierpi budżet gry. Jeśli taki okres się przeciągnie, to możemy znaleźć się w sytuacji, gdzie nie będziemy mieć już budżetu na ukończenie gry jako takiej.

Zadaniem producenta jest więc pilnowa-nie wydajności i monitorowanie jakości całego projektu. Producent jest swego rodzaju firewal-l’em między zespołem projektowym a wszyst-kimi czynnikami zewnętrznymi. Dba o to, aby jedynym zmartwieniem programistów był ro-dzaj algorytmu sortowania,jaki mają zastoso-wać, a wszystkie problemy takie jak zmiany za-kresu projektu, zmiany w terminach, zmiany w zasobach, etc. były ponad ich głowami. Z drugiej strony, producent jest odpowiedzialny za to, co

programiści oraz inne zespoły wykonają, i jeśli jego zespół nie wyrabia się w czasie, to musi on znaleźć kreatywne rozwiązania, skróty i sztucz-ki, aby jednak osiągnąć cel w zamierzonym cza-sie. Często przypomina to żonglerkę płonącymi kotami podczas jazdy na monocyklu, ale za to w końcu mu płacą.

Reasumując, życie producenta to nieustan-na walka ze światem. Ekwilibrium przenosze-nia zasobami, tak aby jednak wszystko się uda-ło, mimo iż na papierze jasno widać, że czasu jest o kilka tygodni za mało. To także, jak u każ-dego managera, kompromisy między byciem efektywnym kierownikiem a byciem przyjacie-lem swoich ludzi i motywowanie ich w delikat-ny sposób. Bycie producentem to także posiada-nie wiedzy na każdy temat oraz części umiejęt-ności każdego z członków zespołu, tak aby móc wpasować się między nich, a także podejmować decyzje poparte konkretną wiedzą. Najważniej-szą jednak częścią jest posiadanie wizji projek-tu i motywacji do jego ukończenia. Pracowni-cy często bywają zagubieni, wiele rzeczy w pro-jekcie bywa dla nich nie jasnych, i w takich wła-śnie momentach oczekują od producenta ja-snej wizji i odpowiedzi w 10 sekund. Taką od-powiedź zawsze trzeba mieć, i mieć ją na każdy możliwy temat.

Mam nadzieję, że dzięki temu artykułowi część z Was spojrzy inaczej na swojego produ-centa. Jeśli w Waszym projekcie pojawi się na-gle dodatkowy kolega, przesunie się termin al-bo stanie coś równie ciekawego, co ułatwi wam pracę, to wiedzcie, że nie jest to do końca przy-padkowe zrządzenie losu. Jeśli na kolejnym spotkaniu producent będzie się opierał przed wprowadzeniem kolejnej funkcjonalności do gry, która jest akurat Waszym oczkiem w gło-wie, to wiedzcie, że nie robi tego z czystej zło-śliwości, ale ma plan, w który nie da się wpi-sać wszystkiego. Mimo że pozornie czasu jesz-cze zostało całkiem dużo, to często nie bierzecie lub nie zauważacie pewnych czynników ryzyka, które stoją na drodze projektu. Producent je wi-dzi, w końcu jest także jasnowidzem...

ŁUKASZ SZCZEPAŃSKIPracuje na stanowisku Producenta w firmie Game-lion, wchodzącej w skład Grupy BLStream. Łukasz specjalizuje się w technologiach związanych z pro-dukcją oprogramowania na platformy mobilne, ze szczególnym naciskiem na tworzenie gier. Gru-pa BLStream powstała, by efektywniej wykorzy-stywać potencjał dwóch szybko rozwijających się producentów oprogramowania – BLStream i Ga-melion. Firmy wchodzące w skład grupy specja-lizują się w wytwarzaniu oprogramowania dla klientów korporacyjnych, w rozwiązaniach mobil-nych oraz produkcji i testowaniu gier.Kontakt z autorem: [email protected]

Page 74: SDJ_02_2010_PL

02/201074

Efektywność pracyWspinaczka do profesjonalizmu

www.sdjournal.org 75

Czy potrafisz przypomnieć sobie naukę jazdy na rowerze? To sku-pienie, aby przejechać 10 metrów

prosto? Albo pierwszą jazdę samochodem i skupienie na sprzęgle? I jego zgrzyt? A czy teraz, po latach, potrafisz powiedzieć, co należy zrobić, aby jechać bez trzyma-nia kierownicy, rozmawiając jednocześnie przez telefon i podziwiając inne uczest-niczki ruchu (mam tu na myśli oczywiście jazdę na rowerze)?

Tę właśnie drogę od nowicjusza z fioleto-wymi kolanami do pewnego siebie eksperta opisuje model rozwoju kompetencji opraco-wany w latach '70 ubiegłego wieku przez bra-ci Dreyfus.

Nie jest to bynajmniej czysto teoretycz-ny model akademicki. Model powstał pod-czas badań nad zastosowaniem sztucznej in-teligencji (i jej ograniczeniami) do gry w sza-

chy lub pilotowania samolotu. Mam nadzie-ję, że z uwagi na jego bliskość z naszą branżą okaże się ciekawy i zachęcający do spojrzenia we własne wnętrze. Mam również nadzieję, że zainspiruje on wszystkich czytelników do refleksji nad własną drogą do osiągnięcia pro-fesjonalizmu.

Profesja i Rzemiosło (Craftsmanship) są wartościami nabierającymi coraz większego znaczenia w branży programistycznej – póki co, niestety, szczególnie zachodniej. Zwykle jednak postrzega się je jako metodyki, podej-ścia i warsztat. W niniejszym artykule przyj-rzymy się tym wartościom pod kątem świa-domej i racjonalnej drogi rozwoju w kierun-ku profesjonalizmu.

Model zdobywania kompetencjiKażdy, kto nie jest totalnym cyborgiem, za-pewne ma świadomość, że kompetencje w danej dziedzinie oraz sam ich charakter nie są statyczne. Podlegają zmianom w czasie – zwykle w długiej perspektywie czasu, przez co mogą być trudne do zaobserwowania. Mo-del Braci Dreyfus opisuje taką właśnie dro-gę owych zmian, nazywając i charakteryzu-jąc jej etapy. Pozwala to na zwiększenie sa-

moświadomości, a co za tym idzie na racjo-nalne działanie.

Model jako taki z założenia odnosi się do niemal każdej aktywności człowieka, któ-ra podlega rozwojowi. Jego główna koncep-cja opiera się na istnieniu 5 poziomów, wg których zmienia się nasza percepcja danego problemu oraz sposób, w jaki budujemy jego mentalną reprezentację.

1. NoviceSpotykasz się z daną sytuacją po raz pierw-szy. Jesteś zorientowany na konkretne zada-nia, kroki i potrzebujesz kogoś, kto je Tobie wskaże. Jeszcze nie masz pełnej świadomości szerszego celu. Nie masz żadnej intuicji, za-tem potrzebujesz jasnych, bezkontekstowych reguł postępowania, aby nie pogubić się w no-wym środowisku.

2. Advanced begginer Radzisz sobie coraz lepiej, zaczynasz for-mułować proste zasady. Jednak są to zasady ograniczone do wąskiego kontekstu, ponie-waż nie masz jeszcze zdolności spojrzenia na swą pracę z boku. Póki co, masz problem z radzeniem sobie z pojawiającymi się trud-nościami, dlatego potrzebujesz kogoś, kto będzie podpowiadał Ci rozwiązania – naj-lepiej szybko!

Co ważne, wciąż potrzebujesz kogoś, kto powie Ci, co i jak powinieneś zrobić.

Boję się iść dalejDwa pierwsze poziomy kompetencji w przedstawianym modelu charakteryzują się tym, że ktoś mówi nam CO – i najważ-niejsze JAK musimy zrobić. Potrzebujemy zadań, których jesteśmy jedynie wykonaw-

Wspinaczka do profesjonalizmu

Model kompetencji Braci Dreyfus, który odnosi się nie tylko do umiejętności technicznych, ale również do ogólnej aktywności każdego z nas. Model pozwoli Ci uświadomić sobie swój aktualny poziom kompetencji w dowolnej dziedzinie oraz zaplanować dalszą drogę ich rozwoju. Spojrzenie przez pryzmat modelu na kolegów z zespołu pomoże Ci lepiej komunikować się.

Dowiesz się:• Jak zmieniasz się wraz z rozwojem kompe-

tencji;• Jak zdiagnozować na jakim poziomie jesteś

Ty i Twoi współpracownicy;• Na co zwracać uwagę w komunikacji ze ko-

legami z zespołu;• Jak racjonalnie dobrać strategię swojego

rozwoju

Powinieneś wiedzieć:• „Co lubię w życiu robić?”

Poziom trudności

Modelowa ścieżka rozwoju kompetencji – podejście pragmatyczne

Page 75: SDJ_02_2010_PL

02/201074

Efektywność pracyWspinaczka do profesjonalizmu

www.sdjournal.org 75

cą. Towarzyszy temu oczywiście przyjem-ne uczucie zwolnienia z odpowiedzialno-ści – ja tylko wykonywałem rozkazy. Pamię-tajmy jednak, że dla niektórych z nas strach przed podjęciem odpowiedzialności za wła-sne poczynania może być blokadą przed dal-szym rozwojem.

3. CompetentPrzestałeś borykać się ze szczegółami tech-nicznymi problemu, wszystko to, co na po-czątku zgrzytało, teraz działa dosyć gładko. Pozbycie się tych problemów spowodowa-ło, że jesteś już nastawiony na cel i sam decy-dujesz, jak do niego dotrzeć. Dzięki świado-mości celu sam planujesz kroki i zadania pro-wadzące Cię do niego. Robisz to, ponieważ je-steś już w stanie tworzyć modele koncepcyj-ne problemu, co z kolei pozwala na myśle-nie bardziej długofalowe – planowanie. Two-je rozwiązania nie są może jeszcze najlepszy-mi z możliwych, ale działają. Wypracowujesz swoje własne zasady, ale starasz się jednocze-śnie je weryfikować.

Ponieważ przewidujesz, że mogą istnieć lepsze rozwiązania, że ktoś opracował dobre praktyki i sprawdzone rozwiązania, to szu-kasz informacji u Ekspertów. Prawdopodob-nie nie dowiesz się od nich niczego, ale o tym za chwilę...

Już się zmęczyłemWszyscy mamy tendencję do niewysilania się ponadto, co konieczne. Jest to jak gdyby we-wnętrzny mechanizm mózgu chroniący go przed śmiertlenym przegrzaniem. Dlatego chętnie zatrzymujemy się na poziomie Com-petent. Radzimy sobie już całkiem dobrze i je-steśmy generalnie zadowoleni z rezultatów. Inni są również z nich zadowoleni. Czego chcieć więcej? Czy nie zrobiłem już wszyst-kiego, co można, i nie mogę wreszcie oddać się czynnościom wegetatywnym typu oglą-danie TV?

4. ProficientWejście na ten poziom i osiągnięcie pozio-mu biegłości w danej dziedzinie wymaga wysiłku intelektualnego. Przede wszystkim musisz chcieć. O ile poprzednie poziomy osiągamy liniowo – po prostu powtarzając pewne czynności wielokrotnie – to w przy-padku Proficient samo powtarzanie nie wy-starczy. Mamy tutaj do czynienia z ogrom-nym skokiem jakościowym. Skokiem, który polega na całkowitym zaangażowaniu w da-ny temat i zgłębianiu jego natury poprzez szersze poszukiwania. Poszukiwania te po-legają na kojarzeniu analogii do innych za-gadnień, pogłębianiu zasobów tychże sko-jarzeń.

Na tym poziomie nie jesteś już nastawio-ny na sam cel, ale na zrozumienie celu w róż-

nych kontekstach. Ujrzałeś tak zwany big-ger picture i masz problem... Do tej pory by-łeś kompetentny w swoim malutkim świe-cie, jednak gdy zobaczyłeś, o co tak napraw-dę chodzi i zrozumiałeś, że jest to jednak bardziej skomplikowane niż się wydawało. Wiem, że nic nie wiem – chichocze za Twymi plecami sam wielki Sokrates. Jesteś po pro-stu skazany na dalsze samodoskonalenie w tej dziedzinie.

Frustrują Cię zbytnie uproszczenia, które widzisz dookoła. Stałeś się zapewne dener-wującym typkiem w zespole, który wciąż za-daje pytania. Pytania, których nikt nie lubi – zaczynające się od A dlaczego...?.

5. ExpertWiedziony wrodzoną ciekawością zgroma-dziłeś na poprzednim etapie niemal całą wie-dzę z danej dziedziny. Cały czas poszerzasz ją - szukasz jednak w innych miejscach niż po-przednio. Szukasz analogii i skojarzeń z zu-pełnie odrębnymi domenami. Być może in-spiruje Cię biologia, mechanika, architektura budowli? Syntezujesz pojęcia na wyższym po-ziomie abstrakcji.

Zrozumiałeś, że wszystko zależy od kon-tekstu, dlatego posługujesz się głównie me-taforami, mówiąc o swej dziedzinie. Paradok-salnie to właśnie metafory są najbardziej traf-ne, ponieważ można je interpretować na róż-ne sposoby w zależności od kontekstu. Nie-ustannie poszukujesz lepszych metod.

Co gorsza – zapytany o argumentację swych decyzji, często nie potrafisz jej podać, ponieważ myślisz intuicyjnie. Rozwiązania same pojawiają się w głowie, po prostu wiesz.

Warto zwrócić uwagęOgólnie rzecz ujmując, model zakłada zmia-nę nastawienia. Z początkowej orientacji na procedury i konieczność na późniejsze na-stawienie w kierunku poszerzenia wachla-rza możliwości. Czyli z tego, co musi być na to, co może być.

Należy jasno podkreślić, że model nie opi-suje naszego rozwoju jako całości. Model do-

tyczy zdobywania konkretnej, mniej lub bar-dziej ogólnej kompetencji. Czyli w pewnych nielicznych aspektach możemy być eksperta-mi, a w innych np. nowicjuszami.

Warto zwrócić uwagę na kilka kwestii związanych z naszą postawą w zależności od poziomu kompetencji w danej dziedzinie.

Najpierw ogólnieNiektórzy z nas, poznając nowe zagadnie-nia z ogólnie znanego już zakresu, prefe-rują rozpoczęcie od trzeciego poziomu – Competent. Dzieje się tak, gdy nasze nawy-ki kognitywne kierują nami, aby najpierw poznać ogólnie problem i zrozumieć, o co chodzi. Dopiero później będziemy szukać przykładów, tutoriali itp. Najpierw musi-my poznać całość oraz metodyki obowiązu-jące w danej dziedzinie, aby zdecydować w ogóle, czy to nas interesuje i czy chcemy za-głębiać się dalej.

Błędna samoocenaBędąc na niższych poziomach, mamy tenden-cję do zawyżania swej samooceny. Nawiasem mówiąc, niektórzy mają chyba do tego wro-dzone skłonności.

Natomiast będąc na wyższych pozio-mach, przychodzi pokora i zaniżamy swe kompetencje. Najbardziej niebezpieczni dla siebie samych, tudzież otoczenia jesteśmy na poziomie Advanced Begginer. Wydaje nam się, że osiągnęliśmy wówczas mistrzostwo, ponieważ nie mamy jeszcze wyrobionego szerszego poglądu. Przypomnijcie sobie, kie-dy zaliczyliście najwięcej upadków z rower-ka; a z drugiej strony, kiedy ostatnio do te-go doszło?

Test na ekspertaDave Thomas – autor klasycznej już książ-ki Pragmatyczny programista przedstawia w swych prezentacjach oparty o model Mo-del Braci Dreyfus humorystyczny, ale da-jący do myślenia test na Eksperta. Jeżeli weźmiesz dwóch ekspertów mających od-mienne zdanie na dany temat, to zaczną

Poziomy kompetencji wg Modelu Braci Dreyfus

• Novice – robisz coś po raz pierwszy, nie do końca wiesz, o co chodzi. Jesteś nastawiony na szybką realizację prostego zadania, czegoś, co działa;

• Advanced Begginer – powoli zaczynasz rozumieć, o co mniej więcej chodzi. Twój mózg zaczyna zauważać powtarzalne wzorce i nie da się tego w żaden sposób wyłączyć. Jed-nak w dalszym ciągu potrzebujesz kogoś, kto powie Ci, co trzeba zrobić;

• Competent – całkiem dobrze orientujesz się w wyuczonej domenie, a samodzielne my-ślenie pozwala Ci na planowanie działań na przyszłość. Dzięki temu możesz przejść z wy-konywania zleconych zadań na samodzielną realizację celów;

• Proficient – odkryłeś, że jest jeszcze coś więcej i zrozumiałeś, że tak naprawdę nic nie wiesz. Zaczynasz syntezować wiedzę z innych dziedzin, aby rozumieć więcej i lepiej. Za-dajesz trudne pytania;

• Expert – w większości podejmujesz decyzje intuicyjnie, po prostu wiesz, co trzeba zrobić. Posługujesz się metaforami. Nieustannie szukasz lepszych metod, czerpiąc jednak inspi-rację z innych dziedzin.

Page 76: SDJ_02_2010_PL

02/201076

Efektywność pracyWspinaczka do profesjonalizmu

www.sdjournal.org 77

się spierać. Właściwie to dyskutować, uży-wając merytorycznych argumentów w ce-lu przekonania adwersarza. Z czasem po-winni się nawzajem przekonać do swych ra-cji – czyli niejako wymienić poglądami. Po tym wciąż będą się spierać, aby nawzajem się przekonać do tego, o czym wcześniej by-li przekonani.

Intuicja – czy jest dozwolona?W modelu Braci Dreyfus istnieją dwa nie-jawne atrybuty: potrzeba_reguł oraz opar-cie_na_intuicji. Na pierwszych szczeblach potrzebujmy jasnych reguł, które z cza-sem stają się mniej istotne, a wręcz zaczy-nają przeszkadzać. Ich miejsce zajmuje tak zwana intuicja. Oczywiście nie mamy tu na myśli jakiejś magicznej formy objawie-nia prawdy.

Chodzi o naturalne procesy kognitywne zachodzące w mózgu każdego z nas. Mózg optymalizuje wydatek energii, ucząc się wy-bierania istotnych danych do przetwarzania. Z czasem pobudzenia neuronów sięgają co-raz to niższych warstw kory nowej. W rezul-tacie ekspert wcale nie myśli szybciej czy w ja-kiś sposób lepiej. Myśli po prostu mniej, a co za tym idzie bardziej wydajnie. Po prostu sku-pia się na istotnych danych, różnica polega na tym, że jego mózg dokładnie wie, które dane są istotne.

Normalni ludzie nazywają to zjawisko w skrócie: intuicja.

Czy w branży technicznej jest w ogó-le miejsce na coś takiego jak intuicja? Coś, czego nie da się ująć w ścisłe ramy, zmie-rzyć i opisać, może wydawać się wręcz nie-pożądane. Oczywiście decyzji intuicyjnych nie można uzasadnić i nie można zweryfi-kować formalnie. Nie da się również opisać jej w formie powtarzalnej procedury w celu wdrożenia przez innych. Wady podejścia in-tuicyjnego są bezdyskusyjne, ale musimy się z nimi pogodzić, ponieważ (o ile model Bra-ci Dreyfus jej poprawny) jesteśmy na nie po prostu skazani.

Model w kontekście ITZakładam, że skoro sięgnąłeś po ten artykuł, to interesujesz się własnym rozwojem. Zakła-dam też, że skoro doszedłeś do tego rozdziału, to Model Braci Dreyfus wydaje Ci się w miarę sensowny. Przyjrzyjmy mu się zatem pod ką-tem strategii rozwoju kompetencji technicz-nych w kontekście naszej branży. Zrobimy to zarówno z perspektywy osobistej, jak i zespo-łu – jako systemu ludzkiego.

Zacznijmy od oczywistych oczywistości: oprogramowanie piszą ludzie, nie platfor-my korporacyjne, języki, narzędzia, meto-dyki czy frameworki (najlepiej webowe i najlepiej z zaokrąglonymi rogami każdej, nawet z natury kanciastej formy). Owszem są to bardzo ważne elementy całości, ale nie wystarczające. Głównym elementem proce-su produkcji czy zespołu w ujęciu systemo-wym są ludzie.

Zimna metodyka zorientowana na staty-stycznie optymalne wykorzystanie zasobów ludzkich (piękne sformułowanie, niepraw-daż?) będzie, owszem, działać. Przyjrzenie się każdemu człowiekowi jako jednostce i ele-mentowi większego systemu ludzkiego mo-że zwiększyć produktywność tegoż systemu i przede wszystkim zwiększyć jakość pracy (w sensie spędzania czasu) – a co za tym idzie, jakość życia.

I co ja robię tuJesteś Nowicjuszem – zwykle jest to Twoja pierwsza praca i wszystko wygląda zupełnie inaczej niż na studiach. Aż palisz się, aby za-kodować w jakimś niskopoziomowym języku Transformatę Cosinusową, ale zamiast tego zostajesz wrzucony w sam środek katastrofy Titanica, który na jutro ma zostać wydobyty z dna i do tego ma lśnić na prezentacji przed zarządem lub klientem.

Nie wiesz, od czego zacząć ani po czym po-znać, że już skończyłeś.

Znając Model Kompetencji Braci Dreyfus, wiesz co robić: domagaj się małych, krótkich, dobrze określonych zadań, ponieważ potrze-

bujesz malutkich sukcesów, które zmotywu-ją Cię do dalszego rozwoju. Chcesz po prostu zrobić coś, co działa. Jesteś teraz zbyt obcią-żony, aby dodatkowo jeszcze pogłębiać wie-dzę na temat specyfikacji – po prostu nie w tym momencie. Jeżeli Twój przełożony tego nie wie, to najprawdopodobniej w swoim fa-chu jest na takim samym poziomie jak Ty w swoim.

Rozsądny manager nie powinien wrzucać nowicjusza na głęboką wodę – a przynajm-niej nie każdego. Taki ktoś zacznie paniko-wać, co jest zaraźliwe.

PistoletOkiełznałeś już nieco chaos techniczny wo-kół siebie – jesteś Zaawansowanym Początku-jącym. W dalszym ciągu potrzebujesz zadań zamiast celów, ale radzisz sobie z nimi cał-kiem dobrze. Zbyt dobrze, nie masz jeszcze pokory. Wystarczy nieco piachu na zakręcie, a rowerek pojedzie w swoją stroną, podczas gdy Ty przyciągany efemeryczną siłą grawita-cji z mniejszą lub większa gracją spotkasz się z ziemią. Niestety w naszej branży efekt prze-cenienia swych kompetencji nie jest widocz-ny natychmiastowo.

Błędy wynikające z braku postrzegania szerszego kontekstu problemu wychodzą na-wet dopiero po miesiącach. Zły projekt, krót-kowzroczna architektura, bałagan w kodzie, brak testów – to boli, ale nie od razu.

Kuszą Cię różnego rodzaju obietnice efek-tywnych narzędzi, generujących gotowe roz-wiązania, bez zbędnych konstrukcji, które po-zwalają na uniknięcie przykrej czynności za-stanowienia się nad konsekwencjami ich za-stosowania.

Jeżeli jest taka możliwość, to Zaawanso-wany Początkujący powinien rozwijać się pod okiem doświadczonego mentora, aby jak naj-szybciej opuścić ten poziom, zanim narobi bi-gosu. Jeżeli natomiast nie masz takiej możli-wości, to najlepszą radą będzie uświadomie-nie sobie beznadziejności swego aktualnego położenia, aby jak najszybciej wskoczyć na następny poziom kompetencji.

Rzetelny rzemieślnikOdważyłeś się samemu podejmować decy-zje i brać za nie odpowiedzialność. Jesteś na-stawiony na cele i samodzielnie dobierasz sposób ich rozwiązania. Definiujesz własne standardy, jednocześnie szukając potwier-dzenia poprawności u bardziej doświadczo-nych. Jesteś po prostu osobą, na której moż-na polegać.

Oczekujesz problemów postawionych ja-ko cele oraz autonomii w ich osiąganiu. Konkretne zadania wykopania dołka w da-nym miejscu o zadanych wymiarach są już dla Ciebie deprymujące. Jeżeli przełożony wciąż traktuje Cię jakbyś był Advanced Beggi-

Strategie dla managerów

• Zlecając zadania Nowicjuszowi, pamiętaj, aby były małe i jasno sformułowane. Powin-ny też pozwalać mu na osiągnięcie szybkiego sukcesu, dzięki czemu zwiększa się mo-tywacja;

• Podchodź krytycznie do rozwiązań Zaawansowanego Początkującego. Nie ma on jeszcze dostatecznego obrazu, aby uświadomić sobie słabe strony swoich pomysłów. Ważne jest dla niego, że w końcu działa;

• Przed Kompetentnym stawiaj cele i pozwól mu na samodzielną ich realizację. Zachęcaj go również do poszerzania zakresu kompetencji;

• Bądź cierpliwy dla Biegłego i uszanuj jego pęd do samodoskonalenia. Rozwój i ambitne problemy są dla niego teraz ważniejsze niż np. podwyżka;

• Wybacz Ekspertowi jego różne dziwactwa. Jeżeli któryś raz z kolei miał rację, to warto mu zaufać i dać większą autonomię;

• Zachęcaj Ekspertów, aby przeszli do poziomu Guru. Tak, nie każdy będzie miał odpowied-nie cechy osobowości, ale warto spróbować – to zawsze się opłaca w skali zespołu lub firmy.

Page 77: SDJ_02_2010_PL

02/201076

Efektywność pracyWspinaczka do profesjonalizmu

www.sdjournal.org 77

ner, to musisz zakomunikować swe aspiracje. Komunikacja to podstawa, a brak komunika-cji to źródło większości problemów, więc nie trać czasu ani swego potencjału.

Masz własny styl, ale niestety tylko jeden i do tego na każdą okazję. Szukaj najlepszych praktyk i sprawdzonych wzorców, aby nie utknąć w grząskim gruncie własnych wydaje mi się, że wiem najlepiej.

Zastanów się dobrze, czy to, co robisz, jest twoją pasją. Jeżeli tak, to będziesz miał moty-wację i znajdziesz czas, aby się doskonalić i przejść na kolejny poziom. Jeżeli jednak nie, to nie ma potrzeby, aby zbytnio się frustro-wać, nie wszyscy przecież muszą zajmować się zawodowo tym, czym się pasjonują.

Pan „dlaczego”Na poprzednim poziomie byłeś dobry w swo-im fachu – dosyć dobrze udało Ci się go zgłę-bić i wypracować dobre metody. Teraz za-miast w głąb, szukasz wszerz. Kwestionu-jesz najlepsze praktyki, gdy widzisz, że Cię ograniczają.

Rozglądaj się w poszukiwaniu szerszych problemów i ucz się dobierać do nich rozwią-zanie. Szukaj skojarzeń i paraleli do innych dziedzin. To poszerzy Twój wachlarz możli-wości i nauczy Cię zaliczać problemy do pew-nych klas problemów. Okaże się, że nie każda aplikacja musi być być np. webowa, dany fra-mework nie jest uniwersalnym młotkiem do wszystkiego, a pewien styl architektoniczny nie nadaje się do każdego projektu. Wszystko zależy od klasy problemu. Jeżeli jesteś progra-mistą, to uświadomisz sobie, że języki progra-mowania, o których palmę zwycięstwa spie-rałeś się na studiach, należą tak naprawdę do pewnej wspólnej klasy – różnią się jedynie lu-krem składniowym i detalami technicznymi. Tu chodzi o coś innego, większego.

Możesz mieć problem z irytacją wynikają-cy z otaczających Cię uproszczeń i błędnych, niepodważalnych założeń. Zaczynasz zada-wać pytania podważające firmowe dogmaty. Dlaczego używamy tego podejścia czy roz-wiązania do tego problemu? Dlaczego robi-my to akurat tak? Uważaj... jesteś pierwszy do zwolnienia.

Jeżeli jesteś przełożonym kogoś, kto za-czyna być biegły, to wykorzystaj jego poten-cjał. Przydziel mu ambitną misję wymaga-jącą przekrojowego wysiłku i raczej syntezy niż analizy.

Ale przede wszystkim wybaczaj jego nad-mierne zastanawianie się na błahymi (z Two-jej perspektywy) sprawami. Szanuj biegłego

podwładnego, bo oto na Twoich oczach po-woli rodzi się Ekspert, który po pewnym cza-sie wniesie ogromną wartość dodaną do fir-my.

Po prostu EkspertSwoje decyzje podejmujesz intuicyjnie – gdzieś w pokładach nieświadomości. Jest to jedyny sposób Twojego mózgu na radze-nie sobie z ogromem informacji zdobytych podczas wieloletniego doświadczenia. Zro-zumiałeś, że wszystko zależy od kontekstu, dlatego chętnie posługujesz się metafora-mi, które wg Ciebie najbardziej trafnie od-dają sytuację. Możesz mieć problemy z po-daniem merytorycznych powodów, dla któ-rych powinno postępować się zgodnie z Twoimi pomysłami.

Z tego powodu koledzy będący na pierw-szych szczeblach kompetencji w Twojej do-menie mogą mieć problem ze zrozumieniem, o czym do nich mówisz. Zwróć uwagę, czy gdy wydajesz im polecenie, nie kiwają jedynie głowami – poproś o opisanie zadań w mailu. Bądź miły i uważaj, aby nie sprawiać, że Twoi współpracownicy będą kończyć rozmowę ze łzami w oczach – nic w ten sposób nie osią-gniecie jako zespół.

Jeżeli jesteś przełożonym eksperta, to masz pewien problem – musisz postępować z nim niczym ze świętą krową. Ekspert bardzo do-brze zdaje sobie sprawę ze swej wartości na rynku, bywa zarozumiały, więc nie będzie się zbyt długo zastanawiał nad zmianą barw klubowych.

Przede wszystkim musisz zapewnić mu tak zwany bigger picture. Zdawkowe po prostu zrób to jest dla Eksperta obraźliwe. Nie zmu-szaj go do podążania wg standardowych pro-cedur – to go spowalnia i irytuje. Nie zawra-caj mu głowy formalnościami typu raporty z wykonanej pracy. Ekspert wie, że to, co robi, robi dobrze, więc okaż mu zaufanie i pozwól na autonomię.

Jeszcze jedna dobra rada, której udziela Dave Thomas na temat ekspertów: Nie zlecaj mu opracowania architektury nowego syste-mu. Znudzony Ekspert wykreuje monstrum integrujące różne rozwiązania i technologie tyko po to, aby się zabawić i sprawdzić, jak to wszystko będzie ze sobą działać.

Należy również uważać na tak zwanych eks-pertów. Czyli ludzi na poziomie Competent w pewnej materii, którzy nie mają jeszcze szer-szej perspektywy. Nie przeszkadza im to jed-nak w lansowaniu swoich rozwiązań (jedy-nych, jakie znają) w każdej możliwej sytuacji.

Częstym przykładem jest np. sugerowanie sil-nika bazodanowego jako kompleksowej plat-formy developmentu całego systemu – pomi-jając problemy oraz ignorując rozwiązania le-piej nadające się do niektórych zastosowań.

Tak więc strzeżmy się ekspertów o ograni-czonym zasobie skojarzeń oraz tych nieprak-tykujących – samozwańczych. Łatwo też wy-obrazić sobie, ile może być warta jedna słusz-na strategiczna decyzja prawdziwego Eks-perta.

GuruCo prawda nie opisany w standardowym mo-delu braci Dreyfus, ale niektórzy wyróżniają tę specjalną klasę Ekspertów. Guru to taki eks-pert, który ma dobry interfejs ludzki (sprzęg, jak mawiano w erze krzemu łupanego). Dzię-ki wysokiej empatii potrafi dostosować swój przekaz do aktualnego poziomu odbiorcy. Po-trafi również operować sugestywnymi przy-kładami oraz prowokować do zadawania wła-ściwych pytań. Dzięki temu każdy, kto uda się do Guru po radę, wraca z jakąś odpowie-dzią – a przynajmniej z natchnieniem. Ema-nuje on aurą, dzięki której wszyscy wokół roz-wijają się szybciej i w dobrym kierunku.

Jeżeli jesteś ekspertem, to warto popraco-wać nad przepoczwarzeniem się do formy Guru, ponieważ tylko wówczas zyskasz sza-cunek i staniesz się bohaterem opowieści snu-tym wnukom przez twych uczniów.

Liczy się doświadczeniePrzedstawiony model kompetencji zakłada liniowy rozwój do trzeciego poziomu – od Novice, poprzez Advanced Begginer po Com-petent. Liniowy, czyli odbywający się propor-cjonalnie do czasu powtarzania (trenowania). Natomiast dalszy rozwój – poprzez Proficient do Expert – nie jest już liniowy, ponieważ wy-maga skoku jakościowego naszej aktywności. Skoku polegającego na poszerzeniu zakre-su zainteresowań w celu rozwinięcia ogólne-go obrazu.

Dlatego warto zastanowić się nad naszym x lat doświadczenia w branży. Czy jest to x lat różnych doświadczeń, czy tak naprawdę je-den rok, powtórzony x razy?

SŁAWOMIR SOBÓTKAKonsultant w firmie BNS IT. Interesuje się szeroko pojętą inżynierią oprogramowania: architekturą systemów JEE, Domain Driven Design, wzorcami, procesem wytwórczym. Hobbystycznie interesuje się psychologią i kognitywistyką. Oprócz dosko-nalenia rzemiosła stara się odnaleźć w progra-mowaniu również pierwiastek sztuki.Autor bloga poświęconego całościowemu ujęciu inżynierii oprogramowania: http://art-of-softwa-re.blogspot.comKontakt z autorem: [email protected]

W Sieci

• strona http://www.infoq.com/presentations/Developing-Expertise-Dave-Thomas;• strona http://www.infoq.com/articles/better-best-practices.

Page 78: SDJ_02_2010_PL

v

Roczna prenumerata

tylko256

Software Developer’s Journal (poprzednio Software 2.0) jest miesięcznikiem głównie dla programistów, którzy li-czą, że dostarczymy im gotowe rozwiązania, oszczędza-jąc im czasu i pracy. Jesteśmy czytani przez tych, któ-rzy chcą być na bieżąco informowani o najnowszych osią-gnięciach w dziedzinie IT i nie chcą, żeby jakiekolwiek istotne wydarzenia umknęły ich uwadze. Aby zadowolić naszych czytelników, prezentujemy zarówno najnowsze rozwiązania, jaki starsze, sprawdzone technologie.

,-Obsługa prenumeraty

UWAGA!

Zmiana danych kontaktowych

Software Press Sp. z o.o. Spółka Komandytowa1. Telefon(022) 427 37 592. Fax(022) 244 24 593. [email protected]. AdresSoftware Press Sp. z o.o. Spółka Komandytowaul. Bokserska 102-682 Warszawa

Page 79: SDJ_02_2010_PL

v

Prenumerujesz – zyskujeszl oszczędność pieniędzy l szybka dostawa l prezentyl bezpieczna płatność on–line

TytułIlość

nume-rów

Ilość zama-

wianych prenu-merat

Od numeru pisma

lub mie-siąca

Cena

Software Developer’s Journal (1 płyta DVD) – dawniej Software 2.0

12 256PLN

Zadzwoń lub

zamówmailowo!

Page 80: SDJ_02_2010_PL

KLUB PROOferta skierowana dla firm

Jeżeli Twoja firma jest prenumeratorem Software Developer’s Journal za comiesięczną dopłatą 50 PLN +VAT możesz dodatkowo otrzymać reklamę.

Wystarczy tylko, aby profil Twojej firmy pokrywał się z naszym magazynem.

Wyślij do nas: logo firmy, dane kontaktowe i informację o firmie

Reklama przez 12 kolejnych numerów tylko za 600 PLN +VAT.

Jeżeli nie posiadasz jeszcze prenumeraty możesz ją zamówić w atrakcyjnej cenie.

Dla nowych prenumeratorów specjalna oferta – 690 PLN.

Future ProcessingFuture Processing to dynamiczna firma technolo-giczna działająca na globalnym rynku oprogramo-wania. Jesteśmy zespołem wysokiej klasy specja-listów posiadających wiedzę i doświadczenie nie-zbędne do realizacji ambitnych projektów informa-tycznych. Jeśli programowanie to Twoja pasja do-łącz do nas! (możliwość pracy zdalnej).

http://www.future-processing.pl

Skontaktuj się z nami:tel. +48 22 877 20 80fax: +48 22 877 20 [email protected]

Kei.plKei.pl działa na rynku usług hostingowych od 2000 roku. Do naszych zadowolonych Klientów z du-mą możemy zaliczyć wiele przedsiębiorstw sekto-ra MSP, instytucji oraz osób prywatnych. W ofer-cie Kei.pl znajdują się pakiety hostingowe, a także usługi dla wymagających Użytkowników – platfor-my e-Biznes oraz serwery fizyczne.

http://www.kei.pl

Opera SoftwareOpera Software’s vision is to deliver the best In-ternet experience on any device. We are offering browser for PC/desktops and embedded pro-ducts that operates across devices, platforms and operating systems. Our browser can deliver a faster, more stable and flexible Internet expe-rience than its competitors.

http://www.opera.com

Architektury systemów ITTwórca frameworków JUVE i serwera aplikacji AVAX oferuje usługi, doradztwo, rozwiązania do tworzenia nowoczesnych, dużych systemów i roz-wiązań informatycznych/internetowych, integrujące architektury ery post-J2EE/.NET, wykorzystujące MDD/MDA dla dziedzin – bankowość, telekomuni-kacja, handel, e-commerce, ERP/Workflow/CRM, rozwiązania internetowe, portalowe.www.mpsystem.com [email protected]

WSISiZ w WarszawieINFORMATYKA ZARZĄDZANIEstudia stopnia I i II (stacjonarne i niestacjonar-ne) specjalności: inżynierskie, magisterskie i licencjackie. Szczegółowe plany studiów, opi-sy poszczególnych specjalności – zapraszamy na stronę uczelni.

http://www.wit.edu.pl

Playsoft Playsoft jako lider portowania aplikacji na plat-formy mobilne wciąż powiększa bazę swo-ich klientów: EA Mobile, Sega, THQ, Kona-mi. W ramach rozszerzania swojej działalno-ści, poszukujemy doświadczonego programi-sty, który byłby odpowiedzialny za tworzenie aplikacji na platformy Iphone, Windows Mobi-le, Android.http://www.playsoft.fr

Page 81: SDJ_02_2010_PL

KLUB PRO

TTS Company Sp. z o.o.Sprzedaż i dystrybucja oprogramowania komputero-wego. Import programów na zamówienie. Ponad 200 producentów w standardowej ofercie. Chcesz kupić oprogramowanie i nie możesz znaleźć polskiego do-stawcy? Skontaktuj się z nami – sprowadzimy nawet pojedyncze licencje.

http://www.OprogramowanieKomputerowe.pl

IT SOLUTIONSWdrożenia i szkolenia z zakresu:• SQL Server• SharePoint Services• MS Project / Server• Tworzenie aplikacji w technologii .NET

http://[email protected]

IT SOLUTIONS

Softline rozwiązania mobilneWiodący producent systemów mobilnych, do-stawca aplikacji użytkowych dla biznesu (Sym-bian OS, Windows Mobile, J2ME ) zaprasza do współpracy. Zostań naszym partnerem. Dołącz do zespołu.

http://www.softline.com.pl

INFOTEX SP.J Śmietanowski i Wsp.Dystrybutor XP Unlimited – Serwer Terminali dla Windows XP i VISTA. Program umożliwia łącze-nie się z dowolnego klienta Windows, Linux z wy-korzystaniem protokołu RDP. Cena wersji Classic dla 5 użytkowników - 165€, dla nieograniczonej liczby - 235€. Ponadto oferujemy opiekę serwiso-wą i aplikacje internetowe na zamówienie.

http://www.infotex.com.pl

Proximetry Poland Sp. z o.o.Proximetry Poland Sp. z o.o. jest polskim od-działem amerykańskiej firmy Proximetry Inc. – dostawcy systemów zarządzania sieciami bez-przewodowymi opartymi na technologiach WiFi i WiMAX. Naszą misją jest dostarczenie klien-tom rozwiązań poprawiających jakość usług (QoS) dostarczanych drogą radiową. Dołącz do najlepszych i zostań członkiem naszej ekipy!

http://www.proximetry.com

Systemy bankowe, ISOF HEUTHES istnieje na rynku od 1989 r. Obok systemów informatycznych dla banków, ofe-ruje nowoczesne oprogramowanie do obsługi firm. System ISOF jest udostępniany klientom w trybie SaaS lub licencji. Pracuje na platfor-mie Linux i zawiera m.in. takie moduły jak CRM, DMS, Magazyn, Sprzedaż, Logistyka oraz Rachunkowość. http://www.isof.pl

Page 82: SDJ_02_2010_PL

Felieton

02/201082

Miło nam poinformować, iż Software Developer's Journal został patronem medialnym drugiej edycji międzynaro-dowej konferencji GeeCON, która odbędzie w dniach 13-

14 maja 2010 r. w Poznaniu.GeeCON to konferencja poświęcona technologiom, u podstaw

których znajduje się język Java wraz z platformą, jaką jest Wirtual-na Maszyna Java. Organizatorami konferencji są Poznańska i Pol-ska Grupa Użytkowników Języka Java oraz stowarzyszenie Gru-pa Informatyczno-Kulturalna. Organizatorzy chcą promować roz-wiązania zwiększające produktywność programistów takie jak ję-zyki dynamicznie typowane (m.in. Groovy, Ruby) czy nowocze-sne szkielety aplikacyjne (m.in. Grails, Apache Wicket). W tym roku chcą położyć nacisk również na technologie mobilne (wśród nich Android oraz JME) i rozwiązania do tworzenia Rich Internet Appli-cation (Flex i JavaFX).

Poprzednia edycja konferencji GeeCON odbyła się w ma-ju 2009 w krakowskim Multikinie. Odwiedziło ją wielu specjali-stów z kraju i świata, m.in. Austrii, Czech, Niemiec, Wielkiej Bryta-nii, Francji, Stanów Zjednoczonych. Wśród prelegentów znaleź-li się m.in.: Adam Bien, Alef Arendsen, Antonio Goncalves, Bru-no Bossola, Corneliu Creanga, Giorgio Natili, Jacek Laskowski, Luc Duponcheel, Miško Hevery, Paweł Wrzeszcz, Piotr Walczy-szyn, Simon Ritter, Stephan Janssen, Szczepan Faber, Thomas Enebo, Václav Pech, Waldemar Kot. W konferencji uczestniczyło prawie 400 gości, którzy ocenili wysiłki organizacyjne i poziom merytoryczny bardzo wysoko – interesujące prezentacje, świa-towej sławy prelegenci i niepowtarzalna atmosfera, a wszystko to przy zachowaniu bardzo niskiej opłaty za uczestnictwo. Dzień

przed właściwą konferencją, w ramach GeeCON University Day, we współpracy z firmą Sun Microsystems, udało się zorganizo-wać serię dedykowanych szkoleń. Nie zabraknie ich również w tym roku (12 maja).

Kolejnym miastem, do którego zapraszają nas organizatorzy, jest Poznań – miasto tętniące życiem, miejsce, gdzie tysiąclet-nia historia spotyka się z nowoczesnością. Tu co roku organizo-wanych jest wiele imprez kulturalnych i targowych, które od-wiedzają goście z całego świata. Poznań oferuje przyjazną at-mosferę dla biznesu, ale również przestrzenie, w których ro-dzą się innowacyjne pomysły. Wystarczy wspomnieć tylko o obecności ośrodków badawczo-rozwojowych wielkich kon-cernów takich jak Microsoft, Carlsberg, Dalkia, GlaxoSmithKli-ne czy Roche.

GeeCON w Poznaniu to nie tylko dwa dni konferencji i możli-wość uczestnictwa w szkoleniach, to również spotkania towarzy-szące, podczas których uczestnicy mają okazję do nawiązywania licznych kontaktów. To wyjątkowa okazja, aby porozmawiać ze światowej klasy specjalistami, których nie zabraknie podczas te-gorocznej edycji. To również szansa podzielania się swoją wiedzą lub prezentacji własnych rozwiązań – do grona prelegentów mo-że bowiem dołączyć każdy, kto zgłosi temat prezentacji w języku angielskim i zostanie przyjęty przez komitet organizacyjny konfe-rencji.

Rejestracja na GeeCON właśnie się rozpoczęła! Do końca marca obowiązują promocyjne ceny. Po więcej szczegółów zapraszamy na strony konferencji – www.geecon.org. Organizatorzy gwarantu-ją, że będzie to niezapomniane wydarzenie!

GeeCON 2010

Page 83: SDJ_02_2010_PL
Page 84: SDJ_02_2010_PL