Tämän oppaan tavoitteena on avata ohjelmoinnin maailmaa erityisesti sinulle, joka olet kiinnostunut pelien kehittämisestä. Emme aio vain opetella ohjelmointikielen syntaksia rivi riviltä. Sen sijaan keskitymme siihen, miten ohjelmoija ajattelee ja miten nämä ajattelutavat auttavat luomaan kiehtovia ja toimivia pelimaailmoja.
Onko kielellä väliä?
C# on erinomainen ensimmäinen ohjelmointikieli useista syistä. Se on selkeä ja johdonmukainen syntaksiltaan, mikä helpottaa peruskäsitteiden oppimista ilman liiallista monimutkaisuutta. Vahva tyypitys auttaa ymmärtämään, miten dataa käsitellään ja vähentää yleisiä virheitä varhaisessa vaiheessa. Lisäksi C# on monipuolinen kieli, jota käytetään laajasti pelikehityksessä (Unity), sovelluskehityksessä ja verkkopalveluissa, mikä antaa hyvän pohjan tulevaisuuden projekteille. Laaja ja aktiivinen yhteisö sekä hyvä dokumentaatio tekevät avun ja lisätiedon löytämisestä helppoa aloittelijalle.
Kun olet ohjelmoinut jonkin aikaa, huomaat että kielellä on vähemmän merkitystä kuin oikealla ajatustavalla. Uuden kielen syntaksin oppiminen on melko vaivatonta, mutta se, miten koodaaja ajattelee, on äärimmäisen tärkeää.
Miksi juuri Visual Studio?
Kun aloittelet ohjelmointia, törmäät nopeasti termiin ”IDE” eli integroitunut kehitysympäristö (Integrated Development Environment). Se on pohjimmiltaan ohjelma, joka tarjoaa kaikki tarvittavat työkalut koodin kirjoittamiseen, testaamiseen ja virheiden etsimiseen yhdessä paketissa. Vaikka tarjolla on monia IDE:itä, tuntuu siltä, että erityisesti C#-kehityksessä Visual Studio on ylitse muiden.
Visual Studio on Microsoftin kehittämä IDE, ja se on vakiintunut valinta erityisesti .NET-kehityksessä (johon C# vahvasti liittyy). Sen suosion syitä on monia:
- Erinomainen tuki useille kielille: Visual Studio on suunniteltu alusta alkaen tukemaan .NET-kehitystä erinomaisesti. Se tarjoaa kehittyneitä ominaisuuksia useiden kielien kirjoittamiseen, kuten automaattisen koodintäydennyksen (IntelliSense), reaaliaikaisen virheentarkistuksen ja refaktorointityökalut (koodin uudelleenjärjestely ilman toiminnallisuuden muutosta). Nämä ominaisuudet voivat merkittävästi nopeuttaa koodausprosessia ja auttaa välttämään tyypillisiä virheitä.
- Integroitu debuggeri: Visual Studion debuggeri on erittäin tehokas työkalu koodin virheiden etsimiseen ja korjaamiseen. Voit asettaa pysäytyskohtia koodiin, tutkia muuttujien arvoja ohjelman suorituksen aikana ja askeltaa koodia rivi riviltä. Tämä on elintärkeää, kun yrität ymmärtää, miksi ohjelmasi ei toimi odotetulla tavalla.
- Pelimoottorit: Erityisesti Unreal– ja Unity-kehityksen yhteydessä Visual Studiolla on hyvät integraatiot. Vaikka Unitylläkin on oma editorinsa, moni kehittäjä pitää Visual Studion koodieditoria ja debuggeria ylivoimaisina.
- Ilmainen versio (Community Edition): Aloittelijoille ja pienille tiimeille on saatavilla täysin ilmainen Visual Studio Community Edition, joka tarjoaa lähes kaikki ammattilaisversion ominaisuudet.
Vaihtoehtoja Visual Studiolle on tietenkin paljon (esim. JetBrains Rider, VS Code ja C# Dev Kit, tai joku muu tekstieditori ja kääntäjä) ja joudut ehkä pohtimaan sopivaa vaihtoehtoa, eteenkin jos työskentelet Linuxilla tai macOS:lla.
Tässä oppaassa käytetään kuitenkin Visual Studiota, koska spesifit ohjeet edes jollekin IDE:lle tekevät alkuun pääsemisestä helpompaa niille, jotka pystyvät myös käyttämään sitä. Jos mahdollista, suosittelen asentamaan Visual Studion, ja jos haluat myöhemmin vaihtaa johonkin toiseen työkaluun, se onnistuu kyllä.
Visual Studion asentaminen
Voit ladata Visual Studion ilmaisversion osoitteesta: https://visualstudio.microsoft.com/downloads/.
Näet sivulla kolme eri versiota:
- Community: Tämä on ilmainen, täysin toimiva versio yksittäisille kehittäjille, opiskelijoille, avoimen lähdekoodin projekteille ja pienille tiimeille. Suosittelen tätä useimmille aloittelijoille.
- Professional: Maksullinen versio, joka tarjoaa lisäominaisuuksia ja -työkaluja ammattikäyttöön ja tiimityöhön.
- Enterprise: Maksullinen, kattavin versio suurille organisaatioille ja vaativiin kehitystarpeisiin.
Valitse Community napsauttamalla sen alla olevaa ”Free download” -painiketta. Kun lataus on valmis, etsi ladattu tiedosto (VisualStudioSetup.exe
) ja kaksoisnapsauta sitä käynnistääksesi asennusohjelman.
Seuraavaksi avautuu varsinainen Visual Studio Installer. Tässä ikkunassa sinua pyydetään valitsemaan työkuormia (workloads). Työkuormat ovat valmiita asennuspaketteja, jotka sisältävät tiettyihin kehitystarpeisiin tarvittavat työkalut ja komponentit. Voit asentaa ja poistaa näitä koska tahansa Visual Studio Installerilla.
Valitse .NET desktop development (voit jättää muut työkuormat asentamatta). Tämä sisältää työkalut Windowsin konsoli- ja työpöytäsovellusten (esim. WPF, WinForms) kehittämiseen C#:lla. Näet oikealla puolella yhteenvedon asennettavista komponenteista ja tarvittavasta levytilasta. Napsauta oikeassa alakulmassa olevaa ”Install”-painiketta.
Asentamisessa voi kestää melko kauan, joten on hyvä idea laittaa Visual Studio asentumaan. Voit vaikka sillä aikaa lukea tämän oppaan loppuun.
Ensimmäinen ohjelmasi
Mennäänpäs suoraan asiaan ja kirjoitetaan ensimmäinen ohjelmasi.
Luo uusi projekti
Avaa Visual Studio ja valitse päävalikosta File -> New -> Project… (Ctrl+Shift+N).
Tämä avaa ”Create a new project” -ikkunan. Tahdomme luoda .NET-konsolisovelluksen, joka on siis tyyppiä ”Console App (.NET Framework)”.
Konsolisovellukset ovat erinomainen tapa aloittaa ohjelmoinnin opettelu. Voit harjoitella syötteen lukemista, tiedon käsittelyä ja tekstin näyttämistä selkeässä, vaiheittaisessa muodossa. Tämä auttaa ymmärtämään ohjelmoinnin perusrakenteita ja algoritmeja ilman visuaalisen suunnittelun tuomaa lisäkompleksisuutta.
Voit joko etsiä hakukentästä ”Console” tai käyttää hakukentän alla olevia pudotusvalikoita rajaamaan tuloksia valitsemalla kieleksi ”C#” ja projektin tyypiksi ”Console”. Valitse sitten ”Console App (.NET Framework)” ja siirrytään seuraavalle sivulle.
Anna projektille nimi (esimerkiksi ”YksinkertainenLaskin
”). Ohjelmointikielissä, kehitysympäristöissä ja tiedostojärjestelmissä välilyönnit nimissä tulkitaan yleensä eri osiksi. Tämä johtaa helposti sekaannuksiin, virheisiin tiedostopoluissa, nimiavaruuksissa ja muissa ohjelmointiin liittyvissä rakenteissa. Käytä mielummin camelCase tai PascalCase –kirjoitustyyliä, jossa ei ole välejä, vaan erilliset sanat aloitetaan suurella kirjaimella.
Tarkista, että projektin sijainti sopii sinulle, ja napsauta ”Create”.
Kirjoita koodi
Nyt eteesi avautuu Visual Studion koodieditori, jossa näet valmiin Program.cs
-tiedoston. Alla on koodi yksinkertaiseen laskimeen, joka suorittaa kahden käyttäjän antaman luvun yhteenlaskun.
Vaikka voitkin vain kopioida koodin tiedostoosi, suosittelen, että sen sijaan kirjoitat sen itse. Koodin kirjoittamisessa on oma tuntumansa ja uskon vakaasti siihen, että ennen kuin oikeasti alat kirjoittamaan koodia, et ikinä oikein pääse siihen sisälle, jonka seurauksena koodaus ajatuksenakin tuntuu aina hieman etäiseltä.
Huomaa, että vain Main-lohkon, eli {}
-merkkien sisällä oleva koodi on muuttunut.
using System; namespace YksinkertainenLaskin { internal class Program { static void Main(string[] args) { Console.WriteLine("Yksinkertainen laskin"); Console.WriteLine("Syötä ensimmäinen luku:"); string luku1Teksti = Console.ReadLine(); float luku1 = Single.Parse(luku1Teksti); Console.WriteLine("Syötä toinen luku:"); string luku2Teksti = Console.ReadLine(); float luku2 = Single.Parse(luku2Teksti); float summa = luku1 + luku2; Console.WriteLine("Summa on: " + summa); Console.WriteLine("Paina Enter lopettaaksesi."); Console.ReadLine(); } } }
Tämä koodi siis tulostaa ruudulle ensin otsikon ”Yksinkertainen laskin” ja pyytää sitten käyttäjää syöttämään kaksi lukua. Lopuksi ohjelma laskee syötettyjen lukujen summan ja tulostaa summan näytölle.
Mikäli saat virheitä (Error), pidä huoli oikeinkirjoituksesta ja että sinulla on lainausmerkit ("
) ja puolipisteet (;
) oikeissa paikoissa. Koodauksessa tällaiset yksityiskohdat ovat erittäin tärkeitä.
Älä murehdi liikaa koodin ymmärtämisestä tässä vaiheessa. On normaalia, että uusia asioita nousee jatkuvasti esiin; ohjelmoinnissa edes ammattilaiset eivät tiedä kaikkea. Anna itsellesi lupa olla ymmärtämättä jokaista riviä heti, ja keskity saamaan asioita toimimaan – ymmärrys syvenee vähitellen ja tulemme käymään asioita läpi yksityiskohtaisesti tulevaisuudessa. On tärkeämpää, että saat hieman ensikosketusta siihen, mistä koodaamisessa on kysymys.
Suorita ohjelma
Paina Visual Studion yläreunasta vihreää ”Start” -painiketta (tai paina F5).
Nyt konsoli-ikkunan pitäisi avautua, ja ohjelma pyytää sinua syöttämään lukuja. Syötä kaksi lukua ja paina Enteriä jokaisen jälkeen. Ohjelma laskee ja näyttää summan.
Onneksi olkoon! Olet täten ohjelmoija.
Nyt kun koodausura on saatu loistavaan alkuun, niin pureudutaanpa hieman tarkemmin siihen, mitä ohjelmointi oikeastaan on.
Harhaluuloja
Monilla aloittelijoilla on ohjelmoinnista tiettyjä ennakko-oletuksia, jotka eivät aina pidä paikkaansa.
Harhaluulo 1: Ohjelmointi on pelkkää koodin kirjoittamista.
Vaikka rapsakan koodin tuottaminen onkin olennainen osa ohjelmointia, se on vain pieni osa laajempaa kokonaisuutta. Pelinkehityksessä ohjelmoija ei vain naputtele satunnaisia rivejä tekstiä. Työhön kuuluu muun muassa:
- Suunnittelu: Ennen kuin yhtäkään koodiriviä kirjoitetaan, on mietittävä, mitä halutaan saada aikaan ja miten se kannattaa toteuttaa. Tämä voi tarkoittaa pelimekaniikkojen, hahmojen tekoälyn, käyttöliittymän tai muiden pelin osa-alueiden suunnittelua (design). Ajattele vaikka uuden vihollisen tyypin lisäämistä peliin. Ohjelmoijan on mietittävä, miten tämä vihollinen liikkuu, hyökkää, reagoi pelaajaan ja muihin pelin elementteihin.
- Ongelmanratkaisu: Ohjelmointi on pohjimmiltaan ongelmanratkaisua. Kun eteen tulee haaste (esimerkiksi miten saada kaksi peliobjektia törmäämään realistisesti), ohjelmoijan on analysoitava ongelma, pilkottava se pienempiin osiin ja etsittävä niihin ratkaisuja.
- Testaus: Kirjoitettu koodi ei aina toimi heti oikein. Ohjelmoijan on testattava koodiaan systemaattisesti löytääkseen ja korjatakseen virheitä (bugeja, englanniksi bugs). Kuvittele, että olet koodannut uuden aseen pelaajalle, mutta se aiheuttaa pelin kaatumisen. Sinun on selvitettävä (debug), missä vika piilee.
- Yhteistyö: Pelikehitys on usein tiimityötä. Ohjelmoijat työskentelevät yhdessä suunnittelijoiden, graafikoiden, äänisuunnittelijoiden, muiden ammattilaisten ja myös muiden ohjelmoijien kanssa. Kommunikointi ja kyky ymmärtää toisten näkökulmia ratkaisee usein valmistuuko projekti ollenkaan.
Harhaluulo 2: Ohjelmoinnissa pitää muistaa kaikki syntaksi ulkoa.
Ohjelmointikielen opettelu voi tuntua ylivoimaiselta, kun näkee kaikki ne oudot sanat ja symbolit. On kuitenkin tärkeää ymmärtää, että kukaan ei muista kaikkea syntaksia ulkoa. Ammattilaisohjelmoijatkin turvautuvat jatkuvasti dokumentaatioon, hakukoneisiin, tekoälyyn ja kehitysympäristöjen tarjoamaan apuun.
Tämän oppaan tavoitteena ei olekaan pakottaa sinua muistamaan jokaista C#-kielen yksityiskohtaa. Sen sijaan pyrimme opettamaan sinulle ne perusperiaatteet, jotka ovat yhteisiä lähes kaikille ohjelmointikielille. Kun ymmärrät miksi jokin asia tehdään tietyllä tavalla, syntaksin löytäminen ja ymmärtäminen on paljon helpompaa. Ajattele vaikka, että haluat saada jonkin toiminnon toistumaan useita kertoja pelissä (esim. pokerikäden korttien jakaminen pakasta). Tällöin tiedät, että tarvitset jonkinlaisen ”toistolauseen” (silmukan). Sen tarkan syntaksin voi aina tarkistaa tarvittaessa.
Mitä enemmän kirjoitat tiettyjä asioita, sitä helpommin ne alkavat tulla ulkomuistista. Koodaa siis paljon, niin saat paljon kokemusta.
Harhaluulo 3: Ohjelmointi on vain matemaatikkojen ja insinöörien hommaa.
Vaikka looginen ajattelu on ohjelmoinnissa tärkeää, sinun ei tarvitse olla matemaatikonero tai fyysikko ryhtyäksesi peliohjelmoijaksi.
Jos haluat kirjoittaa kielisovelluksen, pitää sinun ymmärtää kieliä. Jos haluat tehdä audiosovelluksen, pitää sinun ymmärtää audiota. Jos haluat tehdä monimutkaisen 3D-moottorin, pitää sinun ymmärtää lineaarialgebraa. Ohjelmointi on ongelmanratkaisua. Jos pidät puzzleista, olet varmasti hyvä ohjelmoija. Matemaatiikan ja fysiikan osaamisesta voi olla hyötyä, jos olet erikoistunut ohjelmoimaan niihin liittyvää toiminnallisuutta (kuten fysiikkasimulaatiot tai monimutkaiset tekoälyalgoritmit).
Harhaluulo 4: Kun olen oppinut ohjelmointikielen, olen oppinut kaiken mitä tarvitsen ohjelmointiin.
On helppo ajatella, että kun vihdoin on oppinut yhden ohjelmointikielen, tietää kaiken ohjelmoinnista. Ei se kuitenkaan ihan niin mene. Samalla tavalla, jos opit vaikkapa japanin (日本語) kieliopin, niin ei se tee sinusta eksperttiä kirjojen kirjoittamisessa. Sitten sinulla pitäisi olla jotain sanottavaa ja vieläpä mielellään osata sanoa se hyvin, puhumattakaan kaikesta siitä osaamisesta, mitä sanojen saaminen fyysisen kirjan kansien väliin vaatii.
Ohjelmointi ei ole pelkästään tietyn kielen sääntöjen hallitsemista. Se on ennen kaikkea ongelmanratkaisutaitoa. Eri ohjelmointikielet tarjoavat erilaisia työkaluja ja lähestymistapoja ongelmien ratkaisemiseen, mutta pohjimmiltaan ohjelmoijan työ on sama kielestä riippumatta: ottaa monimutkainen ongelma, pilkkoa se pienempiin osiin ja luoda looginen sarja ohjeita (algoritmeja) sen ratkaisemiseksi.
Kun olet oppinut yhden ohjelmointikielen, sinulla on kyllä perusta ymmärtää ohjelmoinnin peruskonsepteja, kuten muuttujat, ehtolauseet ja silmukat. Kuitenkin todellinen osaaminen kehittyy, kun opit:
- Eri ohjelmointiparadigmoja: On olemassa erilaisia tapoja jäsentää ja kirjoittaa ohjelmakoodia (esimerkiksi olio-ohjelmointi, funktionaalinen ohjelmointi). Eri kielet tukevat näitä paradigmoja eri tavoin, ja niiden ymmärtäminen avartaa ajatteluasi. C# tukee vahvasti olio-ohjelmointia, jota tulemme käsittelemään.
- Algoritmeja ja tietorakenteita: Nämä ovat ohjelmoinnin perusrakennuspalikoita, jotka ovat riippumattomia yksittäisestä kielestä. Algoritmit ovat askel askeleelta -ohjeita tietyn tehtävän suorittamiseen (esimerkiksi tiedon lajittelu), ja tietorakenteet ovat tapoja järjestää ja tallentaa dataa tehokkaasti (esimerkiksi listat, puut, taulukot). Näiden ymmärtäminen auttaa sinua kirjoittamaan tehokkaampaa ja suorituskykyisempää koodia riippumatta kielestä.
- Ohjelmistosuunnittelun periaatteita: Hyvä ohjelmointi ei ole vain toimivaa koodia, vaan myös selkeää, ylläpidettävää ja skaalautuvaa koodia. Tulet huomaamaan, kuinka helppoa on päätyä tilanteeseen, jossa et vain tiedä, että mihin kohtaan ohjelmaasi voisit järkevästi lisätä koodia. Ohjelmistosuunnittelun periaatteet auttavat sinua organisoimaan koodisi loogisesti ja tekemään siitä helpommin ymmärrettävää muille, ja myös tulevaisuuden sinulle.
- Kirjastoja ja frameworkeja: Ohjelmointityössä ei lähes koskaan tehdään kaikkea alusta asti. Eri ohjelmointikielille on valtavasti valmiita kirjastoja ja frameworkeja, jotka tarjoavat valmiita ratkaisuja yleisiin ongelmiin (esim. käyttöliittymien luomiseen, verkkoyhteyksien hallintaan tai pelinkehitykseen). Niiden oppiminen nopeuttaa kehitystä huomattavasti. Pelikehityksessä Unity on erinomainen esimerkki tällaisesta frameworkista, joka käyttää C#-kieltä.
- Työkalut ja prosessit: Ohjelmointiin liittyy paljon muutakin kuin vain koodin kirjoittaminen. Versionhallintajärjestelmät (kuten Git), testausmenetelmät ja erilaiset kehitystyökalut ovat olennainen osa nykyaikaisen ohjelmoijan työkalupakkia.
Ohjelmointikielen oppiminen on loistava alku, mutta se on vasta ensimmäinen askel pitkällä matkalla ohjelmoinnin maailmaan. Näissä oppaissa pyrimme antamaan sinulle laajemman näkemyksen siitä, mitä ohjelmointi todella on ja miten voit kehittää taitojasi monipuolisesti pelikehityksen näkökulmasta.
Tietokoneen toiminta
Ymmärtämällä tietokoneen toimintaa edes alkeellisella tasolla, ohjelmoija voi kirjoittaa tehokkaampaa, responsiivisempaa ja luotettavampaa koodia. Tietämys siitä, miten data liikkuu komponenttien välillä ja mitkä ovat niiden rajoitteet, auttaa välttämään suorituskykyyn liittyviä ongelmia ja tekemään parempia suunnitteluratkaisuja ohjelman arkkitehtuurissa.
Suoritin (CPU): On tietokoneen aivot. Sen tarkoitus on suorittaa kaikki ohjelmien antamat käskyt (konekieli). Se noutaa käskyjä muistista, tulkitsee ne ja suorittaa tarvittavat laskutoimitukset tai muut operaatiot. Ohjelmoijan on tärkeää ymmärtää suorittimen perustoimintaa, jotta voi kirjoittaa koodia, joka on suorituskykyistä ja hyödyntää suorittimen resursseja tehokkaasti. Esimerkiksi rinnakkaisuuden (tästä lisää myöhemmin) hyödyntäminen liittyy suoraan suorittimen ytimien toimintaan.
Muisti (RAM): Toimii väliaikaisena työmuistina. Sen tarkoitus on säilyttää ne tiedot ja ohjelmakoodin osat, joita suoritin tarvitsee välittömästi suorittaakseen tehtäviä. Muisti on paljon nopeampaa kuin levy, joten suoritin voi nopeasti hakea sieltä tarvitsemansa tiedot. Kun ohjelma käynnistetään, se ladataan levyltä muistiin, josta suoritin voi sitä sitten ajaa. Ohjelmoijan on kriittistä ymmärtää muistin toimintaa muistinhallinnan, muuttujien elinkaaren ja ohjelman suoritusnopeuden optimoinnin kannalta. Liian suuri muistinkulutus voi hidastaa ohjelmaa ja jopa kaataa sen.
Levy (kiintolevy, SSD): Toimii pitkäaikaisena tallennustilana. Sen tarkoitus on säilyttää kaikki tiedot, kuten käyttöjärjestelmä, ohjelmat ja omat tiedostosi, silloinkin kun tietokone ei ole päällä. Ohjelmoijan on hyvä ymmärtää levyn toimintaa tiedostojen luku- ja kirjoitusoperaatioiden tehokkuuden kannalta. Hitaat levyoperaatiot voivat olla pullonkaula ohjelman suorituskyvylle. Huomaa myös, että ”levy” voi tarkoittaa mitä tahansa disketistä verkkolevyyn; ”levy” ei välttämättä ole omalla koneellasi, tai edes samalla mantereella kuin koneesi.
Muistin käyttö muuttujien avulla
Muuttujat (variable) ovat ohjelmoinnin perusta. Ne ovat muistipaikkoja, joihin tietokone tallentaa ja josta se hakee tietoa ohjelman suorituksen aikana. Ohjelmointi on pohjimmiltaan muuttujien hallintaa. Ne ovat täysin välttämättömiä, koska ne pitävät kirjaa kaikesta: pelaajan elämäpisteistä, vihollisen sijainnista, esineiden määrästä inventaariossa, onko ovi auki vai kiinni, ja niin edelleen. Ilman muuttujia peli ei muistaisi mitään, ja se olisi vain staattinen kuva, ei interaktiivinen kokemus.
Mikä muuttuja oikeastaan on?
Voit ajatella muuttujaa nimettömänä lokerona tietokoneen muistissa. Kun luot muuttujan, kerrot tietokoneelle, että tarvitset paikan tietynlaiselle tiedolle. Annat tälle lokerolle nimen, jotta voit myöhemmin viitata siihen ja joko tallentaa siihen uutta tietoa tai lukea sieltä jo tallennettua tietoa.
Esimerkiksi, jos mietitään peliä kuten Minecraft, siinä on jatkuvasti tallennettava ja päivitettävä tietoa pelaajan tilasta.
// Luodaan muuttuja pitämään kirjaa kivimäärästä ja alustetaan sen arvoksi 0. int pelaajanKivimaara = 0; // Pelaaja louhii kiven. Päivitetään kivimäärää. pelaajanKivimaara = pelaajanKivimaara + 1; // Nyt kivimäärä on 1. // Pelaaja louhii toisen kiven. pelaajanKivimaara = pelaajanKivimaara + 1; // Nyt kivimäärä on 2. // Pelaaja rakentaa jotain, kuluttaa 2 kiveä. pelaajanKivimaara = pelaajanKivimaara - 2; // Nyt kivimäärä on 0.
Muuttujia käytetään operaattoreiden avulla. Esimerkiksi +
-operaattorilla voidaan summata muuttujia yhteen, ja =
-operaattorilla sijoitetaan arvo muuttujan sisälle.
Yllä olevassa esimerkissä pelaajanKivimaara
on muuttuja. Se on nimetty lokero muistissa. Muuttujan arvo voi muuttua ohjelman suorituksen aikana, mikä tekee siitä nimensä mukaisesti ”muuttuvan”.
Esimerkiksi rivillä pelaajanKivimaara = pelaajanKivimaara + 1;
muuttujaan sijoitetaan arvo, joka on tuon muuttujan muistissa oleva arvo kasvatettuna yhdellä.
Tietotyypit
C# on vahvasti tyypitetty kieli. Tämä tarkoittaa, että kun luot muuttujan C#:ssa, et voi vain antaa sille nimeä. Sinun on myös kerrottava tietokoneelle, minkä tyyppistä tietoa aiot tallentaa tähän muistipaikkaan. Tätä kutsutaan tietotyypiksi (identifier). Tietotyyppi kertoo tietokoneelle kaksi tärkeää asiaa:
- Kuinka paljon muistia varataan: Eri tyyppiset tiedot vaativat eri määrän muistia. Esimerkiksi kokonaisluku (kuten luku 5001) vie vähemmän tilaa kuin pitkä teksti (kuten ”Pelaaja1_LegendaarinenNimi”).
- Mitä operaatioita tiedolla voi tehdä: Et voi esimerkiksi laskea yhteen tekstiä ja numeroa samalla tavalla kuin kahta numeroa. Tietotyyppi kertoo tietokoneelle, miten sen pitää käsitellä kyseistä tietoa.
Tämä auttaa välttämään virheitä ja tekee koodista selkeämpää, kertoen koodin lukijalle enemmän sen aikomuksesta.
Katsotaanpa muutamia yleisimpiä tietotyyppejä, joita tulet käyttämään paljon pelinkehityksessä.
int
(Integer): Kokonaislukuja, kuten pelaajan elämäpisteet (esim. 100), vihollisen vahinko (esim. 25) tai kerättyjen kolikoiden määrä (esim. 150).
int pelaajanElama = 100; int vihollisenVahinko = 25;
float
(Floating-point number): Desimaalilukuja, eli lukuja joissa on pilkku. Käytetään yleensä tarkempiin arvoihin, kuten hahmon nopeuteen (esim. 5.5f), aseen tarkkuuteen (esim. 0.85f) tai esineiden sijaintiin pelimaailmassa. Huomaa f
-kirjain luvun perässä, joka kertoo C#:lle, että kyseessä on float-tyyppinen desimaaliluku.
float hahmonNopeus = 5.5f; float ampujanTarkkuus = 0.85f;
bool
(Boolean): Totuusarvoja, jotka voivat olla vain true
(tosi) tai false
(epätosi). Käytetään tilanteisiin, joissa on kaksi vaihtoehtoa: onko ovi auki (true
) vai kiinni (false
), onko peli käynnissä (true
) vai ohi (false
), onko pelaaja ilmassa (true
) vai maassa (false
).
bool pelaajaIlmassa = false; bool peliKaynnissa = true;
string
(String): Merkkijonoja, eli rimpsu kirjaimia, numeroita ja symboleita peräkkäin. Käytetään hahmojen nimiin (esim. ”Max Payne”), viesteihin (esim. ”Game Over!”) tai esineiden kuvauksiin. Tekstiarvot kirjoitetaan aina lainausmerkkien sisään ("
).
string pelaajanNimi = "Max Payne"; string tervetuloaViesti = "Tervetuloa seikkailuun!";
Ohjelmoinnissa et voi suoraan laittaa yhdentyyppistä dataa toisentyyppiseen muuttujaan.
Jos sinulla on vaikkapa float
-tyyppinen muuttuja luku
, jonka sisällä on 2.0f
ja haluat laittaa sen string
-tyyppiseen muuttujaan, sinun on jotenkin muutettava se oikeantyyppiseksi, eli tässä tapauksessa tekstiksi. Tästä syystä huomaat myös koodissamme kohtia, kuten Single.Parse(luku2Teksti)
, koska siinä yritetään muuttaa (parse) tekstimuuttuja luvuksi.
On totta, että useimmat kääntäjät on rakennettu automaattisesti muuntamaan lukutyypit toisiksi (esim. int-muuttujat float-muuttujiksi), mutta nämä ovat poikkeuksia sääntöön. Puhumme tästä lisää myöhemmin.
Muuttujien nimeämisestä
Kun luot muuttujan, on tärkeää antaa sille kuvaava nimi. Huono nimi, kuten a
tai temp1
, voi tehdä koodista erittäin vaikealukuista ja ymmärrettävää myöhemmin – varsinkin kun koodia on satoja tai tuhansia rivejä. Hyvä nimi, kuten pelaajanElama
tai vihollisenNopeus
, kertoo heti, mitä muuttuja sisältää. Ajattele, että joku toinen kehittäjä (tai sinä itse puolen vuoden päästä) yrittää ymmärtää koodiasi. Selkeät nimet ovat kuin opasteita, jotka auttavat löytämään perille.
// Huono: Mitä tämä tekee? float a = 10.5f; a = a + 5.0f; // Hyvä: Selkeästi ymmärrettävä, mitä muuttuja edustaa. float pelaajanLiikenopeus = 10.5f; pelaajanLiikenopeus = pelaajanLiikenopeus + 5.0f; // Nopeus kasvaa
Pelinkehityksessä tulet luomaan valtavan määrän muuttujia hallitaksesi pelin tilaa. Ymmärtämällä muuttujien tarkoituksen, tietotyyppien merkityksen ja hyvän nimeämiskäytännön periaatteet, luot jo alusta alkaen laadukkaampaa ja ylläpidettävämpää koodia. Muuttujat ovat ensimmäinen ja ehkä tärkein työkalu ohjelmoijan työkalupakissa, kun lähdetään rakentamaan interaktiivisia pelimaailmoja.
Muuttujien nimissä ei saa olla välilyöntejä tai kirjaimia ä, ö, å, koska ne aiheuttaisivat yhteensopivuus- ja kääntämisongelmia eri järjestelmissä. Ne eivät myöskään kuulu standardiin merkistöön. Muita kiellettyjä ovat erikoismerkit (paitsi alaviiva _
) ja numerot nimen alussa. Myös ohjelmointikielen varatut sanat (kuten class
tai int
) ovat kiellettyjä.
Kääntäminen
Kun kirjoitat koodia, teet sen ihmiselle ymmärrettävällä kielellä, olipa se sitten C#, Python tai jokin muu. Tietokoneen suoritin (CPU) ei kuitenkaan ymmärrä näitä korkean tason ohjelmointikieliä suoraan. Se puhuu omaa kieltään, jota kutsutaan konekieleksi. Konekielet ovat hyvin alhaisen tason ohjeita, jotka vastaavat suoraan prosessorin toimintoja. Jotta tietokone voisi suorittaa kirjoittamasi koodin, se on ensin muutettava (käännettävä) konekieleksi.
Konekielen ymmärtämisestä sellaisenaan on nykypäivänä ohjelmoijalle vain rajoitetusti hyötyä. Huomaat ehkä myös allaolevasta esimerkistä, minkä vuoksi suosimme korkeamman tason kieliä, kuten C#.
Konekieli
Tässä hauskuuden vuoksi lyhyt vertailu C#:sta, symbolisesta konekielestä (assembler) ja suorasta konekielestä (heksadesimaalimuodossa) hyvin yksinkertaiselle operaatiolle: kahden luvun yhteenlasku ja tuloksen tallentaminen. Oletetaan kuvitteellinen, hyvin yksinkertainen prosessoriarkkitehtuuri.
C#:
int a = 10; int b = 5; int c = a + b;
Tässä esimerkissä luodaan muuttujat a
ja b
ja asetetaan niihin arvot 10
ja 5
. Tämän jälkeen ne summataan yhteen muuttujaan c
.
Symbolinen konekieli (Assembler):
LOAD A, 10 ; Lataa arvo 10 rekisteriin A LOAD B, 5 ; Lataa arvo 5 rekisteriin B ADD C, A, B ; Summaa rekisterin A ja B sisältö ja tallenna tulos rekisteriin C STORE C, [2000] ; Tallenna rekisterin C sisältö muistiosoitteeseen 2000
Tässä assembler-koodissa on käytetty ihmiselle ymmärrettäviä lyhenteitä (mnemoniikka) operaatioille (LOAD, ADD, STORE) ja rekistereille (A, B, C). Myös muistiosoite on esitetty luettavassa muodossa.
Konekieli (Heksadesimaalimuodossa):
Olettaen, että yllä olevat assembler-käskyt on koodattu tietyiksi binääri- tai heksadesimaaliarvoiksi prosessorin käskykannassa, ne voisivat konekielessä näyttää esimerkiksi tältä:
01 0A 00 ; LOAD A, 10 (A=0A, 10=00) 01 0B 05 ; LOAD B, 5 (B=0B, 5=05) 02 0C 0A 0B ; ADD C, A, B (C=0C, A=0A, B=0B) 03 0C 07 D0 ; STORE C, [2000] (C=0C, muistiosoite 2000 heksana on 07D0)
Kaikki koodi, assembler mukaanlukien, muutetaan lopulta varsinaiseksi konekieleksi, jota tietokoneen suoritin ymmärtää.
Ovatko kaikki ohjelmointikielet käännettäviä?
Kaikki yleisesti käytetyt ohjelmointikielet eivät suoraan käänny konekieleksi samalla tavalla. Ohjelmointikielet voidaan karkeasti jakaa kahteen pääluokkaan sen mukaan, miten niiden koodi suoritetaan:
- Käännettävät kielet (Compiled Languages): Tällaiset kielet (kuten C#, C++, Go) käännetään erillisessä vaiheessa ennen ohjelman suorittamista kokonaan konekieleksi. Kääntäjä (compiler) lukee koko lähdekooditiedoston ja tuottaa siitä suoritettavan tiedoston (esimerkiksi .exe Windowsissa). Tätä suoritettavaa tiedostoa voidaan sitten ajaa useita kertoja ilman uutta käännöstä.
- Tulkitut kielet (Interpreted Languages): Tällaiset kielet (kuten Python, JavaScript, Ruby) eivät käy läpi erillistä täydellistä käännösvaihetta ennen suoritusta. Sen sijaan tulkki (interpreter) lukee lähdekoodia rivi riviltä ja suorittaa jokaisen rivin välittömästi. Joka kerta kun ohjelma ajetaan, tulkki käy koodin läpi uudelleen.
On myös olemassa välimuotoja ja tekniikoita, kuten tavukoodi (bytecode), jota esimerkiksi Java ja C# (.NET-ympäristössä) käyttävät. Tällöin koodi käännetään ensin välikieleksi (tavukoodiksi), joka ei ole suoraan konekieltä, mutta se on alhaisemmalla tasolla kuin alkuperäinen lähdekoodi. Sitten tämä tavukoodi suoritetaan virtuaalikoneen (virtual machine), kuten Java Virtual Machinen (JVM) tai Common Language Runtimen (CLR) (.NET:ssä), toimesta. Virtuaalikone tulkkaa tai kääntää (usein ajonaikaisesti, Just-In-Time -kääntäminen eli JIT) tavukoodin varsinaiseksi konekieleksi suoritettavaksi.
Tässä vaiheessa tärkeää on kuitenkin vain ymmärtää, että kun kirjoitat koodia, on se käännettävä, ennen kuin ohjelma voidaan ajaa. Visual Studio hoitaa kääntämisen, kun painat työkalupalkista löytyvää vihreää ”Start”-painiketta.
Kommenttien useat hyödyt
Koodin kirjoittaminen on vain osa ohjelmoijan työtä; koodin tekeminen ymmärrettäväksi on yhtä tärkeää. Tässä sinua auttavat kommentit. Kommentit ovat tekstipätkiä koodissa, jotka ohjelmoija lisää selittämään, mitä koodi tekee, miksi se tekee niin, tai mitä sillä on tarkoitus tehdä. Kääntäjä jättää kommentit täysin huomiotta, eli ne eivät vaikuta ohjelman toimintaan millään tavalla. Ne ovat siis puhtaasti ihmisten, eivät tietokoneiden, luettavaksi tarkoitettuja.
Mitä kannattaa kommentoida?
Hyvin kommentoitu koodi on kullanarvoista, erityisesti kun teet yhteistyötä muiden kanssa tai palaat omaan koodiisi kuukausien päästä. Tässä muutamia asioita, joita kannattaa kommentoida:
Kompleksiset algoritmit tai logiikka: Jos olet kirjoittanut monimutkaisen laskutoimituksen tai toiminnon, joka ei ole itsestään selvä, selitä sen tarkoitus ja miten se toimii.
// Kaava: F = C * 9/5 + 32. Huomaa int-jakolasku 9/5, joka antaa 1. // Tässä tapauksessa käytämme desimaalilukuja varmistaaksemme tarkkuuden. float fahrenheit = celsius * 9f / 5f + 32f;
Syyt tiettyihin valintoihin: Jos teit tietyn teknisen ratkaisun, joka poikkeaa odotetusta tai tavanomaisesta, selitä miksi. Tämä auttaa välttämään turhia kysymyksiä, turhaa työtä, ja ymmärtämään koodin taustaa.
// Käytetään List<T> sijaan Arrayta, koska koko on kiinteä ja tarvitaan nopeita indeksihakuja. Vihollinen[] vihollisetKentalla = new Vihollinen[10];
Funktioiden ja luokkien käyttötarkoitus: Jokaisen funktion tai luokan alussa on hyvä olla lyhyt selitys sen yleisestä tarkoituksesta. Tämä toimii kuin hakemisto koodissasi. Lisäksi tällaisten XML-tagien avulla pystytään generoimaan dokumentaatio koodille.
/// <summary> /// Liikuttaa pelaajahahmoa annetun suunnan ja nopeuden mukaisesti. /// Varmistaa, ettei pelaaja poistu pelialueen rajojen ulkopuolelle. /// </summary> /// <param name="suunta">Suunta johon pelaaja liikkuu (esim. Vector3.forward).</param> /// <param name="nopeus">Pelaajan liikenopeus.</param> void LiikutaPelaajaa(Vector3 suunta, float nopeus) { // ... (koodi liikuttamiseen) }
Varoitukset ja ”TODO”-merkinnät: Jos jossakin kohdassa on jotain keskeneräistä, väliaikaista tai korjattavaa, kommentti on hyvä paikka muistuttaa siitä. Visual Studio pitää myös automaattisesti kirjaa kommenttien TODO-merkinnöistä (View -> Task List).
// TODO: Tämä väliaikainen ratkaisu tulisi korvata oikealla tietokantakyselyllä pelin valmistuessa. string pelaajanNimi = "Testipelaaja1";
Miksi kommentit ovat hyviä?
- Parantavat luettavuutta: Ne tekevät koodista helpommin ymmärrettävää ihmisille.
- Nopeuttavat kehitystä: Kun koodin tarkoitus on selvä, virheiden paikantaminen ja uusien ominaisuuksien lisääminen on nopeampaa.
- Helpottaa yhteistyötä: Kun useampi ihminen työskentelee saman koodin parissa, kommentit varmistavat, että kaikki ymmärtävät, mitä koodi tekee ja miksi.
- Toimivat dokumentaationa: Kommentit ovat usein ensimmäinen paikka, josta uudet kehittäjät etsivät tietoa koodin toiminnasta.
Milloin kommentit ovat huonoja?
Itsestäänselvän kommentointi: Älä kommentoi koodia, joka on jo itsestään selvää. Se lisää vain turhaa ”melua” ja tekee koodista raskaamman lukea. Alla oleva kommentti on turha, sillä koodi on täysin selkeä ilman sitäkin.
int elama = 100; // Alustetaan elämä 100:ksi. (Turha kommentti)
Vanhentuneet kommentit: Jos koodi muuttuu, mutta kommentti ei päivity, vanhentunut kommentti voi johtaa jopa harhaan ja aiheuttaa sekaannusta. Aina kun muutat koodia, tarkista myös sitä koskevat kommentit.
Roska: Älä käytä kommentteja muistiinpanopaikkana, joka on tarkoitettu vain sinulle tai on täynnä turhaa jargonia. Kommenttien on oltava relevantteja ja hyödyllisiä.
Erimittaiset kommentit
C#:ssa on kaksi perustapaa kommentoida koodia:
Yhden rivin kommentti (//
):
Tämä kommentoi yhden rivin alusta alkaen, tai kohdasta, josta //
alkaa.
// Tämä on yksi rivi kommenttia. int pisteet = 0; // Tässä kommentoidaan rivin loppuosa.
Monen rivin kommentti (/* ... */
):
Tämä kommentoi kaikki merkkien /*
ja */
välissä olevat rivit.
/* Tämä on monen rivin kommentti. Kaikki tässä olevat rivit ovat vain kehittäjien luettavaksi. */ void Hyokkaa() { // Tämä koodi on osa funktiota. // ... }
Harjoituksia
Harjoitus tekee mestarin!
Kokeile, pystytkö tekemään SuomiGameHUBin GitHubissa olevat viisi muuttujatehtävää.
Metodit (Funktiot) – uudelleenkäyttöä
Ohjelmoinnissa kohtaat jatkuvasti tilanteita, joissa samaa tai hyvin samankaltaista koodia tarvitaan useassa eri paikassa.
Esimerkiksi, jos Minecraftissa heität leivän laavaan, siitä seuraa rimpsu asioita: näytetään palamisanimaatio, laitetaan leipä kellumaan pinnalla tai mahdollisesti valumaan pitkin laavaa, sekä lopulta tuhotaan leipä oikealla hetkellä.
Voisit tietysti kirjoittaa saman koodirimpsun uudestaan ja uudestaan villalle, kepeille, lasille ja joka ikiselle esineelle, mutta se olisi tehotonta ja aiheuttaisi helposti virheitä. Ja mitä sitten kun päätätkin, että näyttäisi paremmalta, jos laavassa olevat esineet muuttuisivat hiljalleen mustiksi? Sinun pitäisi lisätä sama animaatiokoodi kaikille esineille erikseen. Puuduttavaa!
Tähän ongelmaan ratkaisun tarjoavat funktiot, joita C#-kielessä kutsutaan usein metodeiksi.
Mikä metodi on?
Metodi on koodilohko, joka suorittaa spesifin tehtävän. Se on kuin pieni, nimetty ”paketti” ohjeita, jonka voit kutsua suoritettavaksi milloin tahansa ohjelmassasi.
Kun ohjelma kohtaa metodin kutsun, se hyppää metodin koodilohkon sisään, suorittaa siellä olevat ohjeet ja palaa sitten takaisin siihen kohtaan, mistä kutsu tuli, jatkamaan ohjelman suoritusta.
Voit ajatella metodia kuin pelin sisäistä ”toimintoa” tai ”kykyä”. Esimerkiksi Fortnitessa on toimintoja kuten ”kerää resurssi” tai ”avaa arkku”. Nämä toiminnot sisältävät tietyn sarjan vaiheita (näytä avausanimaatio, päivitä teksti käyttöliittymään, aseta esineet inventaarioon). Samalla tavalla metodi paketoi nämä vaiheet yhden nimen alle.
Miksi metodeja käytetään?
Kun olet kirjoittanut metodin, voit kutsua sitä niin monta kertaa kuin haluat ohjelmasi eri kohdista. Sinun ei tarvitse kirjoittaa samaa koodia moneen kertaan. Tämä tekee koodista lyhyempää, siistimpää ja vähentää virheiden riskiä. Jos löydät virheen metodista, sinun tarvitsee korjata se vain yhdessä paikassa, ei kymmenessä.
On toki tilanteita, joissa sinulla on kolmessa paikkaa samanlaista koodia ja niiden yhdistäminen yhdeksi metodiksi vaatii selvitystyötä ja luovia ratkaisuja. Tällainen refaktorointi kuuluu kuitenkin ohjelmoijan työkalupakkiin ja jokainen hyvä koodari tekee sitä urallaan. Yhdistelty metodi voi säästää todella paljon työtä, varsinkin jos sitä halutaan käyttää myöhemmin uusissakin paikoissa. Harkitse, milloin työmäärä on sen arvoista.
Metodit tekevät myös koodista helpommin luettavaa ja ymmärrettävää. Monimutkainen ohjelma voidaan pilkkoa pienempiin, hallittavampiin osiin, joista jokainen hoitaa yhden tietyn tehtävän. Tämä parantaa koodin selkeyttä ja helpottaa sen ylläpitoa. Sen sijaan, että sinulla olisi yksi valtava koodilohko, sinulla on pienempiä, nimikoituja osioita, jotka kertovat, mitä ne tekevät. Jos sitten tahdot muuttaa jotain osaa koodista, löydät sen helposti metodin nimellä: ”Ahaa, tuossa oli se kohta, missä lisätään esineet inventaarioon!”
Otetaanpa metodista esimerkkinä C#-ohjelmiesi perusta, eli Main()
:
static void Main(string[] args) { // Tässä on koodisi pääasiallinen suorituspiste // Console.WriteLine() ja Console.ReadLine() ovat esimerkkejä metodeista Console.WriteLine("Tervetuloa!"); Console.ReadLine(); }
static void Main(string[] args)
: Tämä on C#-ohjelman päämetodi, josta ohjelman suoritus alkaa. Kaikki ohjelmasi käynnistyksen yhteydessä suoritettava koodi sijoitetaan yleensä tänne tai kutsutaan täältä. Tässä vaiheessa emme syvennystatic
,void
taistring[] args
-termeihin; ne ovat tärkeitä, mutta eivät olennaisia metodin peruskäsitteen ymmärtämiseksi.Console.WriteLine("Tervetuloa!");
: Tämä on metodikutsu.WriteLine
on metodi, joka kuuluuConsole
-luokkaan. Se ottaa sisäänsä tekstin (tässä tapauksessa ”Tervetuloa!”) ja metodin sisällä oleva koodi pitää huolen, että se tulostuu konsoliin.Console.ReadLine();
: Tämä on toinen metodikutsu. Se odottaa, että käyttäjä syöttää jotain ja painaa Enteriä.
Huomaat varmasti, että emme tiedä, tai välttämättä ole edes kovin kiinnostuneita miten WriteLine
tai ReadLine
tekevät tehtävänsä, ellei ilmene ongelmia vaikkapa suorituskyvyn kanssa. Haluamme vain, että lopputulos on se, mitä luvattiin, ja ohjelma pyörii sutjakasti.
Esimerkki oman metodin tekemisestä pelissä
Kuvittele peliä kuten Hades, jossa pelaaja voi kerätä erilaisia voimia ja parannuksia taistelun aikana. Yksi tällainen toiminto voisi olla pelaajan elämäpisteiden parantaminen. Jos haluamme parantaa pelaajaa usein, tai eri lähteistä, voimme tehdä siitä oman metodin.
Tässä on yksinkertainen esimerkki metodista, joka parantaa pelaajaa tietyllä määrällä elämäpisteitä. (Huomaa, että avainsana static
ei ole normaalisti vaatimus metodeille. Pystymme vain sen avulla yksinkertaistamaan esimerkin tässä vaiheessa pienempään koodimäärään.)
Voit korvata laskinohjelmassa Program-lohkon tällä, tai luoda esimerkille uuden projektin.
internal class Program { // Muuttuja pitämään kirjaa pelaajan elämäpisteistä static int pelaajanElama = 50; // Metodi pelaajan parantamiseen static void ParannaPelaajaa() { int parannusMaara = 20; // Kuinka paljon elämää parannetaan pelaajanElama = pelaajanElama + parannusMaara; // Lisätään parannusmäärä nykyiseen elämään Console.WriteLine("Pelaajaa parannettiin! Uusi elämä: " + pelaajanElama); } // Päämetodi, josta ohjelman suoritus alkaa static void Main(string[] args) { Console.WriteLine("Elämä alussa: " + pelaajanElama); // Kutsutaan toimintoa "paranna" ensimmäisen kerran ParannaPelaajaa(); // Kutsutaan toimintoa "paranna" toisen kerran ParannaPelaajaa(); Console.ReadLine(); // Pysäyttää konsolin, jotta näet tulosteen } }
Metodi on siis nimetty koodilohko, joka suorittaa tietyn tehtävän ja jota voit kutsua suoritettavaksi useita kertoja.
Ajattele Console.WriteLine()
-metodia. Et tiedä, miten se toimii sisäisesti, mutta tiedät, että se tulostaa tekstin ruudulle. Tämä on olio-ohjelmoinnissa segregaation tai erottelun periaate. Samoin oma ParannaPelaajaa
-metodisi olisi vastuussa pelaajan elämän lisäämisestä ja kuka tahansa voi käyttää sitä, ilman että heidän täytyy tietää miten pelaaja parannetaan.
Tulevaisuudessa syvennymme metodien käyttöön tarkemmin, kun olemme ensin käsitelleet luokkia ja muita C#-kielen peruskonsepteja.
Ehtolauseet – pelin päätöksenteko
Pelimaailmat ovat täynnä tilanteita, joissa peli reagoi pelaajan toimiin tai pelin tilaan. Mitä tapahtuu, jos pelaaja kerää tarpeeksi kolikoita? Pitääkö vihollisen hyökätä, jos pelaaja on sen kantaman sisällä? Onko pelaajan elämä nollassa, jolloin peli päättyy? Näiden kaltaiset tilanteet vaativat, että ohjelma tekee päätöksiä. Tähän tarkoitukseen käytetään ehtolauseita (conditions).
Ehtolauseet antavat ohjelmalle kyvyn valita eri toimintareittien välillä sen mukaan, onko jokin tietty ehto tosi vai epätosi. Ne ovat ohjelmoinnin perusrakennuspalikoita, jotka mahdollistavat interaktiivisen pelilogiikan. Pelin ”älykkyys” ja reagointikyky rakentuvat näiden ehtojen varaan.
Miten ehtolauseet toimivat?
C#:ssa yleisin ehtolause on if
-lause.
if (ehto) { // Koodi, joka suoritetaan, jos ehto on tosi }
Ehtolause alkaa avainsanalla if
, jonka jälkeen sulkujen sisään kirjoitetaan se ehto, joka tarkistetaan. Kaarisulkeiden { ... }
sisään puolestaan kirjoitetaan koodi, joka suoritetaan vain, jos ehto on tosi. Jos ehto on epätosi, tämä koodilohko ohitetaan kokonaan, ja ohjelman suoritus jatkuu kaarisulkeiden jälkeen.
Joskus haluat tehdä jotain tietyllä ehdolla, ja jotain muuta vain, jos ehto ei täyty. Tähän käytetään else
-lohkoa:
if (ehto) { // Koodi, joka suoritetaan, jos ehto on tosi } else { // Koodi, joka suoritetaan vain, jos ehto on epätosi }
Lisäksi, jos sinulla on useita eri ehtoja, voit ketjuttaa niitä käyttämällä else if
niin monta kertaa kuin haluat:
if (ensimmainenEhto) { // Suoritetaan, jos ensimmäinen ehto on tosi } else if (toinenEhto) { // Suoritetaan, jos ensimmäinen ehto oli epätosi, mutta toinen ehto on tosi } else { // Suoritetaan, jos yksikään ehdoista ei ollut tosi }
else if
voi olla hieman suorituskykyisempi kuin jos vain käyttäisit monta if
-lausetta peräkkäin. Prosessori tarkistaa jokaisen if
-lauseen poikkeuksetta, mutta else if
-lauseen vain jos sitä edeltävä if
-lause ei ollut totta.
Loogiset ehdot
Sulkujen sisään sijoitetaan ehtona jokin vertailu (esim. x > 10
tai pelaajanElama == 0
). C# tukee matematiikan loogisia ehtoja:
- Pienempi kuin:
a < b
- Pienempi tai yhtä suuri kuin:
a <= b
- Suurempi kuin:
a > b
- Suurempi tai yhtä suuri kuin:
a >= b
- Yhtä suuri kuin
a == b
- Ei yhtä suuri kuin:
a != b
Muistat ehkä että kun puhuimme muuttujista, että bool
-tietotyyppi on aina joko true
tai false
. Loogiset ehdot palauttavat aina jomman kumman näistä boolean arvoista. Näiden ehtojen avulla voit suorittaa erilaisia toimintoja eri päätöksille.
Huomaa, että yhtäsuuruus tarkistetaan kahdella yhtäsuuruusmerkillä: ==
. Yksinäinen =
-merkki toimii aina sijoitusoperaattorina.
Boolean-arvojen kanssa ”yhtä suuri kuin” ja ”ei yhtä suuri kuin” voidaan myös lyhentää:
if (valoPunainen) { // Auto on pysähdyksissä. } else if (!valoVihrea) { // Valo ei ollut punainen, mutta se ei myöskään ole vihreä. // Sen täytyy siis olla keltainen. } else { // Auto voi liikkua! }
Yllä tarkastellaan kahta bool
-muuttujaa, valoPunainen
ja valoVihrea
.
Ensimmäisessä tarkistuksessa katsotaan, että onko (valoPunainen == true)
, mutta kääntäjä osaa ymmärtää pelkästä bool-muuttujan nimestä (valoPunainen)
, että halutaan tarkistaa, onko se true. Vastaavasti (valoVihrea != true)
voidaan lyhentää muotoon (!valoVihrea)
.
Esimerkki: Pelaajan elämäpisteet ja pelin loppuminen
Kuvittele peliä kuten Apex Legends tai Call of Duty, jossa pelaajalla on elämäpisteitä. Kun elämäpisteet tippuvat nollaan tai sen alle, pelaaja kuolee ja peli voi päättyä tai hän putoaa taistelusta.
internal class Program { // Pelaajan elämäpisteet. Aloitetaan 100 pisteestä. static int pelaajanElama = 100; // Pelaajan vahingoittamisen metodi, jota kutsutaan muualta static void OtaVahinkoa() { int otettuVahinko = 60; pelaajanElama = pelaajanElama - otettuVahinko; Console.WriteLine("Pelaaja otti " + otettuVahinko + " vahinkoa. Elämää jäljellä: " + pelaajanElama); // Tarkistetaan, onko pelaaja kuollut if (pelaajanElama <= 0) { Console.WriteLine("Pelaajan elämä loppui! Peli päättyi."); } else { Console.WriteLine("Pelaaja on yhä elossa. Jatka taistelua!"); } } // Päämetodi, josta ohjelman suoritus alkaa static void Main(string[] args) { Console.WriteLine("Pelaajan elämä alussa: " + pelaajanElama); // Vihollinen tekee vahinkoa OtaVahinkoa(); // Otetaan lisää vahinkoa ja kokeillaan uudelleen OtaVahinkoa(); Console.ReadLine(); // Pysäyttää konsolin, jotta näet tulosteen } }
- Aluksi
pelaajanElama
on 100. - Pelaaja ottaa 60 vahinkoa. Elämää jää 40.
- Ensimmäinen
if
-lause tarkistaa, onkopelaajanElama <= 0
. Koska 40 ei ole pienempi tai yhtä suuri kuin 0, ehto on epätosi. Siksielse
-lohko suoritetaan, ja näemme ”Pelaaja on yhä elossa. Jatka taistelua!”. - Pelaaja ottaa vielä 60 vahinkoa. Elämää jää -20.
- Toinen
if
-lause tarkistaa uudelleen, onkopelaajanElama <= 0
. Koska -20 on pienempi tai yhtä suuri kuin 0, ehto on nyt tosi. Siksiif
-lohko suoritetaan, ja näemme ”Pelaajan elämä loppui! Peli päättyi.”.
Tämä yksinkertainen esimerkki näyttää, miten ehtolauseilla ohjelma voi ”päätellä” ja reagoida pelin tilaan. Niiden avulla voit luoda monimutkaisia pelilogiikoita, joissa hahmot reagoivat ympäristöön, tehtävät etenevät ja peli muuttuu pelaajan tekemien valintojen mukaisesti.
Harjoituksia
Harjoitus tekee koodarin!
Kokeile, pystytkö tekemään SuomiGameHUBin GitHubissa olevat viisi ehtolausetehtävää.
Silmukat – toistuvat toiminnot pelissä
Ohjelmoinnissa ja pelikehityksessä tulee jatkuvasti eteen tilanteita, joissa jokin toiminto pitää suorittaa useita kertoja. Ajattele peliä kuten Hearthstone, jossa pelaajalle jaetaan alussa neljä korttia pakasta. Voit tietysti kirjoittaa saman koodin neljä kertaa peräkkäin kierroksen alkuun. Mutta kuvittele hieman samankaltainen peli, jossa pitäisikin jakaa 100 korttia? Tällöin koodi paisuisi valtavaksi, ja sen ylläpito olisi mahdotonta.
Tähän ongelmaan ratkaisun tarjoavat silmukat (englanniksi loops). Silmukka on koodilohko, joka suoritetaan joko tietty määrä kertoja, tai toistuvasti niin kauan kuin jokin tietty ehto on voimassa. Ne ovat korvaamaton työkalu, kun haluat automatisoida peräkkäin toistuvia tehtäviä.
Miksi silmukoita käytetään?
Silmukoita käytetään, koska ne tekevät koodista:
- Tehokkaampaa: Sen sijaan, että kirjoittaisit saman koodin useita kertoja, kirjoitat sen vain kerran ja annat silmukan toistaa sen.
- Ylläpidettävämpää: Jos sinun täytyy muuttaa toistuvan toiminnon logiikkaa, teet muutoksen vain yhdessä paikassa – silmukan sisällä.
- Joustavampaa: Voit helposti muuttaa, kuinka monta kertaa jokin toiminto suoritetaan muuttamalla vain yhtä numeroa tai ehtoa.
Yleisimmät silmukat C#:ssa
C#:ssa on useita erilaisia silmukoita, joista kaksi yleisintä ja tärkeintä ovat for
-silmukka ja while
-silmukka.
for
: kun tiedät, montako kertaa toistetaan
for
-silmukka on täydellinen silloin, kun tiedät tarkalleen, kuinka monta kertaa haluat toistaa koodilohkon. Sen rakenne sisältää kolme osaa:
for (aloitus; ehto; askel) { // Koodi, joka suoritetaan jokaisella toistolla }
aloitus
: Suoritetaan vain kerran silmukan alussa. Tässä luodaan yleensä laskurimuuttuja (esim.int i = 0;
).ehto
: Tarkistetaan jokaisen toiston alussa. Jos ehto on tosi, silmukan koodi suoritetaan. Jos ehto on epätosi, silmukka päättyy.askel
: Suoritetaan jokaisen toiston jälkeen. Tässä päivitetään yleensä laskuria (esim.i++
, joka tarkoittaai = i + 1;
).
Esimerkki: Inventaarion esineiden päivittäminen
Kuvittele peliä kuten Skyrim tai Baldur’s Gate 3, joissa pelaajan inventaariossa voi olla suuri määrä esineitä. Voi olla tarpeen käydä läpi kaikki esineet esimerkiksi päivittääkseen niiden määrän, laskiessaan kokonaispainon tai lisätäkseen niihin jonkin vaikutuksen.
static void Main(string[] args) { // Oletetaan, että pelaajan inventaariossa on 7 esinettä. int esineidenMaara = 7; Console.WriteLine("Tarkistetaan inventaarion esineitä..."); // Käydään läpi jokainen esine inventaariossa for (int i = 0; i < esineidenMaara; i++) { // Tässä voitaisiin esimerkiksi päivittää esineen tila, // laskea sen paino, tai tarkistaa, onko se käytettävissä. Console.WriteLine("Käsitellään esine numero: " + (i + 1)); // Kuvittele tässä kohtaa koodia, joka esim. tarkistaa // esineen kestävyyden tai päivittää sen visuaalisen esityksen. } Console.WriteLine("Kaikki inventaarion esineet käsitelty."); Console.ReadLine(); }
int esineidenMaaraInventaariossa = 7;
: Määritellään muuttuja, joka kertoo inventaariossa olevien esineiden määrän.for (int i = 0; i < esineidenMaaraInventaariossa; i++)
:int i = 0;
: Luodaan laskurimuuttujai
ja asetetaan sen arvoksi 0.i < esineidenMaaraInventaariossa;
: Ehto tarkistetaan. Niin kauan kuini
on pienempi kuinesineidenMaaraInventaariossa
(eli 7), silmukka jatkuu.i++
: Joka toiston jälkeeni
kasvaa yhdellä.
- Silmukan sisällä koodi suoritetaan 7 kertaa. Jokaisella kierroksella tulostuu viesti, joka osoittaa käsiteltävän esineen numeron. Tässä kohtaa voisi olla monimutkaisempaa logiikkaa, joka käsittelisi oikeita esineolioita.
- Kun
i
saavuttaa arvon 7, ehto7 < 7
muuttuu epätodeksi, ja silmukka päättyy.
while
: toistetaan kunnes ehto on tosi
while
-silmukka on hyödyllinen silloin, kun haluat toistaa koodia niin kauan kuin jokin ehto on voimassa, etkä välttämättä tiedä etukäteen, kuinka monta toistoa tarvitaan.
while (ehto) { // Koodi, joka suoritetaan niin kauan kuin ehto on tosi }
ehto
: Tarkistetaan jokaisen toiston alussa. Jos ehto on tosi, silmukan koodi suoritetaan. Jos ehto on epätosi, silmukka päättyy.
Esimerkki: Käyttäjän syötteen validointi dialogissa
Kuvittele tekstipohjaista seikkailupeliä, kuten Disco Elysium tai vanhemmat Fallout-pelit, joissa pelaaja tekee valintoja dialogissa. Peli voi vaatia pelaajaa syöttämään numeron 1, 2 tai 3 valitakseen vaihtoehdon. while
-silmukkaa voidaan käyttää varmistamaan, että pelaaja syöttää kelvollisen valinnan ennen kuin peli jatkuu.
static void Main(string[] args) { Console.WriteLine("Olet risteyksessä. Mihin suuntaan haluat mennä?"); Console.WriteLine("1. Idän puoleinen polku"); Console.WriteLine("2. Lännen puoleinen polku"); Console.WriteLine("3. Palaa takaisin kylään"); int pelaajanValinta = 0; bool valintaKelvollinen = false; // Kysy pelaajalta valintaa niin kauan kuin valinta ei ole kelvollinen while (!valintaKelvollinen) { Console.Write("Syötä valintasi (1-3): "); string syoteTekstina = Console.ReadLine(); // Luetaan käyttäjän syöte tekstinä // int.TryParse yrittää muuntaa tekstin numeroksi turvallisesti. // Jos muunnos onnistuu, 'pelaajanValinta' saa arvon ja ehto on tosi. if (int.TryParse(syoteTekstina, out pelaajanValinta)) { // Tarkistetaan, onko numero sallittujen rajojen sisällä (1, 2 tai 3) if (pelaajanValinta >= 1 && pelaajanValinta <= 3) { valintaKelvollinen = true; // Valinta on kelvollinen, silmukka voi päättyä } else { Console.WriteLine("Virheellinen valinta. Syötä numero 1, 2 tai 3."); } } else { Console.WriteLine("Virheellinen syöte. Syötä numero, älä tekstiä."); } } Console.WriteLine("Valitsit vaihtoehdon numero: " + pelaajanValinta); // Tässä kohtaa peli jatkuisi pelaajan valinnan mukaisesti if (pelaajanValinta == 1) { Console.WriteLine("Lähdet idän puoleista polkua pitkin..."); } else if (pelaajanValinta == 2) { Console.WriteLine("Suuntaat lännen puoleista polkua pitkin..."); } else if (pelaajanValinta == 3) { Console.WriteLine("Palaat takaisin kylään..."); } Console.ReadLine(); }
- Ohjelma tulostaa pelaajalle dialogivaihtoehdot.
pelaajanValinta
alustetaan nollaksi javalintaKelvollinen
epätodeksi.while (!valintaKelvollinen)
: Silmukka alkaa ja jatkuu niin kauan kuinvalintaKelvollinen
onfalse
.- Silmukan sisällä ohjelma pyytää pelaajalta syötettä.
int.TryParse(syoteTekstina, out pelaajanValinta)
: Tämä on tärkeä metodi. Se yrittää muuntaasyoteTekstina
-muuttujan sisällön kokonaisluvuksi. Jos muunnos onnistuu, se tallentaa luvunpelaajanValinta
-muuttujaan ja palauttaatrue
. Jos muunnos epäonnistuu (esim. käyttäjä syöttää ”abc”), se palauttaafalse
.if
-lause tarkistaa ensin, palauttikoint.TryParse()
arvontrue
, eli oliko syöte numero.- Jos se oli numero, toinen
if
-lause tarkistaa, onko numero 1, 2 tai 3.- Jos numero on sallittu,
valintaKelvollinen
asetetaantrue
:ksi, jonka seurauksenawhile
-silmukan ehto (!valintaKelvollinen
) muuttuu epätodeksi, ja silmukka päättyy. - Jos numero ei ole sallittu (esim. 5), ohjelma tulostaa virheen ja silmukka jatkuu, koska
valintaKelvollinen
on yhäfalse
.
- Jos numero on sallittu,
- Jos syöte ei ollut numero, ohjelma tulostaa virheen ja silmukka jatkuu.
- Jos se oli numero, toinen
- Kun pelaaja on syöttänyt kelvollisen numeron (1, 2 tai 3), silmukka päättyy, ja ohjelma tulostaa pelaajan valinnan mukaisen viestin.
Tämä esimerkki näyttää, miten while
-silmukkaa käytetään tehokkaasti toistamaan toimintoa (syötteen kysymistä) niin kauan kuin tietty ehto (kelvollinen syöte) ei ole täyttynyt. Tämä on hyvin yleinen ja käytännöllinen käyttötapaus peleissä ja ohjelmoinnissa yleensä.
Varoitus: Ääretön silmukka!
On tärkeää varmistaa, että silmukan ehto muuttuu jossain vaiheessa epätodeksi. Jos se pysyy aina totena, saat aikaan äärettömän silmukan (infinite loop), joka jumittaa ohjelman. Esimerkiksi, jos pelaajanElama
ei koskaan laskisi nollaan tai sen alle while
-esimerkissä, ohjelma jäisi jumiin ikuisesti.
Silmukat ovat tehokkaita työkaluja, jotka mahdollistavat dynaamiset ja toistuvat toiminnot peleissä. Niiden avulla voit luoda useita vihollisia, käsitellä inventaarioita, päivittää pelitilaa jatkuvasti tai animoida objekteja. Ne ovat olennainen osa lähes jokaista peliä.
Harjoituksia
Harjoitus tekee mestaajan!
Kokeile, pystytkö tekemään SuomiGameHUBin GitHubissa olevat viisi loop-tehtävää.
Tekoäly avuksi – Muuttaako LLM ohjelmoijan työtä?
Viime aikoina olet ehkä kuullut paljon suurista kielimalleista (Large Language Model, eli LLM), kuten ChatGPT:stä, Grokista, Geministä, sekä työkaluista kuten Copilotista, ja niiden kyvystä tuottaa koodia. On luonnollista miettiä, uhkaako tämä kehitys ohjelmoijan ammattia vai onko siitä enemmänkin apua. Tässä kohtaa on tärkeää ymmärtää LLM:ien vahvuudet ja heikkoudet ohjelmoinnin kontekstissa.
On totta, että LLM:t voivat nykyään generoida yllättävänkin monimutkaista koodia. Ne voivat auttaa luomaan perustoiminnallisuuksia, tuottamaan boilerplate-koodia (toistuvaa peruskoodia) tai jopa ehdottamaan ratkaisuja tiettyihin ongelmiin. Tämä voi nopeuttaa kehitysprosessia tietyissä tilanteissa ja säästää ohjelmoijan aikaa rutiininomaisissa tehtävissä. Voit esimerkiksi pyytää LLM:ää luomaan perusluokan hahmolle, jolla on tietyt ominaisuudet, ja se voi tuottaa toimivan pohjan.
Kuitenkin on kriittisen tärkeää ymmärtää, että LLM ei ole itsenäinen ohjelmoija. Sen tuottama koodi perustuu valtavaan määrään olemassa olevaa koodia, jolla se on koulutettu. Se ei ymmärrä koodin merkitystä samalla tavalla kuin ihminen. Se ei osaa itsenäisesti tehdä luovia ratkaisuja täysin uusiin ongelmiin tai ymmärtää pelisuunnittelun hienovaraisuuksia ja tavoitteita syvällisesti.
Ohjelmoijan rooli muuttuu LLM:ien myötä, mutta se ei katoa. Tulevaisuudessa ohjelmoijan on ehkä entistäkin tärkeämpää:
- Ymmärtää perusteet: Vaikka LLM voi tuottaa koodia, ohjelmoijan on kyettävä ymmärtämään, mitä tämä koodi tekee, miksi se on kirjoitettu tietyllä tavalla ja onko se laadukasta ja turvallista. Jos et ymmärrä perusperiaatteita, et voi arvioida LLM:n tuottaman koodin oikeellisuutta tai tehokkuutta.
- Ohjata ja valvoa: LLM tarvitsee selkeät ohjeet siitä, mitä sen halutaan tekevän. Ohjelmoijan on osattava antaa tarkkoja prompteja (kehotteita) ja ohjata tekoälyä oikeaan suuntaan. Lisäksi on välttämätöntä tarkistaa ja testata LLM:n tuottama koodi huolellisesti.
- Ratkaista monimutkaisia ongelmia: LLM:t ovat hyviä tunnistamaan ja soveltamaan olemassa olevia malleja. Todelliset pelinkehityksen haasteet vaativat usein luovaa ongelmanratkaisua ja kykyä yhdistää eri teknologioita ja lähestymistapoja uudella tavalla. Tässä ihmisen ymmärrys ja intuitio ovat edelleen korvaamattomia.
- Kommunikoida ja suunnitella: Pelikehitys on tiimityötä. Ohjelmoijan on kyettävä kommunikoimaan muiden tiimin jäsenten kanssa, ymmärtämään heidän tarpeitaan ja osallistumaan suunnitteluprosessiin. LLM ei voi korvata tätä ihmisten välistä vuorovaikutusta.
Sen sijaan että näkisimme LLM:t uhkana, meidän tulisi ehkä nähdä ne tehokkaina työkaluina, jotka voivat auttaa meitä olemaan tuottavampia. Jotta voimme hyödyntää näitä työkaluja tehokkaasti, meidän on ensin ymmärrettävä ohjelmoinnin perusteet ja osattava ajatella kuin ohjelmoija. Nämä oppaat pyrkivät antamaan sinulle juuri ne eväät, jotta voit navigoida tässä muuttuvassa maisemassa menestyksekkäästi.
Lopuksi
Näillä eväillä pääset hyvään alkuun ohjelmoinnissa. Jos haluat selata oppaasta löytyviä esimerkkejä tai aiheisiin liittyviä tehtäviä, suuntaa SuomiGameHUBin Githubiin.
Github-sivulta löytyy myös viimeinen Loppuharjoitus, joka yhdistää kaikkea tähän mennessä oppimaasi.
Tarkoituksena on kirjoittaa jatkoa tälle oppaalle myöhemmin. Tarkkaile sivustoa!