DevOps C++ in "kuhinjske vojne", ali kako sem začel pisati igre med jedjo

"Vem, da nič ne vem" Sokrat

Za kogar: za IT ljudi, ki jim ni mar za vse razvijalce in želijo igrati njihove igre!

O čem: o tem, kako začeti pisati igre v C/C++, če jih nenadoma potrebujete!

Zakaj bi morali prebrati tole: Razvoj aplikacij ni moje strokovno področje, vendar poskušam kodirati vsak teden. Ker obožujem igre!

živijo, ime mi je Andrej Grankin, sem DevOps pri Luxoftu. Razvoj aplikacij ni moja posebnost, vendar poskušam kodirati vsak teden. Ker obožujem igre!

Industrija računalniških iger je ogromna, govori se, da je celo večja od današnje filmske industrije. Igre so bile napisane od zore računalnikov z uporabo, po sodobnih standardih, kompleksnih in osnovnih razvojnih metod. Sčasoma so se začeli pojavljati igralni motorji z že programirano grafiko, fiziko in zvokom. Omogočajo vam, da se osredotočite na razvoj same igre in ne skrbite za njene temelje. Toda skupaj z njimi, z motorji, razvijalci "oslepijo" in degradirajo. Sama izdelava iger je postavljena na tekoči trak. In količina izdelkov začne prevladovati nad njihovo kakovostjo.

Hkrati pa smo pri igranju iger drugih ljudi nenehno omejeni z lokacijami, zapletom, liki in igralnimi mehanikami, ki so si jih izmislili drugi. Tako sem spoznal, da ...

... prišel je čas, da ustvarim svoje lastne svetove, podrejene samo sebi. Svetovi, kjer sem Oče, Sin in Sveti Duh!

In iskreno verjamem, da boste s pisanjem lastnega igralnega mehanizma in igranjem na njem lahko sezuli čevlje, obrisali okna in nadgradili svojo kabino ter tako postali izkušenejši in popolnejši programer.

V tem članku vam bom poskušal povedati, kako sem začel pisati male igre v C/C++, kakšen je proces razvoja in kje v živahnem okolju najdem čas za hobi. Je subjektivna in opisuje proces posameznikovega začetka. Gradivo o nevednosti in veri, o moji trenutni osebni sliki sveta. Z drugimi besedami, "Uprava ni odgovorna za vaše osebne možgane!"

Practice

"Znanje brez prakse je neuporabno, praksa brez znanja je nevarna." Konfucij

Moj zvezek je moje življenje!


Torej v praksi lahko rečem, da se zame vse začne z beležko. Tam ne le zapisujem vsakodnevna opravila, ampak tudi rišem, programiram, oblikujem diagrame poteka in rešujem probleme, tudi matematične. Vedno uporabljajte beležko in pišite samo s svinčnikom. Je čist, priročen in zanesljiv, IMHO.

DevOps C++ in "kuhinjske vojne", ali kako sem začel pisati igre med jedjo
Moj (že napolnjen) zvezek. Takole izgleda. Vsebuje vsakdanja opravila, ideje, risbe, diagrame, rešitve, črno knjigovodstvo, šifro itd.

Na tej stopnji mi je uspelo dokončati tri projekte (to je v mojem razumevanju "popolnosti", ker je vsak izdelek mogoče razvijati relativno neskončno).

  • Projekt 0: To je demo scena 3D Architect, napisana v C# z uporabo igralnega mehanizma Unity. Za platforme macOS in Windows.
  • 1. igra: konzolna igra Simple Snake (vsi poznana kot »Snake«) za Windows. Napisano v C.
  • 2. igra: konzolna igra Crazy Tanks (vsi poznana kot »Tanks«), napisana v C++ (z uporabo razredov) in tudi za Windows.

Projekt 0. Demo arhitekta

  • Platforma: Windows (Windows 7, 10), Mac OS (OS X El Capitan v. 10.11.6)
  • Jezik: C#
  • Igralni motor: Unity
  • Navdih: Darrin Lile
  • Repozitorij: GitHub

DevOps C++ in "kuhinjske vojne", ali kako sem začel pisati igre med jedjo
Predstavitev 3D Scene Architect

Prvi projekt ni bil implementiran v C/C++, ampak v C# z uporabo igralnega mehanizma Unity. Ta motor ni bil tako zahteven glede strojne opreme kot Unreal Engine, poleg tega pa se je zdel lažji za namestitev in uporabo. Drugih motorjev nisem upošteval.

Moj cilj v Unityju ni bil razviti igre. Želel sem ustvariti 3D sceno z nekim likom. On ali bolje rečeno Ona (jaz sem bil model dekleta, v katerega sem bil zaljubljen =) se je moral premikati in komunicirati s svetom okoli njega. Pomembno je bilo le razumeti, kaj je Unity, kakšen je razvojni proces in koliko truda je potrebno, da nekaj ustvariš. Tako se je rodil projekt Architect Demo (ime je bilo izumljeno skoraj od nikoder). Programiranje, modeliranje, animacija, teksturiranje mi je vzelo verjetno dva meseca vsakodnevnega dela.

Začel sem z videoposnetki vadnic na YouTubu o ustvarjanju 3D modelov v Blender. Blender je odlično brezplačno orodje za 3D modeliranje (in več), ki ne zahteva namestitve. In tukaj me je čakal šok ... Izkazalo se je, da so modeliranje, animacija, teksturiranje ogromne ločene teme, o katerih lahko pišete knjige. To še posebej velja za like. Za modeliranje prstov, zob, oči in drugih delov telesa boste potrebovali poznavanje anatomije. Kako so zgrajene obrazne mišice? Kako se ljudje gibljejo? Moral sem "vstaviti" kosti v vsako roko, nogo, prst, falange prstov!

Modelirajte ključnico in dodatne vzvodne kosti, da bo animacija videti naravna. Po takšnih lekcijah se zaveš, koliko dela imajo ustvarjalci animiranih filmov, da ustvarijo 30 sekund videa. Toda 3D filmi trajajo več ur! In potem gremo iz kinematografov in rečemo nekaj takega: »To je sranje risanka/film! Lahko bi jim šlo bolje ...« Bedaki!

In še nekaj glede programiranja v tem projektu. Kot se je izkazalo, mi je bil najbolj zanimiv matematični del. Če zaženete sceno (povezava do repozitorija v opisu projekta), boste opazili, da se kamera vrti okoli lika dekleta v krogli. Za programiranje takšne rotacije kamere sem moral najprej izračunati koordinate položaja točke na krogu (2D), nato pa še na krogli (3D). Smešno je to, da sem v šoli sovražil matematiko in sem jo znal s C-minus. Delno verjetno zato, ker ti v šoli preprosto ne razložijo, kako za vraga se ta matematika uporablja v življenju. Ko pa si obseden s svojim ciljem, sanjami, se tvoj um zbistri in odpre! In težke naloge začnete dojemati kot vznemirljivo pustolovščino. In potem pomislite: "No, zakaj vam vaš *najljubši* matematik ni mogel normalno povedati, kje je mogoče te formule uporabiti?"

DevOps C++ in "kuhinjske vojne", ali kako sem začel pisati igre med jedjo
Izračun formul za izračun koordinat točke na krogu in na krogli (iz mojega zvezka)

Igra 1. Preprosta kača

  • Platforma: Windows (preizkušeno na Windows 7, 10)
  • Jezik: Mislim, da sem napisal v čistem C-ju
  • Igralni motor: Windows konzola
  • Navdih: javidx9
  • Repozitorij: GitHub

DevOps C++ in "kuhinjske vojne", ali kako sem začel pisati igre med jedjo
Preprosta igra kača

3D scena ni igra. Poleg tega je modeliranje in animiranje 3D-predmetov (zlasti likov) zamudno in težavno. Po igranju z Unityjem sem prišel do spoznanja, da moram nadaljevati ali bolje rečeno začeti pri osnovah. Nekaj ​​preprostega in hitrega, a hkrati globalnega, za razumevanje same strukture iger.

Kaj je enostavno in hitro? Tako je, konzola in 2D. Natančneje, celo konzolo in simbole. Navdih sem spet šel iskat na internet (sploh se mi zdi internet najbolj revolucionaren in nevaren izum XNUMX. stoletja). Izkopal sem video programerja, ki je naredil konzolo Tetris. In po vzoru njegove igre sem se odločil narediti "kačo". Iz videoposnetka sem izvedel dve temeljni stvari - igralno zanko (s tremi osnovnimi funkcijami/deli) in izhod v medpomnilnik.

Zanka igre bi lahko izgledala nekako takole:

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

Koda predstavlja celotno funkcijo main() hkrati. In cikel igre se začne po ustreznem komentarju. V zanki so tri osnovne funkcije: Input(), Logic(), Draw(). Najprej vnos podatkov Input (predvsem nadzor pritiskov tipk), nato obdelava vnesenih podatkov Logic, nato izhod na zaslon - Draw. In tako na vsakem kadru. Tako nastane animacija. Je kot v risankah. Običajno obdelava vnesenih podatkov traja največ časa in, kolikor vem, določa hitrost slike v igri. Toda tukaj se funkcija Logic() izvede zelo hitro. Zato morate hitrost sličic nadzorovati s funkcijo Sleep() s parametrom gameSpeed ​​​​, ki določa to hitrost.

