Hoppa till innehållet

Programmera spel i C++ för nybörjare/Animation 3

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

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.

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