Programmera spel i C++ för nybörjare/Animation 3
(Genomgången förutsätter att du har en fungerande installation av Microsoft Visual C++ 2010 Express och SFML 1.6 på din dator.)
Alla animation måste ske inom spel loopen vilket innebär att föregående kod inte fungerar i ett riktigt spel. När animationen flyttas ut i en funktion avstannar hela spelet när funktionen visas upp, och det är inte så kul.
Förhindra grafik med vita rutor
[redigera]Det första vi måste göra är att utöka klassen. Det är nämligen så att om bilden laddas in i minnet på programmet utan koppling till en sprite i en klass kommer det bara att synas vita rutor istället för bilder på skärmen.
Klassen, i sin enklaste form, ser ut så här:
class Explosion { public : Explosion(); //Konstruktion sf::Sprite Sprite; // en per instans sf::Image Image; //bildfilshållaren }; Explosion::Explosion() { 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 }
Det innebär också 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.
Tid
[redigera]Det vi sedan får fixa är en variabel som tar emot starttiden. Tid räknas i double så vi skriver i
double explosionsstarttid = 0.0f;
Nu har vi bara en enda explosion, om du skulle ha 10 explosioner som kan visas samtidigt skulle du vara tvungen att ha
double explosionsstarttid1 = 0.0f; osv... double explosionsstarttid10 = 0.0f;
Vi måste också ha en klocka som sätter igång när spelet börjar och som aldrig nollställs. Det finns många andra sätt att kontrollera animationer i samband med tid, det här är det sämsta, men det är det sätt som är lättast att förstå eftersom det beräknar tid direkt. Därför använder vi det här.
sf::Clock spelklocka; //Skapa en spelklocka spelklocka.Reset(); //Ställ den på 0
Tanken är att vi skall jämföra tid. Vi har en starttid som sparas genom att Return-tangenten trycks ner:
explosionsstarttid = spelklocka.GetElapsedTime();
Varje spelloop efter detta kommer vi att jämföra tiden som gått med det tiden var när tangenten trycktes ner. Tiden nu får vi från spelklockan. För att detta då skall fungera måste vi förändra funktionen totalt:
bool pang(float x, float y, Explosion &Klassinstans, double speltid, double explosionsstarttid);
- Ljud? Det måste ligga inom spelloopen så vi plockar helt sonika ut det ur funktionen. Vi vet hur det synkar i tid mellan animation och explosionsljud nu.
- Window? Vi behöver inget spelfönster i funktionen eftersom vi har animationen utanför funktionen.
- Image? behövs inte heller längre eftersom den följer med klassen.
- Bool? Vi kommer att använda bool (sant/falskt) för att se om vi skall via upp animationen.
Explosionsanimation
[redigera]Funktionen är enkel och måste byggas ut för ett mer avancerat spel. När man trucker ner Enter tangenten visas animationen upp. Den visas upp så länge funktionen är ”true”.
if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::Return)) {//Entertangent nertryckt //Visa upp explosionen om man trycker ner return-knappen explosionsstarttid = spelklocka.GetElapsedTime(); //Mata in vad tiden är just nu // så att det går att jämföra med hur mycket tid det behövs för att visa nästa bild visaexplosion = true; //Gör det möjligt att se explosionen ljudeffekt.Play(); //spela upp ljudet }//Entertangent nertryckt
I uppritningsloopen har vi sedan:
if (visaexplosion ==true) { if (pang(100, 100, Explosionskopia, spelklocka.GetElapsedTime(),explosionsstarttid) == true) App.Draw(Explosionskopia.Sprite);//Rita ut explosionen }
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 fyra stycken, t.ex:
Klassinstans.Sprite.SetSubRect(sf::IntRect(0,0,bildbredd,bildhojd));
Så är de två första bildens x,y koordinater i övre vänstra hörnet och de två andra är x,y koordinater i nedre högra hörnet på en bildruta i en spritemap. Har man en spritemap med bilder som har olika storlekar är det här i princip det enda sättet att få in precis rätt bild. I föregående exempel använde vi funktionen för att spela upp hela animationen, men i den här (slutgiltiga) versionen avänder vi bara funktionen för att klistra fast rätt bild på vår sprite.
Komplett kod
[redigera]#include <iostream> #include <SFML/System.hpp> #include <SFML/Window.hpp> #include <SFML/Graphics.hpp> #include <SFML/Audio.hpp> #define SFML_STATIC //Se till så att det inte behövs extra DLL-filer using namespace std; // utifall att konsollen behövs för felsökning class Explosion { public : Explosion(); //Konstruktion sf::Sprite Sprite; // en per instans sf::Image Image; //bildfilshållaren }; Explosion::Explosion() { 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 } bool pang(float x, float y, Explosion &Klassinstans, double speltid, double explosionsstarttid); //Funktionen som initierar explosionen 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 double explosionsstarttid = 0.0f; bool visaexplosion = false; //skall den visas? sf::SoundBuffer explosionsljud; //skapa en ljudbuffer/hållare if (!explosionsljud.LoadFromFile("bomb-03.wav")) //ladda in en fil i hållaren { 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. //Skapa en kopia av klassen explosionen Explosion Explosionskopia; sf::RenderWindow App(sf::VideoMode(800, 600, 32), "Test av explosion"); // Skapa fönstret vi skall testa explosionen i while (App.IsOpened()) { //while 1 startar sf::Event Event; //kolla om mus/tangentbord används while (App.GetEvent(Event)) { //while 2 börjar if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::Escape)) App.Close();//avsluta programmet if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::Return)) {//Entertangent nertryckt //Visa upp explosionen om man trycker ner return-knappen explosionsstarttid = spelklocka.GetElapsedTime(); //Mata in vad tiden är just nu // så att det går att jämföra med hur mycket tid det behövs för att visa nästa bild visaexplosion = true; //Gör det möjligt att se explosionen ljudeffekt.Play(); //spela upp ljudet }//Entertangent 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 if (visaexplosion ==true) { if (pang(100, 100, Explosionskopia, spelklocka.GetElapsedTime(),explosionsstarttid) == true) App.Draw(Explosionskopia.Sprite);//Rita ut explosionen } App.Display(); //visa upp ändringarna för användaren } //while 1 slutar } //main slutar bool pang(float x, float y, Explosion &Klassinstans, double speltid, double explosionsstarttid) { bool out = true; double tidsomgaott = speltid-explosionsstarttid; //Hur lång tid har det gått sedan explosionen startade float visningstid = 0.05; //0.05 sekund mellan varje bild Klassinstans.Sprite.SetPosition(x,y); int bildbredd = 64; //varje enskild rutas bredd int bildhojd = 64; //varje enskild rutas höjd // Rad 1 __________________________________________________________________________________________________ if (tidsomgaott >= 0.0 && tidsomgaott < visningstid) { Klassinstans.Sprite.SetSubRect(sf::IntRect(0,0,bildbredd,bildhojd)); } else if (tidsomgaott > visningstid && tidsomgaott < (visningstid * 2)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd,0,bildbredd * 2, bildhojd)); } else if (tidsomgaott > (visningstid * 2) && tidsomgaott < (visningstid * 3)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd * 2,0,bildbredd * 3,bildhojd)); } else if (tidsomgaott > (visningstid * 3) && tidsomgaott < (visningstid * 4)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd *3,0,bildbredd * 4,bildhojd)); } else if (tidsomgaott > (visningstid * 4) && tidsomgaott < (visningstid * 5)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd * 4,0,bildbredd * 5,bildhojd)); } // Rad 2 __________________________________________________________________________________________________ else if (tidsomgaott > visningstid * 5 && tidsomgaott < visningstid *6) { Klassinstans.Sprite.SetSubRect(sf::IntRect(0,bildhojd,bildbredd,bildhojd * 2)); } else if (tidsomgaott > visningstid * 6&& tidsomgaott < (visningstid * 7)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd,bildhojd,bildbredd * 2,bildhojd * 2)); } else if (tidsomgaott > (visningstid * 7) && tidsomgaott < (visningstid * 8)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd * 2,bildhojd, bildbredd * 3,bildhojd * 2)); } else if (tidsomgaott > (visningstid * 8) && tidsomgaott < (visningstid * 9)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd * 3,bildhojd, bildbredd * 4,bildhojd * 2)); } else if (tidsomgaott > (visningstid * 9) && tidsomgaott < (visningstid * 10)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd * 4,bildhojd,bildbredd * 5,bildhojd * 2)); } // Rad 3 __________________________________________________________________________________________________ else if (tidsomgaott > visningstid * 10 && tidsomgaott < visningstid *11) { Klassinstans.Sprite.SetSubRect(sf::IntRect(0,bildhojd * 2,bildbredd,bildhojd * 3)); } else if (tidsomgaott > visningstid * 11 && tidsomgaott < (visningstid * 12)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd ,bildhojd * 2, bildbredd * 2, bildhojd * 3)); } else if (tidsomgaott > (visningstid * 12) && tidsomgaott < (visningstid * 13)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd * 2,bildhojd * 2, bildbredd * 3 ,bildhojd * 3)); } else if (tidsomgaott > (visningstid * 13) && tidsomgaott < (visningstid * 14)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd * 3,bildhojd * 2, bildbredd * 4,bildhojd * 3)); } else if (tidsomgaott > (visningstid * 14) && tidsomgaott < (visningstid * 15)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd * 4,bildhojd * 2, bildbredd * 5, bildhojd * 3)); } // Rad 4 __________________________________________________________________________________________________ else if (tidsomgaott > visningstid * 15 && tidsomgaott < visningstid * 16) { Klassinstans.Sprite.SetSubRect(sf::IntRect(0,bildhojd * 3,bildbredd,bildhojd * 4)); } else if (tidsomgaott > visningstid * 16 && tidsomgaott < (visningstid * 17)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd ,bildhojd * 3, bildbredd * 2, bildhojd * 4)); } else if (tidsomgaott > (visningstid * 17) && tidsomgaott < (visningstid * 18)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd * 2,bildhojd * 3, bildbredd * 3 ,bildhojd * 4)); } else if (tidsomgaott > (visningstid * 18) && tidsomgaott < (visningstid * 19)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd * 3,bildhojd * 3, bildbredd * 4,bildhojd * 4)); } else if (tidsomgaott > (visningstid * 19) && tidsomgaott < (visningstid * 20)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd * 4,bildhojd * 3, bildbredd * 5, bildhojd * 4)); } // Rad 5 __________________________________________________________________________________________________ else if (tidsomgaott > visningstid * 20 && tidsomgaott < visningstid *21) { Klassinstans.Sprite.SetSubRect(sf::IntRect(0,bildhojd * 4,bildbredd,bildhojd * 5)); } else if (tidsomgaott > visningstid * 21 && tidsomgaott < (visningstid * 22)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd ,bildhojd * 4, bildbredd * 2, bildhojd * 5)); } else if (tidsomgaott > (visningstid * 22) && tidsomgaott < (visningstid * 23)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd * 2,bildhojd * 4, bildbredd * 3 ,bildhojd * 5)); } else if (tidsomgaott > (visningstid * 23) && tidsomgaott < (visningstid * 24)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd * 4,bildhojd * 3, bildbredd * 4,bildhojd * 5)); } else if (tidsomgaott > (visningstid * 24) && tidsomgaott <= (visningstid * 25)) { Klassinstans.Sprite.SetSubRect(sf::IntRect(bildbredd * 4,bildhojd * 4, bildbredd * 5, bildhojd * 5)); } if (tidsomgaott > (visningstid * 25)) { out = false;} //Visa bara 25 bilder return out; } //pang slutar