Posts tagged ‘game development’

Przemysł gier na początku dekady

2011-01-01 11:46

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 :)

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

Prosty menedżer zasobów?…

2010-06-28 18:09

Silnikologia (przynajmniej ta warsztatowa) ma swoje dziwnostki. Za jedną z nich uważam przykładanie zbyt dużego znaczenia do tych podsystemów engine‘u gry, które są zdecydowanie mniej ważne niż silnik graficzny czy fizyczny. Podpadają pod to (między innymi): mechanizmy logowania, VFS-y (wirtualne systemy plików), kod odpowiedzialny za wczytywanie i zapisywanie opcji konfiguracyjnych, itp. Blisko szczytu tej listy znajduje się też podsystem, o którym chcę napisać dzisiaj kilka słów. Chodzi mianowicie o menedżer zasobów (resource manager).

Pod tą nazwą kryję się obiekt, którego zadaniem jest – jak nazwa zresztą wskazuje – zarządzanie różnego rodzaju zasobami gry, gdzie pod pojęciem ‘zasobu’ kryje się zwykle jakiś element jej contentu: model, tekstura, próbka dźwiękowa, czcionka, shader… Menedżer ma za zadanie udostępniać odwołania do tych zasobów elementom silnika/gry, które ich potrzebują. To jest jego główny, nadrzędny cel.
Tu jednak dochodzimy do małego paradoksu. Jeśli bowiem jest to jedyne przeznaczenie modułu od zarządzania zasobami, to tak naprawdę mogłoby go w ogóle nie być!… W praktyce jego istnienie usprawiedliwia przynajmniej jeden z poniższych powodów:

  • jednoczesne załadowanie wszystkich zasobów gry czy nawet pojedynczego etapu/krainy/lokacji/itp. nie jest możliwe z uwagi na ograniczony rozmiar pamięci
  • ładowanie zasobów trwa na tyle długo, że rozsądne jest wydzielenie tego procesu do osobnego wątku
  • kompatybilność z DirectX w wersjach poniżej 9Ex wymaga obsługi zjawiska utraty urządzenia, a więc ponownego wczytania zasobów umieszczonych w pamięci karty graficznej
  • między poszczególnymi zasobami występują zależności, które nie pozwalają na łatwe określenie prawidłowej kolejności ich zwalniania

Tego rodzaju wymagania rzeczywiście można rozsądnie zrealizować dopiero wówczas, gdy w logiczny sposób wydzielimy z kodu część, która za kontrolę zasobów będzie odpowiadać. Jeśli jednak żadna z powyższych sytuacji nie aplikuje się do nas, nie ma żadnego powodu, by zabierać się za tworzenie modułu zarządzania zasobami tylko dlatego, że “przecież zasoby gdzieś muszą być”. Coś takiego jak ‘prosty menedżer zasobów’ to oksymoron: jeśli faktycznie może być prosty, to najpewniej znaczy, iż jest zbędny. No, chyba że pod tym pojęciem rozumiemy też coś, co w praktyce da się zredukować do:

  1. Texture* sprites[SPRITES_COUNT];
  2. Sound* sounds[SOUNDS_COUNT];

W takim przypadku powinniśmy zadbać przede wszystkich o to, by nasz Menedżer Zasobów (caps intended) dobrze współpracował z Modułem Wyświetlania Życia Gracza, że o Silniku Od PauzyTM nie wspomnę ;P

Jak się robi screeny

2010-04-20 16:19

W robieniu zrzutów ekranowych (screenshotów) nie ma, wydawałoby się, nic nadzwyczajnego. Wciskamy po prostu klawisz Print Screen i obraz ekranu ląduje nam w systemowym Schowku, po czym możemy go użyć w dowolny sposób. Przynajmniej tego właśnie się spodziewamy zazwyczaj.
W rzeczywistości robienie screenów to dość śliski temat, jeśli mamy do czynienia z czymś więcej niż zwykłymi aplikacjami okienkowymi. Dotyczy to chociażby pełnoekranowych gier, korzystających z bibliotek graficznych typu DirectX i OpenGL. Wykorzystują one mechanizm znany jako hardware overlay, pozwalający – w skrócie – na ominięcie narzutu systemu operacyjnego związanego z zarządzeniem oknami wielu aplikacji na współdzielonym ekranie monitora. Skutkiem ubocznym jest między innymi to, że wbudowany w system mechanizm tworzenia zrzutów ekranu staje się w tej sytuacji bezużyteczny, jeśli chcemy wykonać screenshota tego rodzaju aplikacji.

