LABB 3: SPELETS GRUNDER
Ht1-2
##BESKRIVNING
I denna labb ska vi lära oss grunder med godot med Control-noder och 2D-noder för gränssnitt (UI) och Fysik.
För grafiken i kursen ska vi använda Public Domain-grafik från kenney.nl.
Längst ner på sidan finns en exempelvideo på hur spelet kan se ut i slutändan.
##UPPSÄTTNING
Fortsätt i samma som vi gjorde labb 2 och lägger nu till nya saker.
Ladda ner grafiken och lägg in i spelprojeket. En minimalare katalog med grafik från kenney-resurserna behöver ni laddar ner och använda i labbserien och uppgiftern. [Klicka här](assets.zip) för att ladda ner. Högerklicka och "Extrahera alla" och kopera sedan in filerna till ditt projekt.
##SPEL-SCEN:
Skapa en ny scen, lägg till en Node2D och döper den till `spelet`.
Spara sedan scenen som spelet.tscn.
Gör även spelet.tscn till huvudscen i projektet (högerklicka på scene.tscn i FileSystem tabben och välj "Set as Main Scene")
Skapa en CanvasLayer-node och som barn till den
* en VBoxContainer,
och i den Label-noder som barn.
Ordna så de VBoxContainer och Labels-noderna ligger i det övre högra hörnet.
Dina Labels kan heta:
* time
* side_impulse
* turn_impulse
* jump_impulse
* max_velocity
Lägg till text med t.ex. deras name i deras text-attribut i Inspector-tabben så vi kan se dem i spelet. (Dessa Label-node ska vi senare lägga data i i uppgiften För att vi ska kunna se våra värden medan vi spelar). Tiden ska vi dock jobba vidare med.
Gör dem gärna lite större med så att ni kan se, via Inspectorn och Theme OVerride/Font Size
###SPELTIDEN
När vi spelar spelet vill vi hela tiden vi hela tiden se tiden och räkna hur lång tid det tagit.
Själva tiden får vi från funktionen [`Time.get_ticks_msec()`](https://docs.godotengine.org/en/stable/classes/class_time.html#class-time-method-get-ticks-msec). Ger antalet millisekunder sedan programmet startade.
Börja med att skapa ett script för spel-scen, t.ex. `spelet.gd`. I `_physics_process` i scriptet sätter vi `time`-Labelns text till värdet från `Time.get_ticks_msec()`. Testa att det funkar.
I spelet vill vi inte se millisekunder. Vi vill ha en mer begripligt formatet "MM:SS". Därför ska vi skapa en funktionen som kan omvandla millisekunder till en text på detta format. Den funktionen lägger vi till i auto.dg, så att vi kan komma åt den överallt genom att t.ex. skriva `Auto.` och sedan lägga till och anropa funktionen.
* Till aguto.gd lägger vi till en funktion som:
* Tar ett antla millisekunder som argument - millisekunder.
* Räknar ut och returnerar en text på formatet "MM:SS" t.ex. 05:04
**OBS! inklusive extra 0or för sekunder eller minuter som ännu inte nått över 10.**
Tips: funktionen kan använda [%-operatorn](https://www.ida.liu.se/~TDDE04/docs/modulo.sv.shtml) för att gå från millisekunder i bas 10 till sekunder i bas 60. Detta finns beskrivet på denna sidan en bit ner.
* Om man skickar in 64520 så ska svaret bli 01:04
* Om man skickar in 5879832 ska varet bli 37:59
* Om man skickar in 556301 ska svaraet bli 09:16
När vi lägger funktioner i auto.gd kan vi komma åt dem överallt i spelet i alla olika script genom att skriva `Auto.` och sedan funktionens namn osv... Det är som `Time.` eller `Input.`
Observera här att vi tar in heltal men ska skicka ut en Sträng ... Vi kan behöva överföra från tal till text med funktionen `str()`.
Vi behöver även skapa text med +-operatorn för Strägnar som innebär att vi slår ihop text.
Printa hela tiden så ni ser vad ni får ut från funtionen
Testa så att ni ser att det fungerar.
###OMSTART och TILLBAKA:
I olika sammanhang vill vi starta om spelet. Det är om vi:
* Klarat banan och ska spela om med nya värden
* Faller utanför banan och behöver starta om banan
* Fastnar någon stans men kan inte falla utanför banan och behöver avbryta
* Börja med att registrera "R" som ett namn kopplat till knappen R
* I en `_physics_process`funktion skriver använder ni Input och kollar om "R" används så som ni gjort tidigare.
* Anropa då en funktion som vi skapar i auto.gd som heter restart(), med `Auto.restart()``
* I restart() ska ni bara köra koden `get_tree().reload_current_scene()`.
Vinsten med detta är att vi även kan anropa Auto.restart() på andra ställen i koden och inte bara ha den vid restart.
Läs på om Input via DOCS-sidorna.
Vid omstart blir tiden fel. Den visar hur länge programmet varit igång, inte hur länge den pågående spelomgången varit igång.
Nu måste vi dra bort tiden som gått sedan `spelet.tscn`-scnen startas. Vi kan fånga tiden i `_ready()` i en variable och sedan använda den variable för att dra bort start-tiden från totala tiden...
När det är klart borde vi kunna testa vår tidsfunktion med omstart så att vår time-Label hela tiden visar 00:00 om vi trycker på "R".
##FYSIKSYSTEMET
Nu ska vi börja jobba med fysiksystemet med noder osm RigidBody2D, StaticBody2D, och Area2D. Det har ni tidigare gjort, om än inte med dessa noder, i videos och i avskrivningslabbarna.
Vi använder även TileMapsLayers som ger oss beteende från StaticBody2D, men ett enklare sätt att bygga banor i rutnät. Som bonus ska vi flytta fysiska objekt med PathFollowing2D och Path2D så att vi kan rita lite mer komplicerade rörelsemönster. Det kan man även göra rörliga platformar och hissar med.
###MARK
Det första vi behöver är mark med TilemaLayer och aktiverat fysik-lager.
Vi använder en 64x64-Tileset som finns med i assets-mappen (`tilesheet_complete.png`). Det är en samling bilder i en fil.
Vi måste koppla tiles, aktivera ett fysiklager samt ange fysisk form för de rutor som ska vara ha kollision. På Video-sidan finns en video om hur man sätter upp detta med Kenny-grafiken och sätter en fysisk form för en av Tiles ... som man sedan kan upprepa för flera och mer variation. Ett litet tips är även att man kan trycka F för att fylla varje tilempa med fysik direkt.
Rita sedan en grundläggande bana med platt mark med lite hinder så att vi kan åka runt.
###ENKEL SPELARE - `BOLLEN`
* Skapa nu en ny scen med en Ridigbody2D.
* Döp den t.ex. till `bollen` och spara scenen.
* För vår RigidBody2D vill vi ha en boll-grafik och en CircleShape2D som shape för vårt CollisionShape2D-nod.
* Lägg även till en Camera2D-nod som barn till bollen och sätt den som "Enabled" så att spelet följer spelaren hela tiden. (I inspectorn, där standardvärdet är enabled)
* Aktivera `Continous Collision`, under egenskapen `Solver` med `Cast Shape` som värde (hindrar en snabb boll kan råka "blinka" igenom marken). Detta är ett attribut på din RigidBody2D.
* Vi kan även passa på att hitta egenskapen `Damp` under `Linear` för vår RigidBody2D och sätta den till t.ex. 0.3 så att vi har någon slags friktionskraft.
* Dra in bollen-scenen i spelet-scenen så att vi kan testa att gravitation och kollision med TileMap fungerar.
* Bollen behöver också ett script. Skapa det.
* Rigidbody2Ds styrs med kraft_impulser, inte velocity eller position. Det är för att vi vill jobba med krafter som från en motor.
* För att skapa impulser måste du fånga input från tangetbordet. `"ui_up"`, `"ui_left"`, `"ui_right"` och sedan använda `apply_central_impulse`-funktionen på RigidiBody2D. Funktione vill ha en Vector2 som beskriver kraftens storlek och riktning.
* Vår sidokraft ska hela tiden vara aktiv när vi trycker.
* Vår Hopp-impuls ska ske en gång tills man släpper knappen igen. Input har olika metoder för detta.
I Uppgiften kommer vi utveckla och optimera rörelsen till något riktigt bra. Nu nöjer vi oss med att bollen rullar och hoppar så att det "fungerar". Pröva er fram till lämpliga värden.
**OBS! Tänk på att multiplicera kraften med `delta` i `_physics_process` så att rörelsen blir oberoende av tiden.** Det innebär bara att vi måste ha större krafter eftersom de multipliceras ned med 0.0166666 om datorn går precis i 60 fps (frames per second). Spelet blir nu oberoeden av olika datorer med olika hastighet och/eller att andra program kör samtidigt på datorn. Det är det delta-variabeln är till för :)
###RAYCAST2D - HOPP STOPP
Bollen ska bara kunna hoppa om den har kontakt med marken.
För att lösa det använder vi en RayCast2D-Node som vi placerar som barn till bollen. Eftersom bollen rullar kommer även RayCast2D-noden rulla runt och vi måste förhindra det genom att hela tiden sätta dessa `global_rotation` till 0.0. Det får vi göra i `_physics_process`
Vi får anpassa längden på vår RayCast2D efter bollens storlek i 2D-fönstret.
Sedan måste vi förstås använda RayCast2D för att kolla att vi kan hoppa i vår kod. Titta på DOCS-sidan och begränsa våra hopp så att de bara kan ske om RayCast2D säger att vi har kontakt med något under oss.
###MYNT-SCEN
Nu vill vi tillföra ett mynt, `coin.tscn` som man kan plocka upp och få tidbonus - minusvärde för tiden. Ett mynt vill vi inte krocka med, det skulle kunna påverka spelarens rörelse, ... till det använder man Area2D
I en sådan kollision vill vi:
* visa den tid som dras av
* updatera speltiden med värdet för myntets avdrag
* göra en liten animering med myntet
* ta bort myntet när animeringen är slut
**På video-sidan finns en VIDEO som visar hur man skapar detta coin-scen.** Det som saknas är att dra av tid från speltiden så att vi får valuta för att ta mynten.
Ett sätt att göra det enklare att arbeta med tiden som variabel är att vi flyttar speltiden till auto.gd och arbetar med den där. Då kan vi komma åt tiden som varieable enklet överallt. Men se då till att förändra all kod som använder tiden till den nya platsen.
###FIENDE SOM FÖLJER BANA
Fiender kan göras rörliga på olika sätt. Vi gör en fiende som styrs av en Path2D. Vi kan då skapa väldigt avancerade rörelsemönster. (Denna teknik kan även användas för att skapa rörliga platformar och hissar för den som är intresserad.)
För att göra det krävs insyn i hur vi uppdatera rörelse längs en Path2D som har en PathFollower2D som barn som har t.ex. en Area2D-nod som barn som är det barn som ska röra sig längs pathen.
För fiende gäller att vi vill starta om spelet om spelaren dör :)
Video om detta finns på videosidan.
###MÅLFLAGGA
Vi behöver även målflagga i form av en Area2D. När användaren når målflaggan ska vi senare i uppgiften:
* spara data och
* starta om spelet
Men nu nöjer vi oss med att spelet startar om.
En `goal.tscn` scen som gör anropar en funktion i Auto som som startar om scenen. `_on_goal_flag(bollen)`. bollen ska då vara den faktiska bollen som krockat med våra flagga. Tanken med att skicka med bollen är att ni ska få tag i datat som bollen har, om ni sparar kraftvärden på bollen.
###Exempel på slutprodukt
##REDOVISNING
För att bli godkänd på labben ska ni visa att ni genomfört stegen och att det fungerar samt förklara koden och visa att ni förstår.
Labben visas upp för assistent och lämnas in på [inlämningssidan](https://www.ida.liu.se/~TDDE04/handin/inlamning.sv.shtml)

Sidansvarig: infomaster
Senast uppdaterad: 2024-10-13