DevOps C++ a „kuchynské války“, aneb Jak jsem začal psát hry při jídle

"Vím, že nic nevím" Sokrates

Pro koho: pro IT lidi, kteří plivou na všechny vývojáře a chtějí hrát jejich hry!

O čem: jak začít psát hry v C/C++, pokud to potřebujete!

Proč byste si měli přečíst toto: Vývoj aplikací není moje pracovní specializace, ale snažím se kódovat každý týden. Protože miluji hry!

Dobrý den, jmenuji se Andrej Grankin, Jsem DevOps ve společnosti Luxoft. Vývoj aplikací není moje pracovní specializace, ale snažím se kódovat každý týden. Protože miluji hry!

Průmysl počítačových her je obrovský, dnes se o něm mluví ještě více než o filmovém průmyslu. Hry byly psány od počátku vývoje počítačů, s využitím moderních standardů komplexních a základních vývojových metod. Postupem času se začaly objevovat herní enginy s již naprogramovanou grafikou, fyzikou a zvukem. Umožňují vám soustředit se na vývoj samotné hry a nestarat se o její základy. Spolu s nimi ale s motory „oslepují“ a degradují vývojáři. Samotná výroba her je umístěna na dopravníku. A kvantita produkce začíná převažovat nad její kvalitou.

Zároveň jsme při hraní cizích her neustále limitováni lokacemi, zápletkou, postavami, herními mechanismy, které ostatní vymysleli. Tak jsem si uvědomil, že...

… je čas vytvořit si vlastní světy, podřízené pouze mně. Světy, kde jsem Otec, Syn a Duch svatý!

A upřímně věřím, že když si napíšete svůj vlastní herní engine a hru na něm, budete schopni otevřít oči, setřít okna a napumpovat svou kabinu a stát se zkušenějším a integrálním programátorem.

V tomto článku se vám pokusím přiblížit, jak jsem začal psát malé hry v C/C++, jaký je proces vývoje a kde najdu čas na koníčka v rušném prostředí. Je subjektivní a popisuje proces individuálního startu. Materiál o nevědomosti a víře, o mém osobním obrazu současného světa. Jinými slovy: "Správa není odpovědná za vaše osobní mozky!".

Praxe

"Znalosti bez praxe jsou k ničemu, praxe bez znalostí je nebezpečné." Konfucius

Můj notebook je můj život!


V praxi tedy mohu říci, že u notebooku vše začíná. Zapisuji si tam nejen své každodenní úkoly, ale také kreslím, programuji, navrhuji vývojové diagramy a řeším úlohy včetně matematických. Vždy používejte poznámkový blok a pište pouze tužkou. Je to čisté, pohodlné a spolehlivé, IMHO.

DevOps C++ a „kuchynské války“, aneb Jak jsem začal psát hry při jídle
Můj (již vyplněný) zápisník. Takhle to vypadá. Obsahuje každodenní úkoly, nápady, nákresy, schémata, řešení, černé účetnictví, kód a tak dále.

V této fázi se mi podařilo dokončit tři projekty (to je v mém chápání „finalita“, protože jakýkoli produkt lze vyvíjet relativně donekonečna).

  • Projekt 0: toto je 3D scéna Architect Demo napsaná v C# pomocí herního enginu Unity. Pro platformy macOS a Windows.
  • Hra 1: konzolová hra Simple Snake (známá všem jako „Snake“) pro Windows. napsáno v C.
  • Hra 2: konzolová hra Crazy Tanks (všem známá jako „Tanks“), již napsaná v C ++ (pomocí tříd) a také pod Windows.

Demo architekta projektu 0

  • Platforma: Windows (Windows 7, 10), Mac OS (OS X El Capitan v. 10.11.6)
  • Jazyk: C#
  • Herní engine: Jednota
  • Inspirace: Darrin Lile
  • úložiště: GitHub

DevOps C++ a „kuchynské války“, aneb Jak jsem začal psát hry při jídle
Demo architekta 3D scény

