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

Från Wikibooks


Baserat på VC++ 2010 express, Win 7 och SFML 1.6, du bör ha gjort installationerna för ett fungerande SFML-fönster i ditt projekt innan du börjar. Bygger på kapitlet: Programmera spel i C++ för nybörjare/Studsande boll.

Om man har en boll som studsar mellan väggarna kan man göra ett enkelt squashspel också. Det som behöver förändras från projektet med studsande boll är att:

  • Vänster vägg skall bort. Är det så att bollen försvinner utanför kanten skall den återställas.
  • Vi behöver en långsmal bild som fungerar som racket.
  • farten måste öka för varje gång racketen träffar bollen, men inte annars.
  • Boll och racket måste börja på samma ställe som ”utslagspunkt”.

Om man bygger vidare på den redan existerande koden från förra exemplet får vi börja med att ändra kollisionskoden för vänster vägg.

if (bollX > 766) //den slår in i vänsterväggen                         
  fartX = fartX * -1; //spegelvänd riktning
if (bollX < 0) //den går utanför vänsterväggen                      
  {
  //räkna -1 poäng, dra mer farten till 0 och placera bollen mitt på racketet.
  }

Bild på racket[redigera]

Skapa en png eller liknande i valfritt program. Eftersom ingen del skall vara osynlig fungerar t.o.m. jpg-bilder. Gör den 16x64 pixels stor. Döp den till ”racket.png” och spara den i projektmappen där main.cpp ligger så att programmet kan hitta den.

Lägg in koden från projektet med den studsande bollen och läs in spelets racketbild med följande kod efter det att du läst in boll:

sf::Image racketbild; //skapa en tom bildhållare som heter racket
if (!racketbild.LoadFromFile("racket.png")) return EXIT_FAILURE; //fyll den tomma bildhållaren med bilden racket.png
                                                                  //bilden är 16x64 
sf::Sprite racket(racketbild); //Skapar den grafiska spelpjäsen racket med grafiken från racketbild
racket.SetPosition(1.0f,268.0f); //Placera ut bilden längs vänsterkanten, 1 pixel från kanten och mitt på fönstret. 

Mitten är på 300 (600/2), men eftersom man räknar från övre vänstra hörnet på sprites, och racketen är 64 pixels hög, får vi räkna bort halva racketens höjd 300 - 32 = 268

I slutet:

App.Draw(racket); //Rita upp figuren på den yta spelaren ser

Annars ritas den inte ut.

Styra racketen[redigera]

Nu skall vi kunna köra racketen upp- och ner också. Kommer du ihåg koden för pojken? nfloat ElapsedTime = App.GetFrameTime(); //Skapar en konstant för att hålla hastigheten likvärdig på olika datorer

ElapsedTime = App.GetFrameTime(); //för att få en konstant hastighet på olika datorer
if (App.GetInput().IsKeyDown(sf::Key::Up)) Sprite.Move(0, -100 * ElapsedTime); 
if (App.GetInput().IsKeyDown(sf::Key::Down)) Sprite.Move(0, 100 * ElapsedTime); 

Vi gör om den med ”racket” istället för ”Sprite” och ökar hastigheten till 700 för racketen, samtidigt som vi minskar farten på bollen till 0.2f

float fartX = 0.2f; //rörelse i xled
float fartY = 0.2f; //rörelse i y-led

Nu går det fortfarande att köra racketen utanför kanten, det är inte bra. Vi måste definiera racketens y koordinat precis som vi gjorde med bollen:

float  racketY=0.0f; //y position för racketen

Längre ner, innan man ser om pil ner och upp tryckts fyller vi i:

racketY=racket.GetPosition().y; //var är racketen i y-led 

X-led kan vi strunta i, eftersom racketen bara skall gå upp och ner, inte åt höger och vänster.

Sedan får vi ändra så att det inte går att köra racketen längre upp om den redan nått överkanten och y > 0, liksom den inte skall gå att köra längre ner om y > 536 (600-racketens höjd).

if (App.GetInput().IsKeyDown(sf::Key::Up)) 
{
 if (racketY > 0)
 racket.Move(0, -700 * ElapsedTime); //racket upp
}

 if (App.GetInput().IsKeyDown(sf::Key::Down)) 
{
 if (racketY < 536)
 racket.Move(0, 700 * ElapsedTime);  //racket ner
}

Nu måste vi se om bollen träffar racketen. Det gör vi i den rutin som kotrollerade om bakväggen träffades tidigare.

else if( bollX < 0 ) //den slår in i vänsterväggen			
{   //träffar den racketen?
					  
if(racketY - bollY < 24 && bollY - racketY < 64)
  //om skillnaden mellan bollens höjd (24) och kanten på racketen 
   //är mindre än och om bollen träffar racketen (64hög) någonstans…
 {//om racket träffar
   fartX = fartX * -1; //spegelvänd riktning
                      //annars försvinner bollen
 }//slut om racket träffar
   else
  {//Om racket inte träffar
   boll.SetPosition(1.0f,racketY+20);
  }//slut om racket inte träffar
}//slut på att se om den träffar racketen

Bollens hastighet[redigera]

Så långt allt gott och väl. Vi vet att bollen studsar omkring, vi vet att den försvinner om vi inte träffar den och den studsar iväg från racketen om vi missar. Det som styr bollens hastighet är

float fartX = 0.2f; //rörelse i xled
float fartY = 0.2f; //rörelse i yled

Ändra farten för y till:

float fartY = 0.009f; 

Då går den nästan rakt fram men med en liten, liten vinkling neråt.

Det vi sedan gör är att vi lägger till i koden att farten skall öka med 0.1f i både x- och y-led varje gång racketen träffar. Dvs. när bollen ändrar riktning efter en träff med racketen fyller du i:

fartX = fartX + 0.1f; //rörelse i xled
fartY = fartY + 0.1f; //rörelse i yled

Detta ger obegränsad fart, men eftersom du vill att den skall starta från 0 efter en miss får du lägga till följande rad efter utplaceringen av bollen på racketen:

fartX = 0.0f; //ingen fart
fartY = 0.0f; //ingen fart

Hur får vi upp farten igen? Ett bra alternativ kan vara om man trycker ner space tangenten, den är stor och svår att missa.

if(App.GetInput().IsKeyDown(sf::Key::Space)) 
  {
    fartX = 0.2f; //rörelse i xled
    fartY = 0.009f; //rörelse i yled, liten, liten vinkel
  }

Nu har du ett enkelt squash spel. Vad kan man göra mer?

  • Skapa ett Pong spel med två spelare som spelar mot varandra på samma spelplan.
  • Gör spelplanen längre (men inte högre)
  • Lägg till ljudeffekter för varje gång bollen träffas.
  • Gör en snygg bakgrundsbild som ser ut som en squashplan.
  • Gör ett räknesystem med siffror som kommer ut på skärmen. +1 poäng varje gång du träffar och -1 för varje boll du tappar. Avsluta spelet efter 5 bollar.

Här är den slutgiltiga koden:

#include <iostream>
#include <SFML\System.hpp>
#include <SFML\Graphics.hpp>
#include <SFML\Window.hpp>


int main() 
   {  //Början av programkörningen

    float  bollX=0.0f; //x position för bollen
    float  bollY=0.0f; //y position för bollen
    float  racketY=0.0f; //y position för racketen
    float fartX = 0.2f; //rörelse i xled
    float fartY = 0.009f; //rörelse i yled, liten vinkel för lämplig rörelse

   sf::RenderWindow App(sf::VideoMode(800, 600, 32), "SFML studsande boll"); // Skapa fönstret vi skall visa färgerna i
	float ElapsedTime = App.GetFrameTime(); //Skapar en konstant för att hålla hastigheten likvärdig på olika datorer
 

        sf::Image bollbild; //skapa en tom bildhållare som heter Image
        if (!bollbild.LoadFromFile("ballsprite.png")) return EXIT_FAILURE; //fyll den tomma bildhållaren med bilden boll.png
                                                                 //bilden är 24x24
        sf::Sprite boll(bollbild); //Skapar den grafiska spelpjäsen boll med grafiken från bollbild
		 boll.SetPosition(1.0f,1.0f); //Placera ut bilden i övre vänstra hörnet


		          sf::Image racketbild; //skapa en tom bildhållare som heter racket
        if (!racketbild.LoadFromFile("racket.png")) return EXIT_FAILURE; //fyll den tomma bildhållaren med bilden racket.png
                                                                 //bilden är 16x64
        sf::Sprite racket(racketbild); //Skapar den grafiska spelpjäsen racket med grafiken från racketbild
		 racket.SetPosition(1.0f,268.0f); //Placera ut bilden längs vänsterkanten


        while (App.IsOpened())  // Start spel-loopen
      {  //while 1
         sf::Event Event; 

            while (App.GetEvent(Event)) // Ta hand om händelser 
            { //while 2

              if (Event.Type == sf::Event::Closed) //kryssat på [x] symbolen? stäng programmet
              App.Close(); 

                     if (Event.Type == sf::Event::KeyPressed) // En tangent har tryckts ner

                                    { //if 1

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

                                       } //slut if 1

                            } //slut, while 2


			 ElapsedTime = App.GetFrameTime(); //för att få en konstant hastighet på olika datorer

			 
					racketY=racket.GetPosition().y; //var är racketen i y-led

				  if (App.GetInput().IsKeyDown(sf::Key::Space)) 
                 {
					  	fartX = 0.2f; //rörelse i xled
	                    fartY = 0.009f; //rörelse i yled
                  }


                  if (App.GetInput().IsKeyDown(sf::Key::Up)) 
					   {
						   if (racketY > 0)
						   racket.Move(0, -700 * ElapsedTime); //racket upp
				       }

                   if (App.GetInput().IsKeyDown(sf::Key::Down)) 
					{
						if (racketY < 536)
						racket.Move(0, 700 * ElapsedTime);  //racket ner
					}
			
	

					bollX=boll.GetPosition().x; //var är bollen i x-led
					bollY=boll.GetPosition().y; //var är bollen i y-led


                  if (bollX > 766 ) //den slår in i högerväggen		   
                  fartX = fartX * -1; //spegelvänd riktning
				   
				   else if( bollX < 0 ) //den slår in i vänsterväggen			
                     {   //träffar den racketen?
						  
                               if(racketY - bollY < 24 && bollY - racketY < 64)
                                   //om skillnaden mellan bollens höjd (24) och kanten på racketen 
				   //är mindre än och om bollen träffar racketen (64hög) hågonstans…
				   {//om racket träffar
                                     fartX = fartX * -1; //spegelvänd riktning
                                   //annars försvinner bollen
				   //Låt dessutom bollens hastighet öka.
				   	 fartX = fartX + 0.1f; //rörelse i xled
	                                 fartY = fartY + 0.1f; //rörelse i yled
				   }//slut om racket träffar
				   else
				   {//Om racket inte träffar
					   boll.SetPosition(1.0f,racketY+20);

					   fartX = 0.0f; //ingen fart
	                                   fartY = 0.0f; //ingenfart
				   }//slut om racket inte träffar
				   }//slut på att se om den träffar racketen


				   else if (bollY > 576 || bollY < 0) //den slår in neder- eller överväggen
                  fartY =  fartY * -1; //spegelvänd riktning

                  boll.Move(fartX,fartY); //bollen skickas iväg

                App.Clear(sf::Color(0, 0, 0)); //rensa allt i fönstret och ersätt med svart färg
                 App.Draw(boll); //Rita upp figuren på den yta spelaren ser
		  App.Draw(racket); //Rita upp figuren på den yta spelaren ser
                App.Display(); //visa upp ändringarna för användaren

} //slut, while 1

return 0;
} //slut på programkörningen

Squash blir Pong[redigera]

Ping pong, eller bara Pong på engelska, är ett populärt spel för nybörjare just eftersom det går så lätt att beräkna var bollen befinner sig i en värld där man vet var alla väggar befinner sig. Vårt Squashspel kan lätt byggas om till ett Pong spel.

Ännu en sprite, racket1[redigera]

Om det är så att vi vill ha olika racketar för höger och vänster får vi skriva:

sf::Image racketbild1; //skapa en tom bildhållare som heter racketbild1
if (!racketbild1.LoadFromFile("racket_hoger.png")) return EXIT_FAILURE; //fyll den tomma bildhållaren med bilden racket_hoger.png
//bilden är 16x64

Annars är det enklast att helt enkelt skapa en ny sprite för höger racket men tilldela den samma bild/image som vänster racket redan fått:

sf::Sprite racket1(racketbild1); //Skapar den grafiska spelpjäsen racket1 med grafiken från racketbild
racket1.SetPosition(784.0f,268.0f); //Placera ut bilden längs höger kant

För att kunna styra den nya racketen upp och ner måste även den racketens y-värde definieras i början av koden:

float racket1Y=0.0f; //y position för höger racket

Och innan vi styr racketen upp och ner måste vi så klart veta var på spelplanen racketen befinner sig. Båda racketarna definieras samtidigt som:

racketY=racket.GetPosition().y; //var är racket i y-led på vänster sida
racket1Y=racket1.GetPosition().y; //var är racket i y-led på höger sida

Racketposition[redigera]

Nu är det direkt olämpligt att styra den vänstra racketen med pil upp/ner tangenterna. Det är bättre om man t.ex. styr vänster racket upp och ner med W och S tangenterna och höger racket med pil upp och ner, om man skall sitta två spelare vid samma dator. Fyll i koden:

//Styr racketarna upp och ner längs spelplanens sidor
if (App.GetInput().IsKeyDown(sf::Key::W)) 
{
if (racketY > 0)
racket.Move(0, -700 * ElapsedTime); //racket upp
}

if (App.GetInput().IsKeyDown(sf::Key::S)) 
{
if (racketY < 536)
racket.Move(0, 700 * ElapsedTime); //racket ner
}

if (App.GetInput().IsKeyDown(sf::Key::Up)) 
{
if (racket1Y > 0)
racket1.Move(0, -700 * ElapsedTime); //racket 1 upp
}

if (App.GetInput().IsKeyDown(sf::Key::Down)) 
{
if (racket1Y < 536)
racket1.Move(0, 700 * ElapsedTime); //racket 1ner
}

Slutligen måste vi se när bollen studsar. Som squashspelet var uppbyggt studsade bollen mot vänstra väggen om racketen var framför väggen just där. Vi använder samma kod för högerväggen:

if (bollX > 776 ) 
 { //den slår in i högerväggen 
 if(racket1Y - bollY < 24 && bollY - racket1Y < 64)
  //om skillnaden mellan bollens höjd (24) och kanten på racketen 
  //är mindre än och om bollen träffar racketen (64hög) hågonstans…
     {//om racket träffar
      fartX = fartX * -1; //spegelvänd riktning
     //annars försvinner bollen
     //Låt dessutom bollens hastighet öka.
     fartX = fartX - 0.1f; //rörelse i xled
     fartY = fartY - 0.1f; //rörelse i yled
     }//slut om racket träffar
 else
     {//Om racket inte träffar
      boll.SetPosition(1.0f,racketY+20); //på vänster racket.
       fartX = 0.0f; //ingen fart
       fartY = 0.0f; //ingenfart
      }//slut om racket inte träffar
  }//slut på att se om den träffar högervägg

Observera koden här.

      fartX = fartX * -1; //spegelvänd riktning

Riktningen ändras, men farten är fortfarande inställd på att ökas åt höger. För att bollen helt enkelt inte skall falla ner till golvet måste även farten öka i andra riktningen. Därför skriver vi:

      fartX = fartX - 0.1f; //rörelse i xled
     fartY = fartY - 0.1f; //rörelse i yled

Rita ut racketen[redigera]

Slutligen, ett typiskt nybörjarfel, om inte racketen syns har du glömt att tala om för spelet att den skall rita ut. Fyll i

App.Draw(racket1); //Rita upp racketen längs höger vägg

i botten av koden där alla rutiner för att rita ut grafik finns.

Kod för Pong[redigera]

Här finns den slutgiltiga koden för ett squashspel som ändrats till ping pong.

#include <iostream>
#include <SFML\System.hpp>
#include <SFML\Graphics.hpp>
#include <SFML\Window.hpp>