DevOps C++ in "kuhinjske vojne", ali kako sem začel pisati igre med jedjo
Cikel igre. Programiranje "kače" v beležnici

Če razvijate konzolno igro, ki temelji na znakih, ne boste mogli izpisati podatkov na zaslon z običajnim izpisom toka 'cout' - je zelo počasen. Zato je treba izhod poslati v medpomnilnik zaslona. To je veliko hitreje in igra bo delovala brez napak. Če sem iskren, ne razumem povsem, kaj je medpomnilnik zaslona in kako deluje. Ampak tukaj bom dal primer kode in morda lahko kdo razjasni situacijo v komentarjih.

Pridobivanje medpomnilnika zaslona (tako rekoč):

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

Neposredni prikaz določenega niza scoreLine (vrstica prikaza rezultata):

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

Teoretično v tej igri ni nič zapletenega; mislim, da je dober primer za začetni nivo. Koda je zapisana v eni datoteki in oblikovana v več funkcijah. Brez razredov, brez dedovanja. Vse si lahko sami ogledate v izvorni kodi igre, tako da obiščete repozitorij na GitHubu.

Igra 2. Crazy Tanks

DevOps C++ in "kuhinjske vojne", ali kako sem začel pisati igre med jedjo
Igra Crazy Tanks

Tiskanje znakov na konzolo je verjetno najpreprostejša stvar, ki jo lahko spremenite v igro. Toda potem se pojavi ena težava: simboli imajo različne višine in širine (višina je večja od širine). Tako bo vse videti nesorazmerno, premikanje navzdol ali navzgor pa bo videti veliko hitreje kot premikanje levo ali desno. Ta učinek je zelo opazen pri Snake (Igra 1). "Tanks" (Igra 2) nimajo te pomanjkljivosti, saj je tam izhod organiziran z barvanjem slikovnih pik zaslona z različnimi barvami. Lahko bi rekli, da sem napisal upodabljalnik. Res je, to je malo bolj zapleteno, čeprav veliko bolj zanimivo.

Za to igro bo dovolj, da opišem svoj sistem za prikaz slikovnih pik na zaslonu. Menim, da je to glavni del igre. In vse ostalo si lahko izmislite sami.

Torej, kar vidite na zaslonu, je le niz premikajočih se večbarvnih pravokotnikov.

DevOps C++ in "kuhinjske vojne", ali kako sem začel pisati igre med jedjo
Komplet pravokotnikov

Vsak pravokotnik je predstavljen z matriko, napolnjeno s številkami. Mimogrede, lahko izpostavim eno zanimivo nianso - vse matrice v igri so programirane kot enodimenzionalni niz. Ne dvodimenzionalno, ampak enodimenzionalno! Z enodimenzionalnimi nizi je veliko lažje in hitreje delati.

DevOps C++ in "kuhinjske vojne", ali kako sem začel pisati igre med jedjo
Primer matrice igralnega tanka

DevOps C++ in "kuhinjske vojne", ali kako sem začel pisati igre med jedjo
Predstavitev matrike igralnega tanka kot enodimenzionalne matrike

DevOps C++ in "kuhinjske vojne", ali kako sem začel pisati igre med jedjo
Bolj nazoren primer predstavitve matrike kot enodimenzionalne matrike

Toda dostop do elementov matrike poteka v dvojni zanki, kot da ne bi šlo za enodimenzionalno matriko, ampak za dvodimenzionalno. To je narejeno zato, ker še vedno delamo z matricami.

DevOps C++ in "kuhinjske vojne", ali kako sem začel pisati igre med jedjo
Prečkanje enodimenzionalne matrike v dvojni zanki. Y - identifikator vrstice, X - identifikator stolpca

Opomba: namesto običajnih matričnih identifikatorjev i, j uporabljam identifikatorja x in y. Tako se mi zdi bolj prijetno za oko in bolj razumljivo za možgane. Poleg tega takšen zapis omogoča priročno projiciranje uporabljenih matrik na koordinatne osi dvodimenzionalne slike.

Zdaj o slikovnih pikah, barvi in ​​izpisu zaslona. Funkcija StretchDIBits se uporablja za izpis (glava: windows.h; knjižnica: gdi32.lib). Ta funkcija med drugim prejme: napravo, na kateri se slika prikazuje (v mojem primeru je to Windows konzola), začetne koordinate prikaza slike, njeno širino/višino in samo sliko v obliko bitne slike, ki jo predstavlja niz bajtov. Bitna slika kot niz bajtov!

