Laatinut: Juho Salminen 3.12.2018

Viimeisin muokkaus: 10.1.2019 klo 12.44

Pelin tekeminen iOS:lle

Pelin tekeminen iOS:lle on mahdollista myös Visual Studiolla, mutta tällä hetkellä Jypeli ei taida tukea iOS:ää. Ehkä helpoiten oman sovelluksen tekeminen iOS:lle onnistuu Xcode-kehitysympäristöllä ja Swift-kielellä, jotka molemmat ovat Applen tuotteita ja täten takaavat varman toimivuuden iOS-laitteissa. Samalla tavalla kuin C#:lle on luotu Jypeli-kirjasto, Swift sisältää Applen valmiin SpriteKit-kirjaston pelien luontiin.

Kannattaa huomioida, että Ohjelmointi 1 -kurssille tehtyä pelin koodia ei siis saa suoraan siirrettyä iOS:llä toimivaksi näillä ohjeilla, vaan oletuksena on että luodaan uusi peli alusta alkaen. Vaikka vanhaa koodia ei voi hyväksikäyttää sellaisenaan, voi siitä paikoin olla hyötyä. Tämän ohjeen on tarkoitus antaa perusteet ympäristön käytöstä, puhelimella debuggauksesta ja SpriteKit-kirjaston ominaisuuksista.

Tämä ohje on tehty vain macOS-käyttöjärjestelmälle, Windows/Linux-toimivuudesta ei ole mitään takuita. Lisäksi ohjetta tehdessä oletetaan, että käytössä on macOS Mojave tai uudempi ja iOS-versio vähintään 12. iCloud-tunnukset tarvitaan kehittäjäprofiilin luontiin, joka on edellytys fyysisellä laitteella debuggaamiseen.

Ohjeessa on aluksi käyty läpi työkalujen asentaminen ja vaiheet, joilla malliprojekti saadaan pyörimään niin iOS-simulaattorilla kuin fyysiselläkin laitteella. Ohjeet pelin julkaisusta App Storeen on myös lisätty. Lopuksi on ohjeita Swiftistä ja SpriteKitistä, joiden avulla voidaan aloittaa oman pelin luominen.

Näillä ohjeilla pitäisi saada luotua alla olevan videon kaltainen peli.

1. Työkalujen asentaminen

Macille iOS-sovellusten luomiseen ja testaamiseen riittää pelkästään Xcoden asentaminen, sillä se sisältää kehitysympäristön, SpriteKit-pelikirjaston ja iOS-simulaattorit.

1.1 Xcode

Xcode on hyvin vastaavanlainen kehitysympäristö kuin Visual Studio, joka on tarkoitettu sovellusten kehittämiseen pääosin Applen omille laitteille. Asennus on suoraviivaista, eikä muita ohjelmia tässä vaiheessa tarvita. Lataa Xcode versio 10 App Storesta ja noudata asennuksen aikana tulevia ohjeita. Asennus vaatii tilaa noin 6 gigatavua.

2. Pelin kääntäminen iOS:lle

Tässä osiossa käydään läpi, miten uusi peliprojekti luodaan alusta alkaen.

2.1 Projektin luonti

  1. Avaa Xcode
  2. Luo uusi SpriteKit iOS-projekti (File -> New-> Project). Uudessa avautuvassa ikkunassa valitse Application-osion alta Game ja siirry eteenpäin. Tämän jälkeen anna vielä projektille nimi, ja varmista että Language-kohdassa on valittuna Swiftja Game Technology-kohdassa SpriteKit. Lopuksi valitse tallenuskansio projektille.
  3. Projekti aukeaa projektinäkymään, ja voit testata pohjatiedoston toimivuutta simulaattorilla. Valitse vasemmalta ylhäältä iOS Simulators-kohdasta haluamasi simulaattori ja paina Product-valikossa Run (⌘R). Tämä kääntää ja ajaa ohjelman.
  4. Jos kaikki toimii kuten pitäisi, aukeaa uusi iOS-simulaattori valitsemallesi laitteelle. Näytössä on Hello World -teksti, ja näyttöä painamalla siihen ilmestyy grafiikkaa. Voit sulkea simulaattorin Simulator-valikosta Quit Simulator (⌘Q).

Pikanäppäimiä voi muuttaa ja asettaa valikosta Xcode-> Preferences -> Key Bindings.

2.2 Malliprojektin sisältö

Projektin runko on hieman erilainen kuin Visual Studioon tottuneille, joten sen sisältöä käydään tässä hieman läpi.

.swift-päätteiset tiedostot ovat puhtaita kooditiedostoja, joilla ohjataan sovelluksen toimintaa. GameScene.swift-tiedostossa on pelin varsinaiset tapahtumat, kuten kosketusnäytön tai törmäystapahtumien kuuntelu. GameViewController.swift-tiedosto hoitaa näkymän, eli siellä voidaan esimerkiksi asettaa automaattinen pelin kääntyminen vaakatasoon ja muut näkymää ohjaavat funktiot.

.storyboard-tiedostot ovat valmiita runkoja, joilla voidaan muokata pelin näkymää, esimerkiksi asettaa latausnäyttöön jokin kuva tai luoda erilaisia kenttiä peliin graafisesti.

Assets.xcassets-kansio sisältää pelin sisällön, josta tarkemmin myöhemmin.

Projektiin voidaan lisätä uusia näkymiä ja näkymille kontrollereita, mutta niihin ei mennä tarkemmin tässä, kun taitavat olla enemmän Ohjelmointi 2 -kurssin asiaa.

3. Pelin ajaminen iOS-laitteella

Kun peli toimii simulaattorilla, sen pitäisi hyvin suurella varmuudella toimia myös fyysisellä laitteella. Fyysisellä laitteella debuggaus on myös aina suositeltavaa, koska silloin näkee varmasti, miltä peli näyttää laitteella.

