Jako dzisiejszą ciekawostkę chcę przedstawić pewną technikę, która może być przydatna przy implementacji systemu GUI na przykład na potrzeby gier. Zazwyczaj bowiem elementami interfejsu są różnego rodzaju kontrolki, które – chociaż nierzadko nie przypominają klasycznych widgetów w rodzaju przycisków czy pól tekstowych – posiadają prostokątne obramowanie. Ta ramka musi mieć jedną istotną cechę: powinna dopasowywać się do rozmiaru kontrolki, dopuszczając elementy o dowolnie dużych wymiarach. A jednocześnie powinna ona być utrzymana w odpowiednim stylu graficznym, zwykle spójnym z resztą aplikacji lub gry.
Sposobem na sprostanie tym wymaganiom jest podział prostokąta kontrolki na dziewięć części, różniących się zakresem skalowania, jakiemu są poddawane, gdy kontrolka zmienia rozmiary. Wśród tych części możemy mianowicie znaleźć następujące:
Żeby więc poprawnie narysować naszą kontrolkę, potrzebujemy w ogólności aż dziewięciu różnych obrazków. Oczywiście niekiedy będzie ich mniej, jeśli sprytnie posłużymy się obrotami i odbicia obrazów. Prawie na pewno jednak będzie to więcej niż jeden sprite i każdy z nich trzeba będzie jakoś oznaczyć i zapisać razem z naszą aplikacją, wymyślając do tego mniej lub bardziej skomplikowaną konwencję.
To, co chcę przedstawić, to w gruncie rzeczy taka właśnie konwencja. Nazywa się ona nine-patch images, czyli tytułowe obrazki dziewięciołatkowe. Pochodzi ona z systemu Android i jest jedną z wielu może mało efektownych, ale pomysłowych rozwiązań, ukrytych w jego API.
Idea jest dość prosta. Jeśli mamy już obramowanie w postaci obrazka otoczonego nim prostokąta, nie musimy wycinać z niego poszczególnych części i zapisywać ich osobno. Zamiast tego zaznaczamy je bezpośrednio na obrazku, dodając wpierw jednopikselowe, czarno-białe obramowanie. Czarny fragment określa część podlegającą skalowaniu (czyli środek), zaś biały – niezmieniające rozmiaru mu rogi prostokąta. Najlepiej widać to na ilustracji po lewej stronie.
Można na niej zauważyć, że do określenia obszaru skalowania wystarczy zasadniczo jeden dodatkowy rząd i jedna dodatkowa kolumna pikseli. Pozostała część dodatkowego obramowania może nam wtedy posłużyć do zdefiniowania obszaru treści, czyli tego fragmentu kontrolki, który będzie mógł być wypełniony tekstem i innymi elementami, ustawionymi jako jej zawartość. Obszar treści nie musi pokrywać się z obszarem skalowania, co z kolei jest widoczne na obrazku po prawej stronie.
Z tak przygotowanej grafiki wciąż możemy w miarę łatwo odczytać programowo potrzebne informacje o sposobie rysowania obramowania kontrolki i wypełniania jej treści. Rozwiązanie to ma przy tym tę oczywistą zaletę, że przygotowanie takiej grafiki jest z pewnością mniej pracochłonne i kłopotliwe niż tworzenie od 3 do 9 osobnych sprite‘ów i podawanie informacji o obszarze treści w jakiegoś rodzaju pliku konfiguracyjnym.
Jeśli więc ktoś w ramach silnikologii stosowanej zajmuje się implementacją modułu GUI, może rozważyć zastosowanie podobnego systemu :)
Moi koledzy-częściowo-po-fachu, czyli programiści silników gier, wymyślili niedawno magiczny trzyliterowy akronim DOD – skrót od Data-Oriented Design, czyli projektowanie oparte o dane. Oczywiście określenie ‘niedawno’ jest względnym i pewnie wielu z nich orzekło by, że DOD jest z nimi już całkiem długo. Każdy mem potrzebuje jednak czasu na rozprzestrzenienie się, a w przypadku tego fala tweetów na jego temat dotarła do mnie dopiero niedawno. Niedługo potem rzecz wydała mi się cokolwiek podejrzana.
Podstawowe pytanie brzmi rzecz jasna: o co w tym właściwie chodzi?… Ponieważ mówimy o programowaniu gier, to odpowiedź jest jasna: jeśli nie wiadomo o co chodzi, to chodzi o wydajność. W zaawansowanych grach czasu rzeczywistego mamy do czynienia z ogromną ilością danych, na których trzeba wykonać wiele, często skomplikowanych operacji, a wszystko to jeszcze musi być zrobione dostatecznie szybko, aby możliwe było pokazanie na ekranie kolejnej klatki bez widocznych przycięć. Dlatego też już dawno zauważono, że kodowanie “blisko sprzętu” się opłaca, bo pozwala maksymalnie wykorzystać jego możliwości.
To oczywiście nakłada na kod pewne wymagania oraz stwarza konieczność zwrócenia uwagi na rzeczy, którymi “normalnie” nie ma potrzeby się zajmować. Ładnym przykładem jest chociażby zarządzanie pamięcią. W wielu językach jest ono albo kompletnie pomijalne (garbage collector), albo sprowadza się do dbania o to, aby każdy zaalokowany blok był w końcu zwolniony. Gdy jednak stawiamy na wydajność, powinniśmy też zainteresować się szybkością samej operacji alokacji oraz takim rozmieszczeniem przydzielanych bloków, aby komunikacja na linii procesor-pamięć odbywała się z jak najmniejszą liczbą zgrzytów.
Ten i wiele podobnych szczegółów platformy sprzętowej powodują, że pisanie efektywnego kodu w silnikach gier to często dość literalne postępowanie według zasady Do It Yourself, połączone z ignorowaniem części feature‘ów wysokopoziomowych języków programowania, o których wiadomo, że negatywnie odbijają się na wydajności. Cóż, życie; nie ma w tym nic zaskakującego. Myślę, że każdy co bardziej zaawansowany programista zdążył zdać sobie sprawę z tego, że wszelkie koderskie udogodnienia związane z podniesieniem poziomu abstrakcji mają swój koszt liczony w dodatkowych cyklach procesora (i nie tylko). Rezygnacja z nich jest więc dobrym posunięciem, jeśli chcemy te “stracone” cykle odzyskać.
Robiąc to, będziemy mieli ciastko, ale już nie będziemy mogli go zjeść – a to oczywiście nie jest przyjemne. I po części zapewne stąd wzięło się pojęcie DOD, które nie odnosi się do niczego w gruncie rzeczy nowego, ale pozwala łatwiej odnosić się do tego rodzaju koderskich praktyk poprzez nadanie im nazwy. A przy okazji – jak mi się wydaje – w jakiś nie do końca wytłumaczalny sposób redukuje dysonans poznawczy programistów silników gier, którzy świadomie muszą pozbawiać się możliwości przestrzegania “jedynie słusznych” zasad pisania kodu.
Jak dotąd wszystko jest w gruncie rzeczy bardzo ładne i sensowne, i bez problemu zgadzam się z postulatami Data-Oriented Design tam, gdzie się one aplikują. Zgadzam się nawet z tą domniemaną ukrytą motywacją, zwłaszcza że sam nieraz narzekałem na owe “jedynie słuszne” rady. Za to nijak nie mogę pojąć, dlaczego następnym krokiem – po wynalezieniu pojęcia DOD – był mniej lub bardziej frontalny atak na programowanie obiektowe, określane nieco bardziej znanym (ale naturalnie również trzyliterowym) akronimem OOP.
Nie, nie chodzi o to, że programowanie obiektowe jest doskonałe – bo nie jest, nie było, nigdy nie będzie i nawet nie aspiruje do miana finalnego rozwiązania dla dowolnego problemu (już nie wspominając o tym, że takowe po prostu nie istnieją). Rzecz w tym, że zwolennicy DOD (DOD-a? :]) w nieprzemyślany sposób wybrali sobie przeciwnika, nie zauważając, że jest on paradygmatem zupełnie innego rodzaju niż ich własny. A to przecież takie proste:
Widać to, prawda?… Miedzy powyższymi dwoma podejściami nie tylko nie ma sprzeczności. One są od siebie po prostu niezależne, co oznacza również, że mogą występować razem w jednym programie.
Jeśli Data-Oriented Design koniecznie potrzebuje jakiegoś przeciwnika, to są nim raczej inne xOD-y, których jest już przynajmniej kilka, chociaż wiele nie zostało jeszcze nawet nazwanych. (Dobry przykład to projektowanie oparte o user experience, czyli wrażenie użytkownika, gdzie priorytetem jest m.in. responsywność, nie będąca wcale synonimem wydajności). To, co piewcy DOD zdają się krytykować w swoich publikacjach, to jakieś “projektowanie oparte o eleganckie abstrakcje”, czyli pisanie kodu, który jest sztuką dla sztuki: ładnie wygląda (w założeniu), ściśle trzyma się założeń używanego paradygmatu przy jednoczesnym eksploatowaniu wszelkich jego “zdobyczy” (czyli np. wzorców projektowych). I chociaż bywają w swoich wysiłkach niezwykle twórczy (w prezentacjach z tego tematu spotkałem nawet cytaty z Baudrillarda), to nie zmienia to faktu, że kopią leżącego (czy raczej biją martwego konia, jakby to powiedzieli Amerykanie ;-)). Bo jeśli ktoś naprawdę posuwa się do takich absurdów jak czteropoziomowa hierarchia dziedziczenia obiektów gry, to znaczy że ma znacznie poważniejsze problemy niż okazjonalny cache miss :)
Notka rozpoczyna się od stwierdzenia jednego lub kilku raczej oczywistych faktów, których autor w dość sztampowy sposób używa w celu rozpoczęcia tematu. Najczęściej przywołuje w tym celu swoje nieodległe w czasie doświadczenia związane z tymże tematem, które czasami prowadzą do mniejszej lub większej (częściej mniejszej) zmiany przekonań. Reszta pierwszego paragrafu zazwyczaj podporządkowana jest dobrnięciu do jego końca w sposób, który sprawi wrażenie spójnego wywodu. Jednocześnie jednak sam koniec jest celowo trochę kontrastowy, co ma na celu lekkie zaskoczenie czytelnika i przekonanie jego podświadomości, że lektura reszty może nie być wcale taką stratą czasu na jaką wygląda.
Środkowa część służy zrazu dalszemu rozwinięciu opisu zjawiska, którym to w tym przykładzie jest pewien specyficzny rodzaj humoru. Często jest to wspomagane odpowiednim formatowaniem tekstu, aplikowanym zazwyczaj do pojedynczych słów, które autor z jakiegoś dziwnego powodu uważa za ważne, zwykle zresztą błędnie. Nierzadko są to jego własne wymysły terminologiczne (jak choćby określenie wspomnianego rodzaju humoru jako metasatyry), wyróżnione formatowaniem tylko dlatego, iż bez niego nikt nie zwróciłby na nie najmniejszej uwagi. Byłoby to zresztą niezwykle pożyteczne, jako że ich adekwatność pozostawia zwykle wiele do życzenia.
Następnie przytaczane są różne argumenty lub przykłady tudzież elementy opisywanego zjawiska, wybrane zazwyczaj spośród tych, do których prowadzą linki z trzeciej strony wyników wyszukiwania w Google (albowiem powszechnie wiadomo, że wszyscy zatrzymują się na pierwszej, więc w ten sposób uzyskuje się gwarancję oryginalności źródeł). Pozycje te bardzo często uformowane są w postać listy wypunktowanej, ponieważ nadaje im ona pozory dobrze zorganizowanej struktury (co natychmiast podnosi wiarygodność) przy jednoczesnym unikaniu zbytniego formalizmu, nieodłącznej cechy listy numerowanej.
Ostatni paragraf lub dwa są aż do bólu przewidywalne, gdyż trudno spodziewać się tu czego innego niż podsumowania. Wskazuje ono na główną linię rozumowania z części środkowej lub też zbiera wspólne cechy podanych przykładów, takie jak punktowanie powtarzalnych, szablonowych form imitowanych przez metasatyrę przy jednoczesnym dokładnym trzymaniu się tychże form. Jeśli autor ma akurat dobry dzień, uda mu się celnym zdaniem wskazać sedno zagadnienia (a więc efekt komiczny spowodowany paradoksalnością wspomnianych cech tego humoru), jednakże zawsze będzie to inne zdanie niż to, które w jego zamyśle miało takim być. Definitywne zakończenie czytelnik przyjmuje z westchnieniem ulgi, aczkolwiek zupełnie bez zaskoczenia, bowiem było ono dla niego od początku całkiem oczywiste.
Niekiedy notka uzupełniona jest o komentarz małym drukiem, o treści która w jakiś pokrętny sposób odnosi się do całości notki. Na pewno aczkolwiek nie wspomina ona o nadużywaniu odrobinę zbyt wysublimowanego języka oraz występujących gromadnie, w gruncie rzeczy niepotrzebnych linkach do Wikipedii.
W ramach obowiązkowych corocznych spojrzeń w przeszłość i przyszłość rzucę okiem na dziedzinę, którą osobiście nieco ostatnio zaniedbałem. To oczywiście błąd, bo gry komputerowe – a o nich właśnie mówię – to rzecz każdemu człowiekowi do życia bardzo potrzebna :) W ostatnich latach zaszły na tym polu spore zmiany, które nie były tylko natury technicznej. Można powiedzieć, że przemysł gier dojrzał i uległ dość wyraźnej specjalizacji, a ledwie zarysowany parę lat temu podział został już całkiem dobrze ugruntowany.
Z jednej strony mamy segment core, obejmujący wysokobudżetowe produkcje pochodzące z wielkich studiów developerskich, nad którymi pracują dziesiątki lub setki ludzi. Dominującą platformą stały się tutaj next-genowe konsole (aktualnie XBox 360 i PS3), ale komputery PC trzymają się dobrze w przypadku specyficznych gatunków jak RTS-y czy RPG-i. W tym obszarze dokonuje się największy postęp w dziedzinie “czysto” technologicznej, co obejmuje zarówno szczegółowość i efektowność grafiki, jak również innowacyjność na innych polach. Jednym z nich jest chociażby sterowanie, czego chyba najbardziej znanymi przykładami są Wii Remote i bardzo świeży Microsoft Kinect.
Druga strona, zupełnie już porównywalna rozmiarami z pierwszą i niedającą się już zbywać wzruszeniem ramion to segment gier typu casual. Obecnie to ogromny rynek obejmujący setki milionów graczy na wielu różnych, technologicznie bardzo odmiennych platformach. Wydaje się, że spośród nich najważniejsze są w tej chwili dwie: bardziej zaawansowane telefony komórkowe oraz przeglądarki internetowe. Różnią się one nieco szczegółami – jak choćby tym, co jest źródłem zysku ich twórców – ale w obu przypadkach założeniem jest prostota (i często wtórność) rozgrywki, mały (a często żaden) poziom trudności i przynajmniej teoretyczny brak konieczności poświęcenia długiego czasu na grę. O te zasady oparta jest także sławetna najpopularniejsza gra na świecie.
Gdzieś pomiędzy tymi dwoma wielkimi obszarami rozciąga się też małe poletko indie, czyli gier określanych jako ‘niezależne’. Nakłada się ono częściowo na pozostałe sektory, nierzadko czerpiąc z nich to, co najlepsze. Zwykle są one bowiem proste koncepcyjnie i niezbyt wymagające czasowo – tak jak gry casual. Jednocześnie często charakteryzują się innowacyjnością, a czasem też zadziwiająco dobrą oprawą graficzno-dźwiękową. To właśnie z obszaru indie wywodzi się chociażby hit ostatnich miesięcy, czyli sieciowa gra 3D w układanie sześcianów.
Czy bazując na historii i stanie obecnym da się dokonać jakiejś prawdopodobnych prognoz odnośnie przyszłości przemysłu gier?… Jeśli tak, to chyba najrozsądniejsze wydaje się przypuszczenie, że zarysowany wyżej podział nie ulegnie w najbliższym czasie jakimś gwałtownym zmianom. Czasami tylko słyszę pesymistyczne prognozy zwolenników tworzenia gier core, wieszczących rychły koniec tego segmentu rynku. Zapominają oni jednak o tym, że między nim a sektorem casual nie toczy się wcale gra o sumie zerowej, grupy docelowe obydwu nie mają dużej części wspólnej, a każdy z nich celuje w odmienne gusta i potrzeby graczy.
Moja prognoza jest więc optymistyczna: szansa na to, że wszyscy skończymy pisząc pluginy do FarmVille jest zgoła niewielka :)