int main() 
{ //Början av programkörningen

float bollX=0.0f; //x position för bollen
float bollY=0.0f; //y position för bollen
float racketY=0.0f; //y position för racketen
float racket1Y=0.0f; //y position för höger racket
float fartX = 0.2f; //rörelse i xled
float fartY = 0.009f; //rörelse i yled, liten vinkel för lämplig rörelse

int raknare;

sf::RenderWindow App(sf::VideoMode(800, 600, 32), "SFML pong"); // Skapa fönstret vi skall visa ping pong i
float ElapsedTime = App.GetFrameTime(); //Skapar en konstant för att hålla hastigheten likvärdig på olika datorer


sf::Image bollbild; //skapa en tom bildhållare som heter Image
if (!bollbild.LoadFromFile("ballsprite.png")) return EXIT_FAILURE; //fyll den tomma bildhållaren med bilden boll.png
//bilden är 24x24
sf::Sprite boll(bollbild); //Skapar den grafiska spelpjäsen boll med grafiken från bollbild
boll.SetPosition(1.0f,1.0f); //Placera ut bilden i övre vänstra hörnet


sf::Image racketbild; //skapa en tom bildhållare som heter racket
if (!racketbild.LoadFromFile("racket.png")) return EXIT_FAILURE; //fyll den tomma bildhållaren med bilden racket.png
//bilden är 16x64
sf::Sprite racket(racketbild); //Skapar den grafiska spelpjäsen racket med grafiken från racketbild
racket.SetPosition(1.0f,268.0f); //Placera ut bilden längs vänsterkanten

sf::Image racketbild1; //skapa en tom bildhållare som heter rackettbild1 
//om man har samma bild till bägge racketarna är det bättre att använda samma bild, dvs:
//if (!racketbild1.LoadFromFile("racket.png")) return EXIT_FAILURE;
if (!racketbild1.LoadFromFile("racket.png")) return EXIT_FAILURE; //fyll den tomma bildhållaren med bilden racket.png
//bilden är 16x64
sf::Sprite racket1(racketbild1); //Skapar den grafiska spelpjäsen racket1 med grafiken från racketbild
racket1.SetPosition(784.0f,268.0f); //Placera ut bilden längs höger kant


while (App.IsOpened()) // Start spel-loopen
{ //while 1
sf::Event Event; 

while (App.GetEvent(Event)) // Ta hand om händelser som tangentnedtryckning
{ //while 2

if (Event.Type == sf::Event::Closed) //kryssat på [x] symbolen? stäng programmet
App.Close(); 

if (Event.Type == sf::Event::KeyPressed) // En tangent har tryckts ner

{ //if 1

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

} //slut if 1



} //slut, while 2


ElapsedTime = App.GetFrameTime(); //för att få en konstant hastighet på olika datorer


racketY=racket.GetPosition().y; //var är racket i y-led på vänster sida
racket1Y=racket1.GetPosition().y; //var är racket i y-led på höger sida

if (App.GetInput().IsKeyDown(sf::Key::Space) && fartX==0.0f) 
	//starta om bollen om farten är 0
{
fartX = 0.2f; //rörelse i xled
fartY = 0.009f; //rörelse i yled
}

//Styr racketarna upp och ner längs spelplanens sidor
if (App.GetInput().IsKeyDown(sf::Key::W)) 
{
if (racketY > 0)
racket.Move(0, -700 * ElapsedTime); //racket upp
}

if (App.GetInput().IsKeyDown(sf::Key::S)) 
{
if (racketY < 536)
racket.Move(0, 700 * ElapsedTime); //racket ner
}

if (App.GetInput().IsKeyDown(sf::Key::Up)) 
{
if (racket1Y > 0)
racket1.Move(0, -700 * ElapsedTime); //racket 1 upp
}

if (App.GetInput().IsKeyDown(sf::Key::Down)) 
{
if (racket1Y < 536)
racket1.Move(0, 700 * ElapsedTime); //racket 1ner
}


bollX=boll.GetPosition().x; //var är bollen i x-led
bollY=boll.GetPosition().y; //var är bollen i y-led


if (bollX > 776 ) 
{ //den slår in i högerväggen 
 if(racket1Y - bollY < 24 && bollY - racket1Y < 64)
  //om skillnaden mellan bollens höjd (24) och kanten på racketen 
  //är mindre än och om bollen träffar racketen (64hög) hågonstans…
     {//om racket träffar
      fartX = fartX * -1; //spegelvänd riktning
     //annars försvinner bollen
     //Låt dessutom bollens hastighet öka.
     fartX = fartX - 0.1f; //rörelse i xled
     fartY = fartY - 0.1f; //rörelse i yled
     }//slut om racket träffar
 else
     {//Om racket inte träffar
      boll.SetPosition(1.0f,racketY+20); //på vänster racket.
       fartX = 0.0f; //ingen fart
       fartY = 0.0f; //ingenfart
      }//slut om racket inte träffar
  }//slut på att se om den träffar högervägg

if( bollX < 0 ) //den slår in i vänsterväggen 
{ //träffar den racketen?

if(racketY - bollY < 24 && bollY - racketY < 64)
//om skillnaden mellan bollens höjd (24) och kanten på racketen 
//är mindre än och om bollen träffar racketen (64hög) hågonstans…
{//om racket träffar
fartX = fartX * -1; //spegelvänd riktning
//annars försvinner bollen
//Låt dessutom bollens hastighet öka.
fartX = fartX + 0.1f; //rörelse i xled
fartY = fartY + 0.1f; //rörelse i yled
raknare=raknare+1;
}//slut om racket träffar
else
{//Om racket inte träffar
boll.SetPosition(1.0f,racketY+20);
fartX = 0.0f; //ingen fart
fartY = 0.0f; //ingenfart
}//slut om racket inte träffar
}//slut på att se om den träffar racketen

else if (bollY > 576 || bollY < 0) //den slår in neder- eller överväggen
fartY = fartY * -1; //spegelvänd riktning

boll.Move(fartX,fartY); //bollen skickas iväg

App.Clear(sf::Color(0, 0, 0)); //rensa allt i fönstret och ersätt med svart färg
App.Draw(boll); //Rita upp figuren på den yta spelaren ser
App.Draw(racket); //Rita upp figuren på den yta spelaren ser
App.Draw(racket1); //Rita upp racketen längs höger vägg
App.Display(); //visa upp ändringarna för användaren

} //slut, while 1

return 0;
} //slut på programkörningen