DevOps C++ i "guerres de cuina", o Com vaig començar a escriure jocs mentre menjava

"Sé que no sé res" Sòcrates

Per aqui: per a la gent informàtica que escup a tots els desenvolupadors i volen jugar als seus jocs!

Sobre que: com començar a escriure jocs en C/C++ si ho necessiteu!

Per què hauríeu de llegir això: El desenvolupament d'aplicacions no és la meva especialitat laboral, però intento programar cada setmana. Perquè m'encanten els jocs!

Hola el meu nom és Andrei Grankin, sóc DevOps a Luxoft. El desenvolupament d'aplicacions no és la meva especialitat laboral, però intento programar cada setmana. Perquè m'encanten els jocs!

La indústria dels jocs d'ordinador és enorme, encara més rumorosa avui que la indústria del cinema. Els jocs s'han escrit des de l'inici del desenvolupament dels ordinadors, utilitzant, segons els estàndards moderns, mètodes de desenvolupament complexos i bàsics. Amb el temps, van començar a aparèixer motors de joc amb gràfics, física i so ja programats. Et permeten centrar-te en el desenvolupament del joc en si i no preocupar-te pel seu fonament. Però juntament amb ells, amb els motors, els desenvolupadors "queden cecs" i es degraden. La pròpia producció de jocs es posa a la cinta transportadora. I la quantitat de producció comença a prevaldre sobre la seva qualitat.

Al mateix temps, quan juguem als jocs d'altres persones, estem constantment limitats per les ubicacions, la trama, els personatges, la mecànica de joc que altres persones van plantejar. Així que em vaig adonar que...

… és hora de crear els teus propis mons, subjectes només a mi. Mons on jo sóc el Pare, i el Fill i l'Esperit Sant!

I crec sincerament que escrivint el vostre propi motor de joc i un joc en ell, podreu obrir els ulls, netejar les finestres i bombejar la vostra cabina, convertint-vos en un programador més experimentat i integral.

En aquest article intentaré explicar-vos com vaig començar a escriure petits jocs en C/C++, quin és el procés de desenvolupament i on trobo temps per a un hobby en un entorn ocupat. És subjectiu i descriu el procés d'un inici individual. Material sobre la ignorància i la fe, sobre la meva imatge personal del món en aquest moment. És a dir, "L'administració no és responsable del teu cervell personal!".

Pràctica

"El coneixement sense pràctica és inútil, la pràctica sense coneixement és perillós." Confuci

La meva llibreta és la meva vida!


Així, a la pràctica, puc dir que tot per a mi comença amb una llibreta. Allà no només anoto les meves tasques diàries, sinó que també dibuixo, programo, dissenyo diagrames de flux i resolc problemes, inclosos els matemàtics. Feu servir sempre un bloc de notes i escriviu només amb un llapis. És net, còmode i fiable, IMHO.

DevOps C++ i "guerres de cuina", o Com vaig començar a escriure jocs mentre menjava
La meva llibreta (ja plena). Així es veu. Conté tasques quotidianes, idees, dibuixos, diagrames, solucions, comptabilitat negra, codi, etc.

En aquesta etapa, vaig aconseguir completar tres projectes (això és el que entenc de "finalitat", perquè qualsevol producte es pot desenvolupar de manera relativament infinita).

  • Projecte 0: aquesta és una escena 3D de demostració d'arquitecte escrita en C# amb el motor de joc Unity. Per a plataformes macOS i Windows.
  • Joc 1: joc de consola Simple Snake (conegut per tothom com "Snake") per a Windows. escrit en C.
  • Joc 2: joc de consola Crazy Tanks (conegut per tothom com "Tanks"), ja escrit en C ++ (utilitzant classes) i també sota Windows.

Projecte 0 Arquitecte Demo

  • Plataforma: Windows (Windows 7, 10), Mac OS (OS X El Capitan v. 10.11.6)
  • Idioma: C#
  • Motor de joc: Unitat
  • Inspiració: Darrin Lile
  • Repositori: GitHub

DevOps C++ i "guerres de cuina", o Com vaig començar a escriure jocs mentre menjava
Demostració d'arquitecte d'escenes 3D

El primer projecte no es va implementar en C/C++, sinó en C# mitjançant el motor de joc Unity. Aquest motor no era tan exigent amb el maquinari com Unreal Engine, i també em va semblar més fàcil d'instal·lar i utilitzar. No vaig tenir en compte altres motors.

