Kun olemassaolevat koneet eivät enää inspiroi, on aika siirtyä mielikuvituskoneisiin.
Artikkeli on alun perin julkaistu Skrollin numerossa 2016.1.
Tämä artikkeli englanniksi: Pico-8 – Fascinating fantasy console
Moni tietokoneharrastaja on ehtinyt uransa aikana tutustua lukemattomiin erilaisiin laite- ja softa-alustohin. Jotkut ovat jopa varta vasten kolunneet niitä läpi esimerkiksi pelailu- tai taiteilumielessä. Tällaisen tutkimusmatkailun myötä kehittyy näkemys siitä, millaiset piirteet tekevät alustasta mukavan tai mielenkiintoisen. Pico-8-fantasiakonsoli edustaa yhtä tällaista näkemystä.
Pico-8 on ehkä luontevinta mieltää emulaattoriksi, jonka emuloimaa laitetta ei ole koskaan ollut. Yleishengeltään se on varsin kasibittinen ja helposti kuviteltavissa vaikkapa Game Boy Color -tyyppiseksi käsikonsoliksi. Näytön resoluutio on 128 × 128 pikseliä 16 värillä ja äänet chip-pilpatusta neljällä kanavalla.
Kyseessä ei kuitenkaan ole mikään vaihtoehtohistoriallinen tutkielma, sillä Pico on suunnittelulähtökohdiltaan hyvin toisenlainen kuin sitä pintapuolisesti muistuttavat laitteet. Se ei yritä saada rajallisesta logiikkamäärästä mahdollisimman paljon irti vaan pikemminkin tarjoaa pienen joukon palikoita, joilla on mahdollisimman mukava leikkiä. Kehittäjän toiveissa on, että ennen pitkää Pico-8:n teknisistä puitteista kasvaisi omanlaisensa estetiikka, joka olisi minimalismistaan huolimatta ilmaisuvoimainen.
Pico-8 on hieman jakomielinen otus. Käynnistyttyään se on tilassa, jossa se muistuttaa pikemminkin kotimikroa näppäimistöineen ja hiirineen kuin pelikonsolia. Komentotulkissa voi ladata ohjelmia ja kirjoitella vaikkapa print-käskyjä. Esciä painamalla pääsee editoriin, jossa on omat osastonsa koodille, grafiikalle ja äänelle. Muut kuin sisäiset varusohjelmat eivät kuitenkaan pääse käsiksi näppäimistöön, hiireen tai kunnolla tiedostojärjestelmäänkään – niiden kannalta Pico on rom-moduuleita käyttävä pelikonsoli, jota ohjataan kahdella kaksinappisella pad-ohjaimella.
Picon äidinkieli on Lua, joka on alkujaan pelien skriptaukseen tarkoitettu pieni mutta suhteellisen ilmaisukykyinen kieli. Pico-virtuaalikone ei siis emuloi mitään varsinaista suoritinta – edes tavukoodia ajavaa sellaista. Kaikki on Lua-lähdekoodia, eikä ohjelmoija sen alemmaksi pääse.
Pelit ja muut ohjelmat voi jakaa maailmalle kahdessa päämuodossa. Cart eli rom-moduuli on käytännössä png-kuva, jotka on esittävinään fyysistä pelimoduulia päällyskuvineen, mutta jonka pikselien alabitteihin on vesileimamaisesti tallennettu ohjelmakoodi sekä grafiikka- ja äänidatat. HTML5-muotoon vietyä ohjelmaa puolestaan voi ajaa nykyaikaisissa www-selaimissa ilman varsinaista Pico-8-ohjelmistoa.
Picon näkyvin tekninen piirre on 128 × 128 pikselin näyttöalue 16 värin kiinteällä paletilla. Paletti on värivalikoimaltaan varsin persoonallinen ja helposti tunnistettava, ja sen suunnittelijalla on selvästi enemmän värisilmää kuin keskivertoinsinöörillä.
Vaikka Pico-pelit tyypillisesti käyttävätkin 8 × 8 pikselin palikoista koostuvia taustakarttoja, joiden päällä liikkuu niin ikään 8 × 8 pikselin spritejä, ei tämä ole mikään rajoite. Grafiikkatila on puhdas pikselipuskuri, johon voi piirtää mitä vain – ja koneelle on myös annettu sen verran nopeutta, että ainakin 90-lukulaiset demoefektit saa useimmiten pyörimään sulavasti. Alusta kuitenkin kannustaa 8 × 8 -palikoiden käyttöön sitä kautta, että karttoja ja spritejä piirtävät funktiot ovat nopeampia kuin saman tekeminen pikseli kerrallaan omalla koodilla.
Koodia mahtuu moduulille 15360 tavun verran pakattuna. Editorissa sen maksimipituus on 65536 merkkiä, ja tokenisoidussa muodossa sen pituus ei saa ylittää 8192 tokenia. Nämä rajat eivät tule kovin helposti vastaan – monet parhaistakin peleistä menevät huomattavasti rajan alle. Toisaalta rajan olemassaolo kannustaa pitämään ohjelman yksinkertaisena ja suoraviivaisena, valtaville pelimoottoreille ja monikerroksisille abstraktioille kun ei ole tilaa.
Grafiikalle on varattu moduulilta 12544 tavua ja äänelle 4608. Toki näitä data-alueita voi käyttää halutessaan muuhunkin – muistinkäsittelykäskyt päästävät siihen vapaasti tavutasolla käsiksi. Moduulin datapuolen sisältö kopioidaan ohjelman käynnistyessä käyttäjä-ramiin, josta ohjelma voi sitä tarvittaessa muokata. Käyttäjä-ramissa on lisäksi vajaat 7 kilotavua käyttäjälle varattua muistitilaa ja 8 kilotavua näyttömuistia.
Grafiikkadata koostuu 8 × 8 pikselin kokoisista spriteistä, joissa koko väripalettia voi käyttää vapaasti. Spritejä voi olla määriteltynä enintään 256, ja kartta puolestaan on kooltaan 128 × 32 spriteä. Kartan koon saa kuitenkin tuplattua, jos tyytyy 128 spriteen.
Äänipuolella ”spriteä” vastaava yksikkö on ääniefekti (sfx), joka koostuu 32 nuottipaikasta. Kussakin nuottipaikassa on tilaa paitsi itse nuotille, myös aaltomuodolle, äänenvoimakkuudelle ja tehosteelle, joita on kutakin 8 erilaista. Toistonopeutta voi vaihtaa, ja hitaammat nopeudet sopivat paremminkin musiikkiin kuin tehosteääniin.
Musiikkikappale koostuu tracker-musiikin tapaan kuvioista (pattern), joissa määritellään, mikä ääniefekti soitetaan kullakin neljästä kanavasta. 64 kuvion tilaan saa tarvittaessa useitakin kappaleita käyttämällä kuviokohtaisia silmukka- ja lopetuslippuja.
Siinä missä grafiikkapuolella kaiken voi halutessaan toteuttaa pikselitasolla itse, äänipuolella käyttäjä ei pääse kiinni ”rekistereihin”. Periaatteessa oman soittorutiinin voisi toteuttaa niin, että soitettavaa äänidataa muutetaan lennossa, mutta virtuaalikoneen ajastuksen rajat tulevat tällöin herkästi vastaan. Ääniefektimuistiin sekalaista dataa kirjoittamalla on kuitenkin helppo saada aikaan erilaisia kokeellisia äänimaailmoja.
Ohjelmakoodin, data-ramin ja moduuli-romin lisäksi Picossa on 256 kilotavua työskentelytilaa Lua-tulkille. Tämä on suhteellisen paljon verrattuna Picon muihin muistiavaruuksiin, mutta sen saa helposti täyteen vaikkapa isoilla lukutaulukoilla. Yksi lukutaulukon alkio vie tilaa kahdeksan tavua, joista puolet on varsinaista dataa. Kukin luku koostuu 16 bitin kokonaislukuosasta ja 16 bitin murto-osasta, joten tiukan paikan tullen voi tilaa tiivistää bittiaritmetiikalla.
Tilan käydessä vähiin on Picossa myös mahdollisuus lukea dataa muilta rom-moduuleilta, ja niille voi jopa kirjoittaa. Ohjelmakoodia ei sen sijaan voi suorittaa muilta moduuleilta, sen raja on tiukka. Koska Luasta sinänsä löytyvä mahdollisuus ajaa dataa koodina on poistettu, on lisäkooditilaa kaipaavan rakennettava koodin ajamiseen oma virtuaalikone.
Pico-8 ei suinkaan aja Lua-koodia niin nopeasti kuin alla oleva suoritin antaa myöten, vaan eri toiminnoille on annettu erilaiset suoritusajat. Nopeusrajoitus johtaa harvoin ongelmiin tyypillisiten Pico-ohjelmien kehityksessä, mutta se vakioi alustan rajat ja estää varustelukilpailun. Ensimmäisen sukupolven Raspberry Pi kuulemma riittää mainiosti kaikkein raskaimpienkin Pico-ohjelmien ajamiseen täydellä nopeudella.
Lua muistuttaa pelkillä isoilla kirjaimilla kirjoitettuna erehdyttävästi basicia. Esimerkkinä vaikkapa seuraava loputtomiin tekstiä toistava ohjelma, jonka basic-versio on monille tuttu:
::ALKU::
PRINT ”TERVEHDYS”
GOTO ALKU
Picossa ei ole tarjolla Lua-standardikirjastoa vaan melko suppea valikoima basic-henkisiä valmisfunktioita: piirtokäskyjä, pari äänikäskyä, ohjainten lukufunktiot ja muutamia funktioita muistinkäsittelyä, matematiikkaa, bittiaritmetiikkaa ja merkkijononkäsittelyä varten.
Peruspiirtokäskyillä voi piirtää pikseleitä, suorakaiteita, viivoja, ympyröitä, tekstiä, spritejä ja taustakarttoja. Paletin värejä voi vaihtaa piirtokäskyjen kannalta toisikseen, ja haluamansa värit saa myös asetettua spritejen ja taustagrafiikan kannalta läpinäkyviksi.
Liikkuvan grafiikan piirtotapa muistuttaa enemmän PC:tä kuin kasibittisiä kotimikroja. Picossa ei ole ”rautaspritejä” tai ”rautaskrollausta”, vaan näytön sisältö rakennetaan yleensä uusiksi joka näytönvirkistystä varten: tyhjennä näyttö, piirrä tausta, piirrä haluamasi spritet sen päälle.
Spritejen piirtoon on kaksi funktiota: spr() piirtää yksittäisen 8 × 8 pikselin spriten annettuihin koordinaatteihin, kun taas sspr() piirtää spritet sisältävältä ”arkilta” (sprite sheet) mielivaltaisen alueen mielivaltaisella skaalauksella. Skaalaustoiminto mahdollistaa helposti jotkin temput, jotka ovat useimmilla klassikkokoneilla varsin tyyriitä, esimerkiksi Doom-tyyppisen tekstuurinpiirron.
Ohjelmoija voi hyvin laittaa piirtokäskyjä vaikkapa ikuiseen silmukkaan, mutta hienostuneempi tapa on määritellä funktio nimeltä _draw(), jota kutsutaan joka näytönvirkistyksellä eli 30 kertaa sekunnissa. Aiempi esimerkki näyttäisi tällä tavoin seuraavalta:
FUNCTION _DRAW()
PRINT ”TERVEHDYS”
END
Peliohjainta luetaan funktiolla btn(), joka ottaa parametrikseen tutkittavan napin numeron ja palauttaa tiedon, onko kyseinen nappi alhaalla. Spriteä numero 0 vasemmalle ja oikealle liikuttava ohjelma voisi näyttää tältä:
X=64
FUNCTION _DRAW()
CLS()
SPR(0,X,112)
IF BTN(0) THEN X=X-1 END
IF BTN(1) THEN X=X+1 END
END
Jotta ruudulla näkyisi mitään, on spriteen numero 0 toki ensin piirrettävä jotain sprite-editorin puolella.
Joskus _draw() sisältää niin paljon tehtävää, ettei sitä ehditä suorittaa joka näytönvirkistyksellä. Tällöin ohjelmoijan kannattaa siirtää pelitilan päivittäminen _update()-funktioon, jota kutsutaan periaatteessa 30 kertaa sekunnissa. Periaatteessa siksi, että se ei ole ajastinkeskeytys, vaan sitä vain kutsutaan useamman kerran peräkkäin, jos _draw() venähtää.
Tuplapuskuroinnista ei tarvitse huolehtia: näyttömuistin muutokset tulevat näkyviin vasta, kun _draw() on suoritettu loppuun. Koko näytön kokoiselle tuplapuskurille ei käyttäjä-ramissa olisi toisaalta tilaakaan ellei jyrää osaa ääniefektialueesta pois tieltä.
Äänipuolella tarjolla ovat funktiot sfx() ja music(), joista ensimmäinen soittaa parametrina saamansa numeron mukaisen ääniefektin ensimmäisellä vapaalla äänikanavalla, ja jälkimmäinen aloittaa musiikin soittamisen annetusta kuvionumerosta.
2D-pikkupelien tekijän ei juuri tarvitse välittää käskyjen nopeuksista, mutta ne tulevat vastaan alustan rajoja koetellessa. Kaikki näytön pikselit ehtii piirtää läpi pikselinpiirtofunktion pset() avulla noin puolitoista kertaa näytönvirkistyksessä, ja suora näyttömuistiin kirjoittaminen poke()-funktiolla on noin kolme kertaa nopeampaa. Muistin täyttäminen memcpy()– ja memset()-funktioilla on sen sijaan peräti kymmenisen kertaa nopeampaa, ja myös taustagrafiikan piirtokäsky map() on nopeudeltaan samaa luokkaa.