Hoppa till innehållet

Programmera spel i C++ för nybörjare/Plattformsspel

Från Wikibooks


Man kan säga att det finns två helt skilda sätt att bygga upp ett plattformsspel.

Det första sättet är att bygga upp allting med sprites. Varenda pryl, oavsett om man kan krocka med den eller inte, består av sprites. Programmeringsmässig är det enklast eftersom man kan lägga in spritesen i listor/vectors och snabbt gå igenom dem för att se om man krockat. Speciellt om man har många saker som rör sig samtidigt är det ett enkelt sätt att ha kontroll över spelplanen. Här finns rikitigt fin grafik för plattformsspel som är fri att använda till plattformsspel som bygger på den här modellen:

http://www.spicypixel.net/2008/01/10/gfxlib-fuzed-a-free-developer-graphic-library/


Det andra sättet fungerar bättre om man själv är konstnärligt lagd. Man ritar upp en bild med alla saker man kan krocka med. Därefter kontrollerar man spelarens position och utifrån den beräknar man vad som händer på spelplanen. Den här typen av spel blir mycket vackrare eftersom allting är unikt och speciellt på spelplanen. Nackdelen är att det kräver mycket mer kodning och det kan lätt bli alltför svårt att hantera om det är många saker som rör sig samtidigt på spelplanen.

Ett mellanting är om man ritar hela bakgrunden som en enda bild och sedan lägger ut osynliga spries på de platser man kan krocka. En sprite kan göras osynlig på två sätt. Endera gör man en png-bild med en enda färg och anger just den färgen som genomskinlig. Eller så kommenterar man bort utritningen av spriten:

//App.Draw(spelare1.sprite);

Det kan nämligen vara bra att se var spriten är under tiden man designar spelet. Man kommenterar bort koden när spelet är färdigt. Även om spritesen inte ritas ut finns de på spelplanen och spelet kommer att räkna med dem, de syns bara inte för spelaren.


Gravitation

[redigera]

I plattformsspel är gravitation kanske den viktigaste kraften. Därför måste vi hela tiden ta med den i beräkningen. I verkligheten (i Sverige) är gravitationen 9.82 m/sek. I spel kan du helt bortse från den siffran, istället måste du ta någon egen siffra som passar för det spel som just du tillverkar.