L'objectiu a Unity per a mi no era desenvolupar cap tipus de joc. Volia crear una escena en 3D amb algun tipus de personatge. Ell, o millor dit Ella (jo vaig modelar la noia de la qual estava enamorada =) va haver de moure's i interactuar amb el món exterior. Només era important entendre què és Unity, quin és el procés de desenvolupament i quant esforç es necessita per crear alguna cosa. Així va néixer el projecte Architect Demo (el nom es va inventar quasi per la merda). La programació, el modelatge, l'animació, la texturació em van portar probablement dos mesos de treball diari.

Vaig començar amb vídeos tutorials a YouTube sobre com crear models 3D liquadora. Blender és una gran eina gratuïta per al modelatge 3D (i més) que no requereix instal·lació. I aquí m'esperava un xoc... Resulta que el modelatge, l'animació, la texturació són grans temes separats sobre els quals podeu escriure llibres. Això és especialment cert per als personatges. Per modelar els dits, les dents, els ulls i altres parts del cos, necessitareu coneixements d'anatomia. Com es disposen els músculs de la cara? Com es mou la gent? Vaig haver de "inserir" ossos a cada braç, cama, dit, artells!

Modeleu la clavícula, palanques òssies addicionals, de manera que l'animació sembli natural. Després d'aquestes lliçons, t'adones de la gran feina que fan els creadors de pel·lícules d'animació, només per crear 30 segons de vídeo. Però les pel·lícules en 3D duren hores! I aleshores sortim dels teatres i diem alguna cosa com: “Ta, un dibuix animat / pel·lícula de merda! Haurien pogut fer-ho millor...” Ximples!

I una cosa més sobre la programació en aquest projecte. Com va resultar, la part més interessant per a mi va ser la matemàtica. Si executeu l'escena (enllaç al repositori a la descripció del projecte), notareu que la càmera gira al voltant del personatge de la noia en una esfera. Per programar aquesta rotació de la càmera, primer vaig haver de calcular les coordenades del punt de posició al cercle (2D) i després a l'esfera (3D). El més curiós és que odiava les matemàtiques a l'escola i les sabia amb un menys. En part, probablement, perquè a l'escola simplement no t'expliquen com dimonis s'apliquen aquestes matemàtiques a la vida. Però quan estàs obsessionat amb el teu objectiu, somni, llavors la ment s'aclareix, es revela! I comenceu a percebre les tasques complexes com una aventura emocionant. I llavors penses: "Bé, per què el *estimat* matemàtic normalment no podria dir on es poden inclinar aquestes fórmules?".

DevOps C++ i "guerres de cuina", o Com vaig començar a escriure jocs mentre menjava
Càlcul de fórmules per calcular les coordenades d'un punt en un cercle i en una esfera (del meu quadern)

Joc 1

  • Plataforma: Windows (provat a Windows 7, 10)
  • Idioma: Crec que estava escrit en C pur
  • Motor de joc: consola de Windows
  • Inspiració: javidx9
  • Repositori: GitHub

DevOps C++ i "guerres de cuina", o Com vaig començar a escriure jocs mentre menjava
Joc simple de la serp

L'escena 3D no és un joc. A més, modelar i animar objectes en 3D (especialment personatges) és llarg i difícil. Després de jugar amb Unity, em vaig adonar que havia de continuar, o més aviat començar, des del bàsic. Una cosa senzilla i ràpida, però alhora global, per entendre l'estructura mateixa dels jocs.