První projekt nebyl implementován v C/C++, ale v C# pomocí herního enginu Unity. Tento engine nebyl tak náročný na hardware jako Unreal Engine, a také se mi zdálo jednodušší na instalaci a použití. O jiných motorech jsem neuvažoval.

Cílem v Unity pro mě nebylo vyvíjet nějakou hru. Chtěl jsem vytvořit 3D scénu s nějakou postavou. On, nebo spíše Ona (modeloval jsem dívku, do které jsem byl zamilovaný =) se musel pohybovat a interagovat s vnějším světem. Bylo jen důležité pochopit, co je Unity, co je to proces vývoje a kolik úsilí je potřeba k vytvoření něčeho. Tak se zrodil projekt Architect Demo (název byl vymyšlen skoro z keců). Programování, modelování, animace, texturování mi zabralo snad dva měsíce každodenní práce.

Začal jsem s výukovými videi na YouTube o tom, jak vytvářet 3D modely Mixér. Blender je skvělý bezplatný nástroj pro 3D modelování (a další), který nevyžaduje instalaci. A tady mě čekal šok... Ukazuje se, že modelování, animace, texturování jsou obrovská samostatná témata, o kterých se dají psát knihy. To platí zejména pro postavy. K modelování prstů, zubů, očí a dalších částí těla budete potřebovat znalosti anatomie. Jak jsou uspořádány svaly obličeje? Jak se lidé pohybují? Musel jsem „vložit“ kosti do každé paže, nohy, prstu, kloubu!

Modelujte klíční kost, další kostní páky, aby animace vypadala přirozeně. Po takových lekcích si uvědomíte, jakou obrovskou práci odvádějí tvůrci animovaných filmů, jen aby vytvořili 30 sekund videa. Ale 3D filmy trvají hodiny! A pak vyjdeme z kin a řekneme něco jako: „Tady, posraná karikatura / film! Mohli to udělat lépe…“ Blázni!

A ještě jedna věc k programování v tomto projektu. Jak se ukázalo, nejzajímavější pro mě byla ta matematická. Pokud spustíte scénu (odkaz na úložiště v popisu projektu), všimnete si, že se kamera otáčí kolem dívčí postavy v kouli. Pro naprogramování takové rotace kamery jsem musel nejprve vypočítat souřadnice bodu pozice na kružnici (2D) a poté na kouli (3D). Legrační je, že jsem ve škole nenáviděl matematiku a znal jsem ji s mínusem. Částečně pravděpodobně, protože ve škole vám jednoduše nevysvětlí, jak se tato matematika sakra používá v životě. Ale když jste posedlí svým cílem, snem, pak se mysl vyčistí, odhalí! A složité úkoly začnete vnímat jako vzrušující dobrodružství. A pak si pomyslíte: "No, proč by *milovaný* matematik normálně nemohl říct, kam se tyto vzorce lze opřít?".

DevOps C++ a „kuchynské války“, aneb Jak jsem začal psát hry při jídle
Výpočet vzorců pro výpočet souřadnic bodu na kružnici a na kouli (z mého zápisníku)

hra 1

  • Platforma: Windows (testováno na Windows 7, 10)
  • Jazyk: Myslím, že to bylo napsáno v čistém C
  • Herní engine: konzole Windows
  • Inspirace: javidx9
  • úložiště: GitHub

DevOps C++ a „kuchynské války“, aneb Jak jsem začal psát hry při jídle
Jednoduchá hra Snake

3D scéna není hra. Navíc je modelování a animace 3D objektů (zejména postav) zdlouhavé a obtížné. Po hraní s Unity jsem si uvědomil, že musím pokračovat, nebo spíše začít, od základů. Něco jednoduchého a rychlého, ale zároveň globálního, abyste pochopili samotnou strukturu her.

A co máme jednoduché a rychlé? Přesně tak, konzole a 2D. Přesněji řečeno i konzole a symboly. Opět jsem začal hledat inspiraci na internetu (obecně internet považuji za nejrevolučnější a nejnebezpečnější vynález XNUMX. století). Vyhrabal jsem video jednoho programátora, který vyrobil konzolový Tetris. A v podobě své hry se rozhodl „hada“ porazit. Z videa jsem se dozvěděl o dvou zásadních věcech - herní smyčce (se třemi základními funkcemi / částmi) a výstupu do bufferu.

