DevOps C++ i "kuhinjski ratovi", ili kako sam počeo pisati igre dok jedem

"Znam da ništa ne znam" Sokrat

Za koga: za IT ljude koji pljuju po svim programerima i žele igrati njihove igrice!

O čemu: kako početi pisati igrice u C/C++ ako vam trebaju!

Zašto biste trebali pročitati ovo: Razvoj aplikacija nije moja radna specijalnost, ali pokušavam kodirati svaki tjedan. Jer volim igrice!

Pozdrav moje ime je Andrej Grankin, ja sam DevOps u Luxoftu. Razvoj aplikacija nije moja radna specijalnost, ali pokušavam kodirati svaki tjedan. Jer volim igrice!

Industrija računalnih igara je ogromna, o kojoj se danas više priča nego o filmskoj industriji. Igre su pisane od početka razvoja računala, koristeći, prema modernim standardima, složene i osnovne razvojne metode. S vremenom su se počeli pojavljivati ​​pokretači igara s već programiranom grafikom, fizikom i zvukom. Omogućuju vam da se usredotočite na razvoj same igre i ne brinete o njezinim temeljima. Ali zajedno s njima, s motorima, programeri "oslijepe" i degradiraju. Sama proizvodnja igara stavljena je na pokretnu traku. I kvantiteta proizvodnje počinje prevladavati nad njezinom kvalitetom.

U isto vrijeme, kada igramo tuđe igre, stalno smo ograničeni lokacijama, radnjom, likovima, mehanikom igre koje su drugi smislili. Pa sam shvatio da...

… vrijeme je da stvorite vlastite svjetove, podložne samo meni. Svjetovi u kojima sam Ja Otac, i Sin, i Duh Sveti!

I iskreno vjerujem da ćete pisanjem vlastitog game engine-a i igre na njemu moći otvoriti oči, obrisati prozore i pumpati svoju kabinu, postajući iskusniji i integralniji programer.

U ovom članku pokušat ću vam reći kako sam počeo pisati male igre u C / C ++, koji je proces razvoja i gdje nalazim vremena za hobi u užurbanom okruženju. Subjektivan je i opisuje proces pojedinog početka. Materijal o neznanju i vjeri, o mojoj trenutnoj osobnoj slici svijeta. Drugim riječima, "Administracija nije odgovorna za vaše osobne mozgove!".

Praksa

“Znanje bez prakse je beskorisno, praksa bez znanja je opasna.” Konfucije

Moja bilježnica je moj život!


Dakle, u praksi mogu reći da kod mene sve počinje s bilježnicom. Tamo ne zapisujem samo svoje dnevne zadatke, već i crtam, programiram, dizajniram dijagrame toka i rješavam probleme, uključujući i matematičke. Uvijek koristite blok za pisanje i pišite samo olovkom. Čist je, udoban i pouzdan, IMHO.

DevOps C++ i "kuhinjski ratovi", ili kako sam počeo pisati igre dok jedem
Moja (već popunjena) bilježnica. Ovako to izgleda. Sadrži svakodnevne zadatke, ideje, crteže, dijagrame, rješenja, crno knjigovodstvo, šifru itd.

U ovoj sam fazi uspio dovršiti tri projekta (to je u mom razumijevanju "konačnosti", jer se svaki proizvod može razvijati relativno beskonačno).

  • Projekt 0: ovo je Architect Demo 3D scena napisana u C# koristeći Unity motor za igru. Za macOS i Windows platforme.
  • 1. igra: konzolna igra Simple Snake (svima poznata kao "Snake") za Windows. napisano u C.
  • 2. igra: konzolna igra Crazy Tanks (svima poznata kao "Tanks"), već napisana u C ++ (koristeći klase) i također pod Windowsima.

Projekt 0 Demo arhitekta

  • Platforma: Windows (Windows 7, 10), Mac OS (OS X El Capitan v. 10.11.6)
  • Jezik: C#
  • Motor igre: Jedinstvo
  • Inspiracija: Darrin Lile
  • Spremište: GitHub

DevOps C++ i "kuhinjski ratovi", ili kako sam počeo pisati igre dok jedem
Demo arhitekta 3D scene

Prvi projekt nije implementiran u C/C++, već u C# koristeći Unity game engine. Ovaj motor nije bio toliko zahtjevan za hardver kao Unreal Engine, a također mi se činilo lakšim za instaliranje i korištenje. Druge motore nisam razmatrao.

Cilj u Unityju za mene nije bio razviti nekakvu igru. Htio sam stvoriti 3D scenu s nekom vrstom lika. On, odnosno ona (ja sam modelirao djevojku u koju sam bio zaljubljen =) morao se kretati i komunicirati s vanjskim svijetom. Bilo je samo važno razumjeti što je Unity, kakav je proces razvoja i koliko je truda potrebno da se nešto stvori. Tako je nastao projekt Architect Demo (ime je izmišljeno gotovo iz sranja). Programiranje, modeliranje, animacija, teksturiranje oduzelo mi je sigurno dva mjeseca svakodnevnog rada.

Počeo sam s video-uputama na YouTubeu o tome kako izraditi 3D modele Miješalica. Blender je odličan besplatni alat za 3D modeliranje (i više) koji ne zahtijeva instalaciju. I ovdje me čekao šok ... Ispostavilo se da su modeliranje, animacija, teksturiranje ogromne zasebne teme o kojima možete pisati knjige. To posebno vrijedi za likove. Za modeliranje prstiju, zuba, očiju i drugih dijelova tijela potrebno vam je poznavanje anatomije. Kako su raspoređeni mišići lica? Kako se ljudi kreću? Morao sam “ubaciti” kosti u svaku ruku, nogu, prst, zglobove!

Modelirajte ključnu kost, dodatne poluge kostiju, tako da animacija izgleda prirodno. Nakon takvih lekcija, shvatite koliki veliki posao rade kreatori animiranog filma, samo da bi stvorili 30 sekundi videa. Ali 3D filmovi traju satima! I onda izađemo iz kina i kažemo nešto poput: “Ta, usrani crtić/film! Mogli su i bolje…” Budale!

I još nešto o programiranju u ovom projektu. Kako se pokazalo, najzanimljiviji mi je bio onaj matematički. Ako pokrenete scenu (link na repozitorij u opisu projekta), primijetit ćete da se kamera okreće oko lika djevojke u sferi. Da bih programirao takvu rotaciju kamere, morao sam prvo izračunati koordinate položaja točke na krugu (2D), a zatim na sferi (3D). Smiješno je to što sam u školi mrzio matematiku i znao sam je s minusom. Djelomično vjerojatno zato što vam u školi jednostavno ne objasne kako se ta matematika, dovraga, primjenjuje u životu. Ali kad ste opsjednuti svojim ciljem, snom, tada se um razbistri, otkrije! I počinjete doživljavati složene zadatke kao uzbudljivu avanturu. I onda pomislite: "Pa, zašto *voljeni* matematičar ne bi mogao normalno reći gdje se te formule mogu nasloniti?".

DevOps C++ i "kuhinjski ratovi", ili kako sam počeo pisati igre dok jedem
Izračunavanje formula za izračunavanje koordinata točke na kružnici i na kugli (iz moje bilježnice)

Igra 1

  • Platforma: Windows (testirano na Windows 7, 10)
  • Jezik: Mislim da je napisano čistim C-jem
  • Motor igre: Windows konzola
  • Inspiracija: javidx9
  • Spremište: GitHub

DevOps C++ i "kuhinjski ratovi", ili kako sam počeo pisati igre dok jedem
Jednostavna igra zmija

3D scena nije igra. Osim toga, modeliranje i animiranje 3D objekata (osobito likova) dugo je i teško. Nakon igranja s Unityjem, shvatio sam da moram nastaviti, točnije krenuti od osnova. Nešto jednostavno i brzo, ali u isto vrijeme globalno, za razumijevanje same strukture igara.

A što imamo jednostavno i brzo? Tako je, konzola i 2D. Točnije, čak i konzola i simboli. Opet sam počeo tražiti inspiraciju na Internetu (općenito Internet smatram najrevolucionarnijim i najopasnijim izumom XNUMX. stoljeća). Iskopao sam video jednog programera koji je napravio konzolni Tetris. I nalik svojoj igri odlučio je sasjeći "zmiju". Iz videa sam naučio o dvije temeljne stvari - petlji igre (s tri osnovne funkcije/dijela) i izlazu u međuspremnik.

Petlja igre može izgledati otprilike ovako:

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

Kod predstavlja cijelu funkciju main() odjednom. I ciklus igre počinje nakon odgovarajućeg komentara. Postoje tri osnovne funkcije u petlji: Input(), Logic(), Draw(). Prvo, unos podataka Unos (uglavnom kontrola pritisaka na tipke), zatim obrada unesenih podataka Logika, zatim prikaz na ekranu - Crtanje. I tako svaki kadar. Animacija se stvara na ovaj način. To je kao u crtićima. Obično obrada ulaznih podataka oduzima najviše vremena i, koliko ja znam, određuje broj sličica u sekundi igre. Ali ovdje je funkcija Logic() vrlo brza. Stoga se brzinom sličica u sekundi mora kontrolirati funkcija Sleep() s parametrom gameSpeed ​​​​koji određuje ovu brzinu.