I què tenim senzill i ràpid? Així és, consola i 2D. Més precisament, fins i tot la consola i els símbols. De nou, vaig començar a buscar inspiració a Internet (en general, considero Internet l'invent més revolucionari i perillós del segle XXI). Vaig desenterrar un vídeo d'un programador que va fer la consola Tetris. I a semblança del seu joc, va decidir tallar la "serp". A partir del vídeo, vaig aprendre sobre dues coses fonamentals: el bucle del joc (amb tres funcions/parts bàsiques) i la sortida al buffer.

El bucle del joc podria semblar a això:

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

El codi presenta tota la funció main() alhora. I el cicle de joc comença després del comentari corresponent. Hi ha tres funcions bàsiques al bucle: Input(), Logic(), Draw(). En primer lloc, l'entrada de dades d'entrada (principalment el control de les pulsacions de tecles), a continuació, el processament de les dades introduïdes Lògica, i després es mostra a la pantalla - Dibuix. I així cada fotograma. L'animació es crea d'aquesta manera. És com dibuixos animats. En general, processar les dades d'entrada pren més temps i, pel que jo sé, determina la velocitat de fotogrames del joc. Però aquí la funció Logic() és molt ràpida. Per tant, la velocitat de fotogrames ha de ser controlada per la funció Sleep() amb el paràmetre gameSpeed ​​​​, que determina aquesta velocitat.

DevOps C++ i "guerres de cuina", o Com vaig començar a escriure jocs mentre menjava
cicle de joc. Programació de la serp al bloc de notes

Si esteu desenvolupant un joc de consola simbòlic, mostrar dades a la pantalla amb la sortida de flux habitual "cout" no funcionarà: és molt lent. Per tant, la sortida s'ha de dur a terme al buffer de pantalla. Molt més ràpid i el joc funcionarà sense problemes. Per ser sincer, no entenc ben bé què és un buffer de pantalla i com funciona. Però aquí donaré un exemple de codi i potser algú dels comentaris podrà aclarir la situació.

Obtenció del buffer de pantalla (si puc dir-ho):

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

Sortida directa a la pantalla d'una determinada línia scoreLine (la línia per mostrar les puntuacions):

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

En teoria, no hi ha res complicat en aquest joc, em sembla un bon exemple de joc d'entrada. El codi està escrit en un fitxer i disposat en diverses funcions. Sense classes, sense herència. Podeu veure-ho tot al codi font del joc si aneu al repositori de GitHub.

Joc 2 Crazy Tanks

DevOps C++ i "guerres de cuina", o Com vaig començar a escriure jocs mentre menjava
Joc Crazy Tanks

Imprimir caràcters a la consola és probablement la cosa més senzilla que podeu convertir en un joc. Però llavors apareix un problema: els personatges tenen diferents alçades i amplades (l'alçada és més gran que l'amplada). Així, tot semblarà desproporcionat, i moure's cap avall o cap amunt semblarà molt més ràpid que moure's cap a l'esquerra o la dreta. Aquest efecte és molt notable a "Snake" (Joc 1). Els "tancs" (joc 2) no tenen aquest inconvenient, ja que la sortida s'organitza pintant els píxels de la pantalla amb diferents colors. Es podria dir que vaig escriure un renderitzador. És cert que això ja és una mica més complicat, encara que molt més interessant.

Per a aquest joc, n'hi haurà prou amb descriure el meu sistema per mostrar píxels a la pantalla. Crec que aquesta és la part principal del joc. I tot el que puguis inventar tu mateix.

Per tant, el que veus a la pantalla és només un conjunt de rectangles de colors en moviment.

DevOps C++ i "guerres de cuina", o Com vaig començar a escriure jocs mentre menjava
Conjunt rectangular

Cada rectangle està representat per una matriu plena de números. Per cert, puc destacar un matís interessant: totes les matrius del joc estan programades com una matriu unidimensional. No bidimensional, sinó unidimensional! Les matrius unidimensionals són molt més fàcils i ràpides de treballar.

DevOps C++ i "guerres de cuina", o Com vaig començar a escriure jocs mentre menjava
Un exemple de matriu de tancs de joc

DevOps C++ i "guerres de cuina", o Com vaig començar a escriure jocs mentre menjava
Representació de la matriu d'un tanc de joc amb una matriu unidimensional

DevOps C++ i "guerres de cuina", o Com vaig començar a escriure jocs mentre menjava
Un exemple més il·lustratiu d'una representació matricial mitjançant una matriu unidimensional

Però l'accés als elements de la matriu es produeix en un doble bucle, com si no fos una matriu unidimensional, sinó bidimensional. Això es fa perquè encara estem treballant amb matrius.

DevOps C++ i "guerres de cuina", o Com vaig començar a escriure jocs mentre menjava
Travessant una matriu unidimensional en un doble bucle. Y és l'ID de fila, X és l'ID de columna

Tingueu en compte que en lloc dels identificadors de matriu habituals i, j, faig servir els identificadors x i y. Per tant, em sembla, més agradable a la vista i més clar per al cervell. A més, aquesta notació permet projectar còmodament les matrius utilitzades sobre els eixos de coordenades d'una imatge bidimensional.

Ara sobre píxels, color i pantalla. La funció StretchDIBits (Encapçalament: windows.h; Biblioteca: gdi32.lib) s'utilitza per a la sortida. Entre altres coses, es passa a aquesta funció: el dispositiu on es mostra la imatge (en el meu cas, aquesta és la consola de Windows), les coordenades d'inici de visualització de la imatge, la seva amplada/alçada i la imatge. en forma de mapa de bits (mapa de bits), representat per una matriu de bytes. Mapa de bits com una matriu de bytes!

La funció StretchDIBits() funciona:

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

La memòria s'assigna per endavant per a aquest mapa de bits mitjançant la funció VirtualAlloc(). És a dir, es reserva el nombre de bytes necessaris per emmagatzemar informació sobre tots els píxels, que després es mostrarà a la pantalla.

Creació d'un mapa de bits 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);

A grans trets, un mapa de bits consisteix en un conjunt de píxels. Cada quatre bytes de la matriu és un píxel RGB. Un byte per valor vermell, un byte per valor verd (G) i un byte per color blau (B). A més, hi ha un byte per sagnat. Aquests tres colors -vermell/verd/blau (RGB)- es barregen entre si en diferents proporcions- i s'obté el color de píxel resultant.

Ara, de nou, cada rectangle, o objecte del joc, està representat per una matriu numèrica. Tots aquests objectes del joc es col·loquen en una col·lecció. I després es col·loquen al terreny de joc, formant una gran matriu numèrica. Vaig assignar cada nombre de la matriu a un color específic. Per exemple, el número 8 és blau, el número 9 és groc, el número 10 és gris fosc, etc. Així, podem dir que tenim una matriu del terreny de joc, on cada número és una mena de color.

Així doncs, tenim una matriu numèrica de tot el terreny de joc d'una banda i un mapa de bits per mostrar la imatge de l'altra. Fins ara, el mapa de bits està "buit": encara no té informació sobre els píxels del color desitjat. Això vol dir que l'últim pas serà omplir el mapa de bits amb informació sobre cada píxel en funció de la matriu numèrica del camp de joc. Un exemple il·lustratiu d'aquesta transformació es troba a la imatge següent.

DevOps C++ i "guerres de cuina", o Com vaig començar a escriure jocs mentre menjava
Un exemple d'ompliment d'un mapa de bits (matriu de píxels) amb informació basada en la matriu numèrica (matriu digital) del camp de joc (els índexs de color no coincideixen amb els índexs del joc)

També presentaré un tros de codi real del joc. A la variable colorIndex a cada iteració del bucle se li assigna un valor (índex de color) de la matriu numèrica del camp de joc (mainDigitalMatrix). A continuació, el color en si s'escriu a la variable de color en funció de l'índex. A més, el color resultant es divideix en la proporció de vermell, verd i blau (RGB). I juntament amb el sagnat (pixelPadding), aquesta informació s'escriu al píxel una i altra vegada, formant una imatge en color al mapa de bits.

El codi utilitza punters i operacions per bits, que poden ser difícils d'entendre. Així que us aconsello que llegiu per separat en algun lloc com funcionen aquestes estructures.

Omplint un mapa de bits amb informació basada en la matriu numèrica del camp de joc:

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

Segons el mètode descrit anteriorment, es forma una imatge (marc) al joc Crazy Tanks i es mostra a la pantalla amb la funció Draw(). Després de registrar les pulsacions de tecles a la funció Input() i el seu processament posterior a la funció Logic(), es forma una nova imatge (marc). És cert que els objectes del joc ja poden tenir una posició diferent al terreny de joc i, en conseqüència, es dibuixen en un lloc diferent. Així és com es produeix l'animació (el moviment).

En teoria (si no has oblidat res), entendre el bucle del joc del primer joc ("Snake") i el sistema per mostrar els píxels a la pantalla del segon joc ("Tancs") és tot el que necessites per escriure qualsevol dels teus jocs en 2D per a Windows. Sense so! 😉 La resta de parts són només un vol de fantasia.

Per descomptat, el joc "Tancs" està dissenyat molt més complicat que el "Serp". Ja feia servir el llenguatge C++, és a dir, vaig descriure diferents objectes de joc amb classes. He creat la meva pròpia col·lecció: podeu veure el codi a headers/Box.h. Per cert, és probable que la col·lecció tingui una fuga de memòria. Apuntadors utilitzats. Treballat amb memòria. He de dir que el llibre m'ha ajudat molt. Iniciació en C++ a través de la programació de jocs. Aquest és un gran començament per als principiants en C++. És petit, interessant i ben organitzat.

Va trigar uns sis mesos a desenvolupar aquest joc. Vaig escriure principalment durant el dinar i els berenars a la feina. Es va asseure a la cuina de l'oficina, va trepitjar el menjar i va escriure codi. O a casa per sopar. Així que vaig tenir aquestes "guerres de cuina". Com sempre, vaig utilitzar activament un quadern i hi van néixer totes les coses conceptuals.

Al final de la part pràctica, trauré uns quants escanejos del meu quadern. Per mostrar el que exactament estava escrivint, dibuixant, comptant, dissenyant...

DevOps C++ i "guerres de cuina", o Com vaig començar a escriure jocs mentre menjava
Disseny de la imatge del tanc. I la definició de quants píxels ha d'ocupar cada tanc a la pantalla

DevOps C++ i "guerres de cuina", o Com vaig començar a escriure jocs mentre menjava
Càlcul de l'algorisme i fórmules per a la rotació del dipòsit al voltant del seu eix

DevOps C++ i "guerres de cuina", o Com vaig començar a escriure jocs mentre menjava
Diagrama de la meva col·lecció (la que té la fuita de memòria, molt probablement). La col·lecció es crea com una llista enllaçada

DevOps C++ i "guerres de cuina", o Com vaig començar a escriure jocs mentre menjava
I aquests són intents inútils d'introduir la intel·ligència artificial al joc

Теория

"Fins i tot un viatge de mil milles comença amb el primer pas" (Saviesa xinesa antiga)

Passem de la pràctica a la teoria! Com trobes temps per a la teva afició?

  1. Determineu què voleu realment (per desgràcia, això és el més difícil).
  2. Establir prioritats.
  3. Sacrifiqueu tot allò "superflu" pel bé de les prioritats més altes.
  4. Avanceu cap als vostres objectius cada dia.
  5. No espereu que hi hagi dues o tres hores de temps lliure per a una afició.

D'una banda, cal determinar què voleu i prioritzar. D'altra banda, és possible abandonar alguns casos/projectes a favor d'aquestes prioritats. En altres paraules, hauràs de sacrificar tot el que és “superflu”. Vaig sentir en algun lloc que a la vida hi hauria d'haver un màxim de tres activitats principals. Aleshores podreu tractar-los de la millor manera possible. I els projectes/adreces addicionals començaran a sobrecarregar-se cursis. Però tot això és, probablement, subjectiu i individual.

Hi ha una certa regla d'or: mai tingueu un dia 0%! Ho vaig saber en un article d'un desenvolupador independent. Si esteu treballant en un projecte, feu-hi alguna cosa cada dia. I no importa quant guanyis. Escriu una paraula o una línia de codi, mira un vídeo tutorial o clava un clau a la pissarra; fes alguna cosa. El més difícil és començar. Un cop comencis, probablement faràs una mica més del que volies. Així que avançaràs constantment cap al teu objectiu i, creu-me, molt ràpidament. Després de tot, el fre principal a totes les coses és la procrastinació.

I és important recordar que no s'ha de subestimar i ignorar la "serratura" lliure del temps en 5, 10, 15 minuts, esperar uns grans "registres" que durin una o dues hores. Estàs fent cua? Pensa en alguna cosa per al teu projecte. Puges per les escales mecàniques? Escriu alguna cosa en una llibreta. Menges a l'autobús? D'acord, llegiu algun article. Aprofiteu totes les oportunitats. Deixa de veure gats i gossos a YouTube! No et fiquis amb el teu cervell!

I l'últim. Si, després de llegir aquest article, us va agradar la idea de crear jocs sense utilitzar motors de joc, recordeu el nom de Casey Muratori. Aquest paio té сайт. A la secció "rellotge -> EPISODIS ANTERIORS" trobareu increïbles tutorials en vídeo gratuïts sobre com crear un joc professional des de zero. En cinc lliçons d'Intro a C per a Windows, podeu aprendre més que en cinc anys d'estudi a la universitat (algú va escriure sobre això als comentaris sota el vídeo).

Casey també explica que desenvolupant el vostre propi motor de joc, entendreu millor els motors existents. En el món dels frameworks, on tothom intenta automatitzar, aprendràs a crear, no a utilitzar. Comprendre la naturalesa mateixa dels ordinadors. I també es convertirà en un programador molt més intel·ligent i madur, un professional.

Molta sort en el camí escollit! I fem el món més professional.

autor: Grankin Andrey, DevOps



Font: www.habr.com