DevOps C++ i "kuhinjski ratovi", ili Kako sam počeo da pišem igrice dok jedem

"Znam da ništa ne znam" Sokrat

Za koga: za IT ljude koji pljuju na sve programere i žele da igraju njihove igrice!

O čemu: kako početi pisati igre na C/C++ ako vam je potrebno!

Zašto biste trebali pročitati ovo: Razvoj aplikacija nije moja radna specijalnost, ali pokušavam da kodiram svake sedmice. Jer volim igrice!

zdravo moje ime je Andrey Grankin, Ja sam DevOps u Luxoftu. Razvoj aplikacija nije moja radna specijalnost, ali pokušavam da kodiram svake sedmice. Jer volim igrice!

Industrija kompjuterskih igara je ogromna, o kojoj se danas više priča od filmske industrije. Igre se pišu od početka razvoja računara, koristeći, po savremenim standardima, složene i osnovne razvojne metode. Vremenom su se počeli pojavljivati ​​motori za igre sa već programiranom grafikom, fizikom i zvukom. Omogućuju vam da se usredotočite na razvoj same igre i ne brinete o njenom temelju. Ali zajedno s njima, sa motorima, programeri "slijepe" i degradiraju. Sama proizvodnja igara stavljena je na pokretnu traku. I količina proizvodnje počinje da prevladava nad njenim kvalitetom.

Istovremeno, kada igramo tuđe igre, stalno smo ograničeni lokacijama, radnjom, likovima, mehanikom igre koju su drugi ljudi smislili. Pa sam shvatio da...

… vrijeme je da kreirate svoje svjetove, podložni 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šaću da vam ispričam kako sam počeo da pišem male igre na C/C++, kakav je proces razvoja i gde nalazim vremena za hobi u užurbanom okruženju. Subjektivan je i opisuje proces individualnog starta. Materijal o neznanju i vjeri, o mojoj ličnoj slici svijeta u ovom trenutku. Drugim riječima, "Uprava nije odgovorna za vaše lične mozgove!".

Praksa

"Znanje bez prakse je beskorisno, praksa bez znanja je opasna." Konfucije

Moja sveska je moj život!


Tako da u praksi mogu reći da za mene sve počinje od sveske. Tu 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 notes i pišite samo olovkom. Čist je, udoban i pouzdan, IMHO.

DevOps C++ i "kuhinjski ratovi", ili Kako sam počeo da pišem igrice dok jedem
Moja (već popunjena) sveska. Ovako to izgleda. Sadrži svakodnevne zadatke, ideje, crteže, dijagrame, rješenja, crno knjigovodstvo, kod i tako dalje.

U ovoj fazi sam uspio da završim tri projekta (to je po mom razumijevanju „konačnosti“, jer se svaki proizvod može razvijati relativno beskonačno).

  • Projekat 0: ovo je Architect Demo 3D scena napisana u C# koristeći Unity game engine. 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 na C ++ (koristeći klase) i također pod Windowsom.

Project 0 Architect Demo

  • Platforma: Windows (Windows 7, 10), Mac OS (OS X El Capitan v. 10.11.6)
  • Jezik: C#
  • Motor igre: jedinstvo
  • inspiracija: Darrin Lile
  • Repozitorijum: GitHub

DevOps C++ i "kuhinjski ratovi", ili Kako sam počeo da pišem igrice dok jedem
3D Scene Architect Demo

Prvi projekat 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 činilo mi se i lakšim za instalaciju i korištenje. Nisam uzimao u obzir druge motore.

Cilj u Unity-u za mene nije bio razviti neku vrstu igre. Hteo sam da napravim 3D scenu sa nekom vrstom lika. On, odnosno ona (ja sam modelirao djevojku u koju sam bio zaljubljen =) morao se kretati i komunicirati sa vanjskim svijetom. Bilo je važno samo razumjeti šta je Unity, šta je razvojni proces i koliko je truda potrebno da se nešto stvori. Tako je rođen Architect Demo projekat (ime je izmišljeno skoro iz sranja). Programiranje, modeliranje, animacija, teksturiranje mi je trebalo vjerovatno dva mjeseca svakodnevnog rada.

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

