Programmera spel i C++ för nybörjare/Programmera med tid


Att programmera med tid

redigera

Anpassad för SFML 1.6


Det finns egentligen två olika sätt att använda sig av tid när det gäller alla typer av spelprogrammering. Det ena är att se till så att man har en fast ”framerate”, dvs antalet visade bilder per sekund, och bygger upp spelets rörelser och animationer utifrån det.

Fast tid

redigera

Man antar att spelet hela tiden visar upp ett visst antal rutor i sekunden och det antalet ändras inte. Fördelen med detta är att spelet går lika fort oavsett processorklocka. Om du vill se hur det inte skall se ut kan du t.ex. ladda hem det gamla DOS spelet ”Command and Conquer 1” och köra det på en modern laptop. Innan du byggt din första lastbil kommer datorn att bomba dig med atombomber helt enkelt eftersom spelet bygger på ett antagande att du och datorn gör ungefär lika många handlingar i sekunden, men med moderna processorer gör datorn tusentals handlingar när du gjort en så spelet är ospelbart med moderna datorers processorer.

Nackdelen är att den omvända situationen kan uppstå när du har en gammal dator som skall köra ditt moderna spel där du har en förväntad framerate på 60. Då kommer framförallt saker som rör sig: fallande stenblock, väggar som rasar, snöbollar i luften osv att gå ryckigt eftersom de helt enkelt inte kan röra sig i den hastighet du skapat spelet i.


Rörlig tid

redigera

Proffsen röstar på rörlig tid. Då går spelet snabbt på en modern dator men långsamt på en gammal dator, men poängen är att alla handlingar du gör kommer att utföras på ett, för ögat, smidigt sätt så länge frameraten ligger över 25 rutor i sekunden vilket är gränsen för ”hackig” grafik.

Klockan

redigera

I SFML har du en inbyggd klocka som beräknar tiden i sekunder (float). Klockan är en klass som heter sf::Clock och koden för att skapa en klocka är enkel. Så här skapar du en ny klocka i spelet:

sf::Clock NyKlocka;

Skall du ställa in tiden på 0, ungefär som man återställer ett tidtagarur skriver du:

NyKlocka.reset();

Vill du veta hur lång tid de gått sedan klockan nollställdes skriver du:

 float TidSomGaott;
 TidSomGaott = NyKlocka.GetElapsedTime();
 cout <<  TidSomGaott << endl; //så kan du skriva ut tiden

Varför räknas tiden i float? Du vill antagligen kunna räkna i både hundra- och tusendelssekunder. Skulle tiden vara en integer/heltal skulle alla beräkningar visa att tiden = 0 om det sker något i spelet som har en tid under en hel sekund, och det vill vi definitivt inte använda.

Hastighet utan tidsgräns, exempel

redigera

Om du vill styra en figur på spelplanen, t.ex. en ko, kan du alltså ge den en fast hastighet. Ger vi den hastigheten 50 kommer den att röra sig 50 pixels varje gång skärmbilden uppdateras. Om vi har en framerate på 25 innebär det att figuren/kon rör sig 25 * 50 = 1250 pixels i sekunden men en framerate på 60 innebär en förflyttning på 60 * 50 = 3000 pixels i sekunden.

const float Speed = 50.f; //ge den hastigheten 50
float Left = 0.f; //den rör sig inte i sidled
float Top  = 0.f; //den rör sig inte i höjdled

while (NamnPaoSpelfoenstret.IsOpened()) //när spelet startar
{ //Hastighet Vänster/Höger/Upp/Ner = 50 pixels/frame i samtliga fall
   if (NamnPaoSpelfoenstret.GetInput().IsKeyDown(sf::Key::Left))  Left -= Speed; 
   if (NamnPaoSpelfoenstret.GetInput().IsKeyDown(sf::Key::Right)) Left += Speed;
   if (NamnPaoSpelfoenstret.GetInput().IsKeyDown(sf::Key::Up))    Top  -= Speed;
   if (NamnPaoSpelfoenstret.GetInput().IsKeyDown(sf::Key::Down))  Top  += Speed;
}

Hastighet med tidsgräns och klocka

redigera

Om du vill att en figur skall röra sig lika snabbt på spelplanen oavsett vilken processor eller annan hårdvara datorn har, måste du utgå från hur snabbt bilderna visas och multiplicera hastigheten med det.

sf::Clock NyKlocka; //skapa en klocka

const float Speed = 50.f; //Ge figuren en hastighet 
float Left = 0.f; //den rör sig inte i sidled
float Top  = 0.f; //den rör sig inte i höjdled

while (NamnPaoSpelfoenstret.IsOpened())//när spelet startar
{
 float TidSomGaott;
 TidSomGaott = NyKlocka.GetElapsedTime();
 //Hastighet Vänster/Höger/Upp/Ner = 
 // 50 * 0.017  = 0.85 pixlar/frame för framerate 60 i samtliga fall
 // 50 * 0.14   = 7 pixlar/frame för framerate 30 i samtliga fall
   if (NamnPaoSpelfoenstret.GetInput().IsKeyDown(sf::Key::Left))  Left -= Speed * TidSomGaott;
   if (NamnPaoSpelfoenstret.GetInput().IsKeyDown(sf::Key::Right)) Left += Speed * TidSomGaott;
   if (NamnPaoSpelfoenstret.GetInput().IsKeyDown(sf::Key::Up))    Top  -= Speed * TidSomGaott;
   if (NamnPaoSpelfoenstret.GetInput().IsKeyDown(sf::Key::Down))  Top  += Speed * TidSomGaott;
}

Anta att frameraten är 60 på en snabb dator och 30 på en långsam. Då rör sig figuren 60 * 50 = 3000 pixels på en sekund på den snabba men 1500 pixels på den långsamma. Men för att visa upp 60 bildrutor tar det 1 sekund på en snabb dator men 2 sekunder på den långsamma, så sluthastigheten blir samma (3000 * 1 = 1500 * 2).

Hastighet med tidsgräns utan klocka

redigera

Om du inte vill hålla på och beräkna tid finns det ett annat sätt, man kan beräkna tiden sedan senaste uppdateringen i spelfönstret. Det gör man med kommandot:

NamnPaoSpelfoenstret.GetFrameTime();

då får du tiden sedan senaste uppdateringen i sekunder. Om du har en framerate på 60/sekunden blir resultatet 1/60 = 0.016666666666 medan om du har en framerate på 25 blir resultatet 1/25= 0.04

Ta reda på spelets framerate

redigera

Vill man ta reda på vilken framerate man har i spelet kan man göra motsatsen:

sf::Clock NyKlocka; //skapa en klocka

while (NamnPaoSpelfoenstret.IsOpened()) //när spelet startar
{
   float Framerate = 0.0f; 
   Framerate = 1.f / NyKlocka.GetElapsedTime(); //ta fram frameraten
   cout << ”Frameraten = ” << Framerate << endl; //skriv ut frameraten
   NyKlocka.Reset(); //nollställ klockan
}

// Eller :

while (NamnPaoSpelfoenstret.IsOpened())//när spelet startar 
{
   float Framerate = 0.0f;
   Framerate = 1.f / NamnPaoSpelfoenstret.GetFrameTime(); 
   //Ta fram tid mellan varje visning av skärmbilden
}

Vertical sync

redigera

Om du inte får upp en högre framerate på spelet än ca 60 hur mycket du än försöker beror det på att spelet har en fast "vertical sync". På svenska innebär det att spelet inte kan visa fler bilder än bildskärmens Hertztal/uppdateringar varje sekund, vilket i grund och botten är en fördel eftersom spelaren ändå inte kan se bilder som ritas upp innan datorskärmen kan visa dem. Ibland kanske du ändå vill se exakt hur snabbt spelet är oavsett vad skärmen kan visa, och då skriver du bara i koden:

NamnPaoSpelfoenstret.UseVerticalSync(false);

I början. Observera att "false" är standard, du måste aktivt sätta den till "true" om du vill låsa spelets uppdateringshastighet mot skärmens uppdateringshastighet.

Fast framerate

redigera

Slutligen kan du rent programmeringsmässigt tvinga spelet att ha en viss framerate genom att skriva:

NamnPaoSpelfoenstret.SetFramerateLimit(60); // begränsad till 60 bilder i sekunden

Undvik det om du tror att spelet kommer att spelas på äldre datorer som inte klarar av så snabb uppdatering. Om du inte vill ha någon begränsning alls skriver du:

NamnPaoSpelfoenstret.SetFramerateLimit(0);  // ingen begränsning

SFML2.3.X

redigera

SFML version 2 hanterar både fönster och kontroller annorlunda. Det är mycket som inte stämmer och just synkroniseringen har fått en rejäl genomgång.

Följande kod fungerar i version 2.3.X

#include <iostream>
#include <SFML/Graphics.hpp>

int main()
{
	sf::Clock NyKlocka; //skapa en klocka
	const float Speed = 50.f; //ge den hastigheten 50
	float Left = 0.f; //den rör sig inte i sidled
	float Top = 0.f; //den rör sig inte i höjdled


	sf::RenderWindow window(sf::VideoMode(300, 300), "SFML -tidsberäkning");

	//Tillåt synkronisering till bildskärmens uppdatering, avstängd som standard
	window.setVerticalSyncEnabled(true);
	sf::CircleShape shape(100.f);
	shape.setFillColor(sf::Color::Green);

	while (window.isOpen()) //när spelet startar
	{
		//Se hur mycket tiden som gått
		sf::Time elapsed1 = NyKlocka.getElapsedTime();
		//Starta om klockan
		NyKlocka.restart();

		//Hastighet Vänster/Höger/Upp/Ner = 
		// 50 * 0.017  = 0.85 pixlar/frame för framerate 60 i samtliga fall
		// 50 * 0.14   = 7 pixlar/frame för framerate 30 i samtliga fall
		if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) Left -= Speed * elapsed1.asSeconds();
		if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) Left += Speed * elapsed1.asSeconds();
		if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) Top -= Speed * elapsed1.asSeconds();
		if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) Top += Speed * elapsed1.asSeconds();

		//Kontrollera att hastigheten ändras
	   std::cout << "LeftSpeed= "<< Left << std::endl;


		sf::Event event;
		while (window.pollEvent(event))
		{
			if (event.type == sf::Event::Closed)
				window.close();
		}

		window.clear();
		window.draw(shape);
		window.display();
	}

	return 0;
} 

Källor

redigera

http://www.sfml-dev.org/tutorials/1.6/window-time.php

http://www.sfml-dev.org/tutorials/2.3/system-time.php