Programmera spel i C++ för nybörjare/Sprites och spelpjäser 7
(Genomgången förutsätter att du har en fungerande installation av Microsoft Visual C++ 2010 Express och SFML 1.6 på din dator.)
Arv
redigeraEn 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
redigeraAnta 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
redigeraAnta 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
redigeraNä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
redigeraKomplett 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
redigeraAnta 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
redigeraSom 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