Göm menyn

TDDE71 Programmering och datastrukturer

Projekt veckobrev


Veckobrev 2 (Onsdag 12/11 -- Onsdag 19/11)

Status: Kravspecifikation är skriven och ni har nu en uppfattning om vad projektet ska ge för resultat. Ni har även skissat upp ett klassdiagram men än så länge är antagligen många delar av hur allt ska hänga ihop och fungera suddigt.

Arbetsuppgifter

Arbetsuppgifter som ska utföras under veckan:

  • Planera veckan: när ska ni jobba, var ska ni jobba, vad ska var och en åstadkomma? Målet bör vara minst 10h för var och en.
  • Vidareutveckla er objektorienterade analys till en objektorienterad design (se rubrik nedan)
  • Bonus: uppgiften "release 0.1" lämnas senast fredag den 21:e
  • Bonus: statusrapport för 5:e - 12:e nov lämnas senast fredag den 14:e
  • Intresseanmälan: Jag har ett litet spelexempel som kan simuleras "live" med diskussion vad som händer "under huven". Är er grupp intresserad av en sådan "workshop" på tisdag kl 10-12 behöver ni intresseanmäla detta till mig (klas.arvidsson@liu.se)

Objektorienterad design (i betydelse detaljerad konstruktionsritning för mjukvara)

Det är avsevärt mycket enklare att skriva en programvara om det är klart och tydligt hur den ska fungera och hur varje del ska samarbeta med övriga delar. Den objektorienterade analysen kommer inte hela vägen med detta. Den ger vilka objekt som ska finnas, översiktligt vad varje objekt ska göra (dess ansvar), vilka objekt som har koppling till varandra (samarbetspartners), och kopplingens art (arv, aggregation, komposition, association). Den ger däremot inte alla funktioner som behövs, vilka parametrar som kommer behövas, var all data ska lagras, och hur allt detta ska användas. Detta behöver ni komma fram till.

Observera att er objektorienterade design kan utvecklas en del i taget fram till projektslut. Ni behöver alltså inte göra ritningen först och sedan bygga efter ritningen, utan ni kan skissa upp en liten del av ritningen, provbygga, ändra skissen, bygga om och sedan bygga ut med en ny del av ritningen. Så kan ni göra dag för dag, vecka för vecka. Lägg till något, få det att fungera fullt ut, lägg till något mer, få det att fungera, osv.

Jag rekommenderar att börja med det ni inte vet hur ni ska lösa - ni behöver mest tid till detta - och det är skönt bara ha enkla saker kvar när deadline närmar sig. Exempel på delar jag tror är viktiga att lösa tidigt:

  • Hur skapas en ny klass som representerar ett spelobjekt och läggs till i spelmotorn så klassen syns och reagerar på input, update, render?
  • Hur skapas en ny klass som representerar ett state (t.ex. en menysida) och läggs till så spelmotorn använder den?
  • Hur ska ni hantera byten av state så att en meny går att navigera? När ska respektive state skapas, var lagras det, när tas det bort?
  • Hur ska banor/rum/nivåer/sektioner i spelet lagras, läsas in, och sedan hanteras av spelmotorn?
  • Hur ska inställningar/spelstate/information som behövs av flera spelobjekt skickas mellan objekt? Var ska de lagras?
  • Hur ska ett objekt som behöver skapa en instans av ett annat objekt göra så det nya objektet börjar hanteras av spelmotorn? (T.ex. när ett vapen ska avlossa en missil.)
  • Hur ska ett objekt som inte behövs mer göra så spelmotorn tar bort det? (T.ex. när en missil träffar sitt mål och förvandlar både sig och målet till ett rökmoln.)
  • Hur ska ett objekt som behöver aktuell information från ett annat objekt få tillgång till den informationen? (T.ex. en målsökande missil som följer spelarens position.)

Tips 1: För att komma fram till detta är det värdefullt att gå igenom användningsfall. Dvs fundera på en spelsekvens och tänk igenom vad som händer under huven. Exempel användningsfall: Spelet är i normalt spelläge och användaren trycket på Ctrl. SFML registrerar knapptrycket i sin händelsekö. Spelets huvudloop anropar "handleinput" på aktuellt state "Gamemode". Gamemode skickar händelsen vidare till alla spelobjekt tillsammans med en lista att fylla med ev nya objekt. Spelarobjektet skickar händelsen och listan vidare till det vapen som är aktivt. Vapnet reagerar med att skapa ett nytt missilobjekt. Missilobjektet läggs till i listan erhållen som parameter. Vapenobjektet har gjort sitt. Spelarobjektet har gjort sitt. Tillbaka i Gamemodeobjektet flyttas nya objekt över till listan med ala objekt. Nästa gång objekten renderas kommer missilen att synas.

