27 maj 2008

Spelannonser

Det tycks mig som om lättklädda kvinnor används i annonser för just gratisspel i ännu större uträckning än andra produkter...







Och när vi ändå är på temat kvinnor och spel, ännu ett intressant inlägg. Man skulle ju kunna hoppas att vi hade kommit en bit sedan dess. Kanske?

Etiketter:

25 maj 2008

Don't panic

Idag uppmärksammar vi Towel Day, till minne av Douglas Adams.

Skördetid

Nu ramlar skolprojekten in på löpande band. Det mest lyckade hittills - om än inte det med den mest underhållande produkten - är SaftspeletTM. Byggt av mig, Gustav Nykvist, John Olsson, Marcus Arenius och Rickard Bondesson i kursen TDDD26, Utveckling av interaktiva system. Ladda ner och prova!

Ett lite roligare projekt var ju i TSBK07, Datorgrafik, där jag, Viktor och Åsa byggde en spel där man är ett ufo som beamar upp kossor från marken. Riktigt tjusigt blev det till slut också. Tyvärr får ni inte spela det, eftersom det inte kompilerar under Windows och jag inte känner för att lägga upp hela källkoden i bloggen. Synd va?

Projekt nummer tre har deadline på onsdag. Vi ses då.

23 maj 2008

Studieflykt igen

Då var det tenta-p igen, och dags att bli entusiastisk. Inför alla hobbyprojekt som har legat och skräpat sedan förra tenta-p alltså. Den här gången har mitt spelprojekt fått en renässans.

Tänkte att jag skulle gå och bli lite teknisk nu. Det är ju ändå bara C-are som läser den här bloggen (om någon, nu för tiden). Det finns väldigt mycket små saker att tänka på när man vill bygga en gedigen spelmotor. När man inte längre nöjer sig med fulhack börjar man leta vitt och brett över internet efter snygga lösningar.

Ett problem är hur man vill spara alla dynamiska objekt på hårddisken mellan spel. Det vill säga all data om kartor, monster, animationer, spelardata och så vidare. Den vanligaste lösningen är att på något sätt "serialisera" ett objekt, svd skriva de enskilda datumen (jo, faktiskt) som objektet består av - tal, tecken eller andra enkla datatyper som i slutänden bara är enskilda bytes - i en bestämd ordning så att man på ett kontrollerat sätt kan läsa in dem igen när objektet ska återskapas. Det här är den i mitt tycke estetiskt snyggaste lösningen på detta problem som jag har hittat. I huvuddrag fungerar den så här:

* Kräver speciell implementation i varje enskild klass som ska kunna serialiseras, men på ett snyggt och läsbart sätt.
* Ger dig kontroll över exakt hur datan skrivs till fil, vilket gör det möjligt att byte-hacka ihop egna datafiler för testning.
* Kapslar in all filhantering.
* Serialiserar på djupet, det vill säga en Serializable kan äga andra Serializables som automatiskt skrivs till fil när det ägande objektet gör det, utan att bryta inkapsling.

Huvuddragen i det här designmönstret finns beskrivna här, men jag tänker inte gå igenom i detalj hur alla klasser och metoder som behövs är implementerade. Om du vill grotta ner dig i ämnet och använda metoden i din egen kod så rekommenderar jag denna utmärkta artikel, som jag själv har tagit idén från.

Mönstret bygger på tre "pure virtual" klasser (eller "abstrakta klasser" eller "interface"), Serializer, DeSerializer och Serializable, enligt nedan.
class Serializer {
public:
virtual void PutLong (long l) = 0;
virtual void PutDouble (double d) = 0;
virtual void PutString (std::string const & str) = 0;
virtual void PutBool (bool b) = 0;
};

class DeSerializer {
public:
virtual long GetLong() = 0;
virtual double GetDouble() = 0;
virtual std::string GetString() = 0;
virtual bool GetBool() = 0;
};

