Programmera spel i C++ för nybörjare/Sprites och spelpjäser3

Från Wikibooks


(Genomgången förutsätter att du har en fungerande installation av Microsoft Visual C++ 2010 Express och SFML 1.6 på din dator.)

Använda klasser[redigera]

Då har vi en sprite som heter boy och en sprite som heter cow. Om vi har ett enkelt spel med få spelpjäser, som det här som bara har två, räcker det. Anta att vi skall göra spelet ”Risk” istället. Det där spelet där man skall erövra världen och fyra olika spelare har hundratals spelpjäser. Om man skulle programmera Risk på samma sätt skulle samtliga pjäser för alla spelarna vara fördefinierade innan spelet börjar. Visst, man kan göra så, men det är onödigt omständligt.

En annan väg att gå vore om man hade en ”ritning” över hur en spelare skall se ut och varje gång en ny skall ut på spelplanen skapas en spelpjäs från den ritningen i datorns minne för att sedan placeras ut på spelplanen. När spelpjäsen ”dör” raderas den och försvinner ut från spelet så att det minnet kan återställas. Låter inte det så mycket bättre?

C++ stora styrka är just att det är ”Objekt Orienterat”, dvs. variablerna skall helst ligga inom de olika objekten och inte i själva main-loopen. På det sättet innehåller varje objekt sina egna värden. Just när man skapar spel är detta ovärderligt och den absolut största styrkan jämfört med andra programmeringsspråk som inte har den här möjligheten.

Om du lär dig hur man skapar klasser i spel kan du skapa klasser i alla andra program och du har kommit ett stooooooooooort steg på vägen till att bli professionell C++ programmerare, det kommer du däremot aldrig att bli om du inte förstår dig på klasser. Är klasser något för nybörjare? I vilket annat programspråk som helst skulle jag säga nej eftersom klasser är så svåra att förstå sig på, men just i C++ är det fundamentalt, det som hela programspråket egentligen vilar på, och just därför är du tvungen att lära dig. Att det blir så oändligt mycket enklare att programmera spel med klasser, än utan, gör inte saken sämre.

Så, vi har en pojke och en ko, dessa kan man tänka har en gemensam ritning som vi kan kalla spelare. Det som är gemensamt för både pojken och kon är att de har en hastighet och en position baserad på ett x,y värde. Den skall dessutom få en ras (b eller c för boy eller cow). Vi behöver alltså ge varje figur fyra värden som alltid är gemensamma.

Int  hastighet; //Hur snabb är den
double x; // var är den i sidled i programmet
double y; //var är den i höjdled i programmet.
char ras; //kan vara b eller c

Dessutom kan vi ha med en funktion som säger något om spelaren.

Void ljud(char vem)
{if (vem ==b)
cout << ”HJÄÄÄÄLP” << endl;
if (vem == c)
cout << ”MUUUUUUUU!” << endl;
}


Skapa en class[redigera]

En klass för en spelpjäs definieras på ett sätt som är likadant för alla klasser i C++,

class klassnamn 
{
private:
 variabler

public:
 variabler
 funktionsddefinitioner
}; //obs semikolon här

//funktioner
klassnamn::funktionsdefinition()
{
funktion som används av klassen
}

Det är väl ganska enkelt att förstå, men det finns två saker som är konstiga. Dels orden private och public.

Private eller public[redigera]

Enkelt sagt är det så att det som står under public kan man komma åt utifrån i programkoden, men det som står under private kan bara förändras av funktioner inom klassen. Som standard är allting i en klass private, så om du inte skriver någonting alls är allting i klassen private. En del programmerare skriver bara pulic: och sedan det som är öppet för alla och låter resten vara utan etikett, det är ju ändå private som standard.

Om du är den enda programmeraren kan du säkert hålla reda på allting i klassen, så du skulle kunna kalla allt public istället bara för att underlätta din egen programmering. Om man däremot är en grupp programmerare vill man ha vissa variabler som man inte kan förändra hur som helst och då är just att man kan skydda dem med private den stora räddaren.

Funktioner utanför klassen[redigera]

man kan skriva funktionerna inuti klasserna, men skrivs de utanför går det lättare att felsöka klasserna om det blir någonting galet. Enklast är att skriva funktionsdeklarationen inuti klassen och sedan skriva funktionen utanför med koden:

klassnamn::funktionsnamn(variabler)
{
gör något här
}

Ibland står det

Inline
klassnamn::funktionsnamn(variabler)
{
gör något här
}

för att visa att funktionen egentligen tillhör en klass. Varför dubbla kolon ::? Det visar att vi skall köra funktionen inuti klassen. Vi kan göra en jämförelse, kommer du ihåg att man kan skriva:

using namespase std
cout << ”hej” << endl;

eller

std::cout <<”hej”<<endl;

i det sista exemplet säger vi att funktionen cout som finns i klassbiblioteket std skall användas.

Klassen spelare[redigera]

//Klassdefinition
 class spelare 
{
public:
int  hastighet; //Hur snabb är den
double spelare_x; // var är den i sidled i programmet
double spelare_y; //var är den i höjdled i programmet
char ras;
void spelare::ljud(char vem);
}; //obs semikolon här

//funktioner
void spelare::ljud(char vem)
{if (vem =='b')
std::cout << "HJÄÄÄÄLP" << endl;
if (vem == 'c')
std::cout << "MUUUUUUUU!" << endl;
}

Vi blir tvungna att skriva std::cout eftersom det annars kan uppstå svårigheter då funktioner inte läser av using namespace std och vi behöver cout funktionen.

Char och namn[redigera]

I exemplet använder jag mig av en enda bokstav för att visa vilken ras det är. b för boy/pojke och c för cow/ko. Många unga spelprogrammerare vill kunna spara text i variablerna. Skapar man en ko kanske man vill att den skall ha namnet "Rosa". Det är inte överdrivet svårt att göra det. Utgå t.ex. ifrån att inget namn har mer än 20 bokstäver. Sedan fyller du i variabeln i klasssen:

char namn[20];

Problemet som uppstår blir när man vill döpa den. Skriver du

ko.namn="Rosa";  //FEEEEEEEEL!!!

kommer kompilatorn att skrika till och vägra köra koden eftersom Rosa är färre än 20 bokstäver. Då kan man endera räkna bokstäverna i de namn som läggs in och fylla ut dem med "Space" tills det blivit 20 bokstäver. Ett enklare alternativ är att använda funktionen strcpy_s t.ex. så här:

strcpy_s(ko.namn,"Rosa"); //Ge kon ett namn

Skall man arbeta med text bör man alltid fylla i includesatsen för det och skriva:

#include <string>

bland de andra include-satserna i programmets början.

Skapa spelarna[redigera]

Hur skapar vi någonting av den här klassen? Det är enkelt, skall vi skapa en ko skriver vi helt enkelt i koden:

//Skapa en ko
spelare ko; //ko blir en kopia av klassen spelare
ko.hastighet = 50; //grundhastighet, skall multipliceras med ”ElapsedTime ”.
ko.spelare_x=400.f;
ko.spelare_y=100.f;
ko.ras='c';
ko.ljud(ko.ras); //Skriver ut MUUUUUU! I det svarta konsollfönstret.

Komma åt värdena[redigera]

Som du ser är det lätt att komma åt de olika värdena i klassen, speciellt om du gjort allting public. Det är bara att ange klassnamn.värde för att komma åt det. Och är det public kan du också ändra det hur du vill utifrån. Om t.ex. hastighet hade varit satt som private hade du bara kommit åt det genom att skapa en funktion som läste av det. Enklast vore att låta kon råma ut ”Jag springer 50” istället för MUUUUUU!. Eftersom funktionen tillhör klassen kan funktionen komma åt alla värdena i klassen, även om de är private.

En så enkel funktion som ljud hade lika gärna kunnat finnas helt inuti klassen. Då hade det sett ut så här:

 class spelare 
{
public:
Int  hastighet; //Hur snabb är den
double spelare_x; // var är den i sidled i programmet
double spelare_y; //var är den i höjdled i programmet
char ras;
Void ljud(char vem)
  {
  if (vem ==b)
  std::cout << ”HJÄÄÄÄLP” << endl;
  if (vem == c)
  std::cout << ”MUUUUUUUU!” << endl;
  }
}


För att skapa pojken skriver vi:

spelare pojke; //pojke blir en kopia av klassen spelare
pojke.hastighet = 100; //grundhastighet, skall multipliceras med ”ElapsedTime ”.
pojke.spelare_x=100.f;
pojke.spelare_y=100.f;
pojke.ras='b';
pojke.ljud(pojke.ras); //Skriver ut HJÄÄÄÄÄÄLP! I det svarta konsollfönstret.


Koden blir alltså:

#include <iostream>
#include <SFML\System.hpp>
#include <SFML\Graphics.hpp>
#include <SFML\Window.hpp>

using namespace std;


//Klassdefinition
class spelare 
{
 public:
 int  hastighet; //Hur snabb är den
 double spelare_x; // var är den i sidled i programmet
 double spelare_y; //var är den i höjdled i programmet
 char ras;
 void spelare::ljud(char vem);
};

//funktioner
void spelare::ljud(char vem)
{
if (vem =='b')
  std::cout << "HJÄÄÄÄLP" << endl;
if (vem == 'c')
  std::cout << "MUUUUUUUU!" << endl;
}

