Inledning
I denna artikelserie kommer vi att skapa en ”Breakout”-klon. Alltså ett spel som påminner mycket om spelet ”Breakout”. Vafan är Breakout tänker ni. Jo det är en vidareutveckling av alla datorspels urfader/moder nämligen Pong. Som så mycket annat så skapades Breakout av Atari i mitten av 70-talet. Sedan dess har otaliga kopior och nya spel byggt på samma idé dykt upp. En av de mest kända är Taitos Arkanoid som spelades flitigt på hemdatorerna under 80-talet (C64, Amiga etc). Idén är lika enkel som genial och bygger på att spelaren med en ”paddel” ska försöka hålla en (eller flera) bollar i luften och träffa olika föremål. Mer information om spelet Breakou hittar ni här
Bollen följer en enkel fysik och ökar i hastighet med tiden vilket gör att spelet blir svårare allt eftersom. Man får poäng för varje ”ruta” som man träffar och skulle man missa bollen så att den åker ner under paddeln så mister man ett liv.
Vi kommer i denna serie titta på hur vi skapar ett sådant spel med hjälp av XNA. Målgruppen är nybörjare inom programmering och XNA vilket innebär att vi löser alla problem på ett enkelt och rättfram sätt. Vi kommer t.ex. att undvika objektorienterad programmering och inte skapa egna klasser vilket en mer erfaren programmerare antagligen hade gjort. Sådana förbättringar är givetvis upp till var och en och kommer kanske att vara material för en kommande artikel.
Vi kommer att använda oss av en hel del koncept som beskrivits i andra artiklar och referera till dessa men utgångspunkten är en artikelserie ämnad för nybörjare inom (spel)programmering.
Skapa nytt projekt
Eftersom vi använder oss av XNA Game Studio 3.1 så behöver vi Visual Studio Express 2008 (eller Pro) eftersom det i skrivande stund inte kommit en ny version av XNA som fungerar med VS 2010. Hur man installerar VS hittar ni här. Hur man installerar XNA hittar ni här.
Har vi allt vi behöver så startar vi Visual Studio och startar ett nytt projekt, File – New Project.
Vi väljer att skapa ett nytt Windows Game (3.1) projekt och döper det till Breakout.
Vi får nu en massa färdig kod som är grunden till vårt XNA-spel. Detta beskrivs mer ingående i XNA – En överblick som ni hittar här, och som ingår i artikel Asteroids – del1. Eftersom människan av naturen är lat så kommer en sammanfattning ske även i denna artikel.
Från början innehåller vårt XNA-projekt ett antal metoder som är viktiga:
- Game1(), här kan du ändra inställningar på skärmen via graphics, t.ex. upplösning och fullskärmsläge etc.
- Initialize(), initiering av variabler kan du placera här.
- LoadContent(), all inladdning från Content gör du här.
- Update(GameTime gameTime), typisk för förflyttning och kollisionsberäkningar. All logik i spelet bör placeras här.
- Draw(GameTime gameTime), all uppritning sköts härifrån.
De mesta centrala delarna är just Update och Draw. Dessa metoder körs hela tiden så ofta som det bara går och utgör själva hjärtat av spelet. Draw är som regel låst till att försöka köra 60 gånger per sekund (60 fps). Metoden Update kan köras mer än en gång mellan varje Draw.
Lägga till grafik för ”paddeln”
Vi börjar med att skapa grafik till ”paddeln” som vi ska styra med musen i spelet. Det går bra att göra en egen bild i t.ex. Photoshop, Paint.net eller liknande program och göra den i vilken storlek som helst fast då måste en del kod i denna artikel korrigeras.
Vi har likt TV-kockar förberett lite och skapat en bild för paddeln med storleken 90x17 pixlar, som vi döpt till player.png. Denna fil kan ni ladda hem som player.zip.
Placera bildfilen player.png i content mappen i projektmappen. Projektmappen hittar ni under Mina dokument\Visual Studio 2008\Projects\Breakout\Breakout och i den finner ni mappen Content. Användare av Windows XP borde inte ha några problem att hitta Mina Dokument vars sökväg är c:\Documents and Settings\Användarnamn. Användare av Windows 7 hittar enklast dit via biblioteket dokument eller via sökvägen c:\user\Användarnamn (eller c:\Användare\användarnamn).
I Solution Explorern i Visual Studio så har du en mapp som heter Content. Denna mapp är direkt kopplad till den inbyggda Content Managern. Allt innehåll vi vill kunna ladda via Content Managern måste läggas in i Solution Explorern.
Högerklicka på Content i Solution Explorern och välj "Add -> Existing Item".
Vi väljer player.png och klickar på Add
Nu är det bara att sätta igång med att koda.
Skapa variabler
Vi behöver bara skapa två variabler för att kunna rita ut paddeln på skärmen och styra den med musen.
Vi börjar med att skapa två globala variabler, player_gfx av typen Texture2D och player_position av typen Vector2. Vi lägger alltså till följande 2 rader innan metoden Game1 i vår kod.
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D player_gfx; //Grafik till paddeln
Vector2 player_position; //Position för paddeln
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
Klassen Texture2D används för inladdning av 2D-grafik så att vi kan använda den i vårt program. Klassen Vector2 lagrar två värden av typen float som kallas X och Y. Begreppet vektor borde inte vara något nytt för de som kan sin matematik. Vi kommer att använda X- och Y-värdet för vår player_position för att bestämma vart på skärmen som paddeln ska ritas ut.
Upplösning
Denna artikel var ursprungligen skriven för XNA 3.1. Standard upplösning som användes då var 800x600 pixlar. Med senare versioner av XNA (4.0+) så har denna upplösning ändrats vilket gjorde att paddeln inte syntes. Vi behöver se till att upplösningen 800x600 pixlar används. Detta görs enklast genom att ange önskad upplösning i konstruktorn som nedan:
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
//ange önskad upplösning
graphics.PreferredBackBufferHeight = 600;
graphics.PreferredBackBufferWidth = 800;
}
Uppdatera LoadContent()
Nu ska vi ladda grafiken via Content Manager. I XNA så sköter vi inladdningen i metoden LoadContent(). Vi passar även på att skapa ett Vector2-objekt för vår position.
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
player_gfx = Content.Load<Texture2D>("player");
player_position = new Vector2(200, 570);
}
I anropet Content.Load anger du vilken sorts data du vill ladda in. I vårt fall är datan av typen Texture2D som även matchar datatypen som vi valde på variabeln player_gfx. Som inparameter till Content.Load anger du vilken asset du vill ladda. Med detta menas någon fil som finns inlagd under Content i Solution Explorern. Notera att filändelsen inte ska anges.
Uppdatering av Draw()
I XNA ska all uppritning ske i metoden Draw(GameTime gameTime). Till hjälp har vi klassen SpriteBatch för att rita så kallade sprites. En spriteär grafik i 2D, i vårt fall paddeln.
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.Black);
spriteBatch.Begin();
spriteBatch.Draw(player_gfx, player_position, Color.White);
spriteBatch.End();
base.Draw(gameTime);
}
Vårt objekt av klassen SpriteBatch heter spriteBatch, tänk vilken skillnad en lite bokstav kan göra! Med hjälp av spriteBatch kan vi rita ut gafik. Metoden Draw() kan rita ut på många olika sätt, 7 olika sätt för att vara exakt. Det hela beror på vilket sätt vi finner enklast. Den variant vi använder tar 3 parametrar.
Den första parametern talar om vilken grafik som ska användas, den måste då vara av typen Texture2D. Den andra parametern är en Vector2 som bestämmer var på skärmen grafiken ska hamna. Den sista parametern används för blending. Med blending kan du manipulera färgerna på den sprite som du ritar ut. Färgen Color.White gör så att det inte sker någon manipulering av färgerna. Vi kommer längre fram i artikelserien använda oss av blending för att på ett enkelt sätt slippa ha samma grafik sparat i olika färger.
Uppritning av sprites måste ske mellan anropen Begin() och End().
Vi passar även på att ändra bakgrundsfärg genom att ändra graphics.GraphicsDevice.Clear(Color.CornflowerBlue); till graphics.GraphicsDevice.Clear(Color.Black);
Uppdatering av Update()
All spellogik och uppdateringar som ska ske i spelet, som exempelvis förflyttning av spelare, boll etc., sker i metoden Update(). Vi lägger till följande rader till metoden:
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// TODO: Add your update logic here
MouseState mouse = Mouse.GetState();
if (mouse.X < 0 || mouse.X > 710) //Kontrollerar ifall paddeln är utanför skärm
{
if (mouse.X < 0) player_position.X = 0; //Hindrar paddeln från att hamna
if (mouse.X > 710) player_position.X = 710; //utanför genom att "låsa" fast den
}
else
{
player_position.X = mouse.X; //Paddelns position blir densamma som muspekarens
}
base.Update(gameTime);
}
Statusen för musen (alltså vad som händer med den) kan lagras i en variabeltyp kallas MouseState. I exemplet ovan så sparar vi statusen för musen i variabeln mouse. Statusen läses av med metoden GetState() som nås via Mouse. Det betyder alltså att varje gång som metoden Update() körs så läses statusen av på musen och lagras i vår variabel (mouse).
Vi kan nu komma åt och läsa av vart muspekaren är på skärmen (även om det inte ritas ut någon muspil på skärmen). Detta görs i X-led med mouse.X och i Y-led med mouse.Y. Eftersom vi bara ska kunna röra paddeln i X-led så behöver vi inte ta hänsyn till positionen i Y-led.
Den första if-satsen, if (mouse.X < 0 || mouse.X > 710) kontrollerar ifall paddelns position i X-led är utanför skärmen. Skärmen är 800 pixlar bred. Observera att paddeln är 90 pixlar bred (därav värdet, 800-90=710). Stämmer detta så sätter vi paddelns position till ett fast värde. Antigen i vänstra kanten X=0, eller i högra kanten X=710, beroende på vilken sida som paddeln befinnder sig. Detta för at hindra så att man ej kan föra paddeln utanför skärmen.
Stämmer inte if-satsen, alltså else, så får paddelns position i X-led samma värde som musens position i X-led. Varje gång metoden raw() körs som kommer alltså vår paddel att ritas upp på en ny position om vi rört musen.
Det färdiga resultatet borde så ut såhär: