Programmera spel i C++ för nybörjare/Squash
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
redigeraSkapa 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
redigeraNu 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
redigeraSå 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
redigeraPing 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
redigeraOm 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
redigeraNu ä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
redigeraSlutligen, 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
redigeraHä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