Hoppa till innehållet

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

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.)

En av de största fördelarna med att arbeta med klasser är förmågan att en klass kan ärva variabler och funktioner från en annan klass. Varför är det så bra? Det finns tre fördelar:


  • Har du en fungerande, bugfri klass är det skönt att veta att inga fel kommer från originalet när du bygger ut den till en ny klass som visar sig innehålla buggar.
  • Arbetstiden minskar radikalt för dig
  • Datakoden blir renare när du slipper skriva samma sak om och om igen.

Vi hade klassen ”stridsvagn” i förra exemplet. Anta att vi vill bygga ut den till en speciell sorts stridsvagn, vi vill ha en Tiger tank från andra världskriget, ett långsamt, brutalt monster till stridsvagn. Hur gör vi då på enklaste sätt?

Ursprungsklassen ser ut så här, med litet utbyggnad för att göra den mer stridsvagnslik:

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

#define SFML_STATIC //Se till så att det inte behövs extra DLL-filer 

class stridsvagn 
{
public:
//Konstruktordeklaration, definition utanför klassdeklarationen
stridsvagn(int  hastighet, char typ, double spelare_x, double spelare_y, int bensin, int ammunition, int skada, int pansar);//startvärden
//Destruktion
~stridsvagn(){};

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 typ;
//Nya värden
int bensin; //Hur långt kan den köra
int ammunition;//Hur mycket mer kan den skjuta
int skada; //Hur mycket skada gör den
int pansar; //Hur mycket tål den
sf::Sprite sprite;

//Funktionerna
int stridsvagn::skjut(int skada);
};

//Konstruktionsdeklaration--------------------------------------------------
stridsvagn::stridsvagn (int  ut_hastighet, char ut_typ, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_ammunition, int ut_skada, int ut_pansar) 
{
 hastighet=ut_hastighet;
 typ=ut_typ;
 spelare_x=ut_spelare_x;
 spelare_y=ut_spelare_y;
 bensin = ut_bensin;
 ammunition = ut_ammunition;
 skada = ut_skada;
 pansar = ut_pansar;
 std::cout << "En stridsvagn är byggd!" << std::endl;
}

int stridsvagn::skjut(int skada)
{
int i = 0;
i= sf::Randomizer::Random(1, skada); //skadar 1 till 10 p. vid träff då skada =10
std::cout <<"Skott avlossat!" << std::endl; //tala om att vi skjutit en granat
return i;
}

//Starta programmet----------------------------------------------------------------

int main (int argc, char *argv)
 { //main startar
   //skapa en stridsvagn

   stridsvagn stridsvagn1 (50, 's', 100, 100, 300, 100, 10, 50);
   //fart, typ, x, y, bensin=5 min, ammunition = 100 skott, skada= 10, pansar=50.

   return 0;
}//Main slutar


Det här är en standardpansarvagn, om vi inte gör någonting annat så kommer den att ha dessa värden. Typ = s så vi antar att vi egentligen tittar på en amerikansk Sherman stridsvagn från andra världskriget. En av de vanligaste stridsvagnarna på den allierade sidan. Nu gällde det dock att göra en annan, styggare tysk pansarvagn. Om den skall ha exakt samma variabler och funktioner behövs inget annat än att t.ex. skriva

stridsvagn stridsvagn1 (50, 's', 100, 100, 300, 100, 10, 50);
//fart, typ, x, y, bensin=5 min, ammunition = 100 skott, skada= 10, pansar=50.

Stridsvagn Tiger1 (30, 't', 700, 100, 200, 20, 100);

En stridsvagn som är tyngre, ger mer skada men är betydligt långsammare.

Två stridsvagnar slåss

[redigera]

Anta att vi låter dessa två olika stridsvagnar skjuta varandra i småbitar, då blir den kompletta koden:

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

#define SFML_STATIC //Se till så att det inte behövs extra DLL-filer 

class stridsvagn 
{
public:
//Konstruktordeklaration, definition utanför klassdeklarationen
stridsvagn(int  hastighet, char typ, double spelare_x, double spelare_y, int bensin, int ammunition, int skada, int   pansar);//startvärden
//Destruktion
~stridsvagn(){};

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 typ;
//Nya värden
int bensin; //Hur långt kan den köra
int ammunition;//Hur mycket mer kan den skjuta
int skada; //Hur mycket skada gör den
int pansar; //Hur mycket tål den
sf::Sprite sprite;

//Funktionerna
int stridsvagn::skjut(int skada);
};

//Konstruktionsdeklaration--------------------------------------------------
stridsvagn::stridsvagn (int  ut_hastighet, char ut_typ, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int  ut_ammunition, int ut_skada, int ut_pansar) 
{
 hastighet=ut_hastighet;
 typ=ut_typ;
 spelare_x=ut_spelare_x;
 spelare_y=ut_spelare_y;
 bensin = ut_bensin;
 ammunition = ut_ammunition;
 skada = ut_skada;
 pansar = ut_pansar;
 std::cout << "En stridsvagn är byggd!" << std::endl;
}

int stridsvagn::skjut(int skada)
{
int i = 0;
i= sf::Randomizer::Random(1, skada); //skadar 1 till 10 p. vid träff då skada =10
std::cout <<"Skott avlossat!" << "skada= " << i << std::endl; //tala om att vi skjutit en granat
return i;
}

//Starta programmet----------------------------------------------------------------

int main (int argc, char *argv)
 { //main startar
   //skapa en stridsvagn

   stridsvagn stridsvagn1 (50, 's', 100, 100, 300, 100, 10, 50); //lätt
   //fart, typ, x, y, bensin=5 min, ammunition = 100 skott, skada= 10, pansar=50.

   stridsvagn stridsvagn2 (30, 't', 700, 100, 300, 200, 20, 100); //tung
   //fart, typ, x, y, bensin=5 min, ammunition = 200 skott, skada= 20, pansar=100.

   //Låt de två stridsvagnarna skjuta varandra i småbitar
   while (stridsvagn1.pansar > 0 || stridsvagn2.pansar > 0)
   {
   stridsvagn1.pansar = stridsvagn1.pansar - stridsvagn2.skjut(stridsvagn1.skada); //lätt skjuter
   stridsvagn2.pansar = stridsvagn2.pansar - stridsvagn1.skjut(stridsvagn2.skada); //tung skjuter
   std::cout << "Stridsvagn1 pansar=" << stridsvagn1.pansar << std::endl;
   std::cout << "Stridsvagn2 pansar=" << stridsvagn2.pansar << std::endl << std::endl;
   if (stridsvagn1.pansar <= 0)
     break;
   if (stridsvagn2.pansar <= 0)
     break;
   }

  if (stridsvagn1.pansar > stridsvagn2.pansar)
    std::cout << "Stridsvagn1 vann" << std::endl;
  else if (stridsvagn2.pansar > stridsvagn1.pansar)
    std::cout << "Stridsvagn2 vann" << std::endl;
  else
    std::cout << "Oavgjort" << std::endl;

  return 0;
}//Main slutar


En annan Tigertank

[redigera]

Anta att vi vill ha en annan typ av attack, ett tigerskott som kan ge mycket mer skada vid träff. Då har vi alltså en annan typ av funktion och då går det inte längre att bara ge existerande variabler nya värden. Istället får vi skapa en helt ny klass som ärver allt ifrån klassen stridsvagn. Tigertanken var dessutom känd för sitt extremt tjocka pansar i fronten så vi lägger till en extra variabel.

//OBS-avsiktligt felaktig class-deklaration
class tigertank 
{
public:
int frontpansar;

 //Funktion
 int tigerskott()
 {
  int i = 0;
  i= sf::Randomizer::Random(10, 300); //skadar 10 till 300 p. 
  std::cout <<"Tigerskott avlossat!" << std::endl;
  return i;
  }
};

Anta att klassen ser ut så här. Vi har en ny variabel, ett extra frontpansar som skyddar oss. Dessutom ett extra dödligt skott som vår tigertank kan skjuta på de stackars amerikanska Sherman stridsvagnarna. Problemet är att den här klassen är för enkel, vi behövde ju alla värden från den föregående stridsvagnsklassen också.


Arvet

[redigera]

När man ärver från en annan klass skriver man:

class <nya klassens namn> : public <gamla klassens namn>

Allt som är public i ursprungsklassen blir fritt åtkomligt i den nya klassen. För oss blir det alltså:

class tigertank : public  stridsvagn

Sedan måste vi koppla ihop alla de föregående värdena med den nya tigertankens värden. Grundregeln är

<nya klassnamnet>(gamla konstruktionsdeklarationen):<gamla klassnamnet>(gamla variabelvärdena)

Alldeles för tekniskt? Det är lättare att förstå i en riktig situation, för oss blir det:

tigertank(int  ut_hastighet, char ut_typ, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_ammunition, int ut_skada, int ut_pansar)
:stridsvagn(ut_hastighet,ut_typ,ut_spelare_x,ut_spelare_y,ut_bensin, ut_ammunition, ut_skada, ut_pansar)