Tips 2: Flera problem går att lösa med (ev osynliga) hjälpobjekt. Exempel användningsfall: Spelet är i normalt spelläge och visar en fiende som står vid högra kanten av en plattform. Spelets huvudloop anropar "update" på aktuellt state. Gamemode skickar händelsen vidare till alla spelobjekt. Fiendeobjektet reagerar med att uppdatera sin position så det går ett steg åt höger (det står nu i luften). Fiendeobjektet är klart. Gamemode undersöker vilka av alla objekt som kolliderar. Fiendeobjektet kolliderar med det osynliga objektet Bounce. Fiendeobjektets kollisionshanteringsfunktion reagerar på kollisionen genom att byta riktning och sedan ta ett steg i den nya riktningen (det står nu på plattformen vänt åt andra hållet). Notera: Allt har skett mellan anrop av "render" och kommer inte synas, dock har fienden under en kort tid haft "lite fel" position vilket skulle kunna orsaka "lite oväntade" kollisioner med t.ex. spelaren.

Tips 3: Titta igenom gamla duggor för fler spelrelaterade exempel och lösningar.

Tips 4: Det är viktigt att skilja mellan olika perspektiv och hierarkier. Exempel:

  • Menyhierarki - Användarens upplevelse av menyträdet, ett valt menyalternativ leder till en undermeny och det går att gå tillbaka (meny blir rot med undermeny som barn - helt logiskt)
  • Klasshierarki - En basklass sammanför programkod som behöver finnas i alla dess härledda klasser (menyalternativ blir rot där (under)meny är ett möjligt menyalternativ - känns det bakvänt?)
  • Minneshierarki - När behöver objekt skapas och tas bort? Behöver meny "komma ihåg" något medan en undermeny är aktiv eller räcker det att vid var tid endast lagra den aktiva menyn?

Mer om minneshierarki:

Medan en programvara kör har vi två sätt att lagra data:
  • I våra funktioner (på exekveringsstacken), data finns endast medan funktionen körs - mellan funktionsstart och return
  • I dynamisk allokerat minne (på heapen), data finns från "new" fram till "delete"

Ett exempel på missförstånd eller sammanblandning av de olik hierarkierna är när jag tror att härledd klass A kan spara data i basklassmedlem, och att sedan härledd klass B kan hämta ut denna data. Så fungerar det inte eftersom hela objekten A och B, inklusive basklassens medlemmar får var sitt unikt minnesutrymme. Det vi vinner på att göra en basklass är minskad kodupprepning både mellan klasser och för kod som använder klasserna. Vi sparar varken minne eller prestanda. (Ett alternativt sätt att få bukt med liknande kodupprepning är att använda mallar - templates. Detta ska ni inte göra då det inte ingår i TDDE71. Läs TDDD38 Avancerad C++ om ni vill lära er templates.)

Detta betyder att data som behövs under hela programkörningen måste skapas i main-funktionen, eller skapas med "new" där den först behövs och tas bort med delete där den sist behövs. För att sedan få data dit den behövs den gäller att skicka den som parameter. Data som behövs under hela livstiden av en klass tas med fördel in via klassens konstruktor och lagras som (ev. const) referensdatamedlem i klassen. I det fallet kan sedan alla klassens medlemsfunktioner nå datan från klassmedlemmen.

Tips 5: Detta betyder i sin tur att det kan bli många parametrar för att funktionerna handleinput och update ska få tillgång till allt de behöver, och vilka parametrar som behövs kommer ni upptäcka en sak i taget. Eftersom den parameterlistan kommer användas i alla klasser blir det besvärligt att hålla den uppdaterad överallt. Ett trick att ta till är att göra en klass "context" som innehåller alla parametrar, och endast ha klassen som parameter. På så vis kan nya parametrar läggas till genom att lägga till dem i "context". Då kan ändring i parameterlistan hållas till ändring på ett ställe - i context - samt där contextobjekt skapas av spelmotorn.


Sidansvarig: Eric Ekström
Senast uppdaterad: 2025-11-13