Programmera spel i C++ för nybörjare/Enkelt stridsvagnspel
(Genomgången förutsätter att du har en fungerande installation av Microsoft Visual C++ 2010 Express och SFML 1.6 på din dator.)
När animationen flyttas ut i en funktion avstannar hela spelet när funktionen visas upp, och det är inte så kul. I det här kapitlet skall vi skapa en stridsvagn som åker runt och skjuter och när kulorna kommer 50 pixel från en kant i spelet exploderar dem med en knall. Det här är alltså inget spel i sig utan en demonstration över hur animation och rörelser kan kombineras, så att du själv kan bygga ut koden till ett eget spel.
Nu skall vi för första gången koppla ihop allt vi lärt oss.
- Vi skall skapa en klass för fordon.
- Vi skall skapa en stridsvagn genom att ärva från fordon.
- Vi skall skapa en klass för explosionen.
- Vi skall skapa en klass för skotten.
- Stridsvagnen flyttas med mustangenten och roterar själv i den riktning vi klickar med musen.
- När vi trycker ner space-tangenten skjuter stridsvagnen.
- Då skapas ett skott och färdas en viss sträcka från stridsvagnen
- När skottet nått hela sträckan försvinner skottet och en explosion syns istället.
Den grafik vi behöver är: en pansarvagn, en kula och en explosion.
Pansarvagn:
http://media.strategywiki.org/images/2/2a/MH_Tank.gif (måste göras om till png)
Kula: Eight-Ball-icon.png
http://www.iconarchive.com/show/symbolic-objects-icons-by-mozco/Eight-Ball-icon.html
Explosion:
http://cdn.pimpmyspace.org/media/pms/c/b9/9e/ez/xplosion17.png
De ljud vi behöver är: en liten knall när skottet avlossas och en större knall vid explosionen.
Liten knall : http://www.mediacollege.com/downloads/sound-effects/explosion/ (explosion 3)
Stor knall : http://www.mediacollege.com/downloads/sound-effects/explosion/ (bomb 3)
Sedan vi stulit/lånat kod från alla tidigare projekt har vi alltså något liknande detta:
((OBS i andra exempel var hastigheten en int, men det fungerade helt enkelt inte. Det fick bli 0.01 som standard och en double).
Skapa en explosionsklass
redigeraDet första vi måste göra är att utöka explosionsklassen. Vi måste ha olika lägen definierade och en spärr som slår till när animationen visats färdigt.
Klassen, i sin enklaste form, ser ut så här:
class Explosion { public: Explosion(bool ut_playing, bool ut_has_played, double ut_starttid); //Konstruktion ~Explosion(){};//Destruktor sf::Sprite Sprite; // en per instans sf::Image Image; //bildfilshållaren //var skall explosionen ske? double target_x; double target_y; //Var i sprite sheeten skall vi börja kopiera bilder int topkoordinatX; int topkoordinatY; double starttid; //Vid vilket ögonblick skall explosionen starta? bool playing; //visas den upp? bool has_played; //Har den visats färdigt? void get_explosionsbild() //Ta ut rätt koordinater i sprite sheeten för visning av explosion { //Ta fram rätt bild Sprite.SetSubRect(sf::IntRect(topkoordinatX,topkoordinatY, topkoordinatX + 64,topkoordinatY + 64)); //Funktionen ger övre vänstra hörnets koordinater //då kan vi själva räkna ut nedre högra hörnet eftersom bilderna är 64 x 64 stora } }; Explosion::Explosion(bool ut_playing, bool ut_has_played, double ut_starttid) //Initiering { playing = ut_playing; //Avgör om animationen spelar. Ange false som standard has_played = ut_has_played; //Ställs från funktion utanför klass, false från början starttid = ut_starttid; //När börjar explosionen //Ladda in rätt bild vid skapandet if (!Image.LoadFromFile("xplosion17.png")) //Hämta bildfilen { std::cout << "Kan inte hitta bilden: xplosion17.png " << std::endl; } Sprite.SetImage(Image); //ge bilden till spriten }
Detta innebär att varje gång vi skapar en explosion med ”new” laddas bilden in på nytt i minnet. Korkat, jag vet, men jag vet ingen väg runt problemet. När explosionen börjar ställes "playing" till true och när explosionen har visats klart sätts "has_played" till true. På det viset kan animationen bara visas en enda gång. Startiden är viktig, för det är skillnaden mellan starttiden och den tid som spelet varit igång som avgör vilken bild i animationen som skall visas upp. "Target_x" och "target_y" är koordinaterna till den plats som explosionen skall stå och explodera på. Slutligen har vi funktionen: void get_explosionsbild() som vi använder för att "skära ut" rätt del av sprite sheeten beroende på hur lång tid som gått.
Listor
redigera//Lista för att spara skott i Pansarvagnsskott *skottlista[100];//max 100 skott på planen int skott_i=0; //0-100 avgör var vi är i listan int si =0; //räknare //Lista för att spara explosioner i Explosion *explosionslista[100];//max 100 explosioner på planen int exp_i=0; //0-100 avgör var vi är i listan int ei =0; //räknare
Vi har två listor i spelet. varje gång pansarvagnen skjuter skapas en kanonkula och en explosion samtidigt. Så länge kanonkulan syns, syns inte explosionen. När kanonkulan träffar ett mål (i det här fallet är det väggen i spelet) sätter vi kulans värde "kan_ses" till false så att den inte ritas ut och hastigheten till 0. Därefter placerar vi animationen på samma ställe och visar upp den. Hyfsta enkelt att förstå. Explosionen är inställd så att om man har fler än ett skott som exploderar nästan samtidigt stoppas ljudet och börjar om från början för varje skott. Nackdelen med att använda listor är att de fylls upp. Efter 100 skott kan stridsvagnen inte göra så mycket mer. Ett sätt att komma runt det problemet är att använda länkade listor (som man gjorde i C programmering förr) eller använda en "Vector" eller "Deque" istället (som C++ programmerare använder), men mer om det i senare kapitel.
Explosionsanimation
redigeraif (exp_i > 0) {//det finns explosioner i listan while (ei <= (exp_i -1) && exp_i < 100) { //Man har inte nått slutet på listan if (explosionslista[ei]->playing == true && explosionslista[ei]->has_played == false ) //man är under 26 bilder { explosionsbildruta(spelklocka.GetElapsedTime(), *explosionslista[ei]); //Ge spritens bild rätt x och y koordinater, funktion explosionslista[ei]->get_explosionsbild(); //Välj rätt bild till explosionen, funktion inom klassen Explosion explosionslista[ei]->Sprite.SetPosition(explosionslista[ei]->target_x, explosionslista[ei]->target_y); //Placera ut den på rätt ställe App.Draw(explosionslista[ei]->Sprite); //Rita ut bilden på explosionen } ei++; //Gå till nästa post på explosionslistan } //Man har inte nått slutet på listan ei=0; //Nollställ ei igen så att du kan räkna upp listan en gång till }//Slut på explosioner i listan
Det här är principen för animationer i spel. Man går igenom en lista med de sprites som har någon form av animation, har en funktion som väljer ut rätt bild baserat endera på tid eller på spelets framerate och som tilldelas spriten innan den visas upp.
- Finns det explosioner i listan? I sådana fall skall de troligtvis ritas upp.
- Kontrollera om varje explosion kan ses och inte har spelats upp färdigt. Om båda är true tas en bildruta fram till spriten. Jag använder funktionen: explosionsbildruta.
- Funktionen skickar sitt värde till en annan funktion inuti klassen Explosion som i sin tur ritar upp sin egen sprites bild. Man hade kunnat ha en funktion som först tog fram x- och sedan y-värdet för att därefter rita upp rätt bild inne i klassens sprite, men det hade varit minst lika krångligt.
- Explosionen placeras ut på den punkt kulan försvann.
- Kö-räknaren nollställs så att den kan göra om hela proceduren vid nästa omgång, 1/60 sekund senare.
Slutligen har jag suttit och definierat varje ruta för sig vid animationen. Det går att göra så, men det är bevis för att man är en total nybörjare. Det går i alla fall hyfsat enkelt att se. Koordinaterna är två stycken, X och Y för övre vänstra hörnet. Detta går bra när samtliga bilder i den sprite sheet vi använder har exakt samma storlek. Om storleken skulle variera skulle vi vara tvungna att ange koordinaterna för neder högerhörn också.
void explosionsbildruta(double speltid, Explosion &Klassinstans) //Tar fram rätt bild i ett sprite sheet { //De koordinater vi är ute efter int utx = 0; int uty = 0; double tidsomgaott = speltid-Klassinstans.starttid; //Hur lång tid har det gått sedan explosionen startade //Starttiden finns lagrad inuti klassen double visningstid = 0.05; //0.05 sekund mellan varje bild
- Speltid - starttid visar hur lång tid som gått av animationen. visningstiden avgör animationshastigheten.
- utx och uty är koordinaterna i sprite sheeten
Koden för hur funktionen beräknar x- och y- koordinaterna är lång och ganska självförklarande, men på slutet kontrollerar vi om man är inne på ruta 26, dvs. om animationens samtliga 25 bilder har visats. Om så är fallet sätts klassens "has_played" till true och den explosionsanimationen kan inte spelas upp igen.
Inuti klassen finns:
void get_explosionsbild() //Ta ut rätt koordinater i sprite sheeten för visning av explosion { //Ta fram rätt bild Sprite.SetSubRect(sf::IntRect(topkoordinatX,topkoordinatY, topkoordinatX + 64,topkoordinatY + 64)); //Funktionen ger övre vänstra hörnets koordinater //då kan vi själva räkna ut nedre högra hörnet eftersom bilderna är 64 x 64 stora }
Det är den funktionen som matas med utx och uty så att rätt bild kan visas upp.
Komplett kod
redigera#include <iostream> #include <SFML\System.hpp> #include <SFML\Graphics.hpp> #include <SFML\Window.hpp> #include <SFML\Audio.hpp> #define M_PI 3.14159265358979323846 /* pi som statisk konstant*/ #define SFML_STATIC //Se till så att det inte behövs extra DLL-filer using namespace std; //Class för skottet class Pansarvagnsskott { public : Pansarvagnsskott(double ut_hastighet, double ut_vinkel, bool ut_kan_ses); //Konstruktion ~Pansarvagnsskott(){}; double target_x; double target_y; double hastighet; double vinkel; bool kan_ses; sf::Sprite Sprite; // en per instans sf::Image Image; //bildfilshållaren }; Pansarvagnsskott::Pansarvagnsskott(double ut_hastighet, double ut_vinkel, bool ut_kan_ses) { if (!Image.LoadFromFile("Eight-Ball-icon.png")) //Hämta bildfilen { std::cout << "Kan inte hitta bilden: Eight-Ball-icon.png " << std::endl; } hastighet=ut_hastighet; vinkel=ut_vinkel; kan_ses = ut_kan_ses; Sprite.SetRotation(vinkel);//Ge den rätt vinkel redan från start Sprite.SetImage(Image); //ge bilden till spriten } class Explosion { public: Explosion(bool ut_playing, bool ut_has_played, double ut_starttid); //Konstruktion ~Explosion(){};//Destruktor sf::Sprite Sprite; // en per instans sf::Image Image; //bildfilshållaren //var skall explosionen ske? double target_x; double target_y; //Var i sprite sheeten skall vi börja kopiera bilder int topkoordinatX; int topkoordinatY; double starttid; //Vid vilket ögonblick skall explosionen starta? bool playing; //visas den upp? bool has_played; //Har den visats färdigt? void get_explosionsbild() //Ta ut rätt koordinater i sprite sheeten för visning av explosion { //Ta fram rätt bild Sprite.SetSubRect(sf::IntRect(topkoordinatX,topkoordinatY, topkoordinatX + 64,topkoordinatY + 64)); //Funktionen ger övre vänstra hörnets koordinater //då kan vi själva räkna ut nedre högra hörnet eftersom bilderna är 64 x 64 stora } }; Explosion::Explosion(bool ut_playing, bool ut_has_played, double ut_starttid) //Initiering { playing = ut_playing; //Avgör om animationen spelar. Ange false som standard has_played = ut_has_played; //Ställs från funktion utanför klass, false från början starttid = ut_starttid; //När börjar explosionen //Ladda in rätt bild vid skapandet if (!Image.LoadFromFile("xplosion17.png")) //Hämta bildfilen { std::cout << "Kan inte hitta bilden: xplosion17.png " << std::endl; } Sprite.SetImage(Image); //ge bilden till spriten } //En moderklass för alla fordon i spelet class fordon { public: //Konstruktordeklaration, definition utanför klassdeklarationen fordon(double hastighet, double spelare_x, double spelare_y, int bensin, int pansar);//startvärden //Destruktion ~fordon(){}; double 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 int bensin; //Hur långt kan den köra int pansar; //Hur mycket tål den sf::Sprite sprite; //Används i grafiskt läge }; //Konstruktionsdeklaration, fordon-------------------------------------------------- fordon::fordon (double ut_hastighet, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_pansar) { hastighet=ut_hastighet; spelare_x=ut_spelare_x; spelare_y=ut_spelare_y; bensin = ut_bensin; pansar = ut_pansar; //Också vill vi se att det verkligen skapas ett fordon när underklasserna skapas std::cout << "Ett fordon rullar ut från fabriken!" << std::endl; } //Skapa en klass som ärver fordon class stridsvagn : public fordon { public: int ammunition; // Avgör hur många skott som finns i stridsvagnen. stridsvagn(double ut_hastighet, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_pansar): fordon(ut_hastighet, ut_spelare_x, ut_spelare_y, ut_bensin, ut_pansar) { ammunition=100; } //Destruktion ~stridsvagn(){}; //Funktioner void skott() { std::cout << "Skott kommer!" << std::endl; ammunition = ammunition-1; std::cout << "Skott kvar=" <<ammunition << std::endl; } }; void explosionsbildruta(double speltid, Explosion &Klassinstans); //Funktion som tar fram x och y koordinat på sprite sheet för explosionsbilder //och som stänger av animationen om fler än 25 bilder visas bool check_hit(double x, double y); //Funktion som kollar om kulan träffat ett mål double mot_koordinat(sf::Sprite &sprite, sf::RenderWindow &window, double position_X, double position_Y, int gradjusterare); //Funktion som roterar pansarvagnen mot en speciell koordinat //******************************************************PROGRAMSTART******************************************************** int main (int argc, char *argv) { //main startar //För att kunna styra animationer i spelet måste vi kunna hålla koll på tiden. //Det finns många sätt att göra det på och det här är det sämsta //men det fungerar och är lätt att förstå. sf::Clock spelklocka; //Skapa en spelklocka spelklocka.Reset(); //Ställ den på 0 //Övriga variabler double explosionsstarttid = 0.0f; //bool visaexplosion = false; //skall den visas? //Lista för att spara skott i Pansarvagnsskott *skottlista[100];//max 100 skott på planen int skott_i=0; //0-100 avgör var vi är i listan int si =0; //räknare //Lista för att spara explosioner i Explosion *explosionslista[100];//max 100 explosioner på planen int exp_i=0; //0-100 avgör var vi är i listan int ei =0; //räknare //Olika värden för att styra stridsvagnen float newx = 0.0f; //ditt stridsvagnen skall gå float newy = 0.0f; //dit stridsvagnen skall gå float speed = 2.0f; //stridsvagnens hastighet. double vinkel =0.0f; //Stridsvagnens vinkel //Skapa först en stridsvagn //stridsvagn(int ut_hastighet, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_pansar) stridsvagn stridsvagn1(0.01f, 100.0,100.0,100,100); std::cout << "Pansarvagnen har " << stridsvagn1.ammunition << " skott" << endl ; //Skapa bild sf::Image bild; //skapa en tom bildhållare som heter bild if (!bild.LoadFromFile("MH_Tank.png")) return EXIT_FAILURE; //fyll den tomma bildhållaren med bilden MH_tank.png stridsvagn1.sprite.SetImage(bild); //nu har vi en stridsvagn, Placera ut stridsvagnen på spelplanen stridsvagn1.sprite.SetPosition(stridsvagn1.spelare_x,stridsvagn1.spelare_y); //Det är jobbigt att inte riktigt veta var mitten är på stridsvagnen //Ändra positionen där stridsvagnen är från övre vänstra hörnet till mitt på bilden. stridsvagn1.sprite.SetCenter(stridsvagn1.sprite.GetSize().x / 2 , stridsvagn1.sprite.GetSize().y / 2); //Ladda in de två ljuden //Ljud för att avlossa kanonen sf::SoundBuffer kanonljud; //skapa en ljudbuffer/hållare if (!kanonljud.LoadFromFile("explosion-03.wav")) //ladda in en fil i hållaren { std::cout << "Kan inte hitta ljudfilen: explosion-03.wav " << endl; } sf::Sound kanoneffekt; //skapa ett ljud i spelet för kanonen som vi döper till kanoneffekt kanoneffekt.SetBuffer(kanonljud); // Ladda in ljudfilens värden i ljudet så att det går att spela upp. //Ljud när granaten exploderar sf::SoundBuffer explosionsljud; //skapa en ljudbuffer/hållare if (!explosionsljud.LoadFromFile("bomb-03.wav")) //ladda in en fil i hållaren { std::cout << "Kan inte hitta ljudfilen: bomb-03.wav " << endl; } sf::Sound ljudeffekt; //skapa ett ljud i spelet som vi döper till ljudeffekt ljudeffekt.SetBuffer(explosionsljud); // Ladda in ljudfilens värden i ljudet så att det går att spela upp. /************** Programstart ***********************************************************/ sf::RenderWindow App(sf::VideoMode(800, 600, 32), "Test av pansarvagn"); // Skapa fönstret vi skall testa explosionerna i while (App.IsOpened()) { //while 1 startar, spelet körs sf::Event Event; //kolla om mus/tangentbord används while (App.GetEvent(Event)) { //while 2 börjar, kontrollerar händelser if (Event.Type == sf::Event::MouseButtonPressed) // En musknapp har tryckts ner {//musknapp, vilken som helst if (Event.MouseButton.Button == sf::Mouse::Left) { //vänster musknapp //Vrid stridsvagnen i rätt vinkel mot_koordinat(stridsvagn1.sprite, App, App.GetInput().GetMouseX(), App.GetInput().GetMouseY(), 90); stridsvagn1.spelare_x = App.GetInput().GetMouseX(); //Ge stridsvagnen en X-koordinat att gå mot stridsvagn1.spelare_y = App.GetInput().GetMouseY(); //Ge stridsvagnen en Y-koordinat att gå mot } //vänster musknapp }//slut mus, vilken knapp som helst if (Event.Type == sf::Event::Closed) //kryssat på [x] symbolen? stäng programmet App.Close(); if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::Escape)) App.Close();//avsluta programmet om man klickar på ESC /************************* Kanonskott *******************************************************************************************/ // Här kommer koden för att avlossa kanonen! // Ett explosionsljud spelas upp // Mängden ammunition i stridsvagnen räknas ner ett steg // En ny kanonkula skapas och läggs in i en array // Kanonkulans mittpunkt sätts mitt på kulan istället för i övre högra hörnet // Kanonkulan har sin mittpunkt på samma punkt som pansarvagnen har sin // För varje kula skapar vi en potentiell explosion med samma indexnummer // Explosionens mittpunkt placeras på samma ställe som kulans mittpunkt // Räknaren skott_i flyttar ett steg upp och väntar på nästa kula // Slutligen synkroniseras skottlistan med explosionslistan /********************************************************************************************************************/ if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::Space)) {//Spacetangent nertryckt kanoneffekt.Play(); //spela upp kanonljudet stridsvagn1.skott(); //Skjut med stridsvagnens funktion skottlista[skott_i] = new Pansarvagnsskott(1.0f,stridsvagn1.sprite.GetRotation(), true); //Skapa ett nytt skott med: //hastighet 1.0f och vinkel exakt samma som stridsvagnen och tru e= den syns. skottlista[skott_i]->Sprite.SetCenter(skottlista[skott_i]->Sprite.GetSize().x / 2 , skottlista[skott_i]->Sprite.GetSize().y / 2); //Sätt koordinaten mitt på skottlista[skott_i]->Sprite.SetPosition(stridsvagn1.sprite.GetPosition().x,stridsvagn1.sprite.GetPosition().y); //Placera ut den på samma punkt som pansarvagnen //Samtidigt som vi skapar ett skott måste vi skapa en explosion för skottet. Alla skott har en möjlighet att explodera. explosionslista[skott_i] = new Explosion(false, false, spelklocka.GetElapsedTime()); //Den skall inte visas ännu, den har inte spelats klart men starttiden är skapelseögonblicket //Detta fungerar inte, mittpunkten måste kodas med siffror av någon anledning //explosionslista[skott_i]->Sprite.SetCenter(explosionslista[skott_i]->Sprite.GetSize().x / 2 , explosionslista[skott_i]->Sprite.GetSize().y / 2); explosionslista[skott_i]->Sprite.SetCenter(32,32); //Hela bilden är 64 x 64 skott_i++; //Räkna upp till nästa plats i kön för nästa skott exp_i = skott_i; //Synkronisera de två listorna (skott och explosioner) så att de alltid pekar på samma postnummer i //båda listorna. Det behövs sedan för att rita ut korrekt animation på rätt plats }//Spacetangent nertryckt } //while 2 slutar //Slutligen visar vi upp ändringarna om och om igen många gånger i sekunden App.Clear(sf::Color(0, 100, 0)); //rensa allt i fönstret och ersätt med grönfärg /**************************** Flytta på skotten på spelplanen ****************************************/ if (skott_i > 0) {//det finns skott i listan while (si <= (skott_i -1) && skott_i < 100) { //Man har inte nått slutet på listan //Flytta skotten vinkel = skottlista[si]->vinkel; // 0 = rakt upp, startposition. Motsols 0-360 grader. newx= sin(vinkel*M_PI/180.0) * skottlista[si]->hastighet; //Flytt i x-led newy= cos(vinkel*M_PI/180.0) * skottlista[si]->hastighet; //Flytt i Y-led skottlista[si]->Sprite.Move(newx*-1, newy*-1); //gånger * -1 eftersom rakt upp är 0 och inte 90 grader if (check_hit(skottlista[si]->Sprite.GetPosition().x, skottlista[si]->Sprite.GetPosition().y) == true) //Man befinner sig 50 pixlar från kanten med kulan, dags för en explosion {//träff //******************************************************************************/ // När en explosion initieras sker det under flera steg // Kom ihåg att indexet/platsen i listan är samma för kulan som för explosionen // // För att inte orsaka en explosion som låter hela tiden kontrollerar vi // om kulan är i rörelse, om så är fallet låter explosionen. Den är tyst när hastighet=0 // och kulan inte är i rörelse längre // // Gör så att animationen av explosionen kan ses // Ge samma koordinater till explosionen som kulan har // Vi måste alltså trolla bort kanonkulan. Vi gör helt enkelt så att den inte kan ses // Sätt sedan kulans hastighet till 0, så att den inte åker omkring med explosionen // Spela upp explosionsljudet // // Slutligen flyttar vi på kulan om den kan ses // // Gå fram i kön till nästa kula och gör samma sak på den. // När du gått igenom hela kön ställer vi si på 0 så att vi kan gå igenom hela listan igen nästa varv //***************************************************************************/ //Enklast möjligt; samma explosionsljud används hela tiden if (skottlista[si]->hastighet > 0) {//kulan är i rörelse explosionslista[si]->playing = true; //börja spela upp animationen explosionslista[si]->target_x = skottlista[si]->Sprite.GetPosition().x; //x och y explosionslista[si]->target_y = skottlista[si]->Sprite.GetPosition().y; if(ljudeffekt.GetStatus() != sf::Sound::Status::Playing) { ljudeffekt.Play(); //spela upp ljudet om den inte spelas redan // while(ljudeffekt.GetStatus() == sf::Sound::Status::Playing) {} } else { ljudeffekt.Stop(); //stoppa och påbörja ljudet igen ljudeffekt.Play(); //while(ljudeffekt.GetStatus() == sf::Sound::Status::Playing) {} } }//kulan är i rörelse skottlista[si]->kan_ses=false; //Gör så att kulan försvinner skottlista[si]->hastighet = 0; //ge kulan hastighet 0 }//träff //Rita ut kanonkulebilden om den inte har träffat if (skottlista[si]->kan_ses==true) App.Draw(skottlista[si]->Sprite); si++; //Gå framåt i kön till nästa kula } //Man har inte nått slutet på listan si=0; //Nollställ si igen så att du kan räkna upp listan en gång till }//Slut på skott i listan //Var skall pansarvagnen gå? vinkel = stridsvagn1.sprite.GetRotation(); // 0 = rakt upp, startposition. Motsols 0-360 grader. newx= sin(vinkel*M_PI/180.0) * stridsvagn1.hastighet; newy= cos(vinkel*M_PI/180.0) * stridsvagn1.hastighet; stridsvagn1.sprite.Move(newx*-1, newy*-1); //Multiplikation * -1 eftersom rakt upp är 0 och inte 90 grader App.Draw(stridsvagn1.sprite); //Rita upp stridsvagnen //********** Visa upp animationerna*******************************************************************************************// // Dags att rita upp animationerna av explosionerna // Vi gör det sist av allt så att explosionerna ligger ovanpå // både kanonkulor och stridsvagnar. // // Precis som vi går igenom listan av kulor som skall flyttas // går vi igenom listan av explosioner för att se om värdet "playing" är true/sant // och om den inte spelats upp klart. Om båda är sant visas bildrutan upp // // Funktionen "explosionsbildruta" används för att välja ut rätt x/y koordinater // för rätt bild i sprite sheeten. if (exp_i > 0) {//det finns explosioner i listan while (ei <= (exp_i -1) && exp_i < 100) { //Man har inte nått slutet på listan if (explosionslista[ei]->playing == true && explosionslista[ei]->has_played == false ) //man är under 26 bilder { explosionsbildruta(spelklocka.GetElapsedTime(), *explosionslista[ei]); //Ge spritens bild rätt x och y koordinater, funktion explosionslista[ei]->get_explosionsbild(); //Välj rätt bild till explosionen, funktion inom klassen Explosion explosionslista[ei]->Sprite.SetPosition(explosionslista[ei]->target_x, explosionslista[ei]->target_y); //Placera ut den på rätt ställe App.Draw(explosionslista[ei]->Sprite); //Rita ut bilden på explosionen } ei++; //Gå till nästa post på explosionslistan } //Man har inte nått slutet på listan ei=0; //Nollställ ei igen så att du kan räkna upp listan en gång till }//Slut på explosioner i listan //**********Slut på att visa upp animationerna***************************************************** App.Display(); //visa upp ändringarna för användaren } //while 1 slutar return 0; } //main slutar********************************************************************************** //Funktioner som används i spelet void explosionsbildruta(double speltid, Explosion &Klassinstans) //Tar fram rätt bild i ett sprite sheet { //De koordinater vi är ute efter int utx = 0; int uty = 0; double tidsomgaott = speltid-Klassinstans.starttid; //Hur lång tid har det gått sedan explosionen startade //Starttiden finns lagrad inuti klassen double visningstid = 0.05; //0.05 sekund mellan varje bild //25 bilder = värden mellan 0.25-1.25 vid 0.05 //Räkna ut Y koordinaten if (tidsomgaott <= (visningstid * 5)) //rad 1 uty = 0; else if(tidsomgaott > visningstid * 5 && tidsomgaott <= visningstid *10)//rad 2 uty = 64; else if(tidsomgaott > visningstid * 10 && tidsomgaott <= visningstid *15)//rad 3 uty = 128; else if(tidsomgaott > visningstid * 15 && tidsomgaott <= visningstid *20)//rad 4 uty = 192; else if(tidsomgaott > visningstid * 20 && tidsomgaott <= visningstid *25)//rad 5 uty = 256; //Räkna ut x koordinaten //Rad 1 if (tidsomgaott >= 0.0 && tidsomgaott < visningstid) utx = 0; //ruta 1 else if (tidsomgaott > visningstid && tidsomgaott < (visningstid * 2)) utx= 64; else if (tidsomgaott > (visningstid * 2) && tidsomgaott < (visningstid * 3)) utx= 128; else if (tidsomgaott > (visningstid * 3) && tidsomgaott < (visningstid * 4)) utx= 192; else if (tidsomgaott > (visningstid * 4) && tidsomgaott < (visningstid * 5)) utx= 256; //Rad 2 else if (tidsomgaott > visningstid * 5 && tidsomgaott < visningstid *6) utx= 0; //ruta 1 else if (tidsomgaott > visningstid * 6 && tidsomgaott < (visningstid * 7)) utx= 64; else if (tidsomgaott > (visningstid * 7) && tidsomgaott < (visningstid * 8)) utx= 128; else if (tidsomgaott > (visningstid * 8) && tidsomgaott < (visningstid * 9)) utx= 192; else if (tidsomgaott > (visningstid * 9) && tidsomgaott < (visningstid * 10)) utx= 256; //Rad 3 else if (tidsomgaott > visningstid * 10 && tidsomgaott < visningstid *11) utx= 0; //ruta 1 else if (tidsomgaott > visningstid * 11 && tidsomgaott < (visningstid * 12)) utx= 64; else if (tidsomgaott > (visningstid * 12) && tidsomgaott < (visningstid * 13)) utx= 128; else if (tidsomgaott > (visningstid * 13) && tidsomgaott < (visningstid * 14)) utx= 192; else if (tidsomgaott > (visningstid * 14) && tidsomgaott < (visningstid * 15)) utx= 256; //Rad4 else if (tidsomgaott > visningstid * 15 && tidsomgaott < visningstid * 16) utx= 0; //ruta 1 else if (tidsomgaott > visningstid * 16 && tidsomgaott < (visningstid * 17)) utx= 64; else if (tidsomgaott > (visningstid * 17) && tidsomgaott < (visningstid * 18)) utx= 128; else if (tidsomgaott > (visningstid * 18) && tidsomgaott < (visningstid * 19)) utx= 192; else if (tidsomgaott > (visningstid * 19) && tidsomgaott < (visningstid * 20)) utx= 256; //Rad5 else if (tidsomgaott > visningstid * 20 && tidsomgaott < visningstid *21) utx= 0; //ruta 1 else if (tidsomgaott > visningstid * 21 && tidsomgaott < visningstid * 22) utx= 64; else if (tidsomgaott > visningstid * 22 && tidsomgaott < visningstid * 23) utx= 128; else if (tidsomgaott > visningstid * 23 && tidsomgaott < visningstid * 24) utx= 192; else if (tidsomgaott > visningstid * 24 && tidsomgaott <= visningstid * 25) utx= 256; else if (tidsomgaott > (visningstid * 25)) Klassinstans.has_played = true; //Visa bara 25 bilder, sluta spela upp animationen Klassinstans.topkoordinatX = utx; //skriv in koordinaterna direkt i klassen Klassinstans.topkoordinatY = uty; //låt klassen beräkna bilden }//funktionsslut //Kolla om kulan har träffat en vägg*********************************************************** bool check_hit(double x, double y) //se om kulan träffat { bool kulan_har_traeffat = false; if (x < 50 || x > 750) kulan_har_traeffat = true; if (y < 50 || y > 550) kulan_har_traeffat = true; return kulan_har_traeffat; } //Rotera stridsvagn mot en speciell koordinat ***************************************************************************************** double mot_koordinat(sf::Sprite &sprite, sf::RenderWindow &window, double position_X, double position_Y, int gradjusterare) { double spritensvinkel; //Den vinkel vår sprite stannar på double PI = 3.14159265358979323846; //Utifall att pi inte är definierad if (position_X <= sprite.GetPosition().x) //Kollar om inte x eller y är likadana sprite.SetRotation (((-1* 360 / PI * (atan2(static_cast<double> (sprite.GetPosition().y - position_Y) , static_cast<double> (sprite.GetPosition().x - position_X))))/2)+gradjusterare); else sprite.SetRotation (((-1* 360 / PI *(atan2(static_cast<double> (sprite.GetPosition().y - position_Y) , static_cast<double> (sprite.GetPosition().x - position_X))))/2)+gradjusterare); window.Draw(sprite); //Rita ut ändringen spritensvinkel = sprite.GetRotation(); //ta fram vinkeln // cout << spritensvinkel << endl; kontrollera vinkeln i konsollfönstret return spritensvinkel; //Skicka tillbaka vinkeln } //***************************************PROGRAMSLUT*****************************************************************//
Slutsats
redigeraVad kan vi säga om det här? I enklare spel fungerar listor med ett fast antal poster bra. Får man bara litet känsla för hur animationer visas går det enkelt att använda listor. Har du ett spel där alla pjäser är kända när du börjar: 52 kort i en kortlek, 32 schackpjäser osv går det hur bra som helst och hålla ett spel på den här nivån. Problemet är att det inte går att radera de klasskopior som ligger i listorna, en minnesläcka bildas. För att komma runt det finns två sätt. Det ena är att använda länkade listor, ett rätt gammaldags och plåttrigt system. Det andra är att lägga in kopiorna i en "Vector" eller ett "Deque", olika moderna listtyper i C++ som kan växa obehindrat utan minnesläckor. Jag kommer bara att använda mig av dem i kommande kapitel.
Skall du ha mer än ett explosionsljud som låter samtidigt kan du endera skapa en bunt "Sound" som delar samma ljudfil. Kontrollera om ljudet i ett valt "Sound" inte spelas och i sådana fall spela upp det, eller skapa ett nytt "Sound" varje gång en granat exploderar. SFML klarar 512 stycken olika "Sound" kopior innan det kraschar. Även ljud kan läggas i "Vector" och "Deque", vilket underlättar speltillverkning en hel del.
Vector
redigeraDet finns ett tillägg till C++ som heter STL. I det paketet finns (bland annat) tre riktigt bra klasser som används just som kontainrar. Man har dem för att själv slippa hålla ordning på minneshanteringen. De som främst används är:
- Vector = spelare läggs vanligen in i slutet av listan.
- Deque = spelare läggs vanligen in i början eller slutet av listan
- List = används vanligen som mellankontainer om man snabbt t.ex. vill sortera en vector.
För att visa fördelarna med en vector har jag skrivit om hela koden är ovanför så att den använder vectorer (med obegränsat minne) istället för arrays (som tar slut vid 100 skott enligt koden).
Det finns litet olika jämförelser mellan array och vector vi kan göra: När vi skapar en lista för skotten skriver vi:
Pansarvagnsskott *skottlista[100];
Men om vi skapar en vector skriver vi:
std::vector<Pansarvagnsskott> vSkottlista;
När vi lägger in nya poster i listan skriver vi:
skottlista[skott_i] = new Pansarvagnsskott(1.0f,stridsvagn1.sprite.GetRotation(), true); //Lägger en kula i listan
på det gamla sättet men med en vector skriver vi:
vSkottlista.push_back( Pansarvagnsskott(1.0f,stridsvagn1.sprite.GetRotation(), true) ); //Lägger en kula i vector
Man använder inga -> tecken för att komma åt värdena i en vector. För att ställa om en klassinstans mittpunkt skriver man t.ex.:
vSkottlista.at(skott_i).Sprite.SetCenter(8,8); //Kanonkula. Sätt koordinaten mitt på, bilden är 16x16 stor
Jämför med hur man gör i en vanlig lista:
skottlista[skott_i]->Sprite.SetCenter(8,8);
När spelet är slut är det enkelt att radera hela vectorn med allt sitt innehåll med kommandot clear.
vSkottlista.clear();
Radera enstaka element i en vector
redigeraMan kan inte gå igenom en vector på samma sätt som man gör med en lista, kontrollera post efter post och radera de som motsvarar ett indexnummer. Det man får göra i en vector är att man noterar vilket indexnummer posten man står på har. Därefter ställer man sig på den första posten och sedan raderar man den post man sparat indexnumret till, med utgångspunkt från första posten. Krångligt?
Anta att vi vill radera post nummer fyra i en vector. Koden är då:
ei = 4; vSkottlista.erase(vSkottlista.begin() + ei); //Radera motsvarande kanonkula
Slutligen: när jag modifierade koden från listor till vectorer blev all grafik vita rutor, bilderna saknades. För att komma runt det problemet laddas bilderna in i själva programmet istället för inuti klasserna.
Include vector
redigeraNär man lägger till en vector kan det gå bra att inte lägga in någon "include" sats. Det finns andra headerfiler som kan inkludera den. I alla fall i visual c++ ingår STL containrarna som standard. Men, för att vara riktigt säker, bör du lägga till:
#include <vector>
Använder du ett deque får du lägga till den includesatsen också/istället.
#include <deque>
Komplett kod baserad på vector istället för array
redigera#include <iostream> #include <vector> //Lägg till när du använder en vector #include <SFML\System.hpp> #include <SFML\Graphics.hpp> #include <SFML\Window.hpp> #include <SFML\Audio.hpp> #define M_PI 3.14159265358979323846 /* pi som statisk konstant*/ #define SFML_STATIC //Se till så att det inte behövs extra DLL-filer using namespace std; //Class för skottet class Pansarvagnsskott { public : Pansarvagnsskott(double ut_hastighet, double ut_vinkel, bool ut_kan_ses); //Konstruktion ~Pansarvagnsskott(){}; double target_x; double target_y; double hastighet; double vinkel; bool kan_ses; sf::Sprite Sprite; // en per instans sf::Image Image; //bildfilshållaren }; Pansarvagnsskott::Pansarvagnsskott(double ut_hastighet, double ut_vinkel, bool ut_kan_ses) { hastighet=ut_hastighet; vinkel=ut_vinkel; kan_ses = ut_kan_ses; Sprite.SetRotation(vinkel);//Ge den rätt vinkel redan från start Sprite.SetImage(Image); //ge bilden till spriten //Obs, ingen image här - det ger vita rutaor i en vector } class Explosion { public: Explosion(bool ut_playing, bool ut_has_played, double ut_starttid); //Konstruktion ~Explosion(){};//Destruktor sf::Sprite Sprite; // en per instans sf::Image Image; //bildfilshållaren //var skall explosionen ske? double target_x; double target_y; //Var i sprite sheeten skall vi börja kopiera bilder int topkoordinatX; int topkoordinatY; double starttid; //Vid vilket ögonblick skall explosionen starta? bool playing; //visas den upp? bool has_played; //Har den visats färdigt? void get_explosionsbild() //Ta ut rätt koordinater i sprite sheeten för visning av explosion { //Ta fram rätt bild Sprite.SetSubRect(sf::IntRect(topkoordinatX,topkoordinatY, topkoordinatX + 64,topkoordinatY + 64)); //Funktionen ger övre vänstra hörnets koordinater //då kan vi själva räkna ut nedre högra hörnet eftersom bilderna är 64 x 64 stora } }; Explosion::Explosion(bool ut_playing, bool ut_has_played, double ut_starttid) //Initiering { playing = ut_playing; //Avgör om animationen spelar. Ange false som standard has_played = ut_has_played; //Ställs från funktion utanför klass, false från början starttid = ut_starttid; //När börjar explosionen Sprite.SetImage(Image); //ge bilden till spriten //Obs, ingen image här - det ger vita rutaor i en vector } //En moderklass för alla fordon i spelet class fordon { public: //Konstruktordeklaration, definition utanför klassdeklarationen fordon(double hastighet, double spelare_x, double spelare_y, int bensin, int pansar);//startvärden //Destruktion ~fordon(){}; double 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 int bensin; //Hur långt kan den köra int pansar; //Hur mycket tål den sf::Sprite sprite; //Används i grafiskt läge }; //Konstruktionsdeklaration, fordon-------------------------------------------------- fordon::fordon (double ut_hastighet, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_pansar) { hastighet=ut_hastighet; spelare_x=ut_spelare_x; spelare_y=ut_spelare_y; bensin = ut_bensin; pansar = ut_pansar; //Också vill vi se att det verkligen skapas ett fordon när underklasserna skapas std::cout << "Ett fordon rullar ut från fabriken!" << std::endl; } //Skapa en klass som ärver fordon class stridsvagn : public fordon { public: int ammunition; // Avgör hur många skott som finns i stridsvagnen. stridsvagn(double ut_hastighet, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_pansar): fordon(ut_hastighet, ut_spelare_x, ut_spelare_y, ut_bensin, ut_pansar) { ammunition=100; } //Destruktion ~stridsvagn(){}; //Funktioner void skott() { std::cout << "Skott kommer!" << std::endl; ammunition = ammunition-1; std::cout << "Skott kvar=" <<ammunition << std::endl; } }; void explosionsbildruta(double speltid, Explosion &Klassinstans); //Funktion som tar fram x och y koordinat på sprite sheet för explosionsbilder //och som stänger av animationen om fler än 25 bilder visas bool check_hit(double x, double y); //Funktion som kollar om kulan träffat ett mål double mot_koordinat(sf::Sprite &sprite, sf::RenderWindow &window, double position_X, double position_Y, int gradjusterare); //Funktion som roterar pansarvagnen mot en speciell koordinat //******************************************************PROGRAMSTART******************************************************** int main (int argc, char *argv) { //main startar //För att kunna styra animationer i spelet måste vi kunna hålla koll på tiden. //Det finns många sätt att göra det på och det här är det sämsta //men det fungerar och är lätt att förstå. sf::Clock spelklocka; //Skapa en spelklocka spelklocka.Reset(); //Ställ den på 0 //Övriga variabler double explosionsstarttid = 0.0f; //bool visaexplosion = false; //skall den visas? //Variabler för att hålla ordning i listorna int skott_i=0; // avgör var vi är i kanonkulelistan int si = 0; //räknare int exp_i=0; //0-100 avgör var vi är i explosionslistan int ei = 0; //räknare //Vector för pansarvagnsskott std::vector<Pansarvagnsskott> vSkottlista; //Vector för explosioner std::vector<Explosion> vExplosionslista; //Bilderna för skotten, dels en granat och dels en explosion //Ligger laddningen inom klassen skapas vita rutor istället sf::Image KanonkulaImage; //kanonkula if (!KanonkulaImage.LoadFromFile("Eight-Ball-icon.png")) //Programmet försöker att ladda in en bildfil i det tomma bildobjektet std::cout << "Kan inte hitta bilden: Eight-Ball-icon.png " << std::endl; sf::Image ExplosionImage; //explosion if (!ExplosionImage.LoadFromFile("xplosion17.png")) //Programmet försöker att ladda in en bildfil i det tomma bildobjektet std::cout << "Kan inte hitta bilden: xplosion17.png " << std::endl; //Olika värden för att styra stridsvagnen float newx = 0.0f; //ditt stridsvagnen skall gå float newy = 0.0f; //dit stridsvagnen skall gå float speed = 2.0f; //stridsvagnens hastighet. double vinkel =0.0f; //Stridsvagnens vinkel //Skapa först en stridsvagn //stridsvagn(int ut_hastighet, double ut_spelare_x, double ut_spelare_y, int ut_bensin, int ut_pansar) stridsvagn stridsvagn1(0.01f, 100.0,100.0,100,100); //Skapa bild sf::Image bild; //skapa en tom bildhållare som heter bild if (!bild.LoadFromFile("MH_Tank.png")) return EXIT_FAILURE; //fyll den tomma bildhållaren med bilden MH_tank.png stridsvagn1.sprite.SetImage(bild); //nu har vi en stridsvagn, Placera ut stridsvagnen på spelplanen stridsvagn1.sprite.SetPosition(stridsvagn1.spelare_x,stridsvagn1.spelare_y); //Det är jobbigt att inte riktigt veta var mitten är på stridsvagnen //Ändra positionen där stridsvagnen är från övre vänstra hörnet till mitt på bilden. stridsvagn1.sprite.SetCenter(stridsvagn1.sprite.GetSize().x / 2 , stridsvagn1.sprite.GetSize().y / 2); //Ladda in de två ljuden //Ljud för att avlossa kanonen sf::SoundBuffer kanonljud; //skapa en ljudbuffer/hållare if (!kanonljud.LoadFromFile("explosion-03.wav")) //ladda in en fil i hållaren { std::cout << "Kan inte hitta ljudfilen: explosion-03.wav " << endl; } sf::Sound kanoneffekt; //skapa ett ljud i spelet för kanonen som vi döper till kanoneffekt kanoneffekt.SetBuffer(kanonljud); // Ladda in ljudfilens värden i ljudet så att det går att spela upp. //Ljud när granaten exploderar sf::SoundBuffer explosionsljud; //skapa en ljudbuffer/hållare if (!explosionsljud.LoadFromFile("bomb-03.wav")) //ladda in en fil i hållaren { std::cout << "Kan inte hitta ljudfilen: bomb-03.wav " << endl; } sf::Sound ljudeffekt; //skapa ett ljud i spelet som vi döper till ljudeffekt ljudeffekt.SetBuffer(explosionsljud); // Ladda in ljudfilens värden i ljudet så att det går att spela upp. /************** Programstart ***********************************************************/ sf::RenderWindow App(sf::VideoMode(800, 600, 32), "Test av pansarvagn"); // Skapa fönstret vi skall testa explosionerna i while (App.IsOpened()) { //while 1 startar, spelet körs sf::Event Event; //kolla om mus/tangentbord används while (App.GetEvent(Event)) { //while 2 börjar, kontrollerar händelser if (Event.Type == sf::Event::MouseButtonPressed) // En musknapp har tryckts ner {//musknapp, vilken som helst if (Event.MouseButton.Button == sf::Mouse::Left) { //vänster musknapp //Vrid stridsvagnen i rätt vinkel mot_koordinat(stridsvagn1.sprite, App, App.GetInput().GetMouseX(), App.GetInput().GetMouseY(), 90); stridsvagn1.spelare_x = App.GetInput().GetMouseX(); //Ge stridsvagnen en X-koordinat att gå mot stridsvagn1.spelare_y = App.GetInput().GetMouseY(); //Ge stridsvagnen en Y-koordinat att gå mot } //vänster musknapp }//slut mus, vilken knapp som helst if (Event.Type == sf::Event::Closed) //kryssat på [x] symbolen? stäng programmet App.Close(); if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::Escape)) { //Avsluta programmet //Radera vectorer för att ge tillbaka minne vExplosionslista.clear(); vSkottlista.clear(); App.Close();//avsluta programmet om man klickar på ESC } //avsluta programmet /************************* Kanonskott *******************************************************************************************/ // Här kommer koden för att avlossa kanonen! // Ett explosionsljud spelas upp // Mängden ammunition i stridsvagnen räknas ner ett steg // En ny kanonkula skapas och läggs in i en array // Kanonkulans mittpunkt sätts mitt på kulan istället för i övre högra hörnet // Kanonkulan har sin mittpunkt på samma punkt som pansarvagnen har sin // För varje kula skapar vi en potentiell explosion med samma indexnummer // Explosionens mittpunkt placeras på samma ställe som kulans mittpunkt // Räknaren skott_i flyttar ett steg upp och väntar på nästa kula // Slutligen synkroniseras skottlistan med explosionslistan /********************************************************************************************************************/ if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::Space)) {//Spacetangent nertryckt kanoneffekt.Play(); //spela upp kanonljudet stridsvagn1.skott(); //Skjut med stridsvagnens funktion //Nytt- vi använder vektorer vSkottlista.push_back( Pansarvagnsskott(1.0f,stridsvagn1.sprite.GetRotation(), true) ); //Lägger en kula i vector //Skapa ett nytt skott med: //hastighet 1.0f och vinkel exakt samma som stridsvagnen och true= den syns. //vSkottlista.at(skott_i).Sprite.SetCenter(vSkottlista.at(skott_i).Sprite.GetSize().x / 2 , vSkottlista.at(skott_i).Sprite.GetSize().y / 2); //Sätt koordinaten mitt på vSkottlista.at(skott_i).Sprite.SetCenter(8,8); //Sätt koordinaten mitt på, bilden är 16x16 stor //Kan även skrivas vSkottlista[skott_i].Sprite.SetCenter(vSkottlista.at(skott_i).Sprite.GetSize().x / 2 , vSkottlista.at(skott_i).Sprite.GetSize().y / 2); //Sätt koordinaten mitt på vSkottlista.at(skott_i).Sprite.SetPosition(stridsvagn1.sprite.GetPosition().x,stridsvagn1.sprite.GetPosition().y); //Placera ut den på samma punkt som pansarvagnen vExplosionslista.push_back( Explosion(false, false, spelklocka.GetElapsedTime()) );//lägger en explosion i vector vExplosionslista.at(skott_i).Sprite.SetCenter(32,32); //Hela bilden är 64 x 64 //Slutligen, ge bilderna utifrån vExplosionslista.at(skott_i).Sprite.SetImage(ExplosionImage); //ge bilden till spriten vSkottlista.at(skott_i).Sprite.SetImage(KanonkulaImage); //ge bilden till spriten //Både nytt och gammalt skott_i++; //Räkna upp till nästa plats i kön för nästa skott exp_i = skott_i; //Synkronisera de två listorna (skott och explosioner) så att de alltid pekar på samma postnummer i //båda listorna. Det behövs sedan för att rita ut korrekt animation på rätt plats }//Spacetangent nertryckt } //while 2 slutar //Slutligen visar vi upp ändringarna om och om igen många gånger i sekunden App.Clear(sf::Color(0, 100, 0)); //rensa allt i fönstret och ersätt med grönfärg /**************************** Flytta på skotten på spelplanen ****************************************/ if ( vSkottlista.empty() == false ) { // om vectorn inte är tom for (si=0; si < vSkottlista.size(); si++) //for istället för while eftersom vi inte vet antalet poster längre { //Gå igenom hela listan vinkel = vSkottlista.at(si).vinkel; // 0 = rakt upp, startposition. Motsols 0-360 grader. newx= sin(vinkel*M_PI/180.0) * vSkottlista.at(si).hastighet; //Flytt i x-led newy= cos(vinkel*M_PI/180.0) * vSkottlista.at(si).hastighet; //Flytt i Y-led vSkottlista.at(si).Sprite.Move(newx*-1, newy*-1); //gånger * -1 eftersom rakt upp är 0 och inte 90 grader //Kolla träff if (check_hit(vSkottlista.at(si).Sprite.GetPosition().x, vSkottlista.at(si).Sprite.GetPosition().y) == true) {//träff //Enklast möjligt; samma explosionsljud används hela tiden if (vSkottlista.at(si).hastighet > 0) {//kulan är i rörelse vExplosionslista.at(si).playing = true; //börja spela upp animationen. vExplosionslista.at(si).target_x = vSkottlista.at(si).Sprite.GetPosition().x; //x och y vExplosionslista.at(si).target_y = vSkottlista.at(si).Sprite.GetPosition().y; if(ljudeffekt.GetStatus() != sf::Sound::Status::Playing) { ljudeffekt.Play(); //spela upp ljudet om den inte spelas redan // while(ljudeffekt.GetStatus() == sf::Sound::Status::Playing) {} } else { ljudeffekt.Stop(); //stoppa och påbörja ljudet igen ljudeffekt.Play(); //while(ljudeffekt.GetStatus() == sf::Sound::Status::Playing) {} } }//kulan är i rörelse vSkottlista.at(si).kan_ses=false; //Gör så att kulan försvinner vSkottlista.at(si).hastighet = 0; //ge kulan hastighet 0 }//träff if (vSkottlista.at(si).kan_ses==true) App.Draw(vSkottlista.at(si).Sprite); }//gå igenom hela listan }//om vectorn inte är tom //Var skall pansarvagnen gå? vinkel = stridsvagn1.sprite.GetRotation(); // 0 = rakt upp, startposition. Motsols 0-360 grader. newx= sin(vinkel*M_PI/180.0) * stridsvagn1.hastighet; newy= cos(vinkel*M_PI/180.0) * stridsvagn1.hastighet; stridsvagn1.sprite.Move(newx*-1, newy*-1); //Multiplikation * -1 eftersom rakt upp är 0 och inte 90 grader App.Draw(stridsvagn1.sprite); //Rita upp stridsvagnen //********** Visa upp animationerna*******************************************************************************************// // Dags att rita upp animationerna av explosionerna // Vi gör det sist av allt så att explosionerna ligger ovanpå // både kanonkulor och stridsvagnar. // // Precis som vi går igenom listan av kulor som skall flyttas // går vi igenom listan av explosioner för att se om värdet "playing" är true/sant // och om den inte spelats upp klart. Om båda är sant visas bildrutan upp // // Funktionen "explosionsbildruta" används för att välja ut rätt x/y koordinater // för rätt bild i sprite sheeten. //__________________________VECTORSÄTTET___________________________----- if ( vExplosionslista.empty() == false ) { // om vectorn inte är tom for (ei=0; ei < vExplosionslista.size(); ei++) //for istället för while eftersom vi inte vet antalet poster längre { //Gå igenom hela listan if (vExplosionslista.at(ei).playing == true && vExplosionslista.at(ei).has_played == false ) //man är under 26 bilder { explosionsbildruta(spelklocka.GetElapsedTime(), vExplosionslista.at(ei)); //Ge spritens bild rätt x och y koordinater, funktion vExplosionslista.at(ei).get_explosionsbild(); //Välj rätt bild till explosionen, funktion inom klassen Explosion vExplosionslista.at(ei).Sprite.SetPosition(vExplosionslista.at(ei).target_x, vExplosionslista.at(ei).target_y); //Placera ut den på rätt ställe App.Draw(vExplosionslista.at(ei).Sprite); //Rita ut bilden på explosionen } } //Gå igenom hela listan }//Om vectorn inte är tom //**********Slut på att visa upp animationerna***************************************************** App.Display(); //visa upp ändringarna för användaren //Radera slutligen alla kulor och explosioner som inte används if ( vExplosionslista.empty() == false ) { // om vectorn inte är tom for (ei=0; ei < vExplosionslista.size(); ei++) //for istället för while eftersom vi inte vet antalet poster längre { //Gå igenom hela listan if (vExplosionslista.at(ei).playing == false && vExplosionslista.at(ei).has_played == true) //man är över 26 bilder { //animationen är klar vExplosionslista.erase(vExplosionslista.begin() + ei); //Raderar ut den färdiga explosionen vSkottlista.erase(vSkottlista.begin() + ei); //Radera motsvarane kanonkula skott_i = skott_i - 1; //Återställ antalet poster exp_i = skott_i; //I bägge variablerna } //animationen klar } //Gå igenom hela listan } // om vectorn inte är tom } //while 1 slutar return 0; } //main slutar********************************************************************************** //Funktioner som används i spelet void explosionsbildruta(double speltid, Explosion &Klassinstans) { //De koordinater vi är ute efter int utx = 0; int uty = 0; double tidsomgaott = speltid-Klassinstans.starttid; //Hur lång tid har det gått sedan explosionen startade //Starttiden finns lagrad inuti klassen double visningstid = 0.05; //0.05 sekund mellan varje bild //25 bilder = värden mellan 0.25-1.25 vid 0.05 //Räkna ut Y koordinaten if (tidsomgaott <= (visningstid * 5)) //rad 1 uty = 0; else if(tidsomgaott > visningstid * 5 && tidsomgaott <= visningstid *10)//rad 2 uty = 64; else if(tidsomgaott > visningstid * 10 && tidsomgaott <= visningstid *15)//rad 3 uty = 128; else if(tidsomgaott > visningstid * 15 && tidsomgaott <= visningstid *20)//rad 4 uty = 192; else if(tidsomgaott > visningstid * 20 && tidsomgaott <= visningstid *25)//rad 5 uty = 256; //Räkna ut x koordinaten //Rad 1 if (tidsomgaott >= 0.0 && tidsomgaott < visningstid) utx = 0; //ruta 1 else if (tidsomgaott > visningstid && tidsomgaott < (visningstid * 2)) utx= 64; else if (tidsomgaott > (visningstid * 2) && tidsomgaott < (visningstid * 3)) utx= 128; else if (tidsomgaott > (visningstid * 3) && tidsomgaott < (visningstid * 4)) utx= 192; else if (tidsomgaott > (visningstid * 4) && tidsomgaott < (visningstid * 5)) utx= 256; //Rad 2 else if (tidsomgaott > visningstid * 5 && tidsomgaott < visningstid *6) utx= 0; //ruta 1 else if (tidsomgaott > visningstid * 6 && tidsomgaott < (visningstid * 7)) utx= 64; else if (tidsomgaott > (visningstid * 7) && tidsomgaott < (visningstid * 8)) utx= 128; else if (tidsomgaott > (visningstid * 8) && tidsomgaott < (visningstid * 9)) utx= 192; else if (tidsomgaott > (visningstid * 9) && tidsomgaott < (visningstid * 10)) utx= 256; //Rad 3 else if (tidsomgaott > visningstid * 10 && tidsomgaott < visningstid *11) utx= 0; //ruta 1 else if (tidsomgaott > visningstid * 11 && tidsomgaott < (visningstid * 12)) utx= 64; else if (tidsomgaott > (visningstid * 12) && tidsomgaott < (visningstid * 13)) utx= 128; else if (tidsomgaott > (visningstid * 13) && tidsomgaott < (visningstid * 14)) utx= 192; else if (tidsomgaott > (visningstid * 14) && tidsomgaott < (visningstid * 15)) utx= 256; //Rad4 else if (tidsomgaott > visningstid * 15 && tidsomgaott < visningstid * 16) utx= 0; //ruta 1 else if (tidsomgaott > visningstid * 16 && tidsomgaott < (visningstid * 17)) utx= 64; else if (tidsomgaott > (visningstid * 17) && tidsomgaott < (visningstid * 18)) utx= 128; else if (tidsomgaott > (visningstid * 18) && tidsomgaott < (visningstid * 19)) utx= 192; else if (tidsomgaott > (visningstid * 19) && tidsomgaott < (visningstid * 20)) utx= 256; //Rad5 else if (tidsomgaott > visningstid * 20 && tidsomgaott < visningstid *21) utx= 0; //ruta 1 else if (tidsomgaott > visningstid * 21 && tidsomgaott < visningstid * 22) utx= 64; else if (tidsomgaott > visningstid * 22 && tidsomgaott < visningstid * 23) utx= 128; else if (tidsomgaott > visningstid * 23 && tidsomgaott < visningstid * 24) utx= 192; else if (tidsomgaott > visningstid * 24 && tidsomgaott <= visningstid * 25) utx= 256; else if (tidsomgaott > (visningstid * 25)) Klassinstans.has_played = true; //Visa bara 25 bilder, sluta spela upp animationen Klassinstans.topkoordinatX = utx; //skriv in koordinaterna direkt i klassen Klassinstans.topkoordinatY = uty; //låt klassen beräkna bilden } //funktionsslut //Kolla om kulan har träffat en vägg*********************************************************** bool check_hit(double x, double y) //se om kulan träffat { bool kulan_har_traeffat = false; if (x < 50 || x > 750) kulan_har_traeffat = true; if (y < 50 || y > 550) kulan_har_traeffat = true; return kulan_har_traeffat; } //Rotera stridsvagn mot en speciell koordinat ***************************************************************************************** double mot_koordinat(sf::Sprite &sprite, sf::RenderWindow &window, double position_X, double position_Y, int gradjusterare) { double spritensvinkel; //Den vinkel vår sprite stannar på double PI = 3.14159265358979323846; //Utifall att pi inte är definierad if (position_X <= sprite.GetPosition().x) //Kollar om inte x eller y är likadana sprite.SetRotation (((-1* 360 / PI * (atan2(static_cast<double> (sprite.GetPosition().y - position_Y) , static_cast<double> (sprite.GetPosition().x - position_X))))/2)+gradjusterare); else sprite.SetRotation (((-1* 360 / PI *(atan2(static_cast<double> (sprite.GetPosition().y - position_Y) , static_cast<double> (sprite.GetPosition().x - position_X))))/2)+gradjusterare); window.Draw(sprite); //Rita ut ändringen spritensvinkel = sprite.GetRotation(); //ta fram vinkeln return spritensvinkel; //Skicka tillbaka vinkeln } //***************************************PROGRAMSLUT*****************************************************************//