Modelirajte ključnu kost, dodatne koštane poluge, tako da animacija izgleda prirodno. Nakon ovakvih lekcija, shvatite kakav ogroman posao rade kreatori animiranih filmova, samo da naprave 30 sekundi videa. Ali 3D filmovi traju satima! A onda izađemo iz bioskopa i kažemo nešto poput: „Ta, usrani crtani/film! Mogli su i bolje...” Budale!

I još nešto o programiranju u ovom projektu. Kako se ispostavilo, najzanimljiviji dio za mene bio je onaj matematički. Ako pokrenete scenu (link do repozitorija u opisu projekta), primijetit ćete da se kamera rotira oko lika djevojke u sferi. Da bih programirao takvu rotaciju kamere, morao sam prvo izračunati koordinate pozicijske tačke na krugu (2D), a zatim na sferi (3D). Smiješno je što sam mrzeo matematiku u školi i znao sam je sa minusom. Djelomično, vjerovatno, zato što vam u školi jednostavno ne objašnjavaju kako se ova matematika primjenjuje u životu. Ali kada ste opsjednuti svojim ciljem, snom, tada se um čisti, otkriva! I počinjete da doživljavate složene zadatke kao uzbudljivu avanturu. I onda pomislite: „Pa, zašto *voljeni* matematičar normalno ne bi mogao reći gdje se ove formule mogu nasloniti?“.

DevOps C++ i "kuhinjski ratovi", ili Kako sam počeo da pišem igrice dok jedem
Izračunavanje formula za izračunavanje koordinata tačke na kružnici i na sferi (iz moje bilježnice)

Igra 1

  • Platforma: Windows (testirano na Windows 7, 10)
  • Jezik: Mislim da je napisano u čistom C
  • Motor igre: Windows konzola
  • inspiracija: javidx9
  • Repozitorijum: GitHub

DevOps C++ i "kuhinjski ratovi", ili Kako sam počeo da pišem igrice dok jedem
Jednostavna igra zmija

3D scena nije igra. Osim toga, modeliranje i animiranje 3D objekata (posebno likova) je dugo i teško. Nakon što sam se poigrao Unity-om, shvatio sam da moram nastaviti, odnosno početi od osnova. Nešto jednostavno i brzo, ali u isto vrijeme globalno, za razumijevanje same strukture igara.

A šta imamo jednostavno i brzo? Tako je, konzola i 2D. Tačnije, čak i konzola i simboli. Opet sam počeo da tražim inspiraciju na internetu (općenito, internet smatram najrevolucionarnijim i najopasnijim izumom XNUMX. veka). Iskopao sam video jednog programera koji je napravio konzolni Tetris. I po ugledu na svoju igru, odlučio je da poseče "zmiju". Iz videa sam naučio o dvije fundamentalne stvari - petlji igre (sa tri osnovne funkcije/dijela) i izlazu u bafer.

Petlja igre bi mogla 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 pritiska na tastere), zatim obrada unetih podataka Logika, zatim prikaz na ekranu - Crtanje. I tako svaki kadar. Animacija se stvara na ovaj način. To je kao crtani filmovi. Obično obrada ulaznih podataka oduzima najviše vremena i, koliko znam, određuje brzinu kadrova u igri. Ali ovdje je funkcija Logic() vrlo brza. Stoga, brzinu kadrova mora kontrolirati funkcija Sleep() s parametrom gameSpeed, koji određuje ovu brzinu.

DevOps C++ i "kuhinjski ratovi", ili Kako sam počeo da pišem igrice dok jedem
ciklus igre. Programiranje zmija u notepadu

