Programmera spel i C++ för nybörjare/Plattformsspel
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
redigeraI 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
redigeraHä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
redigeraVill 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
redigeraHur 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;}