Space Race – Del 3

Inledning

UPPDATERING: Artikeln har uppdaterats till XNA 4.0.

Läs mer om ändringar från XNA 3.1 till 4.0 under sista rubriken.

Eftersom detta är del 3 i en artikelserie så behöver du läsa del 1 och 2 först om du inte redan gjort det.

I denna artikel ska vi försöka färdigställa projektet och börja få en mer färdig produkt. Trots detta så kommer det finnas utrymme kvar för förbättringar både vad det gäller grafik, kod och spelidé. Det som tas upp i denna artikel är hantering av "game states", tidberäkning, ljud, byte av banor och mer grafik bl.a. ett GUI.

Alla steg i koden kommer inte att beskrivas utan vi fokuserar på att beskriva de viktigaste bitarna för att du skall förstå helheten och koden som du laddar hem. I slutet av artikeln kan du ladda hem och testa projektet.

Game states

I enklare spel räcker det oftast a definiera olika tillstånd (states) som spelet kan befinna sig i. I detta fall, med en spelidé som att ta sig från punkt A till punkt B så snabbt som möjligt med begränsad mängd bränsle, så tänker vi oss följande tillstånd:

  • Redo att börja spela.
  • Spel pågår.
  • Bana avklarad, redo för nästa bana.
  • Misslyckat försök, bara att börja om.

Dessa tillstånd behövs för att kunna presentera viktig information för spelaren. För att hålla reda på de olika tillstånden så skapar vi en enum. En enum är en speciell konstruktion som innehåller namngivna element. En enum kan sedan bara ha ett värde som redan finns som namnmgivet element. Varje element får en unik siffra. En stor fördel med enum är att koden blir mer lättläst och begriplig.

Av de tillstånd som beskrivits gör vi följande enum och döper varje tillstånd till engelska. Exemplet nedan visar var du kan placera enumdeklarationen.

GameState enum

namespace SpaceRace_del3
{
    enum GameState
    {
        READY,
        PLAYING,
        WON,
        GAMEOVER
    }

    public class Game1 : Microsoft.Xna.Framework.Game
    {
		GameState gameState = GameState.READY;
		...

Vi deklarerar variabeln gameState och börjar med att försätta spelet i tillståndet READY. Tillståndsvariabeln gameState använder vi sedan för att påverka vad som sker i spelets Update och Draw. Vi gör detta genom att använda switch-satser för varje tillstånd enligt exemplet nedan:

Exempel med switch på gameState

	switch (gameState)
	{
		case GameState.PLAYING:
			...
			break;
		case GameState.WON:
			...
			break;
		case GameState.GAMEOVER:
			...
			break;
		case GameState.READY:
			...
			break;
	}

I Update väntar tillstånden WON, GAMEOVER och READY egentligen bara på rätt förutsättningar för att gå över till PLAYING. I alla fallen handlar det om att trycka på SPACE för att gå vidare.

I Draw så visas det speciell grafik om gameState är i WON, GAMEOVER eller READY, då spelet ej pågår. Den speciella grafiken är det som kallas "overlays". För tillståndet READY ser det ut som bilden nedan:

bild

Tidräkning

Tiden för att klara av en bana är helt enkelt satt till 2 minuter. Detta är givetvis något som borde anpassas efter hur banan är konstruerad. I koden löses detta med en TimeSpan sruktur kallad levelTime. Det finns också en TimeSpan kallad warningTime som användas för att signalera att tiden närmar sig att ta slut. warningTime är satt till 30 sekunder.

Vid varje Update räknas levelTime ned med den tid som passerat tills dess att levelTime når noll. När detta inträffar tar spelet slut (GAMEOVER).

Ljudeffekter

Den teknik som vi använder för att spela bakgrundsmusik och ljudeffekter beskrivs i artikeln Ljud i XNA så vi nöjer oss med att beskriva vilka ljud vi använder.

Händelse Ljud
Bakgrundsmusik music.mp3
Sköldarna når 0 explosion.wav
Skeppet landra i mål win.wav
Skeppet accelererar throttle.wav

Byte av banor

Banorna lagras som bilder i PNG-format. Det betyder att det bara krävs ett enkelt ritprogram för att skapa nya utmanande banor. För att underlätta bytet av banor så har vi helt enkelt namngett banorna som Level01.png, Level02.png, etc. Med variabeln levelIndex håller vi reda på vilken bana vi är på. För att byta bana ökar vi levelIndex och letar i Content om det finns en bild som heter "Level" + levelIndex + ".xnb". XNB är det format som XNA konverterar all Content till. För att lägga till fler banor behövs endast fler LevelXX.xnb filer i Contentkatalogen.

Vi tar en titt på metoden som laddar nästa bana.

Game1.cs - LoadNextLevel

private void LoadNextLevel()
{
	levelIndex++;
	levelTime = TimeSpan.FromMinutes(2);

	string levelFile = "Level" + levelIndex.ToString("00");
	string levelPath = Path.Combine(StorageContainer.TitleLocation, "Content/" + levelFile + ".xnb");
	if (File.Exists(levelPath))
		level = Content.Load<Texture2D>(levelFile);
	else
	{
		levelIndex = 1;
		level = Content.Load<Texture2D>("Level01");
	}
	//Nollställ skeppet
	ship.Position = FindPixel(level, 0xFF0000FF) - new Vector2(0, ship_normal.Height / 2 + 3);
	ship.ResetShip();
}

Skulle inte "nästa bana" hittas så återgår levelIndex till 1, dvs. första banan laddas igen.

LoadNextLevel återställer skeppets startposition och all tidräkning. Därmed anropas även LoadNextLevel när en bana skall laddas om vid t.ex. tidsbrist eller att skeppet kraschar. Villkoren för att klara av banan och komma till nästa är följande:

  • Skeppet kolliderar med målplattan (färg 0xFFFF0000). Detta löses med modifikation av CheckCollision i SpaceShip.cs.
  • Skeppets hastighet är låg, dvs. du kan inte krascha in i mål.
  • Skeppet pekar tillräckligt uppåt.

CheckCollision har modifierats till att returnerar true om skeppet landat på mål, pekar uppåt och har låg hastighet. "Pekar uppåt" betyder en vinkelavvikelse på max 10 grader åt höger eller vänster vilket löses med metoden PointingUp.

Explosion

När skeppets sköldar når 0 p.g.a. kollisioner så har vi lagt in en liten animation av en explosion. Tekniken för detta beskriv i artikeln Animationer .

Nya mätare

Nivån på skeppets sköld samt den tid som är kvar för att hinna klara av banan visas med en vanlig SpriteFont. Vi gör ett enkelt trick för att få skuggning på utskriften (outline). Tricket går ut på att skriva ut samma text fast svart med någon pixels förskjutning innan texten skrivs ut. Tyvärr finns det inte inställning för "outline" i en SpriteFont.

bild

Vi har lagt lite med tid på att skapa en rolig mätare för bränslenivån. Tanken är att den skall likna en analog mätare som finns i t.ex. bilar. Här finns också rum för förbättringar.

bild

Man skulle kunna kalla detta för ett enkelt GUI (graphical user interface) eller HUD (heads-up display)

Avslutning

I denna sista del av Space Race presenterar vi mer ett färdigt resultat än en guide. Här nedan kan du ladda hem hela projektet med ljudeffekter och allt. Vill du vidareutveckla spelet så är det upp till dig. För privat bruk får du göra vad du vill med koden och grafiken men om du tänker publicera dina resultat av en vidareutveckling så var noga med att ange källan (csharpskolan.se). Helst av allt vill vi bli informerade i förväg.

Som nämnts innan så finns det många förbättringar at göra, har listar vi några förslag:

  • Titelskärm innan spelet börjar.
  • Möjlighet att välja bana vid start.
  • Highscore för banor.
  • Anpassade inställningar på bantider mm lagrade i t.ex. Level01.xml, Level02.xml eller kanske bara som Levels.xml.
  • Fler (bättre) ljudeffekter, t.ex. när skeppet kolliderar.
  • Bättre mätare för tid och sköldar.
  • Anpassning för XBox360.
  • Nätverksstöd...

Listan kan göras ännu längre.

Färdigt projekt

Projekten innehåller all "content" både rå och kompilerad.

Uppgradering till XNA 4.0

Ljudeffekterna (XACT) behövdes uppgraderas. Detta gjordes enkelt genom att kompilera om xap-filen i XACT.

StorageContainer.TitleLocation har försvunnit så sökvägen till banorna fick redigeras lite.

En märklig sag var att ordningen på pixeldatan i texturerna som laddades in har ändrats. Tidagare var ordningen ARGB (alpha, red, green, blue) men verkar numera vara i ordningen #ABGR. Det betyder att färgen röd tidigare definerades som 0xFFFF0000 nu definieras som 0xFF0000FF.

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *

Scroll to top