DevOps C++ i "kuhinjski ratovi", ili kako sam počeo pisati igre dok jedem
ciklus igre. Programiranje zmija u bilježnici

Ako razvijate simboličku konzolnu igru, tada prikazivanje podataka na ekranu korištenjem uobičajenog stream izlaza 'cout' neće raditi - vrlo je sporo. Stoga se izlaz mora izvršiti u međuspremniku zaslona. Toliko brže i igra će raditi bez grešaka. Da budem iskren, ne razumijem baš što je screen buffer i kako radi. Ali ovdje ću dati primjer koda i možda će netko u komentarima moći razjasniti situaciju.

Dobivanje međuspremnika zaslona (ako mogu tako reći):

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

Direktan izlaz na ekran određene linije scoreLine (linija za prikaz rezultata):

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

U teoriji, u ovoj igri nema ništa komplicirano, čini mi se dobrim primjerom početne igre. Kod je napisan u jednoj datoteci i raspoređen u nekoliko funkcija. Nema klasa, nema nasljeđa. Sami možete vidjeti sve u izvornom kodu igre odlaskom na repozitorij na GitHubu.

Igra 2 Crazy Tanks

DevOps C++ i "kuhinjski ratovi", ili kako sam počeo pisati igre dok jedem
Igra Crazy Tanks

Ispisivanje znakova na konzoli vjerojatno je najjednostavnija stvar koju možete pretvoriti u igru. Ali tada se pojavljuje problem: likovi imaju različite visine i širine (visina je veća od širine). Tako će sve izgledati neproporcionalno, a kretanje prema dolje ili gore činit će se puno bržim od kretanja lijevo ili desno. Ovaj efekt je vrlo uočljiv u "Zmiji" (Igra 1). "Tankovi" (Game 2) nemaju takav nedostatak, budući da je izlaz tamo organiziran bojanjem piksela zaslona različitim bojama. Moglo bi se reći da sam napisao renderer. Istina, ovo je već malo kompliciranije, iako puno zanimljivije.

Za ovu igricu će biti dovoljno da opišem svoj sustav za prikaz piksela na ekranu. Mislim da je ovo glavni dio igre. A sve ostalo možete sami smisliti.

Dakle, ono što vidite na ekranu samo je skup pokretnih obojenih pravokutnika.

DevOps C++ i "kuhinjski ratovi", ili kako sam počeo pisati igre dok jedem
Pravokutni set

Svaki pravokutnik je predstavljen matricom ispunjenom brojevima. Usput, mogu istaknuti jednu zanimljivu nijansu - sve matrice u igri programirane su kao jednodimenzionalni niz. Ne dvodimenzionalno, nego jednodimenzionalno! S jednodimenzionalnim nizovima mnogo je lakše i brže raditi.

DevOps C++ i "kuhinjski ratovi", ili kako sam počeo pisati igre dok jedem
Primjer matrice tenka za igru

DevOps C++ i "kuhinjski ratovi", ili kako sam počeo pisati igre dok jedem
Predstavljanje matrice tenka za igru ​​s jednodimenzionalnim nizom

DevOps C++ i "kuhinjski ratovi", ili kako sam počeo pisati igre dok jedem
Još ilustrativniji primjer matričnog predstavljanja jednodimenzionalnim nizom

Ali pristup elementima niza događa se u dvostrukoj petlji, kao da se ne radi o jednodimenzionalnom, već o dvodimenzionalnom nizu. To je učinjeno jer još uvijek radimo s matricama.

DevOps C++ i "kuhinjski ratovi", ili kako sam počeo pisati igre dok jedem
Prelazak jednodimenzionalnog niza u dvostrukoj petlji. Y je ID retka, X je ID stupca

Napominjemo da umjesto uobičajenih matričnih identifikatora i, j, koristim identifikatore x i y. Tako, čini mi se, oku ugodnije, a mozgu jasnije. Osim toga, takva notacija omogućuje prikladno projiciranje korištenih matrica na koordinatne osi dvodimenzionalne slike.

Sada o pikselima, boji i zaslonu. Za izlaz se koristi funkcija StretchDIBits (Header: windows.h; Library: gdi32.lib). Između ostalog, ovoj funkciji se prosljeđuje: uređaj na kojem se slika prikazuje (u mom slučaju to je Windows konzola), koordinate početka prikaza slike, njena širina/visina i slika sama u obliku bitmape (bitmapa), predstavljena nizom bajtova. Bitmapa kao niz bajtova!