Skąd w takim razie biorą się screeny, a nawet całe filmy ze współczesnych gier?… Cóż, istnieją oczywiście metody pozwalające na przechwytywanie obrazów generowanych przez aplikację – można bowiem skłonić ją do wywoływania naszej własnej wersji metody IDirect3DDeviceN::Present zamiast tej wbudowanej :) Brzmi to pewnie tajemniczo i nieco hakersko, ale tak mniej więcej działają programy do przechwytywania wideo typu Fraps.

Do zwykłych screenów zazwyczaj jednak przewiduje się odpowiednie rozwiązania w samych grach. Jednym ze sposobów jest, w przypadku wykrycia wciśnięcia klawisza odpowiadającego za screenshoty (czyli zwykle Print Screen), wyrenderowanie sceny najpierw do tekstury, a ją potem na ekran. Rzeczoną teksturę można później zapisać do pliku.


Aliasing (i nie tylko)
w pełnej krasie

Czy to dobre rozwiązanie? Otóż nie, i to aż z trzech powodów. Stosunkowo najmniej ważnym jest to, iż implementacja może być kłopotliwa, bo wymaga dobrania się do początku i do końca potoku renderingu w aplikacji z kolejnym render targetem. Bardziej istotny jest brak multisamplingu (a więc wygładzania krawędzi wielokątów znanego jako anti-aliasing) w powstałym obrazku. Nie będzie on więc wyglądać zbyt ładnie, już nie wspominając o tym, że nie będzie on odpowiadał temu, co widzimy na ekranie.

Jak więc należy robić screeny? Dokładnie tak, jak nam podpowiada intuicja: należy wziąć to, co widać na ekranie – czyli zawartość bufora przedniego (front buffer) i zapisać to do pliku. W DirectX mamy do tego metodę urządzenia o nazwie GetFrontBufferData, która pobiera nam to jako powierzchnię:

  1. IDirect3DSurface9* screen;
  2. dev->CreateOffscreenPlaneSurface(Width, Height, D3DFMT_A8R8G8B8,
  3.     D3DPOOL_SCRATCH, &screen, NULL);
  4. // screenshot i zapis go do pliku
  5. dev->GetFrontBufferData(0, &screen);
  6. D3DXSaveSurfaceToFile(ScreenFile, D3DXIFF_JPG, screen, NULL, NULL);

Ważne jest, by pamiętać o właściwych rozmiarach powierzchni – takich, jakie ma bufor przedni. W trybie pełnoekranowym odpowiada to buforowi tylnemu, ale dla aplikacji okienkowej wcale nie musi, jeśli rozmiar jej okna możemy zmieniać.

W OpenGL podobną rolę spełnia do GetFrontBufferData spełnia funkcja glReadPixels. Z zapisaniem pobranego obrazu może być tylko “nieco” trudniej ;-)

Łączenie efektów graficznych

2010-01-27 9:29

Internet pełen jest opisów, tutoriali i przykładowych kodów pokazujących, jak implementować różne efekty graficzne. Zakodowanie ich pojedynczo zazwyczaj nie jest więc problemem, o ile mamy jako takie pojęcie o grafice czasu rzeczywistego, bibliotece DirectX/OpenGL i programowaniu w ogóle.
Znacznie większym problemem jest połączenie kilku(nastu/dziesięciu) efektów tak, by było one zaaplikowane w jednym momencie do tej samej sceny. Ze względu na to, że każdy pojedynczy efekt może wymagać kodu w bardzo różnych miejscach potoku graficznego (chociażby w samej aplikacji oraz w kodzie shaderów), zintegrowanie wszystkich tych fragmentów nie wydaje się sprawą prostą.

