Programmera spel i C++ för nybörjare/Space shooter
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. För ljud bör du ha gjort de ändringar som behövs enligt det kapitlet. Se till så att main.cpp ligger i samma mapp som alla mediafielr (ljud och bild).
Nu är det dags att se på mer avancerade komponenter för programmering. Många gånger behöver man kunna lagra mer än bara ett heltal i en variabel. Anta att du har en scout. Du vill veta hur stark, smart och uthållig scouten är (integers) men även vad han har i ryggsäcken (strings). En sådan blandad variabel är t,ex struct.
Struct
[redigera]En struct kan innehålla olika variabler men samlade i en struktur. Den kan man sedan kopiera och bygga ut. Miniorscouterna kan ha en liten struct och ledarna en stor. I vårt spel skall vi ha en generell struct som alla andra spelare utgår ifrån och som vi döper till Image:
struct Image { sf::Sprite sprite; //Följande int/heltal innehåller koordinaterna för varje bild. De har värdena: // 'x', 'y', 'x2' och 'y2'. precis som i skolgeometrin :) //x,y är övre vänstra och x2,y2 är nedre högra hörnet. int x; int y; int x2; int y2; int speed;//Figurens hastighet } ;//slut på generell image struct
Vi vill veta var i världen den befinner sig (x,y,x2,y2) och hastigheten.
Från denna kan vi sedan skapa hjälten i spelet, du.
struct PlayerShip: Image { PlayerShip() { sprite.SetImage(data.player1); sprite.SetPosition(350,475); //placeras 350 till höger och 475 ner på spelplanen x = 350; //Ge spriten sitt x värde. y = 475; //Ge spriten sitt y värde. speed = 6; //hastighet är relativ till frameraten } }player; //struct tilldelas player
Nu kan vi anropa ”player.speed” t.ex. var vi vill i koden och vi kommer åt värdena. Dessutom har vi lagt till två andra värden så att structen byggts ut litet. Den har fått en bild som representerar den och en position på spelplanen. På det viset är en struct behändig att använda. Är det så att en struct innehåller pekare måste du helt plötsligt ange de värdena med -> symbol istället för . symbolen (se nedan vad det gäller att komma åt just kulorna man skjuter som sparas i en pekare. Det finns en annan behållare som heter class. Den enda egentliga skillnaden i dagens läge är att i en class är alla delar som standard deklarerade som privata, medan de som standard är publika i en struct. Du kommer nog ändå att använda class oftare än struct när du blivit mer van vid oop programmering.
I början av spelets kod skapar vi dessutom en stor struct som bara innehåller de olika data som spelet använder sig av:
struct Data { //skapa imagefil-hållarna sf::Image bullet1; //projektiler bild 1 i animationen - vilken storlek som helst, max storlek 32x32 sf::Image bullet2; //projektiler bild 2 i animationen sf::Image Nme1; //fiende bild 1 i animationen - storlek 32x32 sf::Image Nme2; //fiende bild 2 i animationen sf::Image player1;//hjälte bild 1 i animationen - storlek 32x32 sf::Image player2;//hjälte bild 2 i animationen Data() {//ladda in filerna till imagefil-hållarna bullet1.LoadFromFile("bala1.png"); bullet2.LoadFromFile("bala2.png"); Nme1.LoadFromFile("malo1.png"); Nme2.LoadFromFile("malo2.png"); player1.LoadFromFile("nave.png"); player2.LoadFromFile("nave2.png"); } //slut på bild till imagehållare }data; //"data" skall använda denna struct
Då har vi allt samlat på samma ställe och får lättare överblick. Som synes behövs sex bilder till spelet. Två olika för rymdskeppet, två olika för fiendeskeppet och två olika för avlossade granater. Två olika har vi för att göra en superenkel animation som ger ögat intrycket av att de rör sig. Skeppen är 32x32 och skotten 16 x 32. Överst i programkoden finns olika dresser där jag hämtade bilder själv, men då de inte innehåller frias bilder kan jag inte lägga upp dem åtkomliga för dig genom wikibooks.
Kulorna
[redigera]Hade alla värden varit av samma sort kunde man lika gärna ha använt en array/vektor. Struct-en för kulorna är litet annorlunda:
struct BulletStruct: Image { BulletStruct() { speed = 10;//hastigheten högre än något skepp sprite.SetImage(data.bullet1); //Tilldelas bild sprite.SetPosition( (player.x + 15),(player.y) ); // Placeras i mitten av spelarens skepp y = player.y; //Kulorna tilldelas samma y-värde som skeppet. } }*bullet[3]; //Tilldelas bullet genom pekare till kulorna //Man kan ha tre kulor i luften samtidigt
Som du ser är själva structen samma som de övriga, men däremot sker tilldelningen till en pekare. D.v.s. utan struct skulle man kunna ha en array men då måste alla poster i vår array vara av samma sort. En array är per definition alltid en pekare i C++. Nu gör vi så att vi tilldelar vår struct till en pekare och på det sättet har vi en array som kan innehålla ett flertal olika sorters variabler. Fiffigt va?
New - nya skott
[redigera]De andra struct kommandona skapar en av varje enhet. Vi har ett spelarskeppp och ett fiendeskepp. Kulorna däremot mpste det finnas mängder av. Därför mpåste vi skapa dem hela tiden. Principen är enkel, i= vilken post i vår array kulan ligger på:
bullet[i] = new BulletStruct;
Då har vi skapat en ny kula. Det går att göra likadant med fiendeskepp... Man kan inte skapa saker i all oändlighet. Har man använt new, så måste man manuellt ta bort kulan efteråt annars får man minnesläckor i spelet och efter en tid kraschar det.
void Shoot() { //Börja skjuta. for (int i = 0; i < 3; i++) //kolla kulor 1, 2 och 3 { //starta loop kolla tre kulor if ( (i != 0) && (!bulletExists[i]) && (bulletExists[i-1]) ) { //starta kontroll om att det är ok att skjuta if ( (window.GetInput().IsKeyDown(sf::Key::Space)) && (bullet[i-1]->y < 370) ) { //Start, kontroll om senaste kulan är 370 pixlar bort bullet[i] = new BulletStruct; bulletExists[i] = true; } //Slut, kontroll om senaste kulan är 370 pixlar bort } //Sluta kontroll om att det är ok att skjuta else if ( (i == 0) && (window.GetInput().IsKeyDown(sf::Key::Space)) && (bulletExists[i] == false) ) { //Start, kontroll om alla kulor är borta bullet[i] = new BulletStruct; bulletExists[i] = true; } //Slut, kontroll om alla kulor är borta }//sluta loop kolla tre kulor } //Slut på att skjuta
Längre ner i koden, i funktionen för att flytta kulorna, ser vi att det finns in kontroll. Om kulorna har ett y-värde under 0, vilket innebär att de försvunnit utanför överkanten, tas de bort ur spelet och förstörs.
if (bullet[i]->y < 0) { //Om kulan försvunnit utanför överkanten delete bullet[i]; bulletExists[i] = false; }
Orsaken till att -> symbolen används är för att vi vill komma åt ett värde inuti en pekare, du kommer väl ihåg att kulorna skapades som pekare? Om de inte varit pekare utan vanliga variabler i en strrukt hade man istället skrivit: bullet[i].y.
Enkel animation
[redigera]I koden finns en enklaste formen av animation man kan tänka sig. Den bygger på två olika bilder av samma storlek och en klocka. Så här ser den ut för spelaren (sprite=player).
if ((Clock.GetElapsedTime() > 0.0) && (Clock.GetElapsedTime() < 0.1)) player.sprite.SetImage(data.player1); if ((Clock.GetElapsedTime() > 0.1) && (Clock.GetElapsedTime() < 0.2)) player.sprite.SetImage(data.player2); if (Clock.GetElapsedTime() > 0.2) Clock.Reset();
Bilderna laddas in 1/10 sekund så att man får en blinkande effekt. Spelet ändras 60 ggr i sekunden och bilderna 10 ggr = varje bild visas 300 ggr i sekunden. Ställer man om klockan ställer man också om hastigheten som animationen visas upp på.
Loop
[redigera]Eftersom klockan sätts på "reset" på sista raden kommer animationen att loopa, dvs börja om hela tiden. Vill man ha en funktion som bara utlöser en sekvens, en explosion t.ex., skall du sätta klockan på reset innan animationen börjar.
Spritesheet
[redigera]Om du vill använda ett spritesheet, dvs en stor bild som består av en mängd små bilder, får du kopiera ut rätt bild ur spritesheeten vid varje givet ögonblick. Då får man ladda in rätt spritesheet till structen i början av koden, sedan är koden istället:
if ((Clock.GetElapsedTime() > 0.0) && (Clock.GetElapsedTime() < 0.1)) //animation bild 1 <structname>.sprite.SetSubRect(sf::IntRect(0,0,64,64)); //Visa första bilden
Static
[redigera]Allra högst upp i koden finns statiska värden. Om man har ett statiskt värde i programmet som aldrig ändras kan man lägga in det på det viset. Statiska värden är vanligtvis skrivna med versaler:
#define RIGHT_BORDER 725 //höger kant
Felsökning
[redigera]Nu har vi kommit så långt i våra projekt att det där svarta konsollfönstret kan börja komma till användning. I enklare program är det mest till besvär, men om man vill se vad olika variabler är inuti programmet är det tacksamt att slippa skapa textstreams bara för att få ut siffror på skärmen. Då är det mycket enklare att bara skriva
cout << värde << endl;
Kod för ett första fungerande ramverk
[redigera]//Spaceshooter //Koden i detta spel bygger på annan kod hittad på internet //på http://pastebin.com/kAS9E164 // Varningar: warning C4244: 'argument' : conversion from 'int' to 'float', possible loss of data //beror på att spelarna placeras ut på exakta pixlar, det påverkar inte spelet. //Rymdskeppsbilder från http://www.fromsmallpixels.co.uk/pixelart.html //Kulbilder från http://mab.eviscerate.net/bullets.png #include <iostream> #include <SFML\System.hpp> #include <SFML\Window.hpp> #include <SFML\Graphics.hpp> #include <SFML\Audio.hpp> //using namespace std; // utifall att konsollen behövs //Lägg till statiska värden som aldrig ändras*************************************** #define RIGHT_BORDER 725 //höger kant #define LEFT_BORDER 25 //vänster kant #define TOP_BORDER 0 //överkant #define BOTTOM_BORDER 600 //nederkant #define SPAWN_POS1 350 //Där fientliga rymdskepp spawnas i x-led //Globala variabler******************************************************************* sf::RenderWindow window(sf::VideoMode(800,600,32), "Enkel Space Shooter"); //Fönstret spelet visas i sf::Event event; //ev. händelser i spelet sf::Clock Clock; //en spelklocka för att hålla reda på animationer o. dyl. sf::Clock bulletClock[3]; //Vi skjuter tre skott varje gång space-tangenetn trycks ner //Varje skott måste ha en egen klocka bool bulletExists[3] = {false,false,false}; //I början finns inte skotten //Funktioner som används i spelet deklareras i förväg********************************* void EndProgram(); //Hur spelet avslutas void Move(); //Hur figurerna gör när de skjuter void Shoot(); //Hur figurerna gör när de förflyttar sig void MoveBullet(int i); //Hur kulorna rör sig void MoveNme(); //Hur fiendeskeppet förflyttar sig //Structs som spelet använder sig av för att skapa spelarna*************************** //Hade kunnat varit class, men allt är public i dem och struct bearbetas snabbare***** //Första structen innehåller alla bilderna******************************************** //Bilderna ändras från 1 till 2 med 1/10 sekunds skillnad för att simulera //en animation av rymdskeppen och kulorna/projektilerna. struct Data { //skapa imagefil-hållarna sf::Image bullet1; //projektiler bild 1 i animationen - vilken storlek som helst, max storlek 32x32 sf::Image bullet2; //projektiler bild 2 i animationen sf::Image Nme1; //fiende bild 1 i animationen - storlek 32x32 sf::Image Nme2; //fiende bild 2 i animationen sf::Image player1;//hjälte bild 1 i animationen - storlek 32x32 sf::Image player2;//hjälte bild 2 i animationen Data() {//ladda in filerna till imagefil-hållarna bullet1.LoadFromFile("bala1.png"); bullet2.LoadFromFile("bala2.png"); Nme1.LoadFromFile("malo1.png"); Nme2.LoadFromFile("malo2.png"); player1.LoadFromFile("nave.png"); player2.LoadFromFile("nave2.png"); } //slut på bild till imagehållare }data; //"data" skall använda denna struct //Andra structen, generell struct för alla bildobjekten***************************** struct Image { sf::Sprite sprite; //Följande int/heltal innehåller koordinaterna för varje bild. De har värdena: // 'x', 'y', 'x2' och 'y2'. precis som i skolgeometrin :) //x,y är övre vänstra och x2,y2 är nedre högra hörnet. int x; int y; int x2; int y2; int speed;//Figurens hastighet };//slut på generell image struct // I de tre sista structs har varje objekt ärvt från image struct. //De får: en sprite, en position och en hastighet. //Spelarens rymdskepp***************************************************************** struct PlayerShip: Image { PlayerShip() { sprite.SetImage(data.player1); sprite.SetPosition(350,475); //placeras 350 till höger och 475 ner på spelplanen x = 350; //Ge spriten sitt x värde. y = 475; //Ge spriten sitt y värde. speed = 6; //hastighet är relativ till frameraten } }player; //struct tilldelas player //Fiendens rymdskepp******************************************************************* struct Nme: Image { Nme() { sprite.SetImage(data.Nme1); //Fienden Nme får datan från structen data sprite.SetPosition(SPAWN_POS1,0);//=350,0 eftersom //SPAWN_POS1 är en fördefinierad konstant x = 350; //placeras ungefär mitt på spelplanen y = 0; //placeras vid överkanten speed = 2; //fart 1/3 av spelarens } }enemy; //structen tilldelas enemy/fienden //De kulor som skeppen skjuter************************************************************ struct BulletStruct: Image { BulletStruct() { speed = 10;//hastigheten högre än något skepp sprite.SetImage(data.bullet1); //Tilldelas bild sprite.SetPosition( (player.x + 15),(player.y) ); // Placeras i mitten av spelarens skepp y = player.y; //Kulorna tilldelas samma y-värde som skeppet. } }*bullet[3]; //Tilldelas bullet genom pekare till kulorna //Man kan ha tre kulor i luften samtidigt //spelet startar********************************************************************* int main (int argc, char *argv) { //Main startar window.SetFramerateLimit(60); //Antal bilder per sekund Clock.Reset(); //Sätt klockan på 0 while (window.IsOpened()) {//Fönstret visas //Kör de olika funktionerna EndProgram(); //Skall spelet avslutas? window.Clear(); //Rensa skärmen window.Draw(player.sprite); //Skriv ut sprites Shoot(); //Skjut kulorna Move(); //Flytta spelaren MoveNme(); //Flytta fienden //Hantera kulorna for (int i = 0; i < 3; i++) //Räkna igenom alla tre kulorna { //Start kolla kulor if (bulletExists[i]) MoveBullet(i); //Flytta den om den finns if (bulletExists[i]) window.Draw(bullet[i]->sprite); //Rita upp den om den finns if (!bulletExists[i]) bulletClock[i].Reset(); //Återställ klockan för kulorna } //slut kolla kulor window.Display(); //Visa upp spelfönstret } //Avslut om fönstret visas //return EXIT_SUCCESS; return 0; }//Main slutar /*------------------------------------------------------------------------------------ Här nedanför följer de olika funktionerna som används i spelet. Att hålla funktionerna utanför programmet gör koden lättare att hålla ren och programmet blir också lättare att felsöka. ------------------------------------------------------------------------------------*/ // Funktion för att se om programmet skall stängas************************************ void EndProgram() { //start stängningskontroll while(window.GetEvent(event)) { if (event.Type == sf::Event::Closed) window.Close(); } } //slut stängningskontroll //Flytta spelaren************************************************************************* void Move() {//Börja flytta spelaren //Spelaren flyttar med höger- och vänster piltangent //Skeppet stoppar när det når de fördefinierade värdena för kanten if ( window.GetInput().IsKeyDown(sf::Key::Left) && (player.x > LEFT_BORDER) ) {player.sprite.Move(-player.speed,0); player.x -= player.speed;} if ( window.GetInput().IsKeyDown(sf::Key::Right) && (player.x < RIGHT_BORDER) ) {player.sprite.Move(player.speed,0); player.x += player.speed;} //Följande kod ändrar spritens bild, den ändras 10 ggr per sekund. if ((Clock.GetElapsedTime() > 0.0) && (Clock.GetElapsedTime() < 0.1)) player.sprite.SetImage(data.player1); if ((Clock.GetElapsedTime() > 0.1) && (Clock.GetElapsedTime() < 0.2)) player.sprite.SetImage(data.player2); if (Clock.GetElapsedTime() > 0.2) Clock.Reset(); }//Slut på att flytta spelaren /*--------------------------------------------------- Spelaren skjuter genom att trycka ner Space-tangenten. Skjut-funktionen måste gå igenom tre stadier: 1: Vilken kula (av tre) skjuter vi? 2: Finns redan den kulan? 3: har den senaste kulan rört sig tillräckligt långt bort (Y-värdet är bortom 370) för att tillåta nästa kula att skjutas. --------------------------------------------------------*/ //Skjuta kulor************************************************************************** void Shoot() { //Börja skjuta. for (int i = 0; i < 3; i++) //kolla kulor 1, 2 och 3 { //starta loop kolla tre kulor if ( (i != 0) && (!bulletExists[i]) && (bulletExists[i-1]) ) { //starta kontroll om att det är ok att skjuta if ( (window.GetInput().IsKeyDown(sf::Key::Space)) && (bullet[i-1]->y < 370) ) { //Start, kontroll om senaste kulan är 370 pixlar bort bullet[i] = new BulletStruct; bulletExists[i] = true; } //Slut, kontroll om senaste kulan är 370 pixlar bort } //Sluta kontroll om att det är ok att skjuta else if ( (i == 0) && (window.GetInput().IsKeyDown(sf::Key::Space)) && (bulletExists[i] == false) ) { //Start, kontroll om alla kulor är borta bullet[i] = new BulletStruct; bulletExists[i] = true; } //Slut, kontroll om alla kulor är borta }//sluta loop kolla tre kulor } //Slut på att skjuta //Flytta kulorna**************************************************************************** void MoveBullet(int i) { //Start flytta kulor bullet[i]->sprite.Move(0,-bullet[i]->speed); //Flytta kulan bullet[i]->y -= bullet[i]->speed; if ((bulletClock[i].GetElapsedTime() > 0.0) && (bulletClock[i].GetElapsedTime() < 0.1)) //Visa bild 1 bullet[i]->sprite.SetImage(data.bullet1); if ((bulletClock[i].GetElapsedTime() > 0.1) && (bulletClock[i].GetElapsedTime() < 0.2)) //Visa bild 2 bullet[i]->sprite.SetImage(data.bullet2); if (bulletClock[i].GetElapsedTime() > 0.2) bulletClock[i].Reset(); //Återställ klockan window.Draw(bullet[i]->sprite); //Rita ut kulan if (bullet[i]->y < 0) { //Om kulan försvunnit utanför överkanten delete bullet[i]; bulletExists[i] = false; } } //Slut flytta kulor //Flytta fiendeskeppet********************************************************************* void MoveNme() { //Start flytta fiendeskepp enemy.sprite.Move(0,enemy.speed); //flytta fienden enemy.y += enemy.speed; //avgör nytt y if (enemy.y > BOTTOM_BORDER) //Om det flugit utanför nederkanten { enemy.sprite.SetPosition(SPAWN_POS1,TOP_BORDER); //sätt det överst igen enemy.y = 0; } if ((Clock.GetElapsedTime() > 0.0) && (Clock.GetElapsedTime() < 0.1)) //animation bild 1 enemy.sprite.SetImage(data.Nme1); if ((Clock.GetElapsedTime() > 0.1) && (Clock.GetElapsedTime() < 0.2)) //animation bild 2 enemy.sprite.SetImage(data.Nme2); window.Draw(enemy.sprite);//Rita ut fienden } //Slut flytta fiendeskepp /*-------------------------------------------------- PROGRAMSLUT ---------------------------------------------------*/
Nu har vi en svart yta med ett rymdskepp på och ett annat vi kan skjuta på, men ingenting händer vid en träff, det får vi ändra på. Vi måste ta reda på om vi träffar fienden eller inte, och tyvärr ingår en litet otäck bugg - x värdet för kulorna vi skjuter finns i vår struct, men definieras inte. Om inte programmet vet vad x är hittar det på något och kulorna är dömda att missa för evigt. Vi får lägga till:
x = player.x + 15; //halva spelaren in
i koden för att kunna se om vi träffar eller missar i fiendeskeppets struct.
Kollision
[redigera]Vi måste ha en ny funktion
headoncollisioncheck(i);//Kollisionskontroll för att se om en kula träffat
Funktionen kollar inte om man flyger in i en´granat från sidan.
void headoncollisioncheck(int i) //Förutsätter att man vet storleken på föremålen i förväg //Kulorna är 16 pixel breda //Fiendeskeppen är 32x32 pixel stora { //starta collison detect if ((bullet[i]->x > (enemy.x-16)) && (bullet[i]->x < enemy.x +32) && bullet[i]->y < enemy.y ) //Om x värdet för kulan ryms inom överkanten för fienden //och kulan egentligen passerat skeppet (men det går så fort //att det inte syns) händer följande: {//träff delete bullet[i]; //radera kulan bulletExists[i] = false; //ta bort kulan ur listan enemy.sprite.SetPosition(SPAWN_POS1,TOP_BORDER); //sätt fiendeskeppet överst igen enemy.y = 0; } //slut vid träff }//slut på collision
Explosion
[redigera]Sist måste vi skapa en enkel explosion. Vi styr även den med en klocka, men har en egen klocka till exoplosionen. Skapa en explosionsklocka med koden:
sf::Clock ExplosionClock; //en explosionsklocka för att hålla reda på explosionsanimationer.
Nollställ den i början av spelloopen:
ExplosionClock.Reset(); //Ställ explosionsklockan på 0
I struct struct måste du:
sf::Image explosion;//explosionsanimationen
Sedan skall filen läsas in
explosion.LoadFromFile("xplosion17.png"); //en hel spritekarta för en explosionsanimation
Skapa ännu en struct
struct expl: Image { expl() { sprite.SetImage(data.explosion); sprite.SetPosition(0,0); //placeras 350 till höger och 475 ner på spelplanen x = 0; //Ge spriten sitt x värde. y = 0; //Ge spriten sitt y värde. } }explosion; //struct tilldelas explosioner //Rita upp explosionerna void SkapaExplosion(int x, int y) {//Börja visa explosionen explosion.sprite.SetPosition(x,y); //Placera ut där explosionen skall ske explosion.x = x; explosion.y = y; //Rad 1. X förskjuts 64 för varje bild. Y är hela tiden 0 i första och 64 i andra koordinaten if ((ExplosionClock.GetElapsedTime() > 0.0) && (Clock.GetElapsedTime() < 0.2)) //animation bild 1 explosion.sprite.SetSubRect(sf::IntRect(0,0,64,64)); //Visa första bilden if ((ExplosionClock.GetElapsedTime() > 0.0) && (Clock.GetElapsedTime() < 0.4)) //animation bild 2 explosion.sprite.SetSubRect(sf::IntRect(64,0,128,64)); //Visa andra bilden if ((ExplosionClock.GetElapsedTime() > 0.0) && (Clock.GetElapsedTime() < 0.6)) //animation bild 3 explosion.sprite.SetSubRect(sf::IntRect(128,0,192,64)); //Visa tredje bilden if ((ExplosionClock.GetElapsedTime() > 0.0) && (Clock.GetElapsedTime() < 0.8)) //animation bild 4 explosion.sprite.SetSubRect(sf::IntRect(192,0,256,64)); //Visa fjärde bilden if ((ExplosionClock.GetElapsedTime() > 0.0) && (Clock.GetElapsedTime() < 10.0)) //animation bild 5 explosion.sprite.SetSubRect(sf::IntRect(256,0,320,64)); //Visa femte bilden //4 rader till window.Draw(explosion.sprite);//Rita ut explosionen }//Sluta visa explosionen
Lägg slutligen till i koden var explosionen skall uppstå genom att rita upp explosionen vid träff, lägg till:
SkapaExplosion(enemy.x,enemy.y); //Rita upp en explosion där fienden
inuti den funktion som avgör om man fått en träff där x,y är fiendeskeppets x,y värden.
Den slutgiltiga koden:
[redigera]-------------------- komplett kod ------------ //Spaceshooter //Koden i detta spel bygger på annan kod hittad på internet //på http://pastebin.com/kAS9E164 // Varningar: warning C4244: 'argument' : conversion from 'int' to 'float', possible loss of data //beror på att spelarna placeras ut på exakta pixlar, det påverkar inte spelet. //Rymdskeppsbilder från http://www.fromsmallpixels.co.uk/pixelart.html //Kulbilder från http://mab.eviscerate.net/bullets.png //Explosion från http://cdn.pimpmyspace.org/media/pms/c/b9/9e/ez/xplosion17.png //varje del är 64x 64, 5x5 bilder. #include <iostream> #include <SFML\System.hpp> #include <SFML\Window.hpp> #include <SFML\Graphics.hpp> //#include <SFML\Audio.hpp> //OBS se till så att du verkligen kan spela upp ljud //using namespace std; // utifall att konsollen behövs //synnerligen användbart om man skall felsöka //Lägg till statiska värden som aldrig ändras*************************************** #define RIGHT_BORDER 725 //höger kant #define LEFT_BORDER 25 //vänster kant #define TOP_BORDER 0 //överkant #define BOTTOM_BORDER 600 //nederkant #define SPAWN_POS1 350 //Där fientliga rymdskepp spawnas i x-led //Globala variabler******************************************************************* sf::RenderWindow window(sf::VideoMode(800,600,32), "Enkel Space Shooter"); //Fönstret spelet visas i sf::Event event; //ev. händelser i spelet sf::Clock Clock; //en spelklocka för att hålla reda på animationer o. dyl. sf::Clock ExplosionClock; //en explosionsklocka för att hålla reda på explosionsanimationer. sf::Clock bulletClock[3]; //Vi skjuter tre skott varje gång space-tangenetn trycks ner //Varje skott måste ha en egen klocka bool bulletExists[3] = {false,false,false}; //I början finns inte skotten //Funktioner som används i spelet deklareras i förväg********************************* void EndProgram(); //Hur spelet avslutas void Move(); //Hur figurerna gör när de skjuter void Shoot(); //Hur figurerna gör när de förflyttar sig void MoveBullet(int i); //Hur kulorna rör sig void MoveNme(); //Hur fiendeskeppet förflyttar sig void headoncollisioncheck(int i); //Har en kula träffat fienden? //Structs som spelet använder sig av för att skapa spelarna*************************** //Hade kunnat varit class, men allt är public i dem och struct bearbetas snabbare***** //Första structen innehåller alla bilderna******************************************** //Bilderna ändras från 1 till 2 med 1/10 sekunds skillnad för att simulera //en animation av rymdskeppen och kulorna/projektilerna. struct Data { //skapa imagefil-hållarna sf::Image bullet1; //projektiler bild 1 i animationen - vilken storlek som helst, max storlek 32x32 sf::Image bullet2; //projektiler bild 2 i animationen sf::Image Nme1; //fiende bild 1 i animationen - storlek 32x32 sf::Image Nme2; //fiende bild 2 i animationen sf::Image player1;//hjälte bild 1 i animationen - storlek 32x32 sf::Image player2;//hjälte bild 2 i animationen sf::Image explosion;//explosionsanimationen Data() {//ladda in filerna till imagefil-hållarna bullet1.LoadFromFile("bala1.png"); bullet2.LoadFromFile("bala2.png"); Nme1.LoadFromFile("malo1.png"); Nme2.LoadFromFile("malo2.png"); player1.LoadFromFile("nave.png"); player2.LoadFromFile("nave2.png"); explosion.LoadFromFile("xplosion17.png"); //en hel spritekarta för en explosionsanimation } //slut på bild till imagehållare }data; //"data" skall använda denna struct //Andra structen, generell struct för alla bildobjekten***************************** struct Image { sf::Sprite sprite; //Följande int/heltal innehåller koordinaterna för varje bild. De har värdena: // 'x', 'y', 'x2' och 'y2'. precis som i skolgeometrin :) //x,y är övre vänstra och x2,y2 är nedre högra hörnet. int x; int y; int x2; int y2; int speed;//Figurens hastighet };//slut på generell image struct // I de tre sista structs har varje objekt ärvt från image struct. //De får: en sprite, en position och en hastighet. //Spelarens rymdskepp***************************************************************** struct PlayerShip: Image { PlayerShip() { sprite.SetImage(data.player1); sprite.SetPosition(350,475); //placeras 350 till höger och 475 ner på spelplanen x = 350; //Ge spriten sitt x värde. y = 475; //Ge spriten sitt y värde. speed = 6; //hastighet är relativ till frameraten } }player; //struct tilldelas player //Fiendens rymdskepp******************************************************************* struct Nme: Image { Nme() { sprite.SetImage(data.Nme1); //Fienden Nme får datan från structen data sprite.SetPosition(SPAWN_POS1,0);//=350,0 eftersom //SPAWN_POS1 är en fördefinierad konstant x = 350; //placeras ungefär mitt på spelplanen y = 0; //placeras vid överkanten speed = 2; //fart 1/3 av spelarens } }enemy; //structen tilldelas enemy/fienden //De kulor som skeppen skjuter************************************************************ struct BulletStruct: Image { BulletStruct() { speed = 10;//hastigheten högre än något skepp sprite.SetImage(data.bullet1); //Tilldelas bild //tydliga x och y värden måste finnas för att man skall avgöra om det blivit en träff y = player.y; //Kulorna tilldelas samma y-värde som skeppet. x = player.x + 15; //halva spelaren in sprite.SetPosition((player.x + 15),(player.y)); // Placeras i mitten av spelarens skepp } }*bullet[3]; //Tilldelas bullet genom pekare till kulorna //Man kan ha tre kulor i luften samtidigt //Den explosion som uppstår när en kula träffar ett fiendeskepp*********************** struct expl: Image { expl() { sprite.SetImage(data.explosion); sprite.SetPosition(0,0); // sprite.SetSubRect(sf::IntRect(0,0,32,32)); x = 100; //Ge spriten sitt x värde. y = 100; //Ge spriten sitt y värde. } }explosion; //struct tilldelas explosioner //spelet startar********************************************************************* int main (int argc, char *argv) { //Main startar window.SetFramerateLimit(60); //Antal bilder per sekund Clock.Reset(); //Sätt klockan på 0 ExplosionClock.Reset(); //Ställ explosionsklockan på 0 while (window.IsOpened()) {//Fönstret visas //Kör de olika funktionerna EndProgram(); //Skall spelet avslutas? window.Clear(); //Rensa skärmen window.Draw(player.sprite); //Skriv ut sprites Shoot(); //Skjut kulorna Move(); //Flytta spelaren MoveNme(); //Flytta fienden //Hantera kulorna for (int i = 0; i < 3; i++) //Räkna igenom alla tre kulorna { //Start kolla kulor if (bulletExists[i]) MoveBullet(i); //Flytta den om den finns if (bulletExists[i]) headoncollisioncheck(i);//Kollisionskontroll för att se om en kula träffat if (bulletExists[i]) window.Draw(bullet[i]->sprite); //Rita upp den om den finns if (!bulletExists[i]) bulletClock[i].Reset(); //Återställ klockan för kulorna } //slut kolla kulor window.Display(); //Visa upp spelfönstret } //Avslut om fönstret visas return 0; }//Main slutar /*------------------------------------------------------------------------------------ Här nedanför följer de olika funktionerna som används i spelet. Att hålla funktionerna utanför programmet gör koden lättare att hålla ren och programmet blir också lättare att felsöka. ------------------------------------------------------------------------------------*/ // Funktion för att se om programmet skall stängas************************************ void EndProgram() { //start stängningskontroll while(window.GetEvent(event)) { if (event.Type == sf::Event::Closed) window.Close(); } } //slut stängningskontroll //Flytta spelaren************************************************************************* void Move() {//Börja flytta spelaren //Spelaren flyttar med höger- och vänster piltangent //Skeppet stoppar när det når de fördefinierade värdena för kanten if ( window.GetInput().IsKeyDown(sf::Key::Left) && (player.x > LEFT_BORDER) ) {player.sprite.Move(-player.speed,0); player.x -= player.speed;} if ( window.GetInput().IsKeyDown(sf::Key::Right) && (player.x < RIGHT_BORDER) ) {player.sprite.Move(player.speed,0); player.x += player.speed;} //Följande kod ändrar spritens bild, den ändras 10 ggr per sekund. if ((Clock.GetElapsedTime() > 0.0) && (Clock.GetElapsedTime() < 0.1)) player.sprite.SetImage(data.player1); if ((Clock.GetElapsedTime() > 0.1) && (Clock.GetElapsedTime() < 0.2)) player.sprite.SetImage(data.player2); if (Clock.GetElapsedTime() > 0.2) Clock.Reset(); }//Slut på att flytta spelaren /*--------------------------------------------------- Spelaren skjuter genom att trycka ner Space-tangenten. Skjut-funktionen måste gå igenom tre stadier: 1: Vilken kula (av tre) skjuter vi? 2: Finns redan den kulan? 3: har den senaste kulan rört sig tillräckligt långt bort (Y-värdet är bortom 370) för att tillåta nästa kula att skjutas. --------------------------------------------------------*/ //Skjuta kulor************************************************************************** void Shoot() { //Börja skjuta. for (int i = 0; i < 3; i++) //kolla kulor 1, 2 och 3 { //starta loop kolla tre kulor if ( (i != 0) && (!bulletExists[i]) && (bulletExists[i-1]) ) { //starta kontroll om att det är ok att skjuta if ( (window.GetInput().IsKeyDown(sf::Key::Space)) && (bullet[i-1]->y < 370) ) { //Start, kontroll om senaste kulan är 370 pixlar bort bullet[i] = new BulletStruct; bulletExists[i] = true; } //Slut, kontroll om senaste kulan är 370 pixlar bort } //Sluta kontroll om att det är ok att skjuta else if ( (i == 0) && (window.GetInput().IsKeyDown(sf::Key::Space)) && (bulletExists[i] == false) ) { //Start, kontroll om alla kulor är borta bullet[i] = new BulletStruct; bulletExists[i] = true; } //Slut, kontroll om alla kulor är borta }//sluta loop kolla tre kulor } //Slut på att skjuta //Flytta kulorna**************************************************************************** void MoveBullet(int i) { //Start flytta kulor bullet[i]->sprite.Move(0,-bullet[i]->speed); //Flytta kulan bullet[i]->y -= bullet[i]->speed; if ((bulletClock[i].GetElapsedTime() > 0.0) && (bulletClock[i].GetElapsedTime() < 0.1)) //Visa bild 1 bullet[i]->sprite.SetImage(data.bullet1); if ((bulletClock[i].GetElapsedTime() > 0.1) && (bulletClock[i].GetElapsedTime() < 0.2)) //Visa bild 2 bullet[i]->sprite.SetImage(data.bullet2); if (bulletClock[i].GetElapsedTime() > 0.2) bulletClock[i].Reset(); //Återställ klockan window.Draw(bullet[i]->sprite); //Rita ut kulan if (bullet[i]->y < 0) { //Om kulan försvunnit utanför överkanten delete bullet[i]; //radera kulan i ena listan bulletExists[i] = false; // markera den som förbrukad i andra } } //Slut flytta kulor //Flytta fiendeskeppet********************************************************************* void MoveNme() { //Start flytta fiendeskepp enemy.sprite.Move(0,enemy.speed); //flytta fienden enemy.y += enemy.speed; //avgör nytt y if (enemy.y > BOTTOM_BORDER) //Om det flugit utanför nederkanten { enemy.sprite.SetPosition(SPAWN_POS1,TOP_BORDER); //sätt det överst igen enemy.y = 0; } if ((Clock.GetElapsedTime() > 0.0) && (Clock.GetElapsedTime() < 0.1)) //animation bild 1 enemy.sprite.SetImage(data.Nme1); if ((Clock.GetElapsedTime() > 0.1) && (Clock.GetElapsedTime() < 0.2)) //animation bild 2 enemy.sprite.SetImage(data.Nme2); window.Draw(enemy.sprite);//Rita ut fienden } //Slut flytta fiendeskepp void SkapaExplosion(int x, int y) {//Börja visa explosionen explosion.sprite.SetPosition(x,y); //Placera ut där explosionen skall ske explosion.x = x; explosion.y = y; //Rad 1. X förskjuts 64 för varje bild. Y är hela tiden 0 i första och 64 i andra koordinaten if ((ExplosionClock.GetElapsedTime() > 0.0) && (Clock.GetElapsedTime() < 0.2)) //animation bild 1 explosion.sprite.SetSubRect(sf::IntRect(0,0,64,64)); //Visa första bilden if ((ExplosionClock.GetElapsedTime() > 0.0) && (Clock.GetElapsedTime() < 0.4)) //animation bild 2 explosion.sprite.SetSubRect(sf::IntRect(64,0,128,64)); //Visa andra bilden if ((ExplosionClock.GetElapsedTime() > 0.0) && (Clock.GetElapsedTime() < 0.6)) //animation bild 3 explosion.sprite.SetSubRect(sf::IntRect(128,0,192,64)); //Visa tredje bilden if ((ExplosionClock.GetElapsedTime() > 0.0) && (Clock.GetElapsedTime() < 0.8)) //animation bild 4 explosion.sprite.SetSubRect(sf::IntRect(192,0,256,64)); //Visa fjärde bilden if ((ExplosionClock.GetElapsedTime() > 0.0) && (Clock.GetElapsedTime() < 10.0)) //animation bild 5 explosion.sprite.SetSubRect(sf::IntRect(256,0,320,64)); //Visa femte bilden //4 rader till window.Draw(explosion.sprite);//Rita ut explosionen }//Sluta visa explosionen //Kontrollera om ditt skott träffar fienden********************************************** void headoncollisioncheck(int i) //Förutsätter att man vet storleken på föremålen i förväg //Kulorna är 16 pixel breda //Fiendeskeppen är 32x32 pixel stora { //starta collison detect if ((bullet[i]->x > (enemy.x-16)) && (bullet[i]->x < enemy.x +32) && bullet[i]->y < enemy.y ) //Om x värdet för kulan ryms inom överkanten för fienden //och kulan egentligen passerat skeppet (men det går så fort //att det inte syns) händer följande: {//träff delete bullet[i]; //radera kulan bulletExists[i] = false; //ta bort kulan ur listan enemy.sprite.SetPosition(SPAWN_POS1,TOP_BORDER); //sätt fiendeskeppet överst igen enemy.y = 0; SkapaExplosion(enemy.x,enemy.y); //Rita upp en explosion där fienden fanns } //slut vid träff }//slut på collision /*-------------------------------------------------- PROGRAMSLUT ---------------------------------------------------*/
Bygg vidare
- Lägg in en snygg bakgrund.
- Se till så att ljudet fungerar.
- Gör så att fiendeskeppet startar på olika ställen med hjälp av slumptalsgenerator.
- Gör så att fiendeskeppet också kan skjuta.
- Ge fiendeskeppet andra granater.
- Gör så att flera skepp kan tas fram samtidigt på skärmen.