class Serializable {
public:
virtual void Serialize(Serializer *out) = 0;
virtual void DeSerialize(DeSerializer *in) = 0;
};
Bara den här koden ger sig kanske ett hum om konturen på lösningen. Serializer har ansvaret att öppna en fil och skriva enskilda enkla datatyper till den. DeSerializer fungerar på samma sätt, men läser istället in data. Varje klass som ärver av dessa två måste implementera de fyra metoder de deklarerar, plus en konstruktor och en destruktor. Konstruktorn tar ett filnamn som argument och öppna själva filen (i antingen read- eller append-läge) som data ska skrivas till, medan destruktorn, naturligt nog, stänger den igen.

Det finaste med den här kråksången är att det går att implementera Serializer och DeSerializer på många olika sätt. Du kan ha en Serializer för att skriva till filer, en för att skriva till ett minnesutrymme, och en som bara räknar hur många bytes som skulle gå åt OM man skulle skriva objektet till fil, utan att skriva någonting alls själv. För mitt projekt gjorde jag ett eget Serializer/DeSerializer-par för att skriva och läsa filer med hjälp av PHYSFS, ett filinläsningsbibliotek som jag använder för att läsa och skriva zip-filer.

Ansvaret för varje klass som ärver av Serializable är att implementera de båda metoderna Serialize och DeSerialize så att de skriver och läser data på rätt sätt för just den enskilda klassen. (Det vackra är förstås att dessa metoder inte behöver känna till vilken typ av Serializer eller DeSerializer som faktiskt används.) En av mina mest grundläggande klasser som ärver av Serializable är Sprite. Här är en del av dess klassdeklaration:
#include "Serializable.h"
class Serializer;
class DeSerializer;

class Sprite : public Serializable {
public:
void Serialize(Serializer *out);
void DeSerialize(DeSerializer *in);
[...]
private:
[...]
};
Så när jag vill skriva ett sprite-objekt till en fil kan det se ut så här:
Serializer *fss = new FsSerializer("spritefil.dat");
Sprite s();
// [Initiera sprite med data här]
s.Serialize(fss);
Själva skrivandet av objektet har reducerats till en enda rad kod. Najs. När jag vill läsa in objektet igen gör jag så här:
DeSerializer *fsds = new FsDeSerializer("spritefil.dat");
Sprite s();
s.DeSerialize(fss);
//Färdigt Sprite-objekt fyllt med data!
Objektet är nu återställt och klart. Så här är Serialize implementerad i Sprite-klassen:
void Sprite::Serialize(Serializer *out) {
out->PutLong(mSpriteId);
out->PutLong(mDelay);
out->PutLong(mAnimations.size());
for (int i = 0; i <>
mAnimations[i]->Serialize(out);
}
}
I varje implementation av Serialize behöver jag bara koncentrera mig på ett enda objekt, men som ni ser är det inget problem att skriva många objekt efter varandra, så länge varje objekt själv håller reda på hur stort det är. Om jag nöjer med att alltid läsa in filer seriellt, från början till slut, behöver jag ingen mer funktionalitet än så här. vill jag kunna göra lite mer "random access" i filer behövs två saker till: en metod Search(int placeInFile) i min DeSerializer och ett filhuvud för att hålla reda på var i filen olika objekt finns.

För att skriva många spritar till en fil gör jag en klass som heter SpriteFileHeader och som även den ärver av Serializable. Huvudet innehåller en mappning mellan ID-nummer för spritar och index för var i filen de olika spritarna finns. Huvudet är sedan alltid det första som läses in från en ny fil. När det väl är avserialiserat kan jag använda Search-metoden för att hoppa till vilken sprite i filen jag vill.

Hur lätt det är att implementera Search beror så klart på vilken typ av DeSerializer du har gjort. Min FsDeSerializer läser in HELA filen till minnet så fort den skapas och representerar den som en bytebuffer, vilket gör det lätt. Om du använder filströmmar kommer det att se annorlunda ut.

Vi ber om ursäkt för att vi haft problem med indenteringsmaskinen under detta blogginlägg.