Ako razvijate simboličku konzolnu igru, tada prikazivanje podataka na ekranu koristeći uobičajeni izlazni stream 'cout' neće raditi - vrlo je sporo. Stoga se izlaz mora izvesti u međuspremniku ekrana. Toliko brže i igra će raditi bez grešaka. Da budem iskren, ne razumem baš šta je bafer ekrana i kako funkcioniše. Ali ovdje ću dati primjer koda, a možda će neko u komentarima moći razjasniti situaciju.

Dobivanje bafera ekrana (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, nema ništa komplikovano u ovoj igrici, čini mi se dobrim primjerom igre za početnike. Kod je napisan u jednoj datoteci i raspoređen u nekoliko funkcija. Nema klasa, nema nasledstva. I sami možete vidjeti sve u izvornom kodu igre tako što ćete otići u spremište na GitHub-u.

Igra 2 Crazy Tanks

DevOps C++ i "kuhinjski ratovi", ili Kako sam počeo da pišem igrice dok jedem
Igra Crazy Tanks

Štampanje znakova na konzoli je vjerovatno najjednostavnija stvar koju možete pretvoriti u igru. Ali onda se pojavljuje jedan problem: znakovi imaju različite visine i širine (visina je veća od širine). Tako će sve izgledati neproporcionalno, a kretanje dolje ili gore će se činiti mnogo brže od pomicanja lijevo ili desno. Ovaj efekat je veoma primetan u "Snake" (Igra 1). "Tenkovi" (Igra 2) nemaju takav nedostatak, jer je izlaz organiziran farbanjem piksela ekrana različitim bojama. Moglo bi se reći da sam napisao renderer. Istina, ovo je već malo komplikovanije, iako mnogo zanimljivije.

Za ovu igru ​​biće dovoljno da opišem moj sistem za prikazivanje piksela na ekranu. Mislim da je ovo glavni dio igre. A sve ostalo možete sami smisliti.

Dakle, ono što vidite na ekranu je samo skup pokretnih pravougaonika u boji.

DevOps C++ i "kuhinjski ratovi", ili Kako sam počeo da pišem igrice dok jedem
Pravougaonik set

Svaki pravougaonik je predstavljen matricom ispunjenom brojevima. Inače, mogu istaknuti jednu zanimljivu nijansu - sve matrice u igri su programirane kao jednodimenzionalni niz. Ne dvodimenzionalno, već jednodimenzionalno! Jednodimenzionalni nizovi su mnogo lakši i brži za rad.

DevOps C++ i "kuhinjski ratovi", ili Kako sam počeo da pišem igrice dok jedem
Primjer matrice tenka za igru

DevOps C++ i "kuhinjski ratovi", ili Kako sam počeo da pišem igrice dok jedem
Predstavljanje matrice tenka za igru ​​s jednodimenzionalnim nizom

DevOps C++ i "kuhinjski ratovi", ili Kako sam počeo da pišem igrice dok jedem
Ilustrativniji primjer matrične reprezentacije jednodimenzionalnim nizom

Ali pristup elementima niza odvija se u dvostrukoj petlji, kao da nije jednodimenzionalni, već dvodimenzionalni niz. Ovo je učinjeno jer još uvijek radimo sa matricama.

DevOps C++ i "kuhinjski ratovi", ili Kako sam počeo da pišem igrice dok jedem
Prelazak jednodimenzionalnog niza u dvostrukoj petlji. Y je ID reda, X je ID stupca

Imajte na umu da umjesto uobičajenih matričnih identifikatora i, j, koristim identifikatore x i y. Dakle, čini mi se, ugodnije za oko i jasnije za mozak. Osim toga, takva notacija omogućava pogodno projektovanje korištenih matrica na koordinatne ose dvodimenzionalne slike.

Sada o pikselima, boji i displeju. Za izlaz se koristi funkcija StretchDIBits (Zaglavlje: windows.h; Biblioteka: 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 prikazivanja slike, njena širina/visina i slika sebe u obliku bitmape (bitmapa), predstavljenog nizom bajtova. Bitmap 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 se unaprijed dodjeljuje za ovu bitmapu pomoću funkcije VirtualAlloc(). To jest, potreban broj bajtova je rezerviran za pohranjivanje informacija o svim pikselima, koji će se zatim prikazati na ekranu.

Kreiranje bitmape 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);

Grubo govoreći, bitmapa se sastoji od skupa piksela. Svaka četiri bajta u nizu su RGB piksel. Jedan bajt po crvenoj vrijednosti, jedan bajt po zelenoj vrijednosti (G) i jedan bajt po plavoj boji (B). Plus, postoji jedan bajt po uvlačenju. Ove tri boje - crvena / zelena / plava (RGB) - miješaju se jedna s drugom u različitim proporcijama - i dobijena je rezultujuća boja piksela.

Sada, opet, svaki pravougaonik, ili objekt igre, predstavljen je brojevnom matricom. Svi ovi objekti igre smješteni su u kolekciju. A zatim se postavljaju na teren za igru, formirajući jednu veliku numeričku matricu. Svaki broj u matrici sam preslikao 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 neka vrsta boje.

Dakle, imamo numeričku matricu cijelog igrališta s jedne strane i bitmap za prikaz slike s druge strane. Za sada je bitmapa "prazna" - još nema informaciju o pikselima željene boje. To znači da će posljednji korak biti popunjavanje bitmape informacijama o svakom pikselu na osnovu numeričke matrice polja za igru. Ilustrativan primjer takve transformacije je na slici ispod.

DevOps C++ i "kuhinjski ratovi", ili Kako sam počeo da pišem igrice dok jedem
Primjer popunjavanja bitmape (matrice piksela) informacijama zasnovanim na numeričkoj matrici (Digitalna matrica) igrališta (indeksi boja ne odgovaraju indeksima u igri)

Također ću predstaviti dio pravog koda iz igre. Varijabli colorIndex na svakoj iteraciji petlje dodjeljuje se vrijednost (indeks boja) iz numeričke matrice polja za igru ​​(mainDigitalMatrix). Tada se sama boja upisuje u varijablu boje na osnovu indeksa. Nadalje, rezultirajuća boja se dijeli na omjer crvene, zelene i plave (RGB). I zajedno sa uvlačenjem (pixelPadding), ove informacije se upisuju u piksel iznova i iznova, formirajući sliku u boji u bitmapu.

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

Ispunjavanje bitmape informacijama zasnovanim na numeričkoj matrici 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, jedna slika (okvir) se formira u igrici Crazy Tanks i prikazuje na ekranu u funkciji Draw(). Nakon registracije pritiska na tipku u funkciji Input() i njihove naknadne obrade u funkciji Logic() formira se nova slika (okvir). Istina, objekti igre mogu već imati drugačiju poziciju na igralištu i, shodno tome, izvučeni su na drugom mjestu. Tako nastaje animacija (pokret).

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

Naravno, igra "Tenkovi" je dizajnirana mnogo komplikovanije od "Zmije". Već sam koristio C++ jezik, odnosno, opisao sam različite objekte igre sa klasama. Napravio sam svoju kolekciju - kod možete vidjeti u headers/Box.h. Inače, kolekcija najvjerovatnije ima curenje memorije. Korišteni pokazivači. Radio sa memorijom. Moram reći da mi je knjiga mnogo pomogla. Početak C++-a kroz programiranje igara. Ovo je odličan početak za početnike u C++. Mali je, zanimljiv i dobro organizovan.

Za razvoj ove igre bilo je potrebno oko šest mjeseci. Pisala sam uglavnom za vreme ručka i užine na poslu. Sjedio je u kancelarijskoj kuhinji, gazio hranu i pisao šifru. Ili kod kuće za večeru. Tako da sam dobio takve "ratove u kuhinji". Kao i uvijek, aktivno sam koristio bilježnicu iu njoj su se rodile sve konceptualne stvari.

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

DevOps C++ i "kuhinjski ratovi", ili Kako sam počeo da pišem igrice dok jedem
Dizajn slike rezervoara. I definicija koliko piksela svaki rezervoar treba da zauzme na ekranu

DevOps C++ i "kuhinjski ratovi", ili Kako sam počeo da pišem igrice dok jedem
Proračun algoritma i formule za rotaciju rezervoara oko svoje ose

DevOps C++ i "kuhinjski ratovi", ili Kako sam počeo da pišem igrice dok jedem
Dijagram moje kolekcije (najvjerovatnije onaj sa curenjem memorije). Kolekcija je kreirana kao povezana lista

DevOps C++ i "kuhinjski ratovi", ili Kako sam počeo da pišem igrice dok jedem
A ovo su uzaludni pokušaji da se u igru ​​uvuče umjetna inteligencija

Teorija

"Čak i putovanje od hiljadu milja počinje prvim korakom" (Drevna kineska mudrost)

Pređimo sa prakse na teoriju! Kako pronalazite vrijeme za svoj hobi?

  1. Odredite šta zaista želite (avaj, ovo je najteže).
  2. Postavite prioritete.
  3. Žrtvujte sve "suvišno" zarad viših prioriteta.
  4. Krećite se ka svojim ciljevima svaki dan.
  5. Ne očekujte da će biti dva-tri sata slobodnog vremena za hobi.

S jedne strane, morate odrediti šta ž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". Čuo sam negde da u životu treba da postoje najviše tri glavne aktivnosti. Tada ćete se moći nositi s njima na najbolji mogući način. I dodatni projekti/smjerovi će početi da se preopterećuju. Ali ovo je sve, vjerovatno, subjektivno i individualno.

Postoji jedno zlatno pravilo: nikada nemoj imati 0% dana! O tome sam saznao u članku jednog indie programera. Ako radite na projektu, učinite nešto po tom pitanju svaki dan. I nije važno koliko zarađujete. Napišite jednu riječ ili jedan red koda, pogledajte jedan video tutorial ili zakucajte jedan ekser u ploču - samo učinite nešto. Najteži dio je započeti. Jednom kada počnete, vjerovatno ćete učiniti nešto više nego što ste željeli. Tako ćete se stalno kretati ka svom cilju i to, vjerujte, vrlo brzo. Na kraju krajeva, glavna kočnica za sve stvari je odugovlačenje.

I važno je zapamtiti da ne treba potcijeniti i zanemariti besplatnu "pilovku" vremena u 5, 10, 15 minuta, čekati neke velike "klade" u trajanju od sat-dva. Da li stojite u redu? Razmislite o nečemu za svoj projekat. Ideš li pokretnim stepenicama? Zapišite nešto u svesku. Jedete li u autobusu? Dobro, pročitaj neki članak. Iskoristite svaku priliku. Prestanite gledati mačke i pse na YouTubeu! Ne petljaj se sa svojim mozgom!

I poslednji. Ako vam se, nakon što ste pročitali ovaj članak, svidjela ideja o stvaranju igara bez korištenja igrica, zapamtite ime Casey Muratori. Ovaj tip ima web stranica. U odjeljku "gledajte -> PRETHODNE EPIZODE" naći ćete fantastične besplatne video tutorijale o tome kako kreirati profesionalnu igru ​​od nule. U pet lekcija Intro to C za Windows, možda ćete naučiti više nego za pet godina studija na univerzitetu (neko je o tome pisao u komentarima ispod videa).

Kejsi takođe objašnjava da ćete razvojem sopstvenog motora za igre bolje razumeti sve postojeće mašine. U svijetu okvira, gdje svi pokušavaju automatizirati, naučit ćete kako kreirati, a ne koristiti. Shvatite samu prirodu računara. I takođe ćete postati mnogo inteligentniji i zreliji programer - profesionalac.

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

Autor: Grankin Andrey, DevOps



izvor: www.habr.com