Ostatnio aczkolwiek zajmowałem się praktycznym rozwiązywaniem tych kwestii; było to łączenie różnych rodzajów oświetlenia z cieniami generowanymi techniką shadow depth mapping i efektami postprocessingu w rodzaju depth of field. Pozwolę więc sobie podzielić kilkoma uwagami na ten temat. To może jeszcze nie są rady, jak dobrze zaprojektować architekturę silnika 3D, ale mały framework pewnie można o nie oprzeć ;] A zatem:

  • Należy wydzielić kod zajmujący się rysowaniem samych obiektów na scenie, gdyż będzie on wywoływany wielokrotnie. Niektórym może wydawać się to oczywiste, ale w ilu przykładowych kodach wywołania DrawPrimitive czy DrawSubset są w tej samej funkcji co Begin/EndScene? W rzeczywistym kodzie zapewne tak nie będzie, bo dana scena będzie na pewno renderowana wielokrotnie.
  • Trzeba odpowiednio zająć się macierzami przekształceń. Ważne jest na przykład wydzielenie w shaderze macierzy lokalnego przekształcenia każdego obiektu. Nie można jej po prostu złączyć z macierzą WORLD (lub MODELVIEW w OpenGL), bo nasza scena będzie renderowana kilka razy w potencjalnie różnych widokach (kamery, światła, obiektu odbijającego otoczenie, itp.). Dodatkowo mogą być nam potrzebne punkty w różnych przestrzeniach, np. w układzie widoku obserwatora i widoku od konkretnego światła naraz. Wreszcie, nie należy zapominać o prawidłowym przekształcaniu wektorów normalnych. W sumie więc sekcja deklaracji pliku z shaderami może wyglądać np. tak:
    1. float4x4 ObjectTransform; // przekszt. lokalne obiektu
    2. float4x4 CameraWorld; // przekszt. globalne sceny
    3. float4x4 CameraWorldRotation; // jw. ale z samą rotacją
    4. float4x4 CameraView; // przekszt. do przestrzeni widoku
    5. float4x4 CameraProjection; // przekszt. do przestrzeni rzutowania
    6. float4x4 LightWorldViewProjection; // przekst. do przestrzeni światła
    7. // itd.

    Są tutaj jeszcze dwie sprawy warte zaznaczania. Po pierwsze, obiekty rysujące się na scenie muszą wiedzieć, gdzie ustawiać swoją macierz lokalnego przekształcenia. We wszystkich używanych shaderach nazwa odpowiedniej stałej (tutaj ObjectTransform) musi być taka sama; najlepiej też żeby mapowała się na te same rejestry stałych cn. Naturalnie kod renderujący obiekty musi też “wiedzieć”, żeby korzystać właśnie z niej zamiast z macierzy przekształceń z fixed pipeline – czyli np. wywoływać effect->SetMatrix("ObjectTransform", &mat); zamiast device->SetTransform (D3DTS_WORLD, &(currWorld * mat)); w przypadku DirectX).
    Po drugie, nie trzeba “dla efektywności” przekazywać do shadera iloczynów macierzy, jeśli używamy także ich poszczególnych czynników. Można bowiem zupełnie bezkarnie mnożyć je na początku kodu shadera:

    1. float4x4 CameraObjectWorld = mul(ObjectTransform, CameraWorld);
    2. float4x4 CameraWVP = mul(CameraObjectWorld, mul(CameraView, CameraProjection));
    3. // dalej reszta shadera
    4. Out.Position = mul(float4(In.Position, 1), CameraWVP);

    Kompilator wydzieli ten kod w postaci tzw. preshadera i zapewni, że będzie on wykonywany tylko raz (a nie dla każdego wierzchołka/piksela).

  • Konieczne jest zadbanie o dobrą obsługę render targetów. Powinna być ona przezroczysta dla poszczególnych efektów – nie muszą one wiedzieć, czy renderują bezpośrednio na ekran, czy do tekstury. Jednocześnie każdy efekt powinien móc określić, do którego RT chce aktualnie renderować i mieć potem możliwość wykorzystania wyników jako tekstur w kolejnych przebiegach. Generalnie do tych celów wystarcza prosty menedżer oparty np. na słowniku identyfikującym poszczególne RT za pomocą nazw: "ShadowMap", "DepthMap, "Scene" itp.
  • W bardziej skomplikowanych przypadkach trzeba pewnie będzie złączyć shadery. W chwili obecnej jest to pewnie jeden z najbardziej złożonych problemów przy tworzeniu silnika graficznego, ale istnieje szansa, że wprowadzane w DirectX 11 dynamiczne linkowanie shaderów będzie to w istotny sposób ułatwiało.
    Jeśli na razie nie chcemy się mierzyć z tym problemem, to można niekiedy go ominąć kosztem dodatkowych przebiegów renderowania. Przykładowo, cienie można nakładać na gotową scenę z już policzonym oświetleniem zamiast oświetlać i cieniować piksele w jednym passie.

Ogólnie trzeba przyznać, że implementowanie wielu efektów działających naraz w tej samej scenie to zagadnienie złożone i dość trudne. Chociaż więc starałem się podać kilka porad na ten temat, to w rzeczywistości niezbędne jest tutaj spore doświadczenie z różnymi rodzajami efektów, zarówno w teorii jak i praktyce.

PlaySound bez ścięć

2009-07-31 17:03