Funkcija StretchDIBits() v akciji:

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

Pomnilnik je za to bitno sliko vnaprej dodeljen s funkcijo VirtualAlloc(). To pomeni, da je potrebno število bajtov rezervirano za shranjevanje informacij o vseh slikovnih pikah, ki bodo nato prikazane na zaslonu.

Ustvarjanje bitne slike 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);

V grobem je bitna slika sestavljena iz zbirke slikovnih pik. Vsaki štirje bajti v matriki so piksel RGB. En bajt na vrednost rdeče barve, en bajt na vrednost zelene barve (G) in en bajt na vrednost modre barve (B). Poleg tega ostane en bajt za zamik. Te tri barve – rdeča/zelena/modra (RGB) – se mešajo med seboj v različnih razmerjih, da se ustvari končna barva slikovnih pik.

Ponovno je vsak pravokotnik ali predmet igre predstavljen z numerično matriko. Vsi ti igralni predmeti so postavljeni v zbirko. Nato so postavljeni na igralno polje in tvorijo eno veliko numerično matriko. Vsako številko v matriki sem povezal z določeno barvo. Na primer, številka 8 ustreza modri barvi, številka 9 rumeni barvi, številka 10 temno sivi barvi itd. Tako lahko rečemo, da imamo matriko igralnega polja, kjer je vsako število barva.

Tako imamo na eni strani numerično matriko celotnega igralnega polja, na drugi pa bitno sliko za prikaz slike. Zaenkrat je bitna slika "prazna" - še ne vsebuje informacij o slikovnih pikah želene barve. To pomeni, da bo zadnji korak zapolnitev bitne slike z informacijami o vsaki slikovni piki na podlagi numerične matrike igralnega polja. Jasen primer takšne preobrazbe je na spodnji sliki.

DevOps C++ in "kuhinjske vojne", ali kako sem začel pisati igre med jedjo
Primer polnjenja bitne slike (Pixel matrix) z informacijami na podlagi digitalne matrike igralnega polja (barvni indeksi se ne ujemajo z indeksi v igri)

Predstavil bom tudi del prave kode iz igre. Spremenljivki colorIndex pri vsaki ponovitvi zanke je dodeljena vrednost (barvni indeks) iz numerične matrike igralnega polja (mainDigitalMatrix). Barvna spremenljivka je nato nastavljena na samo barvo glede na indeks. Nastala barva se nato razdeli na razmerje rdeče, zelene in modre (RGB). In skupaj s pixelPadding se te informacije znova in znova zapisujejo v slikovno piko in tvorijo barvno sliko v bitni sliki.

Koda uporablja kazalce in bitne operacije, kar je lahko težko razumljivo. Zato vam svetujem, da nekje ločeno preberete, kako takšne strukture delujejo.

Polnjenje bitne slike z informacijami na podlagi numerične matrike igralnega polja:

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

Po zgoraj opisani metodi se v igri Crazy Tanks oblikuje ena slika (okvir), ki se prikaže na zaslonu v funkciji Draw(). Po registraciji pritiskov tipk v funkciji Input() in njihovi kasnejši obdelavi v funkciji Logic() se oblikuje nova slika (okvir). Res je, da imajo igralni predmeti morda že drugačen položaj na igralnem polju in so zato narisani na drugem mestu. Tako nastane animacija (gibanje).

V teoriji (če nisem česa pozabil) je razumevanje igralne zanke iz prve igre ("Snake") in sistema za prikaz slikovnih pik na zaslonu iz druge igre ("Tanks") vse, kar potrebujete za pisanje vaših 2D iger v sistemu Windows. Brez zvoka! 😉 Ostali deli so samo domišljija.

Seveda je igra "Tanks" veliko bolj zapletena kot "Snake". Jezik C++ sem že uporabljal, se pravi, različne objekte igre sem opisoval z razredi. Ustvaril sem svojo zbirko - kodo si lahko ogledate v headers/Box.h. Mimogrede, zbirka najverjetneje pušča spomin. Rabljeni kazalci. Delal s spominom. Moram reči, da mi je knjiga zelo pomagala Začetek C++ skozi programiranje iger. To je odličen začetek za začetnike v C++. Je majhen, zanimiv in dobro organiziran.