Toisin kuin Androidilla, ajureita tai sovelluksia ei tarvitse asentaa, jotta ohjelma saadaan debugattua fyysisellä iOS-laitteella. Apple on kuitenkin rajoittanut, ettei kuka tahansa voi siirtää sovelluksia puhelimiin, joten kehittäjän tulee luoda profiili ja tehdä laitteella asetus, jotta debuggaus onnistuu.

3.1 Kehittäjäprofiilin luominen ja liittäminen projektiin

  1. Navigoi Xcode-> Preferences-> Accounts ja avautuvassa ikkunassa lisää iCloud-tunnuksesi painamalla vasemmassa alakulmassa olevaa +-näppäintä.
  2. Paina iOS Development-kohdasta Create
  3. Onnistuneessa profiilin luomisessa Accounts-valikossa pitäisi nyt näkyä iCloud-tunnuksesi viimeistään Xcoden uudelleenkäynnistyksen jälkeen
  4. Avaa peliprojektisi projektinäkymä painamalla projektin nimeä vasemmalta, jossa näkyy myös muiden tiedostojen nimiä. Aukeaa General-välilehti, jonka Signing-osion alta voit liittää profiilisi projektiin. Myös Team-kohtaan pitää asettaa luomasi profiili.

HUOM! OSX:ssä on bugi, ja jos profiilin luomisvaiheessa kysytään salasanaa codesign wants to acess key "access" in your keychain, niin anna Mac-käyttäjän salasana ja paina Always Allow. Enterin tai Allow-napin painallus antaa virheen, eikä profiilin luonti onnistu.

3.2 Puhelimen liittäminen tietokoneeseen

  1. Kehittäjätunnusten aktivointi ei välttämättä näy puhelimessa heti, joten käynnistä laite uudelleen.
  2. Liitä puhelin johdolla kiinni tietokoneeseen ja pidä se lukitsemattomana.
  3. Kun kysyy Trust This Computer paina Trust
  4. Avaa puheimessa valikko General -> Device Management ja aseta sieltä luomasi kehittäjäprofiili luotetuksi.
  5. Nyt voit ajaa pelin puhelimessasi valitsemalla sen samasta valikosta kuin aiemmin simulaattorit. Fyysinen laite näkyy esimerkiksi nimellä iPhone(Kalle).

Ensimmäisen kerran jälkeen on mahdollista käyttää myös langatonta debuggausta verkon yli. Tämä on ilman muuta kuitenkin hitaampaa. Jos haluat kokeilla, mene Window -> Devices and Simulators ja ruksi laitteen kohdalta Connect via network.

4. Julkaiseminen

App Storessa julkaisemiseen on paljon erilaisia vaatimuksia, ja lisäksi tarvitaan maksulliset (n. 100 € / vuosi) kehittäjätunnukset. Jos sovellusta haluaa kehittää tiiminä, tästä pitää maksaa vielä lisää, sillä tiimitunnukset maksavat 300 euroa. Aiemmin luoduilla free provisioning -tunnuksilla sovelluksia ei voi julkaista.

Alla olevien ohjeiden lisäksi tulee varmistaa ajantasaiset ohjeet julkaisuun Applen kehittäjäsivuilta. Apple noudattaa suhteellisen tarkkoja laatuvaatimuksia, joten kannattaa varmistaa mahdolliset muutokset, joita ei tähän dokumenttiin ole päivitetty.

Ohjeiden lisäksi voit katsoa vinkkejä mallipelin App Store -sivulta.

4.1 Apple Developer -ohjelma

Suurin este Developer-ohjelmaan liittymiselle on hinta, mutta mikäli tästä pääsee yli, tarjoaa ohjelma varsin hyvät puitteet oman sovelluksen julkaisuun ja päivittämiseen.

Apple Developeriksi liitytään kirjautumalla (Enroll) iCloud-tunnuksilla Applen Developer-sivuille. Kirjautuminen on yksinkertaista, eikä vaadi kuin omien tietojen syöttämisen ja maksun suorittamisen. Kannattaa huomioida lisäksi mahdollinen automaattiveloitus, jonka voi ottaa pois päältä, jos ei halua että tilauksen loppuessa maksu veloitetaan automaattisesti uudestaan.

Liittymisen jälkeen Xcodeen pitää laittaa oma kehittäjäprofiili uudestaan, sillä ei ilmeisesti automaattisesti päivitä tietoa siitä, että profiili on muuttunut maksulliseksi.

Maksutapahtuman jälkeen kestää lyhyen aikaa, ennen kuin tunnukset ovat käytettävissä, mutta tästä tulee ilmoitus sähköpostitse.

4.2 Vaatimukset julkaisulle

Tässä perehdytään hieman vaatimuksiin, mutta varmista myös ennen julkaisua Applen viralliset säännöt.

Apple antaa useita vaatimuksia sovellukselle, jotta se soveltuu julkaistavaksi App Storeen. Tärkein sisällöllinen vaatimus on, ettei sovellus loukkaa ketään, eli ei siis sisällä esimerkiksi muita ihmisryhmiä loukkaavaa sisältöä. Asetat itse myöhemmin sisällön kuvaukset pelille, ja näiden on täsmättävä oikean sisällön kanssa, sillä esimerkiksi ikäraja määräytyy sen perusteella. Lapset eivät voi ladata peliä, joka ei ole tarkoitettu lapsille.

Toiminnallisista vaatimuksista tärkein on se, että sovellus toimii kuten sen pitää. Käyttäjäkokemuksen tulee olla mielekäs, eikä sovellus saa kaatua yllättävissä tilanteissa. Käyttäjiä ei myöskään saa johtaa harhaan tuotekuvauksella, eli sovelluksen pitää sisältää ne ja vain ne ominaisuudet, joista on kerrottu.