Sedan var det ju den nya variabeln, frontpansar. Den lägger vi in med:

{
 frontpansar=150;
}

Alla tigertanks skall ju ha samma värde så vi kan ge stridsvagnen ett fast värde för frontpansaret vid konstruktionen. Funktionen tigerskott kan få vara inline, som en del av klassen, eftersom enbart tigertanks skall använda den i vilket fall som helst.

Färdig kod

[redigera]

Komplett kod med en tigertank som ärvt värdena från föregångaren, eller moderklassen som den oftast kallas:

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

#define SFML_STATIC //Se till så att det inte behövs extra DLL-filer 

class stridsvagn 
{
public:
//Konstruktordeklaration, definition utanför klassdeklarationen
stridsvagn(int  hastighet, char typ, double spelare_x, double spelare_y, int bensin, int ammunition, int skada, int pansar);//startvärden
//Destruktion
~stridsvagn(){};

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 typ;
//Nya värden
int bensin; //Hur långt kan den köra
int ammunition;//Hur mycket mer kan den skjuta
int skada; //Hur mycket skada gör den
int pansar; //Hur mycket tål den
sf::Sprite sprite;

//Funktionerna
int stridsvagn::skjut(int skada);
};


//Konstruktionsdeklaration--------------------------------------------------
stridsvagn::stridsvagn (int  ut_hastighet, char ut_typ, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_ammunition, int ut_skada, int ut_pansar) 
{
 hastighet=ut_hastighet;
 typ=ut_typ;
 spelare_x=ut_spelare_x;
 spelare_y=ut_spelare_y;
 bensin = ut_bensin;
 ammunition = ut_ammunition;
 skada = ut_skada;
 pansar = ut_pansar;
 std::cout << "En stridsvagn är byggd!" << std::endl;
}

int stridsvagn::skjut(int skada)
{
int i = 0;
i= sf::Randomizer::Random(1, skada); //skadar 1 till 10 p. vid träff då skada =10
std::cout <<"Skott avlossat!" << std::endl;
return i;
}

class tigertank : public  stridsvagn //Arvet
{
public:
tigertank(int  ut_hastighet, char ut_typ, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_ammunition, int ut_skada, int ut_pansar)
:stridsvagn(ut_hastighet,ut_typ,ut_spelare_x,ut_spelare_y,ut_bensin, ut_ammunition, ut_skada, ut_pansar)
//Observera kolonet som separerar den nya klassens värden från moderklassens värden.
 {
 frontpansar=150;
 //Här kan du lägga in fler nya variabler 
 }

int frontpansar;
//Du får lägga in en ny variabeldeklaration för varje ny variabel i tanken

//Funktioner som bara Tigertanken har
int tigerskott()
{
 int i = 0;
 i= sf::Randomizer::Random(10, 300); //skadar 10 till 300 p. 
 std::cout <<"Tigerskott avlossat!" << std::endl;
 return i;
}

 //Här kan du lägga in fler nya funktioner

};


//Starta programmet----------------------------------------------------------------

int main (int argc, char *argv)
 { //main startar
   //skapa en stridsvagn

  stridsvagn stridsvagn1 (50, 's', 100, 100, 300, 100, 10, 50);
  //fart, typ, x, y, bensin=5 min, ammunition = 100 skott, skada= 10, pansar=50.
  stridsvagn1.skjut(10);

  tigertank tiger1 (30, 't', 700, 100, 150, 200, 20, 100);
  std::cout <<"frontpansar=" << tiger1.frontpansar << std::endl; //150p

  tiger1.skjut(10); 
  tiger1.tigerskott();

  return 0;
}//Main slutar

Observera listan med nya argument i slutet av Tigertank-klassen:

{
frontpansar=150;
}
int frontpansar;

På det sättet skapas ett värde till varje variabel. Även om du inte skulle ha en enda ny variabel i Tigertank-klassen måste du ändå ha med en tom argumentlista:

{}

Annars kommer koden inte att fungera för dig.


Overriding

[redigera]

Anta att du har en funktion i tigertankens class som har exakt samma namn som en funktion i basklassen, vad händer då? Det går lätt att kontrollera genom att lägga till funktionen skjut(int skada) som en del av tigertankens klassdeklaration.

int skjut(int skada)
   {
   int i = 0;
   i= sf::Randomizer::Random(2, 4) * skada; //skadar mycket
   std::cout <<"Död åt amerikanerna!" << std::endl;
   return i;
   }

