Denna vecka skall vi öva oss på bl a: static, #extern, enum, och separata .c-filer.
Börja med att skapa en struct för 2D-vektorer som vi kan använda för positioner men även skulle kunna använda för riktnings- och hastighetsvektorer. Det är lämpligt att de två structmedlemmarna x,y är floats. Dels så att positioner och hastigheter inte är begränsade till heltal och dels för att inte avrundningsfel ska ackumuleras, t ex om du senare vill flytta dina objekt efter någon mjuk kurva som bygger på någon flyttalsformel. Kalla därför din struct för vec2f , dvs vektor av två floats, vilket är ett vanligt vedertaget namn för en sådan här struct. Det är vanligt att även behöva ha motsvarande för ints och doubles (dvs vec2i resp. vec2d).
Lägg structen i en separat källfil som du kallar för vecmath.h. Vet du inte hur du lägger till filer i CodeLite så kan du se det här nedan:
Include guards: Vi kommer till include guards först i nästa lektion, men du kan lika gärna lägga till det här och nu... En .h-fil är typiskt omgärdade av följande rader:
Det gör att .h-filen inte kommer att inkluderas mer än en gång när en .c-fil kompileras. Om den skulle inkluderas fler än en gång så kommer innehållet att finnas med motsvarande antal gånger, vilket kan leda till felet "multiple definitions", t ex av structs. En .h-fil kan lätt råka inkluderas flera gånger om man inkluderar flera .h-filer som i sin tur råkar inkludera samma .h-fil. Detta är inte ovanligt, vilket du snart kommer att märka.
Konstruktionen fungerar enligt följande: #ifndef, #define och #endif är så kallade preprocessor directives: #ifndef betyder "if not defined". Dvs, allt som står mellan #ifndef VECMATH_H och #endif behandlas vid kompileringen (av .c-filen) endast om VECMATH_H inte är definierat. Första gången som preprocessorn ser #include "vecmath.h" och försöker inkludera vecmath.h så är VECMATH_H (naturligtvis) inte definierat och alltså inkluderas innehållet. Första efterföljande rad är just en #define VECMATH_H som alltså ser till att VECMATH_H blir definierat. Om preprocessorn finner på en till #include "vecmath.h" lite senare så kommer VECMATH_H alltså att vara definierat och innehållet i .h-filen tas inte med. Detta är helt enkelt ett vanligt trick för att lösa ett vanligt problem. Vissa kompilatorer tillåter enklare sätt som t ex "#pragma once" för att lösa samma problem.
Övning på structs och skapa ny .h-fil: Kapsla nu in skeppets och bakgrundens variabler med hjälp av en lämplig struct som du lägger i en ny separat .h-fil. Det finns givetvis många lösningar. Den avancerade eleven får gärna välja sin egen struktur men då vara beredd på att lösningsförslagen nedan inte stämmer 100% överens.
Vi föreslår att du använder följande struct:
Skapa denna struct inklusive .h-fil och anpassa din kod så att den använder den. En av många nödvändiga uppdateringar inkluderar att byta ut GfxObject ship, background; i main.c mot GameObject ship, background;. Bl a behöver även inparametrarna för shake() ändras från int* till float*. OBS, därmed behöver även följande rad ändras för att undvika en lurighet med att variabeln t är unsigned och -1 är signed:
Gör alltihop så långt som möjligt själv utan att titta i lösningen för det är en mycket nyttig övning om du är ovan vid C och structs. Räkna med att det tar tid. När du fått koden att kompilera, använd debuggern för att rätta eventuella återstående fel. Använd CodeLite's Help-menu och skriv t ex debug eller breakpoint för att få hjälp. Kompilera och testa att allt fungerar som förut.
Extern: Med include guards så inkluderas en .h-fil max en gång per .c-fil som kompileras. Detta löser t ex att structs som definieras i .h-filen inte blir definierade mer än en gång.
Men .h-filen kan (och måste ofta) ändock bli inkluderad upp till en gång per .c-fil. Därför är det viktigt att inte lägga definitioner av globala variabler och funktioner i .h-filer. Istället lägger man endast deklarationerna i .h-filen - och definitionerna i motsvarande .c-fil. Annars skapas variabeln/funktionen en gång per .c-fil som inkluderar .h-filen. Endast deklaration (utan definition) för variabler gör man med #extern, vilket talar om att definitionen kommer senare (typiskt i en annan .c-fil).
exempel.h
#ifndef EXEMPEL_H
#define EXEMPEL_H
typedef struct { // typ-
int x, y; // definition
} Position; //
extern char str1[]; // deklaration
void func(); // deklaration
#endif // EXEMPEL_H
|
exempel.c
#include exempel.h // för typer
// etc.
char str1[] = "hej"; // definition
void func() // definition
{
char str2[] = "då";
}
|
Vid länkningen är det annars mycket sannolikt att länkaren kommer att klaga på "duplicate symbol" av en och samma variabel/funktion. Deklarationer av en och samma variabel eller funktion får förekomma hur många gånger som helst, så länge deklarationen inte ändras.
, dvs skapande av nya globala variabler (som t ex int apa = 0;), i .h-filerna, ty då skapas variabeln en gång per .c-fil som inkluderar .h-filen. Vid länkningen är det då mycket sannolikt att länkaren kommer att klaga på "duplicate symbol" av en och samma variabel. Istället lägger man skapandet av variabler i .c-filerna och har bara deklarationerna i .h-filen. Endast deklaration (utan definition) för variabler gör man med extern int apa; vilket talar om att definitionen kommer senare (typiskt i en annan .c-fil). Samma sak gäller definitioner av funktioner, dvs funktionskroppen skall ligga i .c-filen, men en extra deklaration (t ex void fkn(int);) ligger i .h-filen. Deklarationer av en och samma variabel eller funktion får förekomma hur många gånger som helst, så länge deklarationen inte ändras.Component-based programming har ökat i popularitet senaste åren som ett svar på att objektorientering inte passar väl för alla problemtyper. I objektorientering representerar man olika typer av objekt med olika klasser. Klasser kan ärva andra klasser och därmed dess typ (dvs i någon mening dess egenskaper). Men i spel är det inte ovanligt att objekt emellanåt fundamentalt skall ändra egenskaper. Ett objekt dör och kan inte längre styra. Ett objekt får en power-up och kan inte längre bli skadat eller får nya kraftfulla egenskaper. Ett objekt är stillastående och ofarligt och förvandlas av någon anledning till ett rörligt monster. I ett styrsystem för robot/bil skall plötsligt ljudet från radion slås av, utritning på instrumentpanelen slås av för vissa element men slås på för andra... Med klasser är det inte enkelt o smidigt att låta ett objekt sluta ärva från en klass och börja ärva av en annan klass. Och antal kombinationer av klasser med olika arv kan snabbt bli ohanterligt. Det är inte omöjligt att lösa problemet med objektorientering, men grundtanken med klasser och arv passar mindre väl.
Istället bygger component-based programming i sin enklaste form på att varje egenskap som finns motsvaras av en boolesk flagga (el dyl) hos objektet som kan slås på eller av. Varje objekt innehåller en uppsättning av alla flaggor. Av praktiska skäl implementeras de ofta som bitar i en variabel som t ex heter properties (eller flera properties-variabler om bitarna inte räcker till). Då kan egenskaper enkelt ändras hos objektet genom att bara slå på eller av en bit.
Logiken för respektive egenskap implementeras i varsin egen funktion. Varje funktion loopar helt enkelt igenom samtliga objekt och utför logiken för de objekt som har motsvarande flagga satt.
Både objektorienterad programmering och component-based programming har sina för- och nackdelar och det är fördelaktigt att känna till båda. Vi har här visat hur man kan blanda. Multitasking. pilnotation. Gå igenom varje "föregående lektion" och gör motsvarande hemuppg. Credits to: http://wrathgames.com/blog/2011/12/26/explosion-sound-effect/ Music: http://wrathgames.com/blog/free-development-resources/music/ Sound bible: http://soundbible.com/tags-explosion.html https://www.freesoundeffects.com/free-sounds/explosion-10070/ https://www.freesound.org/browse/tags/game/?page=2#sound Pekarövning - skriv ut texten baklänges mha pekare. Gör en funktion som returnerar strängen (inline) baklänges. Faktiskt något svårare än det verkar då man måste kopiera strängen. Dessutom - vem deallokerar den? Uppgift för nybörjaren så väl som för den mest förhärdade C-programmerare. Pekarövning 2 - gör funktion som tar in två float-variabler och incrementerar deras värden. Pekarövning 3 - malloc. Då måste man använda pekare. SDL_GetTicks() - för att avläsa tid. Angående mer avancerade förlopp: Hur gör du för att lösa om man vill initiera ett förlopp ifall spelaren trycker på space och därefter space igen efter c:a 1s (med felmarginal +-0.2s ? Allt medan spelet rullar. Lägg till en funktion checkDoublePress() som läser av tangentbordet och har en räknare som räknar upp och även invalideras om inte space är tryckt. Om du dör - backa tiden 30s. Eller kör tiden snabbt baklänges... Undo. Fyrverkeri. Efter 5s skall fiende 2 starta. Skjuta efter 3s. Fiende 3 skall... Slutord: Du har nu fått grundläggande kläm på hur man kan programmera kontrollerade förlopp av många oberoende och beroende händelser. Detta faller ofta inom ramen för s k realtidssystem. (Algoritmer är mer av typen - hur beräknar jag en lösning till det här enskilda problemet.) Kontrollfråga: Antag du har en robot där du vill styra bl a två st motorer. T ex två motorer för en robotarm. Den ena motorn skall rotera 90 grader i steg om 1 grader per tidssteg. Den andra motorn skall starta 10 tidssteg efter den första och rotera 30 grader i steg om 0.1. Om du känner att du har kläm på hur en lösning kan ser ut så är det bra. Annars, gå igenom hemuppgifterna igen noggrannare. Allmänna ord - skall man använda ungersk notation - Microsoft nej. medlemmar: m_name, m_pName, gName, nNames, Skall man ha "{" på ny rad eller ej.. Gör det ni tycker är lämpligast. Det enda som är säkert är att rekommendationerna ständigt ändras och utvecklas.