Suurin syy hylkäämiseen on yleensä metatietojen puute. App Store Connectissa peliä julkaistaessa annetaan paljon tietoja pelistä, ja kaikki vaaditut kentät on täytettävä. Jo Xcode-projektissa on luotava sovelluskuvakkeet useassa eri koossa, ja Connectiin laitetaan esikatselukuvat ja -video pelin toiminnasta. Medialla on suhteellisen tarkat vaatimukset mm. resoluutiolle, jotka tulee täyttää. Myös videoiden frame per second on tarvittaessa muutettava 30 tai alle kuvaa sekunnissa.

Lisäksi vaaditaan, että sovelluksella on tietosuojaseloste, johon laitetaan linkki tuotesivulle. Tämä tulee tehdä myös siinä tapauksessa, että mitään tietoa käyttäjistä ei kerätä. Myös sovelluksen tukiverkkosivu vaaditaan, ja tämän voinee toteuttaa yksinkertaisimmillaan laittamalla kotisivutilaan sivun, jossa on yhteystiedot.

Vaikka itse peli olisi valmis, sen julkaisuun App Store Connectin kautta voi mennä useampi tunti, kun pitää ottaa oikeat kuvat ja videot pelin toiminnasta, kirjoittaa kuvaus ym. Kun vaatimuksia kuitenkin noudataa huolella, sovellus pitäisi mennä heittämällä läpi arvioinnista.

4.3 Julkaiseminen App Storeen

4.3.1 App Store Connect

Kun Apple Developer -tunnukset on luotu, voidaan niillä kirjautua App Store Connectiin (ent. iTunes Connect). App Store Connect on web-sovellus, jossa voidaan julkaista ja päivittää omia sovelluksia, sekä seurata niihin liittyvää analytiikkadataa. Sovellus on saatavilla myös iOS:lle, jolloin se voi lähettää ilmoituksia julkaisu-statuksen muuttumisesta tai uusista käyttäjäarvioista. Kirjaudu App Store Connectiin osoitteessa appstoreconnect.apple.com.

4.3.2 Julkaisutietojen luominen

  1. My Apps-näkymässä luo uusi projekti painamalla vasemmassa yläkulmassa olevaa +-näppäintä ja valitsemalla New App.
  2. Avautuvassa ikkunassa syötä sovelluksen tiedot. Bundle ID-valikosta valitaan Xcode-projekti ja SKU:ksi syötetään vapaavalintainen ID. Jatka painamalla Create.
  3. App Information-sivulla täytetään nimi- ja alaotsikkotiedot sekä linkki Privacy Policyyn. Category-kohdassa asetetaan sovellukselle yläluokka (esim. Games) ja alaluokka (esim. Arcade). Lisäksi tulee hyväksyä lisenssisopimus ja Rating-kohdassa kertoa sovelluksen sisällöstä, jonka perusteella sovellukselle annetaan ikäraja.
  4. Pricing and Availability-sivulla voidaan pelille asettaa hinta ja mahdolliset aluerajoitukset.
  5. Sovelluksen tärkeimmät metatiedot asetetaan iOS App-sivulla. Näihin kuuluvat esikatselukuvat ja -videot, kuvaus, avainsanat, tukisivusto, julkaisijan tiedot ja arvioinnin yhteyshenkilö. Lisäksi Build-kohdasta liitetään Xcode-projekti, mutta tästä on kerrottu tarkemmin luvussa 4.3.3.
  6. Kun kaikki tiedot ovat mielestäsi kunnossa, tarkista vielä kerran, että sovellukset tiedot vastaavat vaatimuksia kaikilta osin. Vain Optional-merkityt kentät voidaan jättää tyhjiksi, muuten tyhjä kenttä todennäköisesti johtaa sovelluksen hylkäämiseen.

HUOM! Niin erikoiselta kuin se kuulostaakin, kaikilla sovelluksilla on oltava Privacy Policy ja tukisivusto. Tämä siis myös, vaikkei käyttäjistä kerättäisi mitään tietoja. Privacy Policyja voi luoda hakukoneella löytyvillä generaattoreilla. Tukisivuston voinee laittaa omaan kotisivutilaan, kuten mallipelissä.

4.3.3 Xcode-projektin liittäminen