Funktionen heter exakt likadant, men kör vi den i main loopen ser vi att den egentligen ger mycket mer skada med utropet "Död åt amerikanerna!". Dvs. det är fullt möjligt att skriva om existerande funktioner så att de bättre passar den nya klassen. "Ja men om man vill använda den gamla klassens skjut-funktion?" kanske någon undrar. Då läggs den till med:

stridsvagn::skjut(10);

Om du lägger in den textremsan i samma funktion ser du att du både får ut "Död åt amerikanerna!" och texten om avlossat skott som originalfunktionen har.

int skjut(int skada)
   {
   int i = 0;
   i= sf::Randomizer::Random(2, 4) * skada; //skadar mycket
   std::cout <<"Död åt amerikanerna!" << std::endl;
   stridsvagn::skjut(10);
   return i;
   }

Om du vill använda original-skjufunktionen inuti "Main" funktionen för programmet skriver du istället:

tiger1.stridsvagn::skjut(10);

Avslutning

[redigera]

Som synes kan vår tigertank både skjuta ett vanligt skott och ett speciellt tigerskott. Den har dessutom alla variabler tillgängliga från föregångaren ”stridsvagn”. Det är fullt möjligt att ärva i flera steg mellan olika klasser, det skall vi titta mer på sedan. Med "overriding" kan vi bygga om existerande funktioner men vi kan också komma åt originalfunktionerna vilket ger oss en rent fantastisk verktygslåda för att göra spel.

Komplett kod med funktionsarv inlagda

[redigera]
#include <iostream>
#include <SFML/System.hpp>
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>

#define SFML_STATIC //Se till så att det inte behövs extra DLL-filer 

class stridsvagn 
{
public:
//Konstruktordeklaration, definition utanför klassdeklarationen
stridsvagn(int  hastighet, char typ, double spelare_x, double spelare_y, int bensin, int ammunition, int skada, int pansar);//startvärden
//Destruktion
~stridsvagn(){};

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 typ;
//Nya värden
int bensin; //Hur långt kan den köra
int ammunition;//Hur mycket mer kan den skjuta
int skada; //Hur mycket skada gör den
int pansar; //Hur mycket tål den
sf::Sprite sprite;

//Funktionerna
int stridsvagn::skjut(int skada);
};


//Konstruktionsdeklaration--------------------------------------------------
stridsvagn::stridsvagn (int  ut_hastighet, char ut_typ, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int  ut_ammunition, int ut_skada, int ut_pansar) 
{
 hastighet=ut_hastighet;
 typ=ut_typ;
 spelare_x=ut_spelare_x;
 spelare_y=ut_spelare_y;
 bensin = ut_bensin;
 ammunition = ut_ammunition;
 skada = ut_skada;
 pansar = ut_pansar;
 std::cout << "En stridsvagn är byggd!" << std::endl;
}

int stridsvagn::skjut(int skada)
{
int i = 0;
i= sf::Randomizer::Random(1, skada); //skadar 1 till 10 p. vid träff då skada =10
std::cout <<"Skott avlossat!" << std::endl;
return i;
}


class tigertank : public  stridsvagn
{
public:
tigertank(int  ut_hastighet, char ut_typ, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_ammunition, int ut_skada, int ut_pansar)
:stridsvagn(ut_hastighet,ut_typ,ut_spelare_x,ut_spelare_y,ut_bensin, ut_ammunition, ut_skada, ut_pansar)
//Observera kolonet som separerar den nya klassens värden från moderklassens värden.
{
 frontpansar=150;
}

int frontpansar;
   //Funktion

int skjut(int skada)
  {
  int i = 0;
  i= sf::Randomizer::Random(2, 4) * skada; //skadar mycket
  std::cout <<"Död åt amerikanerna!" << std::endl;
  stridsvagn::skjut(10); //Moderklassens funktion
  return i;
  }


   int tigerskott()
  {
  int i = 0;
  i= sf::Randomizer::Random(10, 300); //skadar 10 till 300 p. 
  std::cout <<"Tigerskott avlossat!" << std::endl;
  return i;
  }
}; 


//Starta programmet----------------------------------------------------------------

int main (int argc, char *argv)
{ //main startar
  //skapa en stridsvagn
  stridsvagn stridsvagn1 (50, 's', 100, 100, 300, 100, 10, 50);
  //fart, typ, x, y, bensin=5 min, ammunition = 100 skott, skada= 10, pansar=50.
  stridsvagn1.skjut(10);

  tigertank tiger1 (30, 't', 700, 100, 150, 200, 20, 100);
  std::cout <<"frontpansar=" << tiger1.frontpansar << std::endl; //150p

  tiger1.skjut(10); 
  tiger1.tigerskott();

  return 0;
}//Main slutar