Za razvoj te igre je trajalo približno šest mesecev. Pisal sem predvsem med malico in malico v službi. Sedel je v pisarniški kuhinji, teptal hrano in pisal kodo. Ali na večerji doma. Tako sem končal s temi "kuhinjskimi vojnami". Kot vedno sem aktivno uporabljal zvezek in vse konceptualne stvari so se rodile v njem.

Za dokončanje praktičnega dela bom nekajkrat skeniral svojo beležnico. Da pokažem, kaj točno sem zapisala, narisala, preštela, oblikovala ...

DevOps C++ in "kuhinjske vojne", ali kako sem začel pisati igre med jedjo
Oblikovanje slik tankov. In določanje, koliko slikovnih pik naj vsak rezervoar zasede na zaslonu

DevOps C++ in "kuhinjske vojne", ali kako sem začel pisati igre med jedjo
Izračun algoritma in formule za vrtenje rezervoarja okoli svoje osi

DevOps C++ in "kuhinjske vojne", ali kako sem začel pisati igre med jedjo
Shema moje zbirke (tista, v kateri je najverjetneje uhajanje spomina). Zbirka je ustvarjena glede na tip Povezani seznam

DevOps C++ in "kuhinjske vojne", ali kako sem začel pisati igre med jedjo
In to so jalovi poskusi, da bi igri pripeli umetno inteligenco

Teorija

"Tudi tisoč milj dolgo potovanje se začne s prvim korakom" (starodavna kitajska modrost)

Preidimo od prakse k teoriji! Kako najti čas za svoj hobi?

  1. Ugotovite, kaj si resnično želite (žal, to je najtežje).
  2. Določite prioritete.
  3. Žrtvujte vse "odvečno" zaradi višjih prioritet.
  4. Vsak dan se premikajte proti ciljem.
  5. Ne pričakujte, da boste dve ali tri ure prostega časa porabili za hobi.

Po eni strani morate določiti, kaj želite, in dati prednost. Po drugi strani pa je mogoče nekatere aktivnosti/projekte opustiti v korist teh prioritet. Z drugimi besedami, žrtvovati boste morali vse "odvečno". Nekje sem slišal, da bi morale biti v življenju največ tri glavne dejavnosti. Potem jih boste lahko naredili najvišje kakovosti. In dodatni projekti/smeri se bodo preprosto začeli preobremenjevati. Ampak to je verjetno vse subjektivno in individualno.

Obstaja določeno zlato pravilo: nikoli ne imejte 0% dneva! O tem sem izvedel v članku neodvisnega razvijalca. Če delate na projektu, naredite nekaj o tem vsak dan. In ni pomembno, koliko delaš. Napišite eno besedo ali eno vrstico kode, oglejte si en videoposnetek z vadnico ali zabijte žebelj v desko – samo naredite nekaj. Najtežje je začeti. Ko enkrat začnete, boste verjetno na koncu naredili malo več, kot ste želeli. Tako se boste nenehno premikali proti svojemu cilju in to, verjemite, zelo hitro. Navsezadnje je glavna ovira pri vseh stvareh odlašanje.

In pomembno je vedeti, da ne smete podcenjevati in prezreti brezplačne "žagovine" časa 5, 10, 15 minut, počakajte na nekaj velikih "hlodov", ki trajajo uro ali dve. Stojiš v vrsti? Razmislite o nečem za svoj projekt. Peljete se po tekočih stopnicah? Zapiši nekaj v beležko. Ali potujete z avtobusom? Super, preberi kakšen članek. Izkoristite vsako priložnost. Nehajte gledati mačke in pse na YouTubu! Ne onesnažuj svojih možganov!

In še zadnja stvar. Če vam je bila po branju tega članka všeč ideja o ustvarjanju iger brez uporabe igralnih mehanizmov, potem se spomnite imena Casey Muratori. Ta tip ima Spletna stran. V razdelku »oglejte si -> PREJŠNJE EPIZODE« boste našli čudovite brezplačne video vadnice o ustvarjanju profesionalne igre iz nič. V petih lekcijah Intro to C for Windows se boste verjetno naučili več kot v petih letih univerzitetnega študija (o tem je nekdo zapisal v komentarjih pod videom).

Casey tudi pojasnjuje, da boste z razvojem lastnega igralnega mehanizma bolje razumeli obstoječe motorje. V svetu okvirov, kjer se vsi trudijo avtomatizirati, se raje naučiš ustvarjati kot uporabljati. Razumete samo naravo računalnikov. Postali boste tudi veliko bolj inteligenten in zrel programer – profesionalec.

Srečno na izbrani poti! In naredimo svet bolj profesionalen.

Avtor: Grankin Andrej, DevOps



Vir: www.habr.com