Chcąc w prosty sposób odtworzyć dźwięk w programie pod Windows, możemy skorzystać z funkcji WinAPI o nazwie PlaySound. Wystarczy podać jej nazwę pliku dźwiękowego, by ten został odegrany:

  1. PlaySound (TEXT("C:\\Windows\\Media\\ding.wav"), NULL, 0);

Jak to jednak bywa w przypadku metod najprostszych, nie jest to rozwiązanie doskonałe. Jedną z jego wad jest bowiem to, że próbka dźwiękowa jest tutaj wczytywana z dysku tuż przed jej pierwszym odtworzeniem. W większości przypadków trwa to chwilę, którą da się zauważyć jako krótkie opóźnienie przed usłyszeniem dźwięku.
W grach (i nie tylko) jest to oczywiście niedopuszczalne, więc zwykle najlepszym wyjściem jest użycie wyspecjalizowanych bibliotek dźwiękowych. Jeśli jednak nie chcemy dołączać dodatkowych plików DLL lub wykorzystywać DirectX-a, możemy temu opóźnieniu zaradzić.

Funkcja PlaySound pozwala bowiem na odtworzenie dźwięku nie tylko z pliku, ale też z pamięci operacyjnej:

  1. PlaySound ((LPCTSTR)pSound, NULL, SND_MEMORY);

W tym celu posłużyć się trzeba flagą SND_MEMORY i podać wskaźnik do bloku pamięci, w którym znajduje się nasza próbką dźwiękowa. Skąd go wziąć? Ano chociażby wczytać plik dźwiękowy z dysku do pamięci:

  1. const void* LoadSound(const TCHAR* fileName)
  2. {
  3.     // uwaga: brak obsługi błędów
  4.     HANDLE file = CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ,
  5.                              NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  6.     DWORD size = GetFileSize(file, NULL);    // max. 2GB
  7.  
  8.     char* sample = new char [size];
  9.     char* p = sample;
  10.     DWORD bytesRead;
  11.     do
  12.     {
  13.         ReadFile (file, p, size, &bytesRead, NULL);
  14.         p += bytesRead;
  15.     } while (bytesRead > 0);
  16.  
  17.     CloseHandle (file);
  18.     return sample;
  19. }

“Trik” polega na tym, że operację wczytywania możemy przeprowadzić wcześniej, a więc w trakcie ładowania całej gry, pojedynczego etapu, pokoju, krainy, itp. – czyli wtedy, gdy jej potencjalnie długi czas nie będzie nikomu przeszkadzał. Potem zaś da się ją odtworzyć z pamięci i nie doświadczać żadnych opóźnień.
Trzeba tylko pamiętać, żeby na koniec zwolnić pamięć przeznaczoną na wczytaną próbkę.

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

Płaska wizualizacja funkcji 3D

2009-06-02 18:43

Funkcje, których dziedziną jest podzbiór R2 (czyli płaszczyzna) ciężko jest przedstawić na płaskim ekranie w sposób poglądowy, a jednocześnie pozwalający na odczytanie jakichś wartości. O ile bowiem izometryczny rzut 3D wygląda efektownie, to poza ogólnym kształtem powierzchni nie przedstawia wielkiej ilości informacji. Sprawa wygląda trochę lepiej, jeśli taką figurę możemy jeszcze obracać… co aczkolwiek może być z kolei trudne do zrealizowania np. na wydruku :]

Jednym z wyjść może być tutaj zastosowanie sposobu używanego do map wysokości w grach. Nietrudno zauważyć, że są one niczym innym jak właśnie funkcjami typu R2->R, a reprezentowane są najczęściej jako bitmapy w odcieniach szarości: jasność piksela x,y odpowiada w nich wysokości odpowiedniego punktu terenu.
Takie heightmapy wprawdzie łatwo się rysuje, ale niespecjalnie nadają się do pokazania jako ilustracja czegokolwiek – odcienie szarości są przecież z definicji szare i nieciekawe :) Dlatego bardzo spodobał mi się prosty pomysł na uczynienie tej reprezentacji znacznie ładniejszą, o którym to usłyszałem niedawno.

Idea jest prosta: po co używać samych szarości, skoro mamy do dyspozycji całą paletę kolorów?… Każdy kolor w reprezentacji RGB jest przecież tożsamy z liczbą, co widać najbardziej, jeśli weźmiemy jego typową reprezentację 32-bitową:

W niej “największym” kolorem jest kolor biały (0x00FFFFFF, czyli 224 – 1), zaś “najmniejszym” czarny (0x0), a pomiędzy nimi włącznie występuje każdy z 2563 możliwych kolorów. Jeśli teraz przeskalujemy wartości naszej funkcji do tak określonego przedziału (lub nieco mniejszego), a następnie przekonwertujemy każdą z tych 32-(a właściwie 24-)bitowych liczb na odpowiadający jej kolor RGB, to otrzymamy w ten sposób heightmapę o znacznie bogatszej palecie barw niż poprzednio.
Jeśli ponadto używamy dokładnie takiej reprezentacji liczbowej kolorów, jak powyżej (najmłodsze bity na kolor niebieskie, a najstarsze używane na czerwony), to zauważymy, że rozkład barw w wyniku jest całkiem znajomy. I tak obszary niebieskie odpowiadają wartościom najmniejszym, zielone średnim, żółte – wysokim, a czerwone – najwyższym. Łącznie przypomina mapę hipsometryczną – czyli, jakby nie patrzył, mapę wysokości właśnie :)

Do czego może przydać się takie cudo, oprócz wspomnianej już wizualizacji funkcji R2->R na płaszczyźnie? Pewnie do niewielu rzeczy :) Ja wpadłem jednak na jeszcze jedno potencjalne zastosowanie.
Ponieważ wynikowy obrazek używa tak “geograficznych” barw, może on całkiem dobrze wyglądać jako… tło minimapy pokazywanej w grze, która rozgrywa się na terenie opisanym mapą wysokości. Oczywiście rzecz wymagałaby dopracowania: przede wszystkim bardziej starannego dobrania kolorów brzegowych (niekoniecznie białego i czarnego) oraz być może kolorów pośrednich (np. kolor poziomu morza), a i pewnie zastosowania jakichś filtrów na wynikowym obrazku (np. wygładzania). Efekty mogłyby być jednak interesujące, co widać obok.

Engine jest wielki, a ja malutki

2008-10-31 18:03

Zapewne niespotykana często koniunkcja planet sprawiła, że na forum Warsztatu pojawiło się ostatnio kilka przypadków dość specyficznych pytań. Ich autorzy chcieli dowiedzieć się, czy – najogólniej mówiąc – są w stanie coś konkretnego w dziedzinie programowania osiągnąć. “Czy dam radę napisać taką-a-taką grę?” jest przykładem takiego właśnie pytania.

Skrajnie zresztą dziwnego, moim skromnym zdaniem. Nie chodzi tu nawet o fakt, że odpowiedź na podobne pytanie jest niemożliwa do udzielenia. Bardziej zdumiewa mnie to, iż obecni adepci sztuki koderskiej jakoś lubią wynajdywać sobie różne potencjalne przeszkody. A może za mało wiem o programowaniu? Może za słabo znam matematykę? Albo jestem po prostu za młody?… Takie i podobne “argumenty” są bowiem przytaczane przy okazji wspomnianych pytań.
Jest dla mnie dosyć zaskakujące, że dzisiejsi ‘młodzi’ (raczej w sensie umiejętności) tak szybko się “starzeją”. Jeszcze całkiem nieźle pamiętam, że będąc początkującym prawie w ogóle nie przejmowałem się realnością podejmowanych amatorskich projektów. Gra 3D, strategiczna czy sieciowa (MMORPG-ów raczej wtedy nie było) z innowacyjnym gameplayem i nowatorskimi rozwiązaniami technicznymi? Ależ oczywiście, toż to praca najwyżej na tydzień ;D Jak się kończyły takie zamiary, nietrudno zgadnąć. Ani mi, ani podobnym do mnie ‘kolegom po fachu’ nie przeszkadzało to jednak próbować.

Co więc zmieniło się, że teraz widzę coraz więcej nowicjuszy pełnych wątpliwości? Czy chociażby programowanie gier stało się trudniejsze? Powiedziałbym raczej, że wręcz przeciwnie (weźmy np. coraz więcej gotowych silników i coraz lepsze wersje graficznych API), a trend ten wydaje się przy tym stabilny. Ale nawet gdyby tak nie było, to jaki sens zadawanie niedorzecznych pytań w stylu “Czy mi się uda?”. Nie, nie uda ci się – to mogę ci powiedzieć w ciemno. Z każdego nieudanego projektu można jednak wyciągnąć wnioski i nauczyć się pożytecznych rzeczy. I dlatego nie ma co przerażać się, że to trudne, niezrozumiałe czy wymagające napisania wielkich ilości świetnie działającego kodu. Wszystko przecież przychodzi z czasem, a porażki są nieodłączną częścią postępów w nauce.
Zatem – motyki w garść, Słońce czeka ;)

 


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