int main() 
{  //Början av programkörningen

//Skapa fönstret som spelet skall visas upp i
sf::RenderWindow App(sf::VideoMode(800, 600, 32), "Test - klasser"); 

//Skapa en ko-----------------------------------------------------------------
spelare ko; //ko blir en kopia av klassen spelare
ko.hastighet = 50; //grundhastighet, skall multipliceras med "ElapsedTime".
ko.spelare_x=400.f;
ko.spelare_y=100.f;
ko.ras='c';
ko.ljud(ko.ras); //Skriver ut MUUUUUU! I det svarta konsollfönstret.

//Skapa en pojke---------------------------------------------------------------
spelare pojke; //pojke blir en kopia av klassen spelare
pojke.hastighet = 100; //grundhastighet, skall multipliceras med "ElapsedTime".
pojke.spelare_x=100.f;
pojke.spelare_y=100.f;
pojke.ras='b';
pojke.ljud(pojke.ras); //Skriver ut HJÄÄÄÄÄÄLP! I det svarta konsollfönstret.

App.Clear();	
App.Display();

return 0;

} //slut på programkörningen

Kör du programmet kommer det att blinka till. Det saknar programloop, men i det svarta konsollfönstret kommer du att se MUUUUU! Och HJÄÄÄÄÄLP! Så vi vet att de två figurerna (kon och pojken)verkligen har skapats.

En konstruktor[redigera]

Nu vet vi att det bara finns en ko och en pojke på spelplanen, men anta att vi vill skapa en ny ko varje gång vi klickar med musen på spelplanen. Då behöver vi kunna skriva in värdena för de olika variablerna i klassen redan när kospelaren skapas.


  • En konstruktor har exakt samma namn som klassen.
  • En konstruktor ser ut som en funktion, men saknar returvärde eller void.


Anta att vi redan när den skapas vill skriva ut att det är en ko och inte en pojke som skapas. Sä här ser klassdefinitionen ut nu:

//Klassdefinition
 class spelare 
{
public:
int  hastighet; //Hur snabb är den
double spelare_x; // var är den i sidled i programmet
double spelare_y; //var är den i höjdled i programmet
char ras;

//Konstruktordeklaration
spelare (char ras = 'c');//ko som standard
 
//Funktionerna
void spelare::ljud(char vem);

};

 //Konstruktordeklarationen
 spelare::spelare(char ras) 
 {
  std::cout << "En spelare har fötts!" << endl;
 }

//funktioner
void spelare::ljud(char vem)
{
 if (vem =='b')
   std::cout << "HJÄÄÄÄLP" << endl;
 if (vem == 'c')
   std::cout << "MUUUUUUUU!" << endl;
}

När vi kör den dyker återigen spelfönstret upp och försvinner, men till varje spelare som skapas finns ytterligare en textrad med ”En spelare har fötts!”

Man behöver ingen deklaration, jag hade kunnat skriva:

spelare ();//ingenting

och längre ner

 spelare::spelare()
 {
//std::cout << "En spelare har fötts!" << endl;
 }

Då har jag en färdig prototyp om jag vill lägga till någonting senare. Vad är egentligen finessen? Blir det inte krångligare på det här sättet? Fördelen är att alla variabler kan deklareras redan vid skapandet. Det går lika bra att skriva:

//I funktionen
spelare (int  hastighet, char ras, double spelare_x, double spelare_y);//startvärden

och längre ner

 //Konstruktionsdeklaration
 spelare::spelare (int  ut_hastighet, char ut_ras, double ut_spelare_x, double ut_spelare_y)
 {
 hastighet=ut_hastighet;
 ras=ut_ras;
 spelare_x=ut_spelare_x;
 spelare_y=ut_spelare_y;
 std::cout << "En spelare har fötts!" << endl;
 }

Vad innebär detta? Jo varje gång vi skapar en spelare måste vi se till så att vi förser funktionen med korrekta värden. Värdena på klassens variabler matas alltså in utifrån i samband med att en spelare skapas. Vill vi ha en pojke skriver vi in värdena för pojken när den skapas genom att ange nya konstruktionsvärden för pojken.

Istället för att skriva:

spelare pojke; //pojke blir en kopia av klassen spelare
pojke.hastighet = 100; //grundhastighet, skall multipliceras med ”ElapsedTime ”.
pojke.spelare_x=100.f;
pojke.spelare_y=100.f;
pojke.ras='b';
pojke.ljud(pojke.ras);

skriver vi

spelare pojke(100, 'b', 100.f, 100.f);
pojke.ljud(pojke.ras);

För kon skriver vi istället

spelare ko (50, 'c', 100.f, 400.f);
ko.ljud(ko.ras);

Båda sätten går att använda, men det blir mycket mindre att skriva om man anger värdena direkt vid skapandet. Detta är speciellt viktigt om man skapa en helt ny spelare (mer om det här Programmera spel i C++ för nybörjare/Sprites och spelpjäser 5).

OBS Tänk på att deklarationen:

spelare (int  hastighet, char ras, double spelare_x, double spelare_y);//startvärden

måste ligga i den del i funktionen som är public, annars kan man inte komma åt dem utifrån.

Nu har vi sett hur man skapar en ko och en pojke under tiden programmet körs, men om man vill skapa mängder med kor? Vad gör vi då? Det är nästa kapitel.