Jos ei toisin mainita, seuraavan blogikirjoituksen on kirjoittanut Gamasutran yhteisön jäsen.
Kirjoittajan ajatukset ja mielipiteet ovat kirjoittajan eivätkä Gamasutran tai sen emoyhtiön ajatuksia ja mielipiteitä.
Osa 1 – Viestinvälitys
Osa 2 – Muisti
Osa 3 – Data & Välimuisti
Osa 4 – Grafiikkakirjastot
Elämme hienoa aikaa olla kehittäjiä. Kun kaikkien saatavilla on valtava määrä loistavia AAA-luokan pelimoottoreita, yksinkertaisten pelien tekeminen voi olla yhtä helppoa kuin vedä ja pudota. Nykyään ei tunnu olevan enää mitään syytä kirjoittaa engineä. Ja kun yleisesti sanotaan: ”Kirjoita pelejä, älä moottoreita”, miksi sinun pitäisi?
Tämä artikkeli on suunnattu ensisijaisesti yksin kehittäjille ja pienille tiimeille. Oletan, että hänellä on jonkin verran perehtyneisyyttä oliokeskeiseen ohjelmointiin.
Haluan antaa sinulle käsityksen siitä, miten Engine-kehitystä lähestytään, ja käytän yksinkertaista kuvitteellista Engineä havainnollistamaan tätä.
Miksi kirjoittaa Engine?
Lyhyt vastaus on: Älä tee, jos voit välttää sitä.
Elämä on liian lyhyt kirjoittaaksesi moottorin jokaista peliä varten (Otettu Sergei Savchenkon kirjasta 3D Graphics Programming)
Tämänhetkinen valikoima erinomaisia Enginejä, kuten Unity, Unreal tai CryEngine, ovat niin joustavia kuin vain voi toivoa, ja niiden avulla voi tehdä melkein minkä tahansa pelin. Erikoisempiin tehtäviin on toki olemassa erikoisempia ratkaisuja kuten Adventure Game Studio tai RPG Maker, vain muutamia mainitakseni. Edes kaupallisen luokan Enginen hinta ei ole enää peruste.
Ei ole enää kuin muutama kapea-alainen syy kirjoittaa oma engine:
- Haluat oppia, miten moottori toimii
- Tarvitset tiettyjä toiminnallisuuksia, joita ei ole saatavilla tai saatavilla olevat ratkaisut ovat epävakaita
- Haluat uskoa, että voit tehdä sen paremmin/nopeammin
- Haluat pysyä kehityksen hallinnassa
Kaikki nämä ovat täysin päteviä syitä, ja jos luet tätä, kuulut luultavasti johonkin näistä leireistä. Tavoitteeni ei ole mennä pitkälliseen ”Mitä moottoria minun pitäisi käyttää?” tai ”Pitäisikö minun kirjoittaa moottori?” -keskusteluun tässä, vaan hyppään suoraan asiaan. Aloitetaan siis.
Miten epäonnistua moottorin kirjoittamisessa
Odota. Ensin kehotan olemaan kirjoittamatta, seuraavaksi selitän miten epäonnistua? Hieno esittely…
Jokatapauksessa, on paljon asioita jotka pitää ottaa huomioon ennen kuin kirjoitat edes yhden rivin koodia. Ensimmäinen ja suurin ongelma, joka kaikilla, jotka aloittavat pelimoottorin kirjoittamisen, on tämä:
Haluan nähdä pelattavaa mahdollisimman nopeasti!
Mitä nopeammin kuitenkin tajuat, että kestää paljon aikaa, ennen kuin näet jotain mielenkiintoista tapahtuvan, sitä paremmin voit kirjoittaa moottoriasi.
Koodisi pakottaminen näyttämään jonkinlaista grafiikkaa tai pelattavuutta niin nopeasti kuin mahdollista, vain saadaksesi jonkinlaisen visuaalisen vahvistuksen ”edistymisestä”, on suurin vihollisesi tässä vaiheessa. Ota. Sinun. Time!
Älä edes harkitse grafiikan aloittamista. Olet luultavasti lukenut paljon OpenGL / DirectX tutoriaaleja ja kirjoja ja tiedät miten renderöidä yksinkertainen kolmio tai sprite. Saatat ajatella, että lyhyt koodinpätkä pienen meshin renderöimisestä ruudulle on hyvä paikka aloittaa. Se ei ole.
Kyllä, alkuvaiheen edistymisesi on hämmästyttävää. Hitto, voisit juosta ympäriinsä pienellä tasolla First-Personissa vain päivässä kopioimalla koodinpätkiä erilaisista tutoriaaleista ja Stack Overflow’sta. Mutta takaan, että poistat joka ikisen rivin tuosta koodista 2 päivää myöhemmin. Vielä pahempaa on, että saatat jopa lannistua Engineä kirjoittaessasi, koska ei ole motivoivaa nähdä yhä vähemmän ja vähemmän.
Toinen suuri ongelma, jonka kehittäjät kohtaavat kirjoittaessaan Enginejä, on feature creep. Kaikki haluaisivat kirjoittaa Enginesin pyhän Graalin maljan. Kaikki haluavat sen täydellisen moottorin, joka osaa kaiken. Ensimmäisen persoonan räiskintäpelit, taktiset roolipelit, mitä tahansa. Mutta yksinkertainen tosiasia on, että emme pysty siihen. Vielä. Katsokaa vain suuria nimiä. Edes Unity ei voi todella palvella jokaista peligenreä täydellisesti.
Ei kannata edes ajatella kirjoittavansa Engineä, joka voi tehdä useampaa kuin yhtä genreä ensimmäisellä kerralla. Älä!
Mistä oikeastaan aloittaa Engineä kirjoittaessasi
Enginen kirjoittaminen on kuin suunnittelisi oikean moottorin autoon. Vaiheet ovat oikeastaan aika ilmeisiä, olettaen että tiedät minkä pelin (tai auton) parissa työskentelet. Tässä ne ovat:
- Kartoita tarkalleen, mihin moottorisi pitää pystyä JA mihin moottorisi ei tarvitse pystyä.
- Organisoi tarpeet Järjestelmiksi, joita moottorisi tarvitsee.
- Suunnittele täydellinen arkkitehtuuri, joka sitoo kaikki nämä Järjestelmät yhteen.
- Kertaa vaiheet 1. – 3. niin usein kuin mahdollista.
- Koodaa.
Jos (= jos ja vain jos) käytät tarpeeksi aikaa ja vaivaa vaiheisiin 1. – 4. eikä pelisuunnittelu yhtäkkiä muutu kauhupelistä kolikkopeliksi (lue: Silent Hill), koodaaminen on erittäin miellyttävää puuhaa. Koodaaminen on edelleen kaikkea muuta kuin helppoa, mutta täysin hallittavissa, jopa soolokehittäjille.
Tästä syystä tämä artikkeli käsittelee pääasiassa vaiheita 1. – 4. Ajattele vaihetta 5. ”Aukkojen täyttämisenä”. 50.000 LOC of Blanks”.
Kriittisin osa tästä kaikesta on vaihe 3. Keskitämme suurimman osan ponnisteluistamme tähän!
Vaihe 1. Pinpoint the Needs and Need Nots
Kaikki nämä vaiheet saattavat aluksi vaikuttaa melko triviaalilta. Mutta sitä ne eivät todellakaan ole. Voisi ajatella, että First Person Shooter Engine -moottorin kehitysprosessin vaihe 1 voidaan kiehauttaa tähän:
Minun täytyy ladata taso, pelaajan ase, joitain vihollisia tekoälyineen. Valmis, jatketaan vaiheeseen 2.
Kunpa se vain olisi näin helppoa. Paras tapa edetä vaiheessa 1 on käydä läpi koko peli klikkaus kerrallaan, toiminto kerrallaan klikkaamalla kuvaketta työpöydälläsi, kunnes painat Exit-näppäintä krediittien pyörittämisen jälkeen. Tee lista, suuri lista siitä, mitä tarvitset. Tee lista siitä, mitä et todellakaan tarvitse.
Tämä menee luultavasti näin:
Käynnistän pelin ja se menee suoraan päävalikkoon. Käytetäänkö valikossa staattista kuvaa? Leikattua kohtausta? Miten ohjaan päävalikkoa, hiirellä? Näppäimistöllä? Millaisia GUI-elementtejä tarvitsen päävalikkoa varten? Painikkeet, lomakkeet, vierityspalkit? Entä musiikki?
Ja nuo ovat vain makro-pohdintoja. Mene niin yksityiskohtaisesti kuin mahdollista. Se, että päätät tarvitsevasi painikkeita, on hienoa ja hyvä, mutta mieti myös, mitä painike voi tehdä.
Haluan, että painikkeilla on 4 tilaa: Up, Hover, Down, Disabled. Tarvitsenko painikkeille ääntä? Entä erikoistehosteet? Ovatko ne animoituja tyhjäkäyntitilassa?
Jos listasi tarpeista ja tarpeettomuuksista sisältää vain noin 10 kohtaa päävalikon loppuun mennessä, teit jotain väärin.
Tässä vaiheessa simuloit moottoria aivojesi sisällä ja kirjoitat ylös, mitä pitää tehdä. Vaihe 1 selkiytyy joka kerta, älä huolehdi siitä, ettet huomaa mitään ensimmäisellä kerralla.
Vaihe 2. Järjestä tarpeet järjestelmiksi
Sinulla on siis listasi asioista, joita tarvitset ja joita et tarvitse. On aika järjestää ne. On selvää, että graafiseen käyttöliittymään liittyvät asiat, kuten painikkeet, menevät jonkinlaiseen graafisen käyttöliittymän järjestelmään. Renderöintiin liittyvät asiat menevät Graphics Systemiin / Engineen.
Tälleen, kuten askeleen 1 kohdalla, sen päättäminen, mikä menee minne, on selvempää toisella kerralla, askeleen 3 jälkeen. Ensimmäisellä kerralla ryhmittele ne loogisesti kuten yllä olevassa esimerkissä.
Paras referenssi aiheesta ”mikä menee minne” ja ”mikä tekee mitä” on epäilemättä Jason Gregoryn kirja Game Engine Architecture.
Aloita toiminnallisuuden ryhmittely. Ala miettiä tapoja yhdistää niitä. Et tarvitse Camera->rotateYaw(float yaw)
ja Camera->rotatePitch(float pitch)
jos voit yhdistää ne Camera->rotate(float yaw, float pitch)
:ksi. Pidä se yksinkertaisena. Liika toiminnallisuus (muista, feature creep) vahingoittaa sinua myöhemmin.
Mieti, minkä toiminnallisuuden on oltava julkisesti näkyvillä ja minkä toiminnallisuuden on oltava vain itse järjestelmän sisällä. Esimerkiksi Rendererisi täytyy lajitella kaikki läpinäkyvät Sprites ennen piirtämistä. Toimintoa, joka lajittelee nämä spritit, ei kuitenkaan tarvitse paljastaa. Tiedät, että sinun on lajiteltava läpinäkyvät spritit ennen piirtämistä, etkä tarvitse ulkopuolista Systemiä kertomaan tätä sinulle.
Vaihe 3. Arkkitehtuuri (Tai varsinainen artikkeli)
Olisimme yhtä hyvin voineet aloittaa artikkelin tästä. Tämä on se mielenkiintoinen ja tärkeä osa.
Yksi yksinkertaisimmista mahdollisista arkkitehtuureista, joita moottorillasi voi olla, on se, että laitat jokaisen Systeemin luokkaan ja annat Main Game Loopin kutsua niiden aliohjelmia. Se voisi näyttää jotakuinkin tältä:
while(isRunning)
{
Input->readInput();
isRunning = GameLogic->doLogic();
Camera->update();
World->update();
GUI->update();
AI->update();
Audio->play();
Render->draw();
}
Se vaikuttaa aluksi täysin järkevältä. Sinulla on kaikki perusasiat hallussa, Input -> käsittely Input -> Output.
Ja todellakin, tämä riittää yksinkertaiselle Pelille. Mutta sen ylläpito on hankalaa. Syy tähän pitäisi olla ilmeinen: Riippuvuudet.
Kunkin järjestelmän on kommunikoitava muiden järjestelmien kanssa jollakin tavalla. Meillä ei ole mitään keinoa tehdä sitä yllä olevassa pelisilmukassamme. Siksi esimerkki osoittaa selvästi, että jokaisella Järjestelmällä on oltava jokin Viittaus muihin Järjestelmiin, jotta se voi tehdä mitään mielekästä. GUI:n ja pelilogiikan on tiedettävä jotain syötteistämme. Rendererimme on tiedettävä jotain pelilogiikastamme, jotta voimme näyttää jotain mielekästä.
Tämä johtaa tähän arkkitehtuurin ihmeeseen:
Jos se haisee spagetilta, se on spagettia. Ei todellakaan ole sitä, mitä me haluamme. Kyllä, se on helppo ja nopea koodata. Kyllä, saamme hyväksyttäviä tuloksia. Mutta ylläpidettävää se ei ole. Jos muutat pienen koodinpätkän jossakin, sillä voi olla tuhoisat vaikutukset kaikkiin muihin järjestelmiin meidän huomaamattamme.
Lisäksi tulee aina olemaan koodia, johon monet järjestelmät tarvitsevat pääsyn. Sekä GUI:n että Rendererin täytyy tehdä Draw-kutsuja tai ainakin päästä käsiksi jonkinlaiseen rajapintaan, joka hoitaa tämän puolestamme. Kyllä, voisimme vain antaa jokaiselle järjestelmälle valtuudet kutsua OpenGL/DirectX-funktioita suoraan, mutta tuloksena on paljon redundanssia.
Voisimme ratkaista tämän keräämällä kaikki piirtofunktiot Renderer-järjestelmään ja kutsumalla niitä GUI-järjestelmästä. Mutta silloin renderöintijärjestelmässä on erityisiä funktioita GUI:lle. Näillä ei ole sijaa Rendererissä ja on siksi vastoin askelta 1 ja 2. Päätökset, päätökset.
Siten ensimmäinen asia, jota meidän pitäisi harkita, on jakaa moottorimme kerroksiin.
Engine Lasagne
Lasagne on parempi kuin spagetti. Ainakin ohjelmointiteknisesti. Pysyttelemällä Renderer esimerkissämme, mitä haluamme on kutsua OpenGL / DirectX funktioita kutsumatta niitä suoraan System. Tämä haiskahtaa Wrapperilta. Ja suurimmaksi osaksi se onkin. Keräämme kaikki piirtotoiminnot toiseen luokkaan. Nämä luokat ovat vielä yksinkertaisempia kuin järjestelmämme. Kutsutaan näitä uusia luokkia Frameworkiksi.
Ajatuksena on abstrahoida paljon matalan tason API-kutsuja ja muotoilla ne joksikin pelillemme räätälöidyksi. Emme halua asettaa Vertex-puskuria, asettaa Index-puskuria, asettaa tekstuureja, ottaa käyttöön tätä, poistaa käytöstä tuota vain tehdaksemme yksinkertaisen piirtokutsun Renderer-järjestelmässämme. Laitetaan kaikki nämä matalan tason asiat Frameworkiin. Kutsun tätä Frameworkin osaa vain nimellä ”Draw”. Miksi? No, se tekee vain kaiken valmiiksi piirtämistä varten ja sitten piirtää sen. Se ei välitä mitä se piirtää, mihin se piirtää, miksi se piirtää. Se jätetään Renderer Systemille.
Tämä saattaa tuntua oudolta, haluamme nopeutta moottorissamme, eikö niin? Enemmän abstraktiotasoja = vähemmän nopeutta.
Ja olisit oikeassa, jos elettäisiin 90-lukua. Mutta tarvitsemme ylläpidettävyyttä ja voimme elää hädin tuskin havaittavan nopeuden menetyksen kanssa suurimmassa osassa.
Miten meidän Draw Framework sitten pitäisi suunnitella? Yksinkertaisesti sanottuna kuin oma pieni API:mme. SFML on hyvä esimerkki tästä.
Tärkeitä asioita, jotka kannattaa pitää mielessä:
- Pitäkää se hyvin dokumentoituna. Mitä funktioita meillä on? Milloin niitä voidaan kutsua? Miten niitä kutsutaan?
- Pitäkää se yksinkertaisena. Helpot funktiot kuten drawMesh(Mesh* oMesh) tai loadShader(String sPath) tekevät sinut onnelliseksi pitkällä aikavälillä.
- Pitäkää se toimivana. Älä ole liian tarkka.
drawButtonSprite
:n sijasta oledrawSprite
funktio ja anna kutsujan hoitaa loput.
Mitä me saavutamme? Paljon:
- Meidän tarvitsee asentaa Framework vain kerran ja voimme käyttää sitä jokaisessa tarvitsemassamme järjestelmässä (GUI, Renderer….)
- Voitamme helposti muuttaa taustalla olevia API:ita, jos haluamme, kirjoittamatta jokaista järjestelmää uudelleen. Vaihda OpenGL:stä DirectX:ään? Ei ongelmaa, kirjoitat vain Framework-luokan uudelleen.
- Se pitää järjestelmiemme koodin siistinä ja tiiviinä.
- Hyvin dokumentoidun rajapinnan ansiosta yksi henkilö voi työskennellä Frameworkin parissa, kun taas toinen henkilö työskentelee järjestelmäkerroksessa.
Päädymme luultavasti johonkin seuraavanlaiseen:
Nyrkkisääntöni siitä, mitä Frameworkiin kuuluu, on melko yksinkertainen. Jos minun täytyy kutsua ulkoista kirjastoa (OpenGL, OpenAL, SFML…) tai jos minulla on tietorakenteita / algoritmeja, joita jokainen järjestelmä tarvitsee, minun pitäisi tehdä se Frameworkissa.
Meillä on nyt ensimmäinen kerros Lasagnea valmis. Mutta meillä on vielä tämä valtava pallo spagettia sen yläpuolella. Tartutaan siihen seuraavaksi.
Viestintä
Suuri ongelma on kuitenkin edelleen olemassa. Järjestelmämme ovat edelleen kaikki yhteydessä toisiinsa. Emme halua sitä. On olemassa lukuisia tapoja käsitellä tätä ongelmaa. Tapahtumat, Viestit, Abstraktit luokat funktio-osoittimilla (Kuinka esoteerista)…
Pidetään kiinni Viesteistä. Tämä on yksinkertainen käsite, joka on edelleen hyvin suosittu GUI-ohjelmoinnissa. Se soveltuu myös hyvin helpoksi esimerkiksi meidän moottorillemme.
Se toimii kuin posti. Yritys A lähettää viestin yritykselle B ja pyytää tekemään jotain. Nämä yritykset eivät tarvitse fyysistä yhteyttä. Yritys A yksinkertaisesti olettaa, että yritys B tekee sen jossain vaiheessa. Mutta toistaiseksi yritys A ei oikeastaan välitä siitä, milloin tai miten yritys B tekee sen. Se on vain tehtävä. Hitto, yritys B saattaa jopa päättää ohjata viestin yritykselle C ja D ja antaa niiden hoitaa sen.
Voidaan mennä vielä askeleen pidemmälle, yrityksen A ei tarvitse edes lähettää sitä jollekin tietylle henkilölle. Yritys A vain lähettää kirjeen ja kuka tahansa, joka tuntee itsensä vastuulliseksi, käsittelee sen. Näin yritys C ja D voivat käsitellä pyynnön suoraan.
Yritykset vastaavat ilmeisesti meidän järjestelmiämme. Katsotaanpa yksinkertaista esimerkkiä:
- Kehys ilmoittaa syöttöjärjestelmälle, että näppäintä ”A” painettiin
- Syöttö kääntää, että näppäinpainallus ”A” tarkoittaa ”Avaa inventaario” ja lähettää viestin, joka sisältää ”Avaa inventaario”
- GUI käsittelee viestin ja avaa Inventory-ikkunan
- Pelilogiikka käsittelee viestin ja keskeyttää pelin
.
Input ei edes välitä mitä sen viestille tehdään. GUI ei välitä siitä, että myös Game Logic käsittelee samaa Messagea. Jos ne olisi kytketty toisiinsa, Inputin täytyisi kutsua funktiota GUI-järjestelmässä ja funktiota Game Logicissa. Mutta sen ei enää tarvitse. Pystyimme onnistuneesti purkamaan tämän kytkennän käyttämällä Viestejä.
Miltä viesti näyttää? Sillä pitäisi olla ainakin jokin Type. Esimerkiksi inventaarion avaaminen voisi olla jokin enum nimeltä OPEN_INVENTORY
. Tämä riittää tuollaisiin yksinkertaisiin Viesteihin. Kehittyneemmät Viestit, joiden täytyy sisältää tietoja, tarvitsevat jonkin tavan tallentaa nämä tiedot. On olemassa lukuisia tapoja toteuttaa tämä. Helpoin toteuttaa on käyttää yksinkertaista karttarakennetta.
Mutta miten lähetämme Viestejä? Message Busin kautta tietenkin!
Eikö olekin kaunista? Ei enää spagettia, vain vanhaa kunnon tavallista lasagnea. Laitoin tarkoituksella pelilogiikkamme viestiväylän toiselle puolelle. Kuten näet, sillä ei ole yhteyttä Framework-kerrokseen. Tämä on tärkeää, jotta vältytään kiusaukselta ”kutsua vain sitä yhtä funktiota”. Usko minua, haluat tehdä niin ennemmin tai myöhemmin, mutta se rikkoisi suunnittelumme. Meillä on jo tarpeeksi järjestelmiä, jotka ovat tekemisissä Frameworkin kanssa, eikä sitä tarvitse tehdä pelilogiikassamme.
Viestiväylä on yksinkertainen luokka, jolla on viittaukset jokaiseen järjestelmään. Jos sillä on viesti jonossa, Message Bus lähettää sen jokaiseen Systemiin yksinkertaisella handleMessage(Msg msg)
kutsulla. Vastineeksi jokaisella järjestelmällä on viittaus viestiväylään, jotta se voi lähettää viestejä. Tämä voidaan luonnollisesti tallentaa sisäisesti tai välittää funktion argumenttina.
Kaikkien Systeemiemme täytyy siis periä tai olla seuraavan muotoisia:
class System
{
public:
void handleMessage(Msg *msg);
{
switch(msg->type)
{
//// Example
//case Msg::OPEN_INVENTORY:
// break;
}
}
private:
MessageBus *msgBus;
//// Usage: msgBus->postMessage(msg);
}
(Joo, joo, raa’at osoittimet…)
Yhtäkkiä pelisilmukkamme muuttuu niin, että se yksinkertaisesti antaa Viestiväylän lähettää ympäriinsä Viestejä. Meidän täytyy edelleen päivittää säännöllisesti jokainen järjestelmä jonkinlaisen update()
kutsun avulla. Mutta kommunikaatio hoidetaan eri tavalla.
Mutta, kuten kehystemme kanssa, viestien käyttäminen aiheuttaa yleiskustannuksia. Tämä hidastaa moottoria hieman, älkäämme huijatko itseämme. Mutta emme välitä siitä! Haluamme siistin ja yksinkertaisen suunnittelun. Puhtaan ja yksinkertaisen arkkitehtuurin!
Ja mikä on hienointa? Saamme uskomattomia asioita ilmaiseksi!
Konsoli
Jokainen viesti on melko pitkälti funktiokutsu. Ja jokainen Viesti lähetetään aika lailla kaikkialle! Mitä jos meillä olisi järjestelmä, joka yksinkertaisesti tulostaa jokaisen lähetetyn viestin johonkin Output-ikkunaan? Entä jos tämä Järjestelmä voi myös lähettää Viestejä, jotka kirjoitamme tuohon ikkunaan?
Kyllä, olemme juuri synnyttäneet Konsolin. Ja se vaati meiltä vain muutaman rivin koodia. Mieleni räjähti jo silloin, kun näin tämän ensimmäistä kertaa toiminnassa. Se ei ole edes sidottu mihinkään, se on vain olemassa.
Konsoli on tietenkin erittäin hyödyllinen peliä kehitettäessä ja voimme yksinkertaisesti ottaa sen pois julkaisussa, jos emme halua pelaajan pääsevän siihen käsiksi.
Pelin sisäiset elokuvat, uusinnat & Vianmääritys
Mitä jos väärennämme viestejä? Mitä jos luomme uuden järjestelmän, joka yksinkertaisesti lähettää Viestejä tiettyyn aikaan? Kuvittele, että se lähettää jotain kuten MOVE_CAMERA
, jota seuraa ROTATE_OBJECT
.
Ja Voila, meillä on In-Game Cinematics.
Mitä jos yksinkertaisesti nauhoittaisimme pelin aikana lähetetyt Input-viestit ja tallentaisimme ne tiedostoon?
Ja Voila, meillä on Replays.
Mitä jos vain nauhoittaisimme kaiken, mitä pelaaja tekee, ja kun peli kaatuu, saisimme heidät lähettämään nämä tiedostot meille?
Ja Voila, meillä on tarkka kopio pelaajan toimista, jotka johtivat kaatumiseen.
Multi-Threading
Multi-Threading? Kyllä, monisäikeistystä. Irrotimme kaikki järjestelmämme toisistaan. Tämä tarkoittaa, että ne voivat käsitellä viestejään milloin haluavat, miten haluavat ja mikä tärkeintä, missä haluavat. Voimme antaa viestiväylämme päättää, missä säikeessä kukin järjestelmä käsittelee viestin -> Multi-Threading
Frame Rate Fixing
We have too many Messages to process this Frame? Ei ongelmaa, pidetään ne vain viestiväyläjonossa ja lähetetään ne seuraavassa kehyksessä. Tämä antaa meille mahdollisuuden varmistaa, että pelimme toimii tasaisella 60 FPS:n nopeudella. Pelaajat eivät huomaa, että tekoälyllä kestää muutaman ruudun kauemmin ”ajatella”. He huomaavat kuitenkin ruudunpäivitysnopeuden laskun.
Viestit ovat siistejä.
On tärkeää, että dokumentoimme huolellisesti jokaisen viestin ja sen parametrit. Kohtele sitä kuin API:ta. Jos teet tämän oikein, jokainen kehittäjä voi työskennellä eri järjestelmissä rikkomatta mitään. Vaikka järjestelmä olisi offline tai rakenteilla, peli toimii silti ja sitä voidaan testata. Ei audiojärjestelmää? Ei se haittaa, meillä on silti visuaalit. Ei Rendereriä, ei se haittaa, voimme käyttää konsolia…
Mutta viestit eivät ole täydellisiä. Valitettavasti.
Joskus haluamme tietää viestin lopputuloksen. Joskus TARVITSEMME, että ne käsitellään välittömästi. Meidän on löydettävä toteuttamiskelpoisia vaihtoehtoja. Ratkaisu tähän on Speedway. Yksinkertaisen postMessage
funktion lisäksi voimme toteuttaa postImmediateMessage
funktion, joka käsitellään välittömästi. Paluuviestien käsittely on paljon helpompaa. Ne lähetetään handleMessage
funktioon ennemmin tai myöhemmin. Meidän on vain muistettava tämä, kun lähetämme viestin.
Välittömät viestit ilmeisesti rikkovat monisäikeistyksen ja kuvanopeuden korjauksen, jos niitä tehdään liikaa. On siis elintärkeää rajoittaa itseään niiden käytön rajoittamiseksi.
Mutta suurin ongelma tässä järjestelmässä on latenssi. Se ei ole nopein arkkitehtuuri. Jos työskentelet First Person Shooterin parissa, jossa on twitchin kaltaiset vasteajat, tämä saattaa olla ratkaiseva tekijä.
Takaisin arkkitehtuurin suunnitteluun
Olemme päättäneet käyttää järjestelmiä ja viestiväylää. Tiedämme tarkalleen, miten haluamme jäsentää moottorimme.
On aika siirtyä suunnitteluprosessin vaiheeseen 4. Iteraatio. Jotkin toiminnot eivät ehkä mahdu minkään Systemin sisälle, meidän on löydettävä ratkaisu. Joitakin funktioita on kutsuttava laajasti ja ne tukkisivat viestiväylän, meidän on löydettävä ratkaisu.
Tämä vie aikaa. Mutta se on sen arvoista pitkällä aikavälillä.
Viimein on aika koodata!
Vaihe 4. Mistä aloittaa koodaaminen?
Ennen kuin aloitat koodaamisen, lue Robert Nyströmin kirja/artikkeli Game Programming Patterns.
Muuten olen hahmotellut pienen tiekartan, jota voit seurata. Se ei ole läheskään paras tapa, mutta se on tuottava.
- Jos käytät Message Bus -tyyppistä moottoria, harkitse ensin konsolin ja Message Busin koodaamista. Kun nämä on toteutettu, voit teeskennellä minkä tahansa sellaisen järjestelmän olemassaoloa, jota ei ole vielä koodattu. Sinulla on jatkuva kontrolli koko moottoriin kaikissa kehitysvaiheissa.
- Harkitse seuraavaksi siirtymistä graafiseen käyttöliittymään sekä tarvittavaan Draw-toiminnallisuuteen Frameworkin sisällä. Vankka graafinen käyttöliittymä yhdistettynä konsoliin mahdollistaa kaikkien muiden järjestelmien teeskentelyn entistä helpommin. Testaamisesta tulee helppoa.
- Seuraavaksi tulisi Framework, ainakin sen käyttöliittymä. Toiminnallisuus voi seurata myöhemmin.
- Viimeiseksi siirry muihin järjestelmiin, mukaan lukien pelattavuus.
Huomaa, että kaiken pelattavuuteen liittyvän renderöinti saattaa olla viimeinen asia, jonka teet. Ja se on hyvä asia! Se tuntuu paljon palkitsevammalta ja pitää sinut motivoituneena viimeistelemään moottorisi viimeiset silaukset.
Pelisuunnittelijasi saattaa kuitenkin ampua sinut tämän prosessin aikana. Pelattavuuden testaaminen konsolikomennoilla on suunnilleen yhtä hauskaa kuin Counter Striken pelaaminen IRC:n kautta.
Johtopäätökset
Vie aikaa vankan arkkitehtuurin löytämiseen ja pysy siinä! Tämä on neuvo, jonka toivon sinun ottavan pois tästä artikkelista. Jos teet näin, pystyt loppujen lopuksi rakentamaan täydellisen hienon ja ylläpidettävän Moottorin. Tai vuosisata.
Henkilökohtaisesti nautin enemmän moottoreiden kirjoittamisesta kuin pelattavuusjuttujen tekemisestä. Jos sinulla on kysyttävää, voit ottaa minuun yhteyttä Twitterissä @Spellwrath. Viimeistelen parhaillaan toista Engineä käyttäen tässä artikkelissa kuvaamiani menetelmiä.
Osa 2 löytyy täältä.