Pentru mulți începători indie devs, optimizarea codului devine aproape un al doilea gând. Ea este transmisă în favoarea motoarelor sau a cadrelor, sau poate fi considerată o tehnică mai avansată în afara lor. Cu toate acestea, există metode de optimizare care pot fi utilizate în mai multe moduri de bază, permițând codului dvs. să funcționeze mai eficient și pe mai multe sisteme. Să aruncăm o privire la o optimizare de cod de bază pentru a începe.
Nu este neobișnuit pentru dezvoltatorii indie să imite metodele de optimizare ale companiilor mai mari. Nu este neapărat un lucru rău, dar încercarea de a vă optimiza jocul după punctul de întoarcere utilă este o modalitate bună de a vă îndura pe voi înșivă. O tactică inteligentă pentru a urmări eficiența optimizărilor dvs. este să vă segmentați obiectivul demografic și să examinați tipurile de specificații pe care le au mașinile. Benchmarking-ul jocului împotriva calculatoarelor și consolelor pe care jucătorii potențiali îl folosesc vor contribui la menținerea unui echilibru între optimizare și sănătate.
Cu toate acestea, există o serie de optimizări care pot fi folosite întotdeauna pentru a ajuta jocul să funcționeze mai bine. Cele mai multe dintre acestea sunt sisteme agnostice (și unele motoare și cadre deja le iau în considerare), așa că voi include câteva exemple de pseudocode pentru a vă scoate la piciorul drept. Hai să aruncăm o privire.
Adesea manipulate de motoare și uneori chiar de GPU-uri, minimizarea cantității de putere de procesare care intră în obiecte aflate în afara ecranului este extrem de importantă. Pentru propria dvs. construcție, este o idee bună să începeți să vă separați obiectele în două "straturi" - prima fiind reprezentarea lor grafică, iar a doua fiind datele și funcțiile sale (cum ar fi locația). Atunci când un obiect este off-screen, nu mai trebuie să cheltuim resursele care îl redă și, în schimb, ar trebui să optăm pentru urmărire. Urmărirea lucrurilor, cum ar fi locația și starea, cu variabile reduce resursele necesare doar la o fracțiune din costul inițial.
Pentru jocuri cu un număr mare de obiecte sau obiecte care sunt greu de date, ar putea fi util să faceți un pas mai departe prin crearea unor rutine de actualizare separate, setând unul pentru actualizare în timp ce obiectul este afișat pe ecran, iar celălalt pentru off-screen . Configurarea unei separări mai avansate în acest fel poate salva sistemul de a nu trebui să execute o serie de animații, algoritmi și alte actualizări care pot fi inutile atunci când obiectul este ascuns.
Iată un exemplu de pseudocod al unei clase de obiecte care utilizează constrângeri și localizări:
Obiect NPC Int locațieX, locYY; // locația curentă a obiectului pe un plan 2d Funcția drawObject () // o funcție pentru a desena obiectul dvs., care va fi apelat în buclă de actualizare a ecranului // funcția care verifică dacă obiectul se află în portul curent de vizualizare Funcția pollObjectDraw (array currentViewport [minX, minY, maxX, maxY]) // dacă se află în portul de vizualizare, returnează că poate fi trasată Dacă (this.within (currentViewport)) Return true; Altceva Return false;
Deși acest exemplu este simplificat, ne permite să cercetăm dacă obiectul va apărea chiar înainte de desenare, permițându-ne să executăm o funcție destul de simplificată în locul unui apel de tragere completă. Pentru a separa funcțiile în afară de apelurile grafice, poate fi necesar să folosiți un tampon suplimentar - de exemplu, o funcție care să includă orice lucru pe care un jucător îl poate vedea în curând, mai degrabă decât ceea ce este în prezent capabil să vadă.
În funcție de motorul sau cadrul pe care îl utilizați, este posibil să aveți în mod obișnuit obiecte care se actualizează pe fiecare cadru sau "bifați". Acest lucru poate impozita un procesor destul de repede, astfel încât, pentru a ușura acea povară, vom dori să reducem apelurile la fiecare cadru ori de câte ori este posibil.
Primul lucru pe care vom dori să-l separăm este randarea funcției. Aceste apeluri sunt, de obicei, resurse intensive, astfel încât integrarea unui apel care ne poate spune când proprietățile vizuale ale unui obiect s-au schimbat poate reduce redarea drastică.
Pentru a face un pas mai departe, putem folosi un ecran temporar pentru obiectele noastre. Prin atragerea directă a obiectelor la acest container temporar, putem asigura că ele sunt desenate numai atunci când este necesar.
Similar cu prima optimizare menționată mai sus, inițierea iterației codului nostru introduce o primire simplă:
Obiect NPC boolean a schimbat; // setați acest flag la true ori de câte ori se face o schimbare a obiectului // funcția care returnează dacă funcția pollObjectChanged (returneazăChanged ();
În fiecare moment, în loc să realizăm o serie de funcții, putem vedea dacă este chiar necesar. În timp ce această implementare este de asemenea simplă, ea poate începe deja să arate câștiguri uriașe în eficiența jocului dvs., mai ales când vine vorba de obiecte statice și obiecte de actualizare lentă cum ar fi un HUD.
Pentru a continua acest lucru în propriul joc, ruperea pavilionului în mai multe componente mai mici pot fi utile pentru segmentarea funcționalității. De exemplu, ați putea avea steaguri pentru o schimbare de date și o schimbare grafică are loc separat.
Aceasta este o optimizare care a fost utilizată încă din primele zile ale sistemelor de jocuri. Determinarea compromisurilor dintre calculele vii și căutările valorii poate ajuta la reducerea drastică a timpilor de procesare. O utilizare binecunoscută în istoria jocurilor este stocarea valorilor funcțiilor de trigonometrie în tabele deoarece, în majoritatea cazurilor, a fost mai eficient să se păstreze o masă mare și să se recupereze din ea mai degrabă decât să se facă calcule în mers și să se pună presiune suplimentară pe CPU.
În calculul modern, rareori trebuie să facem alegerea între stocarea rezultatelor și rularea unui algoritm. Cu toate acestea, există încă situații în care acest lucru poate reduce resursele utilizate, permițând includerea altor funcții fără supraîncărcarea unui sistem.
O modalitate ușoară de a începe să implementați acest lucru este de a identifica în cadrul jocului calculele care apar în mod obișnuit sau bucăți de calcule: cu cât calculul este mai mare, cu atât mai bine. Realizarea biților recurenți de algoritmi o singură dată și stocarea lor poate salva adesea cantități considerabile de putere de procesare. Chiar izolarea acestor părți în bucle specifice de jocuri poate ajuta la optimizarea performanței.
De exemplu, în multe împușcături de sus în jos există adesea grupuri mari de inamici care efectuează aceleași comportamente. Dacă există 20 de dușmani, fiecare deplasându-se de-a lungul unui arc, mai degrabă decât calculând fiecare mișcare individual, este mai eficient să stocați rezultatele algoritmului în schimb. Acest lucru permite modificarea acestuia pe baza poziției de plecare a inamicului.
Pentru a determina dacă această metodă este utilă pentru jocul dvs., încercați să utilizați benchmarking pentru a compara diferența dintre resursele utilizate între calculul live și stocarea datelor.
În timp ce acest lucru se joacă mai mult în utilizarea resurselor latente, cu grijă pentru obiectele și algoritmii dvs., putem să stivim sarcinile într-un mod care împinge eficiența codului nostru.
Pentru a începe să utilizați sensibilitatea la leneșe în software-ul propriu, mai întâi va trebui să separați ce sarcini din joc nu sunt critice în timp sau pot fi calculate înainte de a fi necesare. Prima zonă de căutare a codului care se încadrează în această categorie este o funcționalitate strict legată de atmosfera jocului. Sistemele meteorologice care nu interacționează cu geografia, efectele vizuale de fundal și sunetul de fundal se pot potrivi cu ușurință în calculul inactiv.
Dincolo de elemente care sunt strict atmosferice, calculele garantate sunt un alt tip de calcul care poate fi plasat în spații inaccesibile. Calculele de inteligență artificială care vor avea loc indiferent de interacțiunea jucătorului (fie că nu iau în considerare jucătorul, fie că este puțin probabil să necesite interacțiunea jucătorilor încă de la început) pot fi făcute mai eficiente, la fel ca mișcările calculate, cum ar fi evenimente scrise.
Crearea unui sistem care folosește lentilă face chiar mai mult decât să permită o eficiență mai mare - poate fi folosită pentru scalarea "bomboane ochi". De exemplu, într-o platformă low-end, poate un jucător doar experimentează o versiune de vanilie a gameplay-ului. Dacă sistemul nostru detectează cadre în așteptare, cu toate acestea, îl putem folosi pentru a adăuga particule suplimentare, evenimente grafice și alte trucuri atmosferice pentru a da jocului un pic mai mult.
Pentru a implementa acest lucru, utilizați funcționalitatea disponibilă în motorul preferat, cadrul sau limba dvs. pentru a măsura cantitatea de CPU utilizată. Setați steaguri din codul dvs. care vă permit să verificați cât de mult este disponibilă o putere de procesare suplimentară și apoi configurați-vă sub-sistemele să privească acest steguleț și să se comporte corespunzător.
Prin combinarea acestor metode, este posibil ca codul dvs. să fie semnificativ mai eficient. Cu această eficiență vine abilitatea de a adăuga mai multe caracteristici, de a rula pe mai multe sisteme, și de a asigura o experiență mai solidă pentru jucători.
Aveți orice optimizări de cod ușor de implementat pe care le utilizați în mod regulat? Dați-mi voie să știu despre ele!