Jotta sovellus saadaan julkaistua, täytyy projekti vielä liittää App Store Connectiin.

  1. Vaihda simulaattoriksi Generic iOS Device.
  2. Siirry valikkoon Product->`Archive.
  3. Avautuvasta ikkunasta paina Distribute App.
  4. Seuraavassa kohdassa valitse iOS App Store ja tämän jälkeen Upload.
  5. Validoinnissa kestää muutama minuutti, jonka jälkeen paina hyväksymispainiketta.
  6. Avaa selaimella App Store Connect ja Build-kohdassa lisää Xcode-projekti. Juuri Xcodella lähetetty projekti ei välttämättä näy välittömästi. Muista päivittää sivu.

4.3.4 Julkaisu ja arviointiprosessi

Kun sovellus on valmis, se voidaan julkaista My Apps-sivulta painamalla Submit for Review. Tällöin pyydetään vielä täyttämään muutamia tietoja muun muassa julkaisuehtoihin ja kolmannen osapuolen sisältöön liittyen.

Lopulta sovellus näkyy App Store Connectissa Waiting for review-tilassa. Kun sovellus otetaan arvioitavaksi, tila muuttuu In review. Loppuvuodesta 2018 sovellusten arviointi kestää noin 2 vuorokautta, mutta esimerkiksi iOS Dev Weekly ylläpitää sivustoa, johon kehittäjät voivat lisätä omia arviointiaikojaan, ja sen perusteella lasketaan keskiarvo. Eri vuodenaikoina arviointiaikataulu voi siis olla huomattavasti pidempikin.

Mallipelin siirtyminen jonosta arvioitavaksi kesti noin 19 tuntia ja arvioinnissa se oli 11 tuntia (19.12.2018). Kokonaisuudessaan prosessi kesti siis 30 tuntia. Uuden version julkaisu oli huomattavasti nopeampaa, sillä arviointi vei vain 3 tuntia, mutta toisaalta joulun ruuhkat vaikuttivat edelleen, ja kesti kolme päivää, ennen kuin päivitys pääsi arvioitavaksi (30.12.2018).

Kun sovellus on arvioitu, saat siitä ilmoituksen. Mikäli se on hylätty (Rejected), saat myös tarkat tiedot hylkäykseen johtaneista syistä. Jos sovellus hyväksytään, sen voi julkaista App Storeen joko itse, tai asettaa vaihtoehdoksi automaattisen julkaisun heti hyväksymisen jälkeen.

4.3.5 Sovelluksen analytiikkadata

App Store Connect tarjoaa mahdollisuuden seurata muun muassa sovellukset latausmääriä, kaatumistilastoja ym. Tähän täydennetään ohjeet julkaistista sovelluksesta saatavien tietojen seuraamiseen App Store Connectin avulla.

Esimerkki sovelluksen tietojen seurannasta
Esimerkki sovelluksen tietojen seurannasta

4.3.6 Version päivittäminen

Päivittäminen on huomattavasti helpompaa, kun suurin työ on jo tehty. Käytännössä riittää, että App Store Connectissa oman sovelluksen sivulla vasemmasta valikosta painetaan + Version or platform. Tämän jälkeen kerrotaan mitä on muuttunut ja liitetään Xcode-projekti ylläolevilla ohjeilla. Päivityksen arviointi on myös nopeampaa, mikäli metatietoja ei muuta ja niitä ei tarvitse arvioida uudelleen.

5. Perusteet SpriteKitistä

Kannattaa ehkä ennen koodaamisen aloittamista tutustua hieman Swift-ohjelmointikieleen. Swift on kielenä hyvin samankaltainen kuin C#, joten perussyntaksin opetteluun ei Ohjelmointi 1:n jälkeen pitäisi mennä kauaa. Eroja on lähinnä sulkujen käytössä ja funktioiden esittelyssä. Perusteet Swiftistä voi lukea esimerkiksi Pienestä Swift-oppaasta. Myös tässä TIM-dokumentissa on käsitelty Swiftiä ja SpriteKitiä luomalla Ohjelmointi 1 -kurssilta tuttu lumiukko-ohjelma.

SpriteKit on perusteiltaan varsin samanlaisia toimintoja sisältävä pelikirjasto kuin Jypelikin, mutta eroja toki löytyy. Kuitenkin kaikki perustoiminnallisuudet on mahdollista toteuttaa kummalla vain. Tämä ohje ei kata kaikkia SpriteKitin ominaisuuksia ja voi vanhentua, ajantasaisen tiedon löytää aina virallisesta dokumentaatiosta.

Pientä Swift-opasta päivitetään, kiitos viittauksista! Tarjolla on myös ollut pong-pelistä Swift & SpriteKit -versio, mutta se ei enää käänny toimivasti, joten sekin pitää päivittää. -ji

04 Dec 18 (edited 04 Dec 18)

5.1 Projektin alustus

Ennen kuin jatkat, kannattaa omasta projektista tyhjentää malliprojekti. GameScene.swift-tiedostosta poista/kommentoi kaikki muut funktiot paitsi sceneDidLoad() siten, että tiedosto näyttää tältä:

import SpriteKit
import GameplayKit

class GameScene: SKScene {
   
    override func sceneDidLoad() {
    }
}

Muokkaa GameViewController.swift-tiedostoa siten, että luokassa on vain seuraava metodi:

override func viewDidLoad() {
        super.viewDidLoad()
        
        if let view = self.view as! SKView? {
            if let scene = GameScene(fileNamed: "GameScene") {
                scene.scaleMode = .aspectFill
                view.presentScene(scene)
            }
            view.ignoresSiblingOrder = true
            view.showsPhysics = true
            view.showsFPS = true
            view.showsNodeCount = true
        }
}

showsPhysics-rivi näyttää fysiikkaobjektien rajat, jolloin törmäyksissä tapahtuvien virheiden selvittely on helpompaa. showsNodeCount on myös hyödyllinen, jos vaikka halutaan poistaa peliobjekteja kun ne ovat pelinäkymän ulkopuolella. Ilman näytössä näkyvää lukua voi olla vaikea selvittää, onko objektit poistettu pelistä. Toki nämä asetukset otetaan pois, kun peli on valmis.

Lopuksi poista GameScene.sks-tiedostosta Hello World -teksti klikkaamalla sitä ja painamalla Delete.

Käännä ja aja ohjelma. Valkoisen latausikkunan jälkeen pitäisi näkyä vain tyhjä musta ruutu sekä oikealla alhaalla node/FPS-tiedot. Jos toimii, voidaan alkaa luomaan peliin omaa koodia.

5.2 Fysiikat SpriteKitillä

Toisin kuin Jypelissä, SpriteKitissä luodaan ensin SpriteNode, joka on ikään kuin ulkokuori objektille. Lisäksi tähän nodeen liitetään fysiikkarunko, PhysicsBody, jotta siihen voidaan vaikuttaa fysiikalla.

Oletuksena SpriteKitin fysiikka noudattaa reaalimaailman painovoimaa. Painovoiman voi kuitenkin asettaa esimerkiksi liikuttamaan objekteja ylöspäin:

self.physicsWorld.gravity = CGVector(dx: 0.0, dy: 10)

5.2.1 SpriteNoden luominen

Yksinkertaisimmillaan valkoisen neliönmallisen SpriteNoden luominen onnistuu seuraavasti. Laita allaoleva koodi sceneDidLoad()-metodin sisään.

let sprite = SKSpriteNode(color: SKColor.white, size: CGSize(width: 50, height: 50)) 
sprite.position = CGPoint(x: 0, y: 0)
addChild(sprite)

Tällöin SpriteNodeen ei vaikuta mitään fysiikkaa, vaan se ilmestyy peliin annettuun pisteeseen ja on siinä.

Voisiko käyttää SKColor.white, niin sovitus eri laitteiden välillä olisi helpompaa? -ji

  • Näköjään voi, automaattinen täydennys ehdotti tuota UIColoria niin siksi tuli myös koodiin. -juvejosa
04 Dec 18 (edited 04 Dec 18)

Tuo sceneDidLoad() muuten ei ole aliohjelma, vaan metodi. Swiftissä aliohjelmat esitellään luokkien ulkopuolella, niitäkin siis on mahdollista tehdä, päinvastoin kuin vaikka Javassa tai C#:ssa, jossa kaikki nk. ’aliohjelmat’ ovat metodeja. -ji

04 Dec 18

5.2.2 PhysicsBodyn luominen

Jos halutaan, että fysiikan lait vaikuttavat, on luotava spritelle fysiikkarunko ennen peliin lisäämistä (siis addChild-kutsua):

sprite.physicsBody = SKPhysicsBody(rectangleOf: sprite.size)

Nyt kun ajat ohjelman, neliö ilmestyy aluksi origon kohdalle, mutta tippuu välittömästi painovoiman mukana alas ja ulos näytöltä.

Fysiikkarunkoon voidaan lisätä fysiikkaominaisuuksia esimerkiksi alla olevilla riveillä:

  sprite.physicsBody.affectedByGravity = true
  sprite.physicsBody.allowsRotation = false
  sprite.physicsBody.isDynamic = true
  sprite.physicsBody.linearDamping = 0.75
  sprite.physicsBody.angularDamping = 0.75
  sprite.physicsBody.friction = 100
  sprite.physicsBody.restitution = 0
  sprite.physicsBody.mass = 5

PhysicsBodya luodessa kannattaa miettiä, kuinka tarkasti haluaa objektin fysiikkarajojen noudattavan näkyviä rajoja. Muiden kuin pallomaisten tai neliömäisten objektien kohdalla muotojen luominen voi olla aika raskasta, ja esimerkiksi auto-objektissakin suorakulmion muotoiset fysiikat voivat olla riittävät.

5.2.3 Rajojen luonti peliin

Rajat voidaan luoda lähes yhtä helposti kuin Jypelissä luomalla fysiikkarunko, joka kiertää pelinäkymän rajat.

physicsBody = SKPhysicsBody(edgeLoopFrom: frame)

Nyt voit kokeilla esimerkiksi painovoimaa muuttamalla, että peliobjektit pysyvät rajojen sisäpuolella.

5.3 Pelaajan syötteen käsittely

5.3.1 Kosketusnäyttö

Kosketusnäytön kuunteleminen on varsin yksinkertaista. Tyhjennä edellisessä kohdassa lisäämäsi kohdat sceneDidLoad()-metodissa, ja luo samaan luokkaan uusi metodi:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    let sprite = SKSpriteNode(color: SKColor.white, size: CGSize(width: 50, 
                                                                height: 50))
    sprite.physicsBody = SKPhysicsBody(rectangleOf: CGSize(
                                            width: sprite.size.width,
                                            height: sprite.size.height))
    for touch: AnyObject in touches {
        sprite.position = touch.location(in: self)
        self.addChild(sprite)
    }
}

Tässä luodaan samalla tapaa valkoinen neliönmuotoinen SpriteNode ja sille fysiikkarunko. Nyt sen sijainti asetetaan kuitenkin siihen pisteeseen, jota kosketusnäytössä painetaan. toucheson tässä siis Set, joka koostuu useista kosketuksista sijainteineen. Jokaiselle kosketukselle lisätään SpriteNode kosketuksen sijaintiin.

Kun ajat ohjelman, nyt siis pitäisi näytölle kosketuskohtaan ilmestyä valkoinen neliö, joka tippuu pois näkyvistä.

5.3.2 Kiihtyvyysanturi

HUOM! Ei toimi simulaattorilla, joten jos haluat testata tätä, varmista että käytössäsi on fyysinen laite.

Puhelimen kiihtyvyysanturilla voidaan lukea tietoa puhelimen kallistuksesta, mikä voi joissain peleissä olla mielekästä. Nyt muutamme peliä siten, että painovoima siirtyy vaikuttamaan siihen suuntaan, mihin puhelinta kallisteaan. Ennen tätä voit halutessasi muuttaa aiemmin alustamasi painovoiman nollaksi.

Lisää GameScene.swift-tiedoston alkuun ìmport CoreMotion. Loput muutokset tehdään GameScene-luokkaan.

Laita luokkaan attribuutiksi uusi ilmentymä CMMotionManagerista.

var motionManager: CMMotionManager!

Metodissa sceneDidLoad aloita kiihtyvyysanturin tietojen lukeminen.

motionManager = CMMotionManager()
motionManager.startAccelerometerUpdates()

Lopuksi luo update()-metodi, joka päivittää painovoimaa kiihtyvyysanturin tiedoilla:

override func update(_ currentTime: TimeInterval) {
        if let accelometerData = motionManager.accelerometerData {
            physicsWorld.gravity = CGVector(dx: accelometerData.acceleration.x * -0.1, 
                                            dy: accelometerData.acceleration.y * 0.1)
        }
}

Kun ajat ohjelman, painovoiman pitäisi muuttua puhelinta kallistettaessa.

5.4 Sisällön tuominen peliin

5.4.1 Kuvien lisääminen

Xcoden projektitiedostoissa on kansio Assets.xcassets, jonka sisälle voidaan tuoda kuvia ja tekstuuria peliin. Klikkaa tyhjää aluetta kahdella sormella ja valitse New Image Set. Tämä luo Assets-kansioon uuden Image Set-kansion. Anna sille nimi ja raahaa nyt tyhjään 1x-paikkaan oma kuvasi, joka tallentuu automaattisesti juuri luotuun kansioon. Vastaavalla tavalla voit sijoittaa kuvia myös valmiiseen AppIcon-settiin, jolla voit laittaa haluamasi ikonin sovellukselle.

Kun haluat käyttää kuvaa pelissä, onnistuu se esimerkiksi korvaamalla edellisessä kohdassa sprite-muuttujan alustus:

let sprite = SKSpriteNode(imageNamed: "kuva")

Jos kuvaa ei ole projektissa, näkyy nyt valkoisen neliön tilalla punainen X. Muussa tapauksessa SpriteNode sai antamasi kuvan ulkoasukseen. Kannattaa huomata, että tällä tavalla SpriteNode perii myös kuvan koon suoraan, jolloin esimerkiksi iso kuva voikin peittää koko näytön. Valitettavasti SpriteNodea ei voida luoda suoraan kuvalla ja koolla alustettuna, joten luomisen jälkeen voit muuttaa kokoa seuraavasti:

sprite.size = CGSize(width: 50, height: 50)

5.4.2 Äänien lisääminen

Helpoiten äänien lisääminen projektiin onnistuu jälleen raahaamalla ne projektinäkymään. Jos ääniä on paljon, on järjevää olla jokin kansio johon yksittäiset tiedostot laittaa. Kun sisältö on projektissa, riittää kutsua

run(SKAction.playSoundFileNamed("aani.mp3", waitForCompletion: false))

ja ääni soi halutussa kohdassa.

Taustamusiikki lisätään luomalla SKAudioNode-olio, joka voi soittaa ääntä silmukalla.

let taustamusiikki = SKAudioNode(fileNamed: "taustamusiikki.mp3")
backgroundMusic.autoplayLooped = true
addChild(taustamusiikki)

5.4.3 Fonttien lisääminen

  1. Lataa haluamasi fontti otf-tiedostona.
  2. Tuo se projektiin raahaamalla.
  3. Avaa projektin info.plist-tiedosto
  4. Paina +-näppäintä ja lisää Fonts provided by application
  5. Avaa se ja lisää Ìtem 0:n Value-kenttään fontin nimi kokonaisuudessaan

Kokeile esimerkiksi luomalla tekstikenttä ohjelmaasi:

let label = SKLabelNode(fontNamed: "PixelDigivolve")
label.text = "jyväskylän yliopisto"
label.position = CGPoint(x: 0, y: 0)
label.zPosition = 1000  // sijoitetaan tasolle, joka on kaikkien muiden päällä
addChild(label)

Keskelle ruutua pitäisi nyt tulla antamasi teksti juuri lataamallasi fontilla.

Tässä vaiheessa pelin pitäisi näyttää allaolevan kaltaiselta:

Projektin tuotos simuloituna
Projektin tuotos simuloituna

5.5 Törmäysten käsittely

Toisin kuin Jypelissä, SpriteKitissä ei ole valmista metodia törmäystapahtumien käsittelyyn eri fysiikkaobjektien välillä. SpriteKitissä pääsee siis menemään hieman pintaa syvemmälle, kun törmäyskäsittelijöitä luodaan.

Aluksi on luotava törmäyskategoriat, esimerkiksi GameScene.swift-tiedostoon. Jokainen fysiikkaobjekti voidaan liittää törmäyskategoriaan, jolloin eri objektien törmäyksillä tapahtuu eri asioita. Jypelissä lähimpänä varmaan objektien tagit, jotka annettiin myös törmäyskäsittelijäaliohjelmaa kutsuttaessa.

SpriteKitissä törmäyskategoriat voidaan luoda seuraavasti:

let kuvaKategoria     : UInt32 = 0x1 << 0
let rajaKategoria     : UInt32 = 0x1 << 1

Muuttujaan asetetaan siis heksaluku (tai vaihtoehtoisesti binääriluku), jota käytetään myöhemmin törmäysten käsittelyssä.

Kategoria on asetettava fysiikkaobjektille luomisen yhteydessä. categoryBitMask:lla annetaan oma kategoria, ja contactTestBitMask:lla se kategoria, jonka kanssa törmäyksiä kuunnellaan.

// tässä laitetaan luotuun kuvaan kategoria
sprite.physicsBody?.categoryBitMask = kuvaKategoria 
sprite.physicsBody?.contactTestBitMask = rajaKategoria

// ja tässä rajoille
physicsBody?.categoryBitMask = rajaKategoria
physicsBody?.contactTestBitMask = kuvaKategoria

GameScene-luokkaan on myös asetettava laajennos SKPhysicsContactDelegate, jotta luokka osaa lukea törmäyksiä. Lisää siis luokan esittelyyn:

class GameScene: SKScene, SKPhysicsContactDelegate {

ja sceneDidLoad()-funktioon

self.physicsWorld.contactDelegate = self

Viimeiseksi on vielä luotava GameScene-luokkaan funktio, jota kutsutaan törmäysten yhteydessä:

 func didBegin(_ contact: SKPhysicsContact) {

    if contact.bodyA.categoryBitMask == rajaKategoria {
        contact.bodyB.node?.removeFromParent()
        contact.bodyB.node?.physicsBody = nil
        contact.bodyB.node?.removeAllActions()
    } else if contact.bodyB.categoryBitMask == rajaKategoria {
        contact.bodyA.node?.removeFromParent()
        contact.bodyA.node?.physicsBody = nil
        contact.bodyA.node?.removeAllActions()
    }
}

Funktion sisällä suoritetaan bittimaskien vertailu ja törmäyksen tapahtuessa annetaan ohjeet mitä tehdään. Koska törmäyskäsittelijä ei tiedä kumpi objekteista törmää kumpaan, suoritetaan molemmilla sama koodi.

Tällä lisäyksellä pelin pitäisi toimia kuten aiemmin, mutta nyt reunoihin osuessaan SpriteNodet katoavat pelistä. Varmista tämä myös tarkkailemalla nodejen lukumäärää.

Jos pelissä on paljon yhdenaikaisia törmäyksiä, kannattaa ottaa käyttöön tarkempi käsittelijäalgoritmi jokaiselle fysiikkarungolle. Tällöin ei käy niin, että osa törmäyksistä jää lukematta.

physicsBody?.usesPreciseCollisionDetection = true

5.6 Hienosäätöä

Tähän mennessä käydyillä ohjeilla pääsee jo varsin pitkälle pelin luomisessa. Lopuksi voidaan vielä käydä läpi efektejä ja tapahtumia, joilla pelistä saa näyttävämmän.

5.6.1 SKEmitterNode

EmitterNodet vastaavat Jypelin efektejä, kuten räjähdyksiä ja savuja, joita voidaan liittää tapahtumiin. Tehdään tässä täydennys edelliseen peliin siten, että objektin osuessa reunaan ja hävitessä, tulee tilalle kipinöitä.

Klikkaa ensin projektinäkymää kahdella sormella ja valitse New File. Rullaa alas kohtaan Resourceja valitse sieltä SpriteKit Particle File. Sitten voit alasvetovalikosta valita templaten, esimerkiksi Spark. Tallenna haluamallasi nimellä. Voit avata projektiin ilmestyneen .sks-tiedoston ja näet, miltä template näyttää.

Tee seuraavaksi GameScene-luokkaan funktio, joka luo parametrina annettuun sijaintiin tuon kipinäsuihkun.

func doSparkles(location: CGPoint) {
    if let emitter = SKEmitterNode(fileNamed: "SparkParticle") {
        emitter.position = location
        emitter.numParticlesToEmit = 50
        emitter.particleLifetime = 1.0
        addChild(emitter)
    }
}

Ja kutsu funktiota esimerkiksi didBegin-metodissa kunkin törmäyskäsittelijän kohdalla seuraavasti:

doSparkles(location: contact.contactPoint)

Nyt SKEmitterNode luodaan törmäyskohtaan aliohjelmassa määrätyksi ajaksi.

Katso myös Liekinheitin.zip. (Toimiikohan tuokaan Xcode 10:llä?) Nuo partikkeliefektit kannattanee suunnitella ja toteuttaa partikkelieditorilla, joka on Xcodessa mukana, ja sitten vaan ladata ja käyttää omassa softassa. Liekinheitin.zip sisältämä projekti on tehty näin. -ji

04 Dec 18 (edited 04 Dec 18)

5.6.2 SKAction ja lisää kosketusnäytöstä

Tähän mennessä pelimme ei ole vielä kovin mielekäs, sillä siinä vain luodaan objekteja, jotka lopulta tuhoutuvat törmätessään seinään. Lisätään hieman pelimäisyyttä ensin poistamalla objektien lisäys pelaajan toimesta ja generoimalla niitä satunnaisiin paikkoihin. Tämän jälkeen muutetaan kosketusnäytön käsittelyä siten, että pelaaja yrittääkin pelastaa objektit koskettamalla niitä ennen kuin ne törmäävät seinään.

Muokataan siis hieman ohjelmaamme. Tässä vaiheessa voit tyhjentää touchesBegan-metodin ja luoda uuden funktion kuten alla:

func addSprite()  {
    let sprite = SKSpriteNode(imageNamed: "kuva")
    sprite.name = "sprite" // tagi lisätään myöhempää käyttöä varten
    sprite.size = CGSize(width: 100, height: 100)
    sprite.physicsBody = SKPhysicsBody(rectangleOf: sprite.size)
    sprite.physicsBody?.categoryBitMask = kuvaKategoria
    sprite.physicsBody?.contactTestBitMask = rajaKategoria
    sprite.position = CGPoint(x: CGFloat.random(in: (-size.width/2 + sprite.size.width)
                                                ...(size.width/2 - sprite.size.width)), 
                              y: size.height/2 - sprite.size.height)
    self.addChild(sprite)
}

Sitten asiaan, eli SKActioneihin. Kuten huomasit, niin addSprite-metodiamme ei vielä kutsuta mistään. Voisimme kutsua funktiota silmukassa, mutta tällöin emme voisi vaikuttaa ajastukseen, jolla välillä uusia spriteja luodaan. Lisää seuraavat rivit sceneDidLoad-metodiin.

run(SKAction.repeatForever(
            SKAction.sequence([SKAction.run(addSprite), SKAction.wait(forDuration: 0.5)])))

Nyt SKAction-luokan metodi repeatForever toistaa sen sisällä olevia actioneita ikuisesti, kuten silmukka. Kun silmukan lisään sisätään sekvenssi sequence-metodilla ja annetaan sille parametrina oman aliohjelmamme suorittava run-metodi ja ei-mitään tekevä wait-metodi, saamme ajastuksen, jolla uusia spriteja lisätään puolen sekunnin välein.

Tässä vaiheessa pelin pitäisi siis toimia siten, että näytön yläosasta tippuu kuvallasi varustettuja objekteja, jotka pohjaan osuessaan häviävät ja aiheuttavat kipinöitä.

Lopuksi lisätään touchesBegan-metodiin toiminnallisuudet, jotta havaitaan käyttäjän kosketus ja siinä pisteessä olevat objektit. Nyt osaat varmasti myös lisätä koodiin efektin ja äänen, kun objekti poistetaan.

for touch: AnyObject in touches {
    let location = touch.location(in:self)
    let touchedNode = self.atPoint(location)
    if touchedNode.name == "sprite" {
        touchedNode.removeFromParent()
    }
}

Kuis olis CGFloat.random(in: min...max) ? Tarjolla on myös protokolla RandomNumberGenerator ja sen toteuttava SystemRandomNumberGenerator ja muillekin numerotyypeille metodi random(…). -ji

  • Tämä toimii! Kiitos. -juvejosa
04 Dec 18 (edited 04 Dec 18)

5.6.3 Viimeistely

Kun peli on mielestäsi valmis, poista vielä GameViewController-luokasta alla olevat rivit, jotta fysiikkarunkojen rajoja tai FPS-laskureita ei näy lopullisessa versiossa.

view.showsPhysics = true
view.showsFPS = true
view.showsNodeCount = true

Tämän jälkeen pelin pitäisi toimia samalla tavalla kuin alun videolla.

5.8 Näkymät ja valikot

Tähän mennessä pelissämme on ollut kaksi näkymää, latausnäytön näkymä ja itse koodaamamme GameScene-näkymä. Lähtökohtaisesti ei ole käytännöllistä, jos peli alkaa heti latausnäkymän jälkeen. Voimme luoda uuden näkymän, aloitusvalikon, jonka sitten myöhemmin liitämme näkymään latausnäytön jälkeen ja laitamme tähän näyttöön siirtymän pelinäkymään.

Aloitetaan luomalla uusi tiedosto nimellä MenuScene.swift, johon rakennetaan haluttua toiminnallisuutta. Lisäksi luodaan myös uusi SpriteKit Scene Filenimellä MenuScene.sks, jossa voidaan helposti asettaa näkymän skaalaus ja taustaväri. MenuScene.swift-tiedostoon kirjoitetaan pohja luokalle:

import SpriteKit

class MenuScene : SKScene {

    override func didMove(to view: SKView) {
    }
}

Seuraavaksi meidän täytyy muuttaa GameViewController-luokkaa, jotta siellä ladataan GameScene-näkymän sijaan juuri luomamme MenuScene. Korvaa seuraavalla rivillä vastaava rivi, jossa MenuScenen tilalla on GameScene:

if let scene = MenuScene(fileNamed: "MenuScene") {

Nyt kun ajat ohjelman, sen pitäisi latausnäytön jälkeen siirtyä suoraan MenuSceneen, eli oletetuksena harmaaseen tyhjään näyttöön.

Lisätään nyt tähän näkymään joku nappula, jota painamalla voidaan siirtyä pelinäkymään. Järkevää olisi luoda oma luokka nappulalle, mutta pidetään tässä asiat hyvin yksinkertaisina. Voit siis luoda uuden nappulan keskellä näyttöä seuraavasti:

let playButton = SKSpriteNode(imageNamed: "play_button") // anna parametriksi projektiin liitetty kuva nappulalle
    playButton.name = "play"
    playButton.position =  CGPoint(x: 0, y: 0)
    addChild(playButton)

Nyt näkymässä on nappula, joka ei vielä tee mitään. Lisätään luokkaan metodi, joka käynnistää siirtymän:

func moveToGameScene() {
    if let view = self.view {
        let reveal = SKTransition.push(with: .down, duration: 1)
        if let scene = GameScene(fileNamed: "GameScene") {
            scene.scaleMode = .aspectFill
            view.presentScene(scene, transition: reveal) 
        }
    }
}

Itse siirtyminen näkymästä toiseen tapahtuu siis presentScene-metodilla. Tälle voidaan antaa parametriksi pelkästään näkymä, tai sen lisäksi myös siirtymä, kuten yllä. Tällöin näkymä ei vaihdu suoraan vaan jonkun siirtymäanimaation kautta. Siirtymäanimaatiot voi määrittää SKTransition-luokan metodeilla.

Metodia on vielä kutsuttava, ja järkevintä lienee tehdä se touchesBegan-metodissa, kuten aiemmin on näytetty. Tässä vielä kertauksena:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    for touch: AnyObject in touches {
        let touchedNode = self.atPoint(touch.location(in:self))
        if touchedNode.name == "play" {
            moveToGameScene()
        }
    }
}

Nyt nappia painamalla siirrytään MenuScene-näkymästä GameScene-näkymään.

5.9 UserDefaults (keskeneräinen)

Erilaiset käyttäjäkohtaiset tiedot on mahdollista tallentaa muuttujiin, jotka säilyvät näkymästä toiseen, myös sovelluksen sulkeuduttua. Yksi mahdollinen käyttökohde on parhaiden pisteiden lista. Mikäli uusi pistemäärä on suurempi kuin aiemmin UserDefaults-muuttujaan tallennettu, korvataan vanha uudella.

Ohje täydentyy...

Dokumentin puutteet

Tämäkään ohje ei ole kaiken kattava, joten kommenttipalkkiin voi lisätä toivomuksia siitä, mitä halutaan lisää. En kuitenkaan tee tätä päivätyönä, joten seuraavana päivänä ei kannata lisäyksiä vielä odottaa :)

These are the current permissions for this document; please modify if needed. You can always modify these permissions from the manage page.