Funkcija StretchDIBits() na djelu:

// 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
               );

Memorija je unaprijed dodijeljena za ovu bitmapu pomoću funkcije VirtualAlloc(). Odnosno, potreban broj bajtova rezerviran je za pohranu informacija o svim pikselima, koji će se zatim prikazati na ekranu.

Stvaranje m_p_bitmapMemory bitmape:

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

Grubo govoreći, bitmapa se sastoji od skupa piksela. Svaka četiri bajta u nizu je RGB piksel. Jedan bajt po crvenoj vrijednosti, jedan bajt po zelenoj vrijednosti (G) i jedan bajt po plavoj boji (B). Osim toga, postoji jedan bajt po uvlaci. Ove tri boje – crvena/zelena/plava (RGB) – miješaju se jedna s drugom u različitim omjerima – i dobiva se rezultirajuća boja piksela.

Sada, opet, svaki pravokutnik, ili objekt igre, predstavljen je matricom brojeva. Svi ovi predmeti igre smješteni su u zbirku. Zatim se postavljaju na igralište, tvoreći jednu veliku numeričku matricu. Svaki broj u matrici preslikao sam u određenu boju. Na primjer, broj 8 je plav, broj 9 je žut, broj 10 je tamno siv i tako dalje. Dakle, možemo reći da imamo matricu polja za igru, gdje je svaki broj neke vrste boje.

Dakle, imamo numeričku matricu cijelog polja za igru ​​s jedne strane i bitmapu za prikaz slike s druge strane. Zasad je bitmapa "prazna" - još nema informacije o pikselima željene boje. To znači da će zadnji korak biti popunjavanje bitmape informacijama o svakom pikselu na temelju numeričke matrice polja za igru. Ilustrativan primjer takve transformacije je na slici ispod.

DevOps C++ i "kuhinjski ratovi", ili kako sam počeo pisati igre dok jedem
Primjer popunjavanja bitmape (Pixel matrica) informacijama na temelju numeričke matrice (Digitalne matrice) polja za igru ​​(indeksi boja ne odgovaraju indeksima u igrici)

Također ću predstaviti dio pravog koda iz igre. Varijabli colorIndex pri svakoj iteraciji petlje dodjeljuje se vrijednost (indeks boje) iz numeričke matrice polja za igru ​​(mainDigitalMatrix). Tada se sama boja upisuje u varijablu boje na temelju indeksa. Nadalje, dobivena boja se dijeli na omjer crvene, zelene i plave (RGB). A zajedno s uvlakom (pixelPadding), te se informacije uvijek iznova upisuju u piksel, tvoreći sliku u boji u bitmapi.

Kod koristi pokazivače i bitovne operacije, što može biti teško razumjeti. Stoga vam savjetujem da negdje zasebno pročitate kako takve strukture funkcioniraju.

Popunjavanje bitmape informacijama na temelju numeričke matrice polja za igru:

// 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;
   }

Prema gore opisanoj metodi, u igrici Crazy Tanks formira se jedna slika (okvir) koja se prikazuje na ekranu u funkciji Draw(). Nakon registracije pritisaka tipki u funkciji Input() i njihove naknadne obrade u funkciji Logic(), formira se nova slika (okvir). Istina, objekti igre mogu već imati drugačiji položaj na igralištu i, prema tome, nacrtani su na drugom mjestu. Tako nastaje animacija (pokret).

U teoriji (ako niste ništa zaboravili), razumijevanje petlje igre iz prve igre (“Snake”) i sustava za prikaz piksela na ekranu iz druge igre (“Tanks”) je sve što trebate da napišete bilo koji vaših 2D igara za Windows. Bez zvuka! 😉 Ostali dijelovi su samo izmišljotina.

Naravno, igra "Tanks" dizajnirana je mnogo kompliciranije od "Zmije". Već sam koristio jezik C++, odnosno opisivao sam različite objekte igre s klasama. Napravio sam vlastitu kolekciju - kod možete vidjeti u zaglavljima/Box.h. Usput, kolekcija najvjerojatnije ima curenje memorije. Rabljeni pokazivači. Radio s memorijom. Moram reći da mi je knjiga puno pomogla. Početak C++ kroz programiranje igara. Ovo je sjajan početak za početnike u C++. Mali je, zanimljiv i dobro organiziran.

Za razvoj ove igre bilo je potrebno oko šest mjeseci. Pisao sam uglavnom za vrijeme ručka i užine na poslu. Sjedio je u uredskoj kuhinji, gazio hranu i pisao kod. Ili kod kuće na večeru. Pa sam dobio takve "kuhinjske ratove". Kao i uvijek, aktivno sam koristio bilježnicu i sve konceptualne stvari rođene su u njoj.

Na kraju praktičnog dijela izvući ću nekoliko skenova svoje bilježnice. Pokazati što sam točno zapisivao, crtao, brojao, dizajnirao…

DevOps C++ i "kuhinjski ratovi", ili kako sam počeo pisati igre dok jedem
Dizajn slike tenka. I definicija koliko piksela svaki spremnik treba zauzimati na ekranu

DevOps C++ i "kuhinjski ratovi", ili kako sam počeo pisati igre dok jedem
Izračun algoritma i formula za rotaciju spremnika oko svoje osi

DevOps C++ i "kuhinjski ratovi", ili kako sam počeo pisati igre dok jedem
Dijagram moje kolekcije (one s curenjem memorije, najvjerojatnije). Zbirka je stvorena kao povezani popis

DevOps C++ i "kuhinjski ratovi", ili kako sam počeo pisati igre dok jedem
A to su uzaludni pokušaji da se umjetna inteligencija ubaci u igru

teorija

"Čak i putovanje od tisuću milja počinje prvim korakom" (stara kineska mudrost)

Prijeđimo s prakse na teoriju! Kako nalazite vrijeme za svoj hobi?

  1. Odredite što stvarno želite (jao, ovo je najteže).
  2. Postavite prioritete.
  3. Žrtvujte sve "suvišno" radi viših prioriteta.
  4. Idite prema svojim ciljevima svaki dan.
  5. Nemojte očekivati ​​da će biti dva ili tri sata slobodnog vremena za hobi.

S jedne strane, morate odrediti što želite i odrediti prioritete. S druge strane, moguće je odustati od nekih slučajeva/projekata u korist ovih prioriteta. Drugim riječima, morat ćete žrtvovati sve "suvišno". Negdje sam čuo da u životu trebaju postojati najviše tri glavne aktivnosti. Tada ćete se s njima moći nositi na najbolji mogući način. A dodatni projekti/smjerovi počet će otrcano preopteretiti. Ali to je sve, vjerojatno, subjektivno i individualno.

Postoji određeno zlatno pravilo: nikad nemajte dan 0%! Saznao sam o tome u članku indie programera. Ako radite na projektu, učinite nešto u vezi s tim svaki dan. I nije važno koliko zaradite. Napišite jednu riječ ili jedan redak koda, pogledajte jedan video s uputama ili zakucajte jedan čavao u ploču—samo učinite nešto. Najteže je započeti. Jednom kada počnete, vjerojatno ćete učiniti malo više nego što ste željeli. Tako ćete se stalno kretati prema svom cilju i to, vjerujte mi, vrlo brzo. Uostalom, glavna kočnica svih stvari je odugovlačenje.

I važno je zapamtiti da ne smijete podcijeniti i zanemariti besplatnu "piljevinu" vremena u 5, 10, 15 minuta, pričekajte neke velike "cjepanice" u trajanju od sat ili dva. Stojite li u redu? Razmislite o nečemu za svoj projekt. Idete li uz pokretne stepenice? Zapiši nešto u bilježnicu. Jedete li u autobusu? Dobro, pročitaj neki članak. Iskoristite svaku priliku. Prestanite gledati mačke i pse na YouTubeu! Ne petljaj se s mozgom!

I zadnji. Ako vam se nakon čitanja ovog članka svidjela ideja stvaranja igara bez korištenja motora za igre, sjetite se imena Casey Muratori. Ovaj tip ima web stranica. U odjeljku "pogledaj -> PRETHODNE EPIZODE" pronaći ćete nevjerojatne besplatne video upute o tome kako stvoriti profesionalnu igru ​​od nule. Možda ćete u pet lekcija Intro to C for Windows naučiti više nego u pet godina studija na fakultetu (o tome je netko napisao u komentarima ispod videa).

Casey također objašnjava da ćete razvojem vlastitog pokretača igara bolje razumjeti sve postojeće motore. U svijetu okvira, gdje svi pokušavaju automatizirati, naučit ćete kako stvarati, a ne koristiti. Razumjeti samu prirodu računala. Također ćete postati puno inteligentniji i zreliji programer - profesionalac.

Sretno na odabranom putu! I učinimo svijet profesionalnijim.

Autor: Grankin Andrej, DevOps



Izvor: www.habr.com