Herní smyčka může vypadat nějak takto:

int main()
   {
      Setup();
      // a game loop
      while (!quit)
      {
          Input();
          Logic();
          Draw();
          Sleep(gameSpeed);  // game timing
      }
      return 0;
   }

Kód představuje celou funkci main() najednou. A herní cyklus začíná po odpovídajícím komentáři. Ve smyčce jsou tři základní funkce: Input(), Logic(), Draw(). Nejprve vstupní data Input (hlavně ovládání stisku kláves), poté zpracování zadaných dat Logic, poté zobrazení na obrazovce - Draw. A tak každý snímek. Tímto způsobem vzniká animace. Je to jako karikatury. Obvykle zpracování vstupních dat zabere nejvíce času a pokud vím, určuje snímkovou frekvenci hry. Zde je ale funkce Logic() velmi rychlá. Proto musí být snímková frekvence řízena funkcí Sleep() s parametrem gameSpeed ​​​​, který tuto frekvenci určuje.

DevOps C++ a „kuchynské války“, aneb Jak jsem začal psát hry při jídle
herní cyklus. Programování hada v poznámkovém bloku

Pokud vyvíjíte symbolickou konzolovou hru, pak zobrazení dat na obrazovce pomocí obvyklého výstupu streamu „cout“ nebude fungovat – je velmi pomalé. Proto musí být výstup proveden ve vyrovnávací paměti obrazovky. Je tak mnohem rychlejší a hra bude fungovat bez závad. Abych byl upřímný, moc nerozumím tomu, co je vyrovnávací paměť obrazovky a jak funguje. Ale uvedu zde příklad kódu a možná někdo v komentářích bude schopen objasnit situaci.

Získání vyrovnávací paměti obrazovky (pokud to mohu říci):

// create screen buffer for drawings
   HANDLE hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0,
 							   NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
   DWORD dwBytesWritten = 0;
   SetConsoleActiveScreenBuffer(hConsole);

Přímý výstup na obrazovku určitého řádku scoreLine (řádek pro zobrazení skóre):

// draw the score
   WriteConsoleOutputCharacter(hConsole, scoreLine, GAME_WIDTH, {2,3}, &dwBytesWritten);

Teoreticky v této hře není nic složitého, zdá se mi to jako dobrý příklad hry na základní úrovni. Kód je zapsán v jednom souboru a uspořádán do několika funkcí. Žádné třídy, žádné dědictví. Sami si vše můžete prohlédnout ve zdrojovém kódu hry tak, že přejdete do úložiště na GitHubu.

Hra 2 Crazy Tanks

DevOps C++ a „kuchynské války“, aneb Jak jsem začal psát hry při jídle
Hra Crazy Tanks

Tisk postav na konzoli je asi to nejjednodušší, co můžete ve hru přeměnit. Pak se ale objeví jeden průšvih: postavy mají různé výšky a šířky (výška je větší než šířka). Vše tedy bude vypadat nepřiměřeně a pohyb dolů nebo nahoru se bude zdát mnohem rychlejší než pohyb doleva nebo doprava. Tento efekt je velmi patrný v "Had" (hra 1). "Tanky" (Hra 2) nemají takovou nevýhodu, protože výstup je organizován malováním pixelů obrazovky různými barvami. Dalo by se říct, že jsem napsal renderer. Pravda, to už je trochu složitější, i když mnohem zajímavější.

Pro tuto hru bude stačit popsat můj systém zobrazování pixelů na obrazovce. Myslím, že tohle je hlavní část hry. A všechno ostatní, co si vymyslíte sami.

Takže to, co vidíte na obrazovce, je jen sada pohyblivých barevných obdélníků.

DevOps C++ a „kuchynské války“, aneb Jak jsem začal psát hry při jídle
Sada obdélníku

Každý obdélník je reprezentován maticí vyplněnou čísly. Mimochodem, mohu zdůraznit jednu zajímavou nuanci - všechny matice ve hře jsou naprogramovány jako jednorozměrné pole. Ne dvourozměrné, ale jednorozměrné! S jednorozměrnými poli se pracuje mnohem snadněji a rychleji.

DevOps C++ a „kuchynské války“, aneb Jak jsem začal psát hry při jídle
Příklad matice herního tanku

DevOps C++ a „kuchynské války“, aneb Jak jsem začal psát hry při jídle
Představuje matici herního tanku s jednorozměrným polem

DevOps C++ a „kuchynské války“, aneb Jak jsem začal psát hry při jídle
Názornější příklad maticové reprezentace jednorozměrným polem

Ale přístup k prvkům pole probíhá ve dvojité smyčce, jako by to nebylo jednorozměrné, ale dvourozměrné pole. Dělá se to proto, že stále pracujeme s matricemi.

DevOps C++ a „kuchynské války“, aneb Jak jsem začal psát hry při jídle
Procházení jednorozměrného pole ve dvojité smyčce. Y je ID řádku, X je ID sloupce

Upozorňujeme, že místo obvyklých maticových identifikátorů i, j používám identifikátory x a y. Takže, zdá se mi, příjemnější pro oko a jasnější pro mozek. Navíc takový zápis umožňuje pohodlně promítat použité matice na souřadnicové osy dvourozměrného obrazu.

Nyní o pixelech, barvě a displeji. Pro výstup je použita funkce StretchDIBits (Header: windows.h; Library: gdi32.lib). Této funkci se mimo jiné předává: zařízení, na kterém se obrázek zobrazuje (v mém případě je to konzole Windows), souřadnice začátku zobrazení obrázku, jeho šířka / výška a obrázek ve formě bitmapy (bitmapy), reprezentované polem bajtů. Bitmapa jako pole bajtů!

Funkce StretchDIBits() v práci:

// screen output for game field
   StretchDIBits(
               deviceContext,
               OFFSET_LEFT, OFFSET_TOP,
               PMATRIX_WIDTH, PMATRIX_HEIGHT,
               0, 0,
               PMATRIX_WIDTH, PMATRIX_HEIGHT,
               m_p_bitmapMemory, &bitmapInfo,
               DIB_RGB_COLORS,
               SRCCOPY
               );

Paměť je pro tuto bitmapu alokována předem pomocí funkce VirtualAlloc(). To znamená, že požadovaný počet bajtů je vyhrazen pro uložení informací o všech pixelech, které se pak zobrazí na obrazovce.

Vytvoření bitmapy m_p_bitmapMemory:

// create bitmap
   int bitmapMemorySize = (PMATRIX_WIDTH * PMATRIX_HEIGHT) * BYTES_PER_PIXEL;
   void* m_p_bitmapMemory = VirtualAlloc(0, bitmapMemorySize, MEM_COMMIT, PAGE_READWRITE);

Zhruba řečeno, bitmapa se skládá ze sady pixelů. Každé čtyři bajty v poli je pixel RGB. Jeden bajt na červenou hodnotu, jeden bajt na zelenou hodnotu (G) a jeden bajt na modrou barvu (B). Navíc je zde jeden bajt na odsazení. Tyto tři barvy - Červená / Zelená / Modrá (RGB) - se vzájemně mísí v různých poměrech - a získá se výsledná barva pixelu.

Nyní je opět každý obdélník nebo herní objekt reprezentován číselnou maticí. Všechny tyto herní předměty jsou umístěny ve sbírce. A pak jsou umístěny na hrací pole a tvoří jednu velkou číselnou matici. Každé číslo v matici jsem namapoval na určitou barvu. Například číslo 8 je modré, číslo 9 žluté, číslo 10 tmavě šedé a tak dále. Můžeme tedy říci, že máme matici hracího pole, kde každé číslo má nějakou barvu.

Máme tedy na jedné straně číselnou matici celého hracího pole a na straně druhé bitmapu pro zobrazení obrázku. Bitmapa je zatím „prázdná“ – ještě nemá informace o pixelech požadované barvy. To znamená, že posledním krokem bude naplnění bitmapy informacemi o každém pixelu na základě číselné matice hracího pole. Názorný příklad takové transformace je na obrázku níže.

DevOps C++ a „kuchynské války“, aneb Jak jsem začal psát hry při jídle
Příklad vyplnění bitmapy (Pixel matrix) informacemi založenými na číselné matici (Digital matrix) hracího pole (barevné indexy se neshodují s indexy ve hře)

Představím také kus reálného kódu ze hry. Proměnné colorIndex je při každé iteraci smyčky přiřazena hodnota (index barvy) z numerické matice hracího pole (mainDigitalMatrix). Poté se do proměnné color na základě indexu zapíše samotná barva. Dále se výsledná barva dělí na poměr červené, zelené a modré (RGB). A spolu s odsazením (pixelPadding) se tato informace zapisuje do pixelu znovu a znovu a tvoří barevný obrázek v bitmapě.

Kód používá ukazatele a bitové operace, které mohou být obtížně srozumitelné. Radím vám tedy, abyste si někde samostatně přečetli, jak takové struktury fungují.

Vyplnění bitmapy informacemi na základě číselné matice hracího pole:

// set pixel map variables
   int colorIndex;
   COLORREF color;
   int pitch;
   uint8_t* p_row;
 
   // arrange pixels for game field
   pitch = PMATRIX_WIDTH * BYTES_PER_PIXEL;     // row size in bytes
   p_row = (uint8_t*)m_p_bitmapMemory;       //cast to uint8 for valid pointer arithmetic
   							(to add by 1 byte (8 bits) at a time)   
   for (int y = 0; y < PMATRIX_HEIGHT; ++y)
   {
       uint32_t* p_pixel = (uint32_t*)p_row;
       for (int x = 0; x < PMATRIX_WIDTH; ++x)
       {
           colorIndex = mainDigitalMatrix[y * PMATRIX_WIDTH + x];
           color = Utils::GetColor(colorIndex);
           uint8_t blue = GetBValue(color);
           uint8_t green = GetGValue(color);
           uint8_t red = GetRValue(color);
           uint8_t pixelPadding = 0;
 
           *p_pixel = ((pixelPadding << 24) | (red << 16) | (green << 8) | blue);
           ++p_pixel;
       }
       p_row += pitch;
   }

Podle výše popsané metody se ve hře Crazy Tanks vytvoří jeden obrázek (rámeček) a zobrazí se na obrazovce ve funkci Draw(). Po zaregistrování stisku kláves ve funkci Input() a jejich následném zpracování ve funkci Logic() se vytvoří nový obrázek (rámeček). Je pravda, že herní předměty již mohou mít na hracím poli jinou pozici, a proto jsou nakresleny na jiném místě. Tak dochází k animaci (pohybu).

Teoreticky (pokud jste na nic nezapomněli) stačí pochopit herní smyčku z první hry („Snake“) a systém zobrazování pixelů na obrazovce z druhé hry („Tanky“). vašich 2D her pro Windows. Nehlučný! 😉 Zbytek dílů je jen úlet fantazie.

Samozřejmě, že hra "Tanks" je navržena mnohem komplikovaněji než "Snake". Jazyk C++ jsem již používal, to znamená, že jsem popisoval různé herní objekty s třídami. Vytvořil jsem vlastní kolekci - kód můžete vidět v headers/Box.h. Mimochodem, sbírka má s největší pravděpodobností únik paměti. Použité ukazatele. Pracovalo se s pamětí. Musím říct, že mi kniha hodně pomohla. Začátek C++ přes programování her. To je skvělý začátek pro začátečníky v C++. Je malý, zajímavý a dobře organizovaný.

Vývoj této hry trval asi šest měsíců. Psal jsem hlavně během oběda a svačin v práci. Seděl v kancelářské kuchyni, šlapal do jídla a psal kód. Nebo doma na večeři. Tak jsem dostal takové "kuchyňské války". Jako vždy jsem aktivně používal notebook a v něm se rodily všechny koncepční věci.

Na konci praktické části vytáhnu pár skenů svého notebooku. Ukázat, co přesně jsem si zapisoval, kreslil, počítal, navrhoval…

DevOps C++ a „kuchynské války“, aneb Jak jsem začal psát hry při jídle
Design obrázku nádrže. A definice toho, kolik pixelů by měl každý tank zabírat na obrazovce

DevOps C++ a „kuchynské války“, aneb Jak jsem začal psát hry při jídle
Výpočet algoritmu a vzorců pro rotaci nádrže kolem její osy

DevOps C++ a „kuchynské války“, aneb Jak jsem začal psát hry při jídle
Schéma mé sbírky (s největší pravděpodobností té s únikem paměti). Sbírka je vytvořena jako Propojený seznam

DevOps C++ a „kuchynské války“, aneb Jak jsem začal psát hry při jídle
A to jsou marné pokusy našroubovat do hry umělou inteligenci

Teorie

"I cesta dlouhá tisíc mil začíná prvním krokem" (starověká čínská moudrost)

Pojďme od praxe k teorii! Jak si najít čas na svůj koníček?

  1. Rozhodněte se, co opravdu chcete (bohužel, to je nejtěžší).
  2. Stanovte si priority.
  3. Obětujte vše „zbytečné“ kvůli vyšším prioritám.
  4. Pohybujte se ke svým cílům každý den.
  5. Nečekejte, že zbudou dvě tři hodiny volného času na koníčka.

Na jedné straně si musíte určit, co chcete a upřednostnit. Na druhou stranu je možné upustit od některých případů/projektů ve prospěch těchto priorit. Jinými slovy, budete muset obětovat vše „nadbytečné“. Někde jsem slyšel, že v životě by měly být maximálně tři hlavní činnosti. Pak se s nimi budete moci vypořádat tím nejlepším možným způsobem. A další projekty/směry začnou přetěžovat banální. Ale to je asi vše subjektivní a individuální.

Existuje určité zlaté pravidlo: nikdy nemít 0% den! Dozvěděl jsem se o tom v článku jednoho nezávislého vývojáře. Pokud pracujete na projektu, pak s tím každý den něco dělejte. A nezáleží na tom, kolik vyděláte. Napište jedno slovo nebo jeden řádek kódu, podívejte se na jedno výukové video nebo zatlučte hřebík do desky – prostě něco udělejte. Nejtěžší je začít. Jakmile začnete, pravděpodobně uděláte trochu víc, než jste chtěli. Budete tedy neustále směřovat ke svému cíli a věřte, že velmi rychle. Ostatně hlavní brzdou všech věcí je prokrastinace.

A je důležité pamatovat na to, že byste neměli podceňovat a ignorovat volné „piliny“ času za 5, 10, 15 minut, čekat na nějaké velké „klády“ trvající hodinu až dvě. Stojíte ve frontě? Přemýšlejte o něčem pro svůj projekt. Jdeš nahoru po eskalátoru? Zapište si něco do sešitu. Jíš v autobuse? Dobře, přečtěte si nějaký článek. Využijte každé příležitosti. Přestaňte sledovat kočky a psy na YouTube! Nezahrávejte si s mozkem!

A poslední. Pokud se vám po přečtení tohoto článku líbila myšlenka vytvářet hry bez použití herních enginů, pak si zapamatujte jméno Casey Muratori. Tento chlap má сайт. V sekci "sledovat -> PŘEDCHOZÍ EPIZODY" najdete úžasné bezplatné videonávody, jak vytvořit profesionální hru od začátku. Za pět lekcí Intro to C for Windows se možná dozvíte víc než za pět let studia na univerzitě (někdo o tom psal v komentářích pod videem).

Casey také vysvětluje, že vývojem vlastního herního enginu budete lépe rozumět všem existujícím enginům. Ve světě frameworků, kde se každý snaží automatizovat, se naučíte tvořit, nikoli používat. Pochopit samotnou podstatu počítačů. A také se z vás stane mnohem inteligentnější a vyzrálejší programátor – profík.

Hodně štěstí na zvolené cestě! A udělejme svět profesionálnějším.

Autor: Grankin Andrej, DevOps



Zdroj: www.habr.com