Vi ska i denna del försöka lägga till en massa funktioner till vårt "spel" som börjar bli klart (alla vet att ett projekt blir aldrig riktigt klart..). Vi ska färglägga rutorna på ett enkelt sätt, öka svårighetsgraden (hastigheten på bollen) allteftersom spelet är igång och tiden går, samt titta lite på hur man enkelt kan läsa in banor från textfiler vilket gör att vi kan designa och skapa nya häftiga banor enkelt med en vanlig texteditor (som notepad).
Färglägg rutorna
Vi börjar med en enkel detalj, nämligen att färglägga rutorna. Vi lägger till följande till Draw():
for (int i = 0; i < tile_position.Count; i++)
{
switch (tile_type[i])
{
case 0: spriteBatch.Draw(tile_gfx, tile_position[i], Color.White);
break;
case 1: spriteBatch.Draw(tile_gfx, tile_position[i], Color.HotPink);
break;
case 2: spriteBatch.Draw(tile_gfx, tile_position[i], Color.YellowGreen);
break;
case 3: spriteBatch.Draw(tile_gfx, tile_position[i], Color.Purple);
break;
case 4: spriteBatch.Draw(tile_gfx, tile_position[i], Color.Yellow);
break;
case 5: spriteBatch.Draw(tile_gfx, tile_position[i], Color.Silver);
break;
}
}
Vi ändrar lite i for-satsen, istället för att bara rita ut rutorna med vit färg så lägger vi till en switch-sats och kollar i vår lista tile_type och utifrån vilken typ det är så ritar vi ut rutorna med olika blending. Dessa färger kan justeras efter smak och tycke...
Öka svårighetsgraden (hastigheten) med tiden
Vi ska nu göra så att hastigheten på bollen ökas ju längre tid som går i spelet. Vi börjar med att lägga till den globala variabeln tid.
int tid = 0;
Vi lägger sedan till följande till Update(): (OBS precis som innan så är det i if-satsen game_state==1 som vi lägger till följande)
ball_position = ball_position + ball_speed * game_speed;
//OBS uppdatering av tidigare förflyttning av bollen
tid += gameTime.ElapsedRealTime.Milliseconds;
if (tid > 20000 * game_speed)
{
tid = 0;
if (game_speed < 6) game_speed = game_speed + 1;
}
if (ball_position.Y >= 600) game_state = 0;
Vi börjar med att justera vår tidigare uppdatering av bollens position genom att lägga till en multiplikation med game_speed (denna variabel är till en början satt till 1 vilket inte påverkar bollens hastighet alls). I vår variabel tid så adderar vi den förlutna tiden i spelet, gameTime.ElapsedRealTime.Milliseconds, räknat i millisekunder. Vi kollar sedan ifall den tid som förflutit är mer än 20000 ms (20 sekunder) multiplicerat med game_speed. Är det så så ökar vi game_speed med ett och nollställer tiden (tid). Detta medför alltså att hastigheten inte ökar var 20:e sekund utan beror även på den aktuella hastigheten, vilket kan vara trevligt. Vi har även satt en övre gräns på game_speed (6).
Vi har även lagt till en enkel kontroll ifall vi missar bollen och den åker utanför skärmen (i nederkanten) så sätts game_state till noll och spelet återgår till utgångsläge igen (läge 0).
Har vi gjort detta och provkör vårt projekt så ser du ut såhär:
Inläsning av banor från fil
Vi ska nu lägga till den otroligt smidiga funktionen att läsa in banor från filer. Detta kommer att underlätta extremt mycket när vi ska ska nya banor och ändra i banor eftersom vi då kan göra detta med ett enkelt textredigeringsprogram och slipper göra ett speciellt program (ban-editor) eftersom det i detta fall är lite "over-kill".
Vi får börja med att skrota den gamla uppritningen av rutor, dvs vår nästlade for-sats i LoadContent() som ritade upp 11 x 7 rutor. Vi kommenterar ut dessa rader i nedanstående kodexempel för att vara extra tydliga:
//for (int j = 0; j < 6; j++)
//{
// for (int i = 0; i < 11; i++)
// {
// tile_position.Add(new Vector2((i * 40) + 200, (j * 20) + 100));
// tile_type.Add(j);
// }
//}
När detta är gjort ska vi använda lite klasser i .NET som används för filhantering. Dessa återfinns i namespace System.IO. Denna importeras inte normalt i ett XNA-projekt utan vi måste själva göra detta. Detta görs överst i koden med raden (under övriga liknande rader):
using System.IO;
Vi behöver inte lägga till "using SystemIO" men gör vi inte detta så måste vi använda klasserna med komplett sökväg, exempelvis System.IO.StreamReader (som vi strax skall använda) vilket är onödigt.
Vi lägger även till en global variabel level_number som vi ska använda för att hålla reda på den aktuella banan som används.
int level_number = 0;
För att snygga till lite så ska vi skapa en egen funktion (metod) som läser in banorna. Vi kommer att kalla denna för LaddaLevel().
public void LaddaLevel(int nummer)
{
StreamReader SR = new StreamReader(nummer.ToString());
string bana = SR.ReadToEnd();
SR.Close();
int temp_positionY = 0;
int temp_positionX = 0;
for (int i = 0; i < bana.Length; i++)
{
switch (bana[i])
{
case ' ':
temp_positionX++;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
tile_position.Add(new Vector2((temp_positionX * 40), (temp_positionY * 20)));
temp_positionX++;
tile_type.Add(int.Parse(bana[i].ToString()));
break;
case '\n':
temp_positionY++;
temp_positionX = 0;
break;
}
}
}
Vår funktion LaddaLevel() tar ett heltal (int) som inparameter. Detta tal är numret (och namnet) för banan som ska laddas. Vi börjar med att skapa en StreamReader som vi kallar för SR och som öppnar banan med det nummer som vi skickar som inparameter till vår funktion (int nummer). Vi anger ingen sökväg utan skriver new StreamReader(nummer.ToString()); vilket innebär att ifall nummer=0 så försöker SR öppna filen med namnet "0" i aktuell mapp. Alltså i den mapp som vårt projekt kompileras och körs i. För att detta ska fungera så skapar vi en fil som vi döper till "0" och lägger i mappen (se bild nedan)
Observera att det i Vista och Windows 7 är c:\users istället för "Documents and Settings".
I denna fil så kan vi "rita" upp vår bana med siffror där varje siffra är en viss tile_type och motsvarar en viss färg. Vi kommer strax återgå till LaddaLevel() och förklara hur detta funkar. Vi skapa en testbana i stil med den på bilden (nedan).
Det går även bra att ladda hem testfilen (0.zip) nedan.
Om vi återgår till LaddaLevel() så läses hela innehållet i filen (exempelvis 0) in i en sträng (bana)med raden string bana = SR.ReadToEnd();. när vi gjort detta så frigör vi filen från användning med SR.Close();.
Därefter blir det lite textbearbetning. Vi skapar två temporära variabler, temp_positionY och temp_positionX som vi ska använda för att bestämma rätt position för det rutor som skall läsas in från filen.
Efter att vi läst in filens innehåll i strängen bana så börjar vi med att stega igenom den, tecken för tecken (char) med en enkel for-sats. Varje sträng är uppbyggd av tecken där även "ny rad" motsvaras av ett tecken (\n). Vi utnyttjar detta med en switch-sats som kollar vilket tecken det är. Är det ett blanksteg, alltså ' ' så gör vi ingenting mer än att öka temp_positionX. Är tecknet en siffra mellan 0 och 5 så motsvarar det en tile och då lägger vi till en tile i vår lista med tile_position.Add() samt konverterar siffertecknet till tal och sparar i vår lista tile_type. Stöter vi på tecknet \n, alltså ny rad så ökar vi temp_positionY med ett och nollställer temp_positionX. På så sätt läser vi igenom hela filen och positionerna för rutorna läggs i listan som vi senare använder för att rita ut dem.
Vi har inte lagt in någon felkontroll så det går att skapa banor med rutor som inte får plats på skärmen.
LaddaLevel(level_number);
Med en enkel och snygg rad så laddas bana 0 in (level_number är satt till 0 från början).
Vi passar även på att göra följande små tillägg till Update(), OBS game_state==1
if (ball_position.Y >= 600)
{
game_state = 0;
game_speed = 1;
tid = 0;
}
Vi ändrar lite på den if-satsen som kontrollerar ifall vi missat bollen. Vi lägger till två rader till if-satsen som gör att när vi missat bollen, så förutom att ändra game_state så nollställs tiden och hastigheten börjar på ett igen. Gör vi inte detta så fortsätter spelet i samma hastighet som innan vi missade bollen.
Provkör vi vårt projekt så bör vi nu se vårt nya fina bana.