I fallet här nedanför är gravitationen 0.0003 neråt och hastigheten när man hoppar uppåt 0.4 (-0.4 eftersom det är uppåt. När man trycker på [SPACE] tangenten kontrollerar spelet om man står på fast grund. Det kontrolleras mot en variabel som finns lagrad i player-klassen:

bool bFastmark; //Står figuren på fast mark

Man skall inte kunna hoppa när man har fötterna i luften eftersom man inte har något att ta spjärn emot då. I och med att man hoppar ställer spelet om att man står på fast mark till false och lägger till gravitationen till hastigheten. -0.4 + 0.0003 = 0.3997 första bildsvepet, sedan kommer 59 till den sekunden om bildskärmen står på 60 hertz. Varje bildsvep minskas hastigheten 0.003 till. Efter en tid kommer hastigheten att vara 0 och sedan blir den positiv. Då faller bollen neråt igen precis som äpplet som föll på Newtons huvud när han sägs ha kommit på gravitationen. 0.0003 verkar inte vara mycket till gravitation, men det ger en lugn rörelse och fungerar bra.

Man kan lockas att göra som i "Pong" eller ping-pong spelet; när man nått en viss höjd ställer man om hastigheten så att den går från negativ till positiv, på samma sätt som ping-pong bollen ändrar riktning när den studsar mot överväggen genom att farten ändras från negativ till positiv. Det fungerar, men det ger en kurva som ser ut som ett upp och nervänt V, vilket inte är särskilt vackert. För att få en kurva som ett upp och nervänt U, som vi vill ha, måste man ha en stegvis minskning av hastigheten och den får vi genom att successivt lägga till gravitationen.

//Om bollen är uppe i luften
if (boll1.bFastmark== false) 
{ //Flytta bollen långsammare beroende på gravitationen
boll1.dHastighet_Y = boll1.dHastighet_Y  + fGravitation;
}
else //står på fast mark
{
boll1.dHastighet_Y = 0;
}

Komplett kod

[redigera]

Här nedanför har du ett riktigt enkelt plattformspel. Alla siffror är hårdkodade och bygger på att spelet är 800x600, spelaren är 64x64 och plattformarna 64x384. Även om klasser används är det så långt från OOP man nästan kan komma. Istället borde det vara ganska lätt att förstå koden, vilket är syftet.

//Fotboll 64 x 64
//http://www.appgamekit.com/documentation/examples/sprites/ball1.png

//Tillverka en bild som plattformar i ett bildbehandlingsprogram
//64 x 384 spara som plattform.png.

#include <iostream>
#include <SFML/System.hpp>
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>
// #include <SFML/Audio.hpp> bara om du vill ha ljud
// #include <SFML/Network.hpp> bara om du gör nätverksspel

#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öknning

/* Fyll i klassdeklarationer */
 class spelare 
{
public:
//Konstruktordeklaration, definition utanför klassdeklarationen
spelare (double  dHastighet_X, double dHastighet_Y, float dSpelare_x, float dSpelare_y);//startvärden
//Destruktion
~spelare(){};

double dHastighet_X; //Hur snabb är den i sidled
double dHastighet_Y; //Hur snabb är den i höjdled
float dSpelare_x; // var är den i sidled i programmet
float dSpelare_y; //var är den i höjdled i programmet
bool bFastmark; //Står figuren på fast mark
sf::Sprite sprite;
};

//Konstruktionsdeklaration----------------------------------------------

spelare::spelare (double  ut_dHastighet_X, double  ut_dHastighet_Y, float ut_dSpelare_x, float ut_dSpelare_y)
{
        dHastighet_X=ut_dHastighet_X;
        dHastighet_Y=ut_dHastighet_Y;
        dSpelare_x=ut_dSpelare_x;
        dSpelare_y=ut_dSpelare_y;
std::cout << "En spelare har fötts!" << std::endl;
}

//Plattformsklass

 class plattform 
{
public:
//Konstruktordeklaration, definition utanför klassdeklarationen
plattform (float fPlattform_x, float fPlattform_y);//startvärden
//Destruktion
~plattform(){};

float fPlattform_x; // var är den i sidled i programmet
float fPlattform_y; //var är den i höjdled i programmet
sf::Sprite sprite;
};

//Konstruktionsdeklaration-------------------------------------------------------------------------
plattform::plattform ( float ut_fPlattform_x, float ut_fPlattform_y)
{
        fPlattform_x=ut_fPlattform_x;
        fPlattform_y=ut_fPlattform_y;
 std::cout << "En plattform har fötts!" << std::endl;
}
/* Fyll i globala variabler */
/* Fyll i funktionsdeklarationer */

int main (int argc, char **argv)
{ //Main startar

/* Fyll i variabler inom main */
float ElapsedTime = 0.0f; //Skapar en konstant för att hålla hastigheten likvärdig på olika datorer
float fGravitation = 0.0003f; //Hur mycket trycker gravitationen ner bollen

/* skapa spelfönstret */
sf::RenderWindow App(sf::VideoMode(800, 600, 32), "Plattformstest"); 

/* ladda in bilder*/
//Spelare boll
sf::Image bollbild;
bollbild.LoadFromFile("ball1.png"); //64 x 64 stor
spelare boll1(0.0f, 0.0f, 100.f, 538.f);
//Hastighet X hastighet Y position X position Y
boll1.sprite.SetImage(bollbild); //Ge spriten bilden
boll1.bFastmark = true; //Den står på botten
//Placera ut spelaren
boll1.sprite.SetPosition(boll1.dSpelare_x,boll1.dSpelare_y);

//Spelare plattform
sf::Image plattformsbild;
plattformsbild.LoadFromFile("plattform.png"); //64 x 256 stor
plattform plattform1(0.0f, 68.0f); //överst
plattform plattform2(416.0f, 236.0f); //mellan
plattform plattform3(0.0f, 442.0f); //nederst

//Ge plattformarna rätt bild
plattform1.sprite.SetImage(plattformsbild); //Ge spriten bilden
plattform2.sprite.SetImage(plattformsbild); //Ge spriten bilden
plattform3.sprite.SetImage(plattformsbild); //Ge spriten bilden

//Placera ut dem
plattform1.sprite.SetPosition(plattform1.fPlattform_x,plattform1.fPlattform_y);
plattform2.sprite.SetPosition(plattform2.fPlattform_x,plattform2.fPlattform_y);
plattform3.sprite.SetPosition(plattform3.fPlattform_x,plattform3.fPlattform_y);

while (App.IsOpened())
  { //while 1 startar, spelloopen körs
	    ElapsedTime=App.GetFrameTime(); //Skapar en konstant för att hålla hastigheten likvärdig på olika datorer
       sf::Event Event; //kolla om mus/tangentbord används
       while (App.GetEvent(Event))
         { //while 2, kontrollerar events
         if (Event.Type == sf::Event::Closed) //kryssat på [x] symbolen? 
         App.Close(); // stäng programmet

         if ((Event.Type == sf::Event::KeyPressed) && (Event.Key.Code == sf::Key::Escape)) //ESC tangent
         App.Close(); // stäng programmet


         } //slut while 2, kontrollerar events

       /*Styr figuren*/
		//Står den på fast mark?
		if (boll1.sprite.GetPosition().y >= 536) //Om den står på marken längst ner
		{
			boll1.bFastmark = true; //Kan den hoppa
			boll1.dHastighet_Y = 0; //Rör sig inte
		}

		//********************************************************************************************************************************************/
		//Collision detect, manuellt och simpelt, alla tre plattformarna kollas
		//********************************************************************************************************************************************/

		//Står den på den nedersta plattformen?
		if (boll1.sprite.GetPosition().x >= plattform3.sprite.GetPosition().x && boll1.sprite.GetPosition().x + 64 <= plattform3.sprite.GetPosition().x + 384)
		{ //inom samma x 
			if ( boll1.sprite.GetPosition().y + 64 > plattform3.sprite.GetPosition().y - 2 && boll1.sprite.GetPosition().y + 64 < plattform3.sprite.GetPosition().y  +2 )
			{
			boll1.bFastmark = true;
			boll1.dHastighet_Y = 0;
			//std::cout << "plattformens y  = " << boll1.sprite.GetPosition().y << std::endl;
			}
	
		}

		if ( boll1.sprite.GetPosition().y + 64 > plattform3.sprite.GetPosition().y - 2 && boll1.sprite.GetPosition().y + 64 < plattform3.sprite.GetPosition().y  +2 )
	    {
        if (boll1.sprite.GetPosition().x >= plattform3.sprite.GetPosition().x + 384) //Längre åt x än plattformenn 
		boll1.bFastmark = false;
		}


					
		//Står den på den mellersta plattformen?
		if (boll1.sprite.GetPosition().x >= plattform2.sprite.GetPosition().x && boll1.sprite.GetPosition().x + 64 <= plattform2.sprite.GetPosition().x + 384)
		{ //inom samma x 
			if ( boll1.sprite.GetPosition().y + 64 > plattform2.sprite.GetPosition().y - 2 && boll1.sprite.GetPosition().y + 64 < plattform2.sprite.GetPosition().y  +2 )
			{
			boll1.bFastmark = true;
			boll1.dHastighet_Y = 0;
			}
	
		}

		if ( boll1.sprite.GetPosition().y + 64 > plattform2.sprite.GetPosition().y - 2 && boll1.sprite.GetPosition().y + 64 < plattform2.sprite.GetPosition().y  +2 )
		{
         if (boll1.sprite.GetPosition().x + 64 <= plattform2.sprite.GetPosition().x) //Längre åt x än plattformenn 
		 boll1.bFastmark = false;
		}

		//Står den på den översta plattformen?
		if (boll1.sprite.GetPosition().x >= plattform1.sprite.GetPosition().x && boll1.sprite.GetPosition().x + 64 <= plattform1.sprite.GetPosition().x + 384)
		{ //inom samma x 
			if ( boll1.sprite.GetPosition().y + 64 > plattform1.sprite.GetPosition().y - 2 && boll1.sprite.GetPosition().y + 64 < plattform1.sprite.GetPosition().y  +2 )
			{
			boll1.bFastmark = true;
			boll1.dHastighet_Y = 0;
			}
	
		}

		if ( boll1.sprite.GetPosition().y + 64 > plattform1.sprite.GetPosition().y - 2 && boll1.sprite.GetPosition().y + 64 < plattform1.sprite.GetPosition().y  +2 )
		{
         if (boll1.sprite.GetPosition().x >= plattform1.sprite.GetPosition().x + 384) //Längre åt x än plattformenn 
		 boll1.bFastmark = false;
		}
		//********************************************************************************************************************************************/

		//Om bollen är uppe i luften
		if (boll1.bFastmark== false) 
		{ //Flytta bollen långsammare neroende på gravitationen
		boll1.dHastighet_Y = boll1.dHastighet_Y  + fGravitation;
		}
		else //står på fast mark
		{
			boll1.dHastighet_Y = 0;
		}

		if (App.GetInput().IsKeyDown(sf::Key::Space))
		{
			//Y värdet
			if (boll1.bFastmark == true) //Hoppa bara om man står
			{
				boll1.bFastmark = false; //Man är i luften
				// boll1.dHastighet_Y = (ElapsedTime * 400) * -1; //alternativ
				boll1.dHastighet_Y = -0.4f;	
			}
		}


		if (App.GetInput().IsKeyDown(sf::Key::Left)) //Gå åt vänster
		{
			if (boll1.dHastighet_X > -0.1)
			{boll1.dHastighet_X = -0.1;}
			else
			(boll1.dHastighet_X = (boll1.dHastighet_X - (-boll1.dHastighet_X * ElapsedTime))/2);
		}


       if (App.GetInput().IsKeyDown(sf::Key::Right))  ///Gå åt höger
		{
		    if (boll1.dHastighet_X < 0.1)
			{boll1.dHastighet_X = 0.1;}
			else
			(boll1.dHastighet_X = (boll1.dHastighet_X + (boll1.dHastighet_X * ElapsedTime))/2);
		}


		 if (App.GetInput().IsKeyDown(sf::Key::Down)) 
		{
		  boll1.dHastighet_X =0; //Stanna bollen
		}


		 if (boll1.sprite.GetPosition().x < 0)
			 boll1.dHastighet_X = boll1.dHastighet_X *-1; //Vänd om den gått utanför vänsterkanten
		 if (boll1.sprite.GetPosition().x > (800-64))
			 boll1.dHastighet_X = boll1.dHastighet_X *-1; //Vänd om den gått utanför högerkanten

		boll1.sprite.Move(boll1.dHastighet_X, boll1.dHastighet_Y); //Låt den dra iväg

		/* visa upp spelet  */
        App.Clear(sf::Color(0, 255, 0)); //rensa allt i fönstret och ersätt med grönt
        /*rita upp spelar sprites här */
        App.Draw(boll1.sprite); //Rita upp figuren på den yta spelaren ser

		App.Draw(plattform1.sprite); //Rita upp figuren på den yta spelaren ser
		App.Draw(plattform2.sprite); //Rita upp figuren på den yta spelaren ser
		App.Draw(plattform3.sprite); //Rita upp figuren på den yta spelaren ser
       App.Display(); //visa upp ändringarna för användaren

 } //while 1 slutar, slut på att spelloopen körs
return 0; //Sista raden för slutet
} //Main slutar

/* Fyll i funktionsbeskrivningar */
  


Alernativ kod

[redigera]

Vill du ha mer fart i spelet kan du sätta X-fart:

if (App.GetInput().IsKeyDown(sf::Key::Left)) //Gå åt vänster
{
 if (boll1.dHastighet_X > -0.1)
     {boll1.dHastighet_X = -0.1;}
 else
     {boll1.dHastighet_X = (boll1.dHastighet_X - (-boll1.dHastighet_X * ElapsedTime));}
}

if (App.GetInput().IsKeyDown(sf::Key::Right))  ///Gå åt höger
{
  if (boll1.dHastighet_X < 0.1)
     {boll1.dHastighet_X = 0.1;}
  else
  {boll1.dHastighet_X = (boll1.dHastighet_X + (boll1.dHastighet_X * ElapsedTime));}
}

Därefter ändrar du gravitationen till:

float fGravitation = 0.0035f; //Hur mycket trycker gravitationen ner bollen

Och slutligen Y-farten när man trycker ner [SPACE] tangenten till:

if (boll1.bFastmark == true) //Hoppa bara om man står
{
 boll1.bFastmark = false; //Man är i luften
 boll1.dHastighet_Y = -1.4f;     
}

Eftersom både gravitationen ökats rejält och Y-farten ökats kommer bollen att kunna hoppa ungefär lika högt som tidigare, men det går mycket fortare. För att kompensera den ökade farten så att man faktiskt kan hoppa med bollen måste också X-hastigheten ökas. Här är den dubblad. Detta för att den kurva bollen gör i luften blir mycket snävare med högre fart eftersom bollen är i luften kortare tid.

Stoppa bollen

[redigera]

Hur gör man för att stoppa bollen? Att kontrollera om knappar är nertryckta är egentligen kopplat till en boolsk variabel, även om det inte är så uppenbart. Endera är knappen nertryckt eller så är den inte det. Det innebär också att vi kan kontrollera om en knapp är nere eller inte med kod:

if (!App.GetInput().IsKeyDown(sf::Key::Down)) 
{std::cout << "nerknapp är inte intryckt " << std::endl;}

På det sättet kan vi också kontrollera om varken Down eller Up (pil ner eller pil upp) är nertryckta och då blir farten i X-led = 0. Alternativt kan vi ha en gravitationsliknande kraft där också som saktar ner farten långsamt tills den när 0, vilket ser snyggt ut i t.ex. bilspel eftersom ingen bil stannar på fläcken.

För att få bollen att stå stilla i X, led när ingen höger/vänster piltangent är nedtryckt:

if (!App.GetInput().IsKeyDown(sf::Key::Down) && !App.GetInput().IsKeyDown(sf::Key::Up)) 
{boll1.dHastighet_X = 0;}