Skip to content

Monogame del 5, [Content pipeline]

Förra gången laddade vi in bilder till våra texturer. Det sättet fungerar såklart alldeles utmärkt men det finns ett väldigt mycket bättre sätt. Med monogame så följder det med ett verktyg för att packa med vad vi kallar för assets. En såkallad assetsmanager som heter MGCB Editor. I ditt solutions fönster så hittar du hela ditt projekt samt en mapp som heter Content.
I den mappen finns det en fil som heter samma som ditt projekt och avslutas med Content. I mitt fall heter den MonogameTutorialContent.mgcb.



Det ska egentligen funka att dubbelklicka på den filen för att öppna editorn, men för mig funkar det inte alls utan jag måste högerklicka på filen och välja "open with". Väl där måste jag sedan välja mgcb-editor-wpf och sedan ok. och först då så får jag upp editorn som ser ut så här...



Här kan vi välja att lägga till alla sina assets som man kan komma att behöva i spelet eller programmet framöver. Man kan ladda in ljudfiler, bildfiler, fontbeskrivningsfiler, shader filer med mer. Vi komma idag bara ladda in samma bild som i förra avsnittet för att se hur det funkar. Det här blir mer en guide över själva editorn och inte så mycket kod.

Vi börjar med menyerna eftersom knapparna bara är genvägar till det som finns i menyn.
Vi börjar med File...
Det är inte mycket som används här. New och open förklarar sig själva men är bara intressant om du öppnar editorn utan projekt. Det bryr vi oss inte om. Import skulle kunna vara intressant om du har kommit över några gamla XNA assets från något gammalt projekt. annars kan vi skippa det också.

Vi hoppar vidare till Edit...
här har vi en meny som heter Add och under den finns några intressanta undermenyer

New Item... Här lägger man till font beskrivningar och shaders bland annat. Det kommer vi till när vi ska rita text och skriva shaders.

New Folder skapar en ny mapp i strukturen, det kan vara bra för att organisera sina assets lite bättre. En mapp för texturer, en för ljud, en för fonter och en för shaders till exempel.

Existing Item. Här kan vi lägga in befintliga filer från våran dator. Här letar vi upp och lägger in våran bild som vi har från förra avsnittet.

Det kommer upp en fråga om man vill kopiera filen till sitt projekt Jag brukar göra så, annars skapas bara en länk till filen. Men det kan sluta med att veta vart alla original filer ligger sparade. Kopierar man så hamnar alla filer som används med i projektet.

View innehåller bara lite inställningar för vad och i vilken ordning saker ska visas i editorn. Inget viktigt.

Build däremot måste göras efter varje gång man lagt till nya assets till projektet. annars kommer man inte hitta filerna när man väl försöker ladda in filerna i koden. Så tryck på Build -> Build.

Rebuild bygger bara om dom assets som har ändrat på sig. Ifall du gjort någon ändring i någon bild så kommer bara den filen att byggas om.

Clean kommer ta bort alla byggda filer och bygga om allt från början. Jag har aldrig behövt göra det så jag kan inte ge något bra exempel på när det skulle behövas göras.

Slutligen ar vi debug mode som verkar vara lite buggig eller experimentell. Den kraschar för mig och jag har aldrig fått ut nått vettigt av att bocka i den.

Skulle det vara något problem i bygget "som vi kommer märka när vi kommer till shaders" så kommer det stå i loggen när vi bygger. Det borde inte kunna bli några fel när vi bygger en bildfil bara. Men det kan vara bra att veta.

Nu ser iallafall min Editor ut så här.



Då kan vi stänga fönstret och hoppa in i konstruktorn. Där lägger vi till raden Content.RootDirectory = "Content";
Med det så talar vi om vart programmet ska hitta våra assets filer någonstans.
  1.  
  2.         public GameTutorialGame()
  3.         {
  4.             graphics = new GraphicsDeviceManager(this);
  5.             Content.RootDirectory = "Content";
  6.         }
  7.  


i initialize funktionen så tar vi bort den gamla koden för att ladda in filer
  1.  
  2.         protected override void Initialize()
  3.         {
  4.             spriteBatch = new SpriteBatch(GraphicsDevice);
  5.  
  6.            
  7.             base.Initialize();
  8.         }
  9.  


och nu hoppar vi in i LoadContent() funktionen skriver vi in

enBild = Content.Load("Assets\\Graphics\\bild");
  1.  
  2.         protected override void LoadContent()
  3.         {
  4.             enBild = Content.Load<Texture2D>("Assets\\Graphics\\bild");
  5.             base.LoadContent();
  6.         }
  7.  


Glöm int att man måste använda sig av dubbla backslashar eller skriva @ först innan strängen
(@"Assets\Graphics\bild")

Om allt är i sin ordning så borde programmet gå att köra. kontrollera sökvägarna ordentligt om du inte får det att funka.
Det är på det här viset vi kommer ladda in våra assets framöver.
Nästa gång ska vi utforska Spritebatchen lite mer. Det kommer bli ett lite roligare avsnitt än vad det här kanske var. på återseende.

Monogame del 4, [Ladda texturer från disk]

Välkommen till del 4.
Det finns mycket att skriva om vad det gäller texturer och hur dom fungerar och vad dom används till. Texturer har en uppsjö olika användningsområden varav ett är att presentera en bild på skärmen. Texturen eller "bilden" är egentligen en buffer eller en blobba minne bestående av en eller fler pixlar. Vi kan säga att det är en lång array av pixlar. Arrayens längd eller minnets storlek är lika med bilden bredd * höjd. Så är bilden 100 pixlar bred och 100 pixlar hög så kommer arrayen ha en längd på 10000 element. Vardera element i arrayen kan variera, men så länge så säger vi att vardera elemten är av typen Color. Så ska tar vi och expanderar det i ett eget avsnitt om bara färger. Men jag vill komma lite längre i den här serien först.

Nu tar vi och börjar om dagens ämne. att ladda texturer från fil från disk.

Innan vi börjar så behöver vi ha en bild att ladda, lämpligen av formatet .png men .bmp och .jpg fungerar också bra för dagens ändamål. Men .png är filformatet vi kommer använda oss av framöver. Jag bjuder på en bild här om du inte kan hitta något eller rita en egen bild just nu.


Spara filen i samma mapp som din exe fil ligger, som i mitt fall är det i följande mapp,
C:\Users\...\source\repos\GameTutorial\GameTutorial\bin\Windows\AnyCPU\Debug\
Det kan såklart skilja sig ifrån hur det ser ut hos dig, men den sista delen efter GameTutorial\ borde inte skilja sig.

Där sparar vi filen och sedan börjar vi skriva lite kod.

Först så deklarerar vi en variabel högst upp i våran klass så här:
  1.  
  2.         Texture2D enBild;
  3.  


Sen tänkte jag att vi skulle öppna upp en ny funktion som Monogame anropar efter Initialize(). Som ni kanske mins från tidigare inlägg så anropas sedan LoadContent() och det är i den funktionen Monogame vill att vi ska ladda in saker till vårat program. Så vi lägger till den funktionen i vårat program

  1.  
  2.         protected override void LoadContent()
  3.         {
  4.             base.LoadContent();
  5.         }
  6.  


Och med följande kod så laddar vi in bilden som en textur och sparar den i våran textur variabel som vi döpte till enBild.

  1.  
  2.             try
  3.             {
  4.                 using (FileStream fs = new FileStream("bild.png", FileMode.Open))
  5.                 {
  6.                     enBild = Texture2D.FromStream(GraphicsDevice, fs);
  7.                 }
  8.             }
  9.             catch (FileNotFoundException e)
  10.             {
  11.                 throw e;
  12.             }
  13.  


Snabbt förklarat vad varje del i koden gör så kopslar vi in koden i ett "try" block ifall någonting går snett så bakar koden ut ur try blocket och kastar en exception på vad som gick fel.
sedan använder vi oss av ett using statement på filströmmen så vi är säkra på att filströmmen kommer stängas när vi har laddat klart filen eller om något går fel. här anger vi ockås vad filen heter och i vilken mapp den ligger. Nu är ingen mapp anged och då antas filen ligga i samma mapp som den körbara filen. "exe filen".

Inga konstigheter hoppas jag. Det här är ingen kors i C#, utan mer ett utforskande och förklarande av monogame. Så är det oklarheter angånde koden så får ni fråga så ska jag förklara eller kanske ännu bättre googla på det så kommer du säkerligen finna svar snabbare.

Nu är vi iallfall redo att rita bilden och det gör vi på samma sätt som vi gjort tidigare i Draw() mellan spritebatch.begin() och end()...


  1.  
  2.         protected override void Draw(GameTime gameTime)
  3.         {
  4.             GraphicsDevice.Clear(Color.Black);
  5.             spriteBatch.Begin();
  6.            
  7.             spriteBatch.Draw(enBild, new Vector2(100,100), Color.White);
  8.  
  9.             spriteBatch.End();
  10.  
  11.             base.Draw(gameTime);
  12.         }
  13.  



Om allt är i sin ordning så borde ni få upp ett fönster som ser ut så här.

Jag lämnar lite kodsnuttar som ni kan testa och fundera över som vanligt. Tack för den här gången...

  1.  
  2.             spriteBatch.Draw(enBild, new Rectangle(100, 100, 50, 50), Color.White);
  3.  


  1.  
  2.             spriteBatch.Draw(enBild, new Vector2(0, 0), Color.Gray);
  3.  


  1.  
  2.             spriteBatch.Draw(enBild, new Rectangle(100, 100, 50, 50), Color.Yellow);
  3.             spriteBatch.Draw(enBild, new Rectangle(200, 100, 50, 50), Color.Red);
  4.             spriteBatch.Draw(enBild, new Rectangle(100, 200, 50, 50), Color.Green);
  5.             spriteBatch.Draw(enBild, new Rectangle(200, 200, 50, 50), Color.Blue);
  6.  


  1.  
  2.             spriteBatch.Draw(enBild, GraphicsDevice.Viewport.Bounds, Color.White);
  3.  

Monogame del 3, [Mer om texturer]

Välkommen till del tre i min lilla Monogame tutorial på svenska.

Vi testade ju på lite texturer i förra delen och jag lämnade er med lite olika exempel ni kunde laborera med. Det sista exemplet var ltie spännade då det trots att vi bara angett blå och röd i våran textur,


Det är egentligen en väldig massa saker som händer och jag tänker idag bara lyfta på locket lite för att vi ska få en grundläggande förståelse för vad det är som händer.
När vi skapar vår textur som vi kallar för papper så anger vi att den ska vara 2 pixlar lång och 1 pixel hög, totalt kommer våra textur bestå av två pixlar den ena anger vi att den ska vara röd och den andra blå. Det är allt vi gör när vi skapar våran textur.

När vi sedan ritar texturen med följande kod
  1.  
  2.             spriteBatch.Begin();
  3.             spriteBatch.Draw(papper, new Rectangle(10, 10, 100, 100), Color.White);
  4.             spriteBatch.End();
  5.  

Så vill vi att våran textur ska täcka upp en yta som är placerad 10 pixlar ut från höger och 10 pixlar ner från överkant på fönstret. Och sen vill vi att den ska fylla upp 100 pixlar i bredd och 100 pixlar i höjd. Det blir ju lite omöjligt när våran textur som vi ska rita bara är 2 pixlar bred och 1 pixel hög.

När grafikkortet ska rita upp en textur på det här viset så kan den använda sig av lite olika tekniker, men den vanligaste tekniken kallas på engelska för bilinear texturefiltering. Krångligt ord måhända men det den gör är att räkna om dom där 100 pixlarna i bredd och 100 pixlarna i höjd till två värden mellan 0 och 1, dessa två koordinater kallar man för U samt V. Man kan säga vart procentuellt i ytan där man ritar är man sedan gör man samma sak för den textur man ska rita ifrån. I det här fallet "papper"

Så första pixeln som ritas är 0% vertikalt in i rektangeln och 0 % horisontellt det blir UV kordinaterna 0U och 0V, man kan skriva ihop det i vektor form och få 0.0 ,0.0. Vektorer är lite av ett ämne för sig som är lite mer matematiskt. 0% in i våran textur så har vi en pixel som är röd, eftersom texturen bara är en pixel hör så spelar det ingen roll vad det vertikal värdet är så vi kan utesluta det, det kommer ändå bara att bli samma som det horisontella värdet. nästa pixel är 1% in och 1 % in är fortfarande röd, inte fören vi kommer till 50 procent in i texturen så ändrar det si och blir blå.

Varje gång som grafikkortet kollar i texturen vad det är för färg i uv koordinaterna så gör den en genomsnitts beräkning som resulterar i att våran bild när den ritas ut blir en färgskala mellan dom färger som finns i texturen. Det här gör att vi kan rita våran textur i vilken storlek som helst och grafikkortet kommer hjälpa oss att skala om bilden så att det passar och ser så bra ut som möjligt. Om vi skulle vilja ha ett mer pixelperfekt resultat så måste vi stänga av den där bilinear filtering och det kommer vi komma till framöver när vi går in djupare i vad spritebachen gör och kan göra.

Det var allt jag ville gå igenom idag men innan vi avslutar så tar vi lite nya exempel på vad vi kan rita med våra texturer.

  1.  
  2.             papper = new Texture2D(GraphicsDevice, 2, 2);
  3.             papper.SetData<Color>(new Color[] { Color.Red, Color.Blue, Color.Green, Color.Yellow });
  4.  

Det här blir en 2 * 2 pixlar stor gradient textur med 4 färger i


  1.  
  2.     public class GameTutorialGame : Game
  3.     {
  4.         GraphicsDeviceManager graphics;
  5.         SpriteBatch spriteBatch;
  6.         Texture2D papper1;
  7.         Texture2D papper2;
  8.         public GameTutorialGame()
  9.         {
  10.             graphics = new GraphicsDeviceManager(this);
  11.            
  12.         }
  13.  
  14.         protected override void Initialize()
  15.         {
  16.             spriteBatch = new SpriteBatch(GraphicsDevice);
  17.             papper1 = new Texture2D(GraphicsDevice, 255, 255);
  18.             Color[] colors = new Color[255 * 255];
  19.             for (int y = 0; y < 255; y++)
  20.             {
  21.                 for (int x = 0; x < 255; x++)
  22.                 {
  23.                     colors[(y * 255) + x] = new Color(x, y, 0);
  24.                 }
  25.             }
  26.             papper1.SetData<Color>(colors);
  27.  
  28.             papper2 = new Texture2D(GraphicsDevice, 2, 2);
  29.             papper2.SetData<Color>(new Color[] { Color.Black, Color.Red, Color.Green, Color.Yellow });
  30.  
  31.             base.Initialize();
  32.         }
  33.  
  34.         protected override void Update(GameTime gameTime)
  35.         {
  36.             if (Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit();
  37.             base.Update(gameTime);
  38.         }
  39.  
  40.         protected override void Draw(GameTime gameTime)
  41.         {
  42.             spriteBatch.Begin();
  43.             spriteBatch.Draw(papper1, new Rectangle(10, 10, 255, 255), Color.White);
  44.             spriteBatch.Draw(papper2, new Rectangle(300, 10, 255, 255), Color.White);
  45.             spriteBatch.End();
  46.  
  47.             base.Draw(gameTime);
  48.         }
  49.     }
  50.  

Nu skapar vi två texturer, papper1 och papper2. I papper1 så sätter vi alla pixlarna manuellt i färgskalan svart till röd till grön till gul. papper två har bara 4 pixlar med samma grundfärger, när vi sedan ritar ut dom bredvid varandra så kan vi jämföra hur bra den där bilinjära filtereffekten är. Tänk på att den bara är 4 pixlar stor men medans den andra är 65025 pixlar stor, men skapar ändå ett ganska bra resultat.



Monogame del 2, [Rita rektanglar]

Välkommen tillbaka.

Från och med nu så ska jag försöka hålla mig till lite kortare avsnitt och ämnen
Idag tänkte jag att vi ska försöka rita något i vårat fönster. Men vi börjar städa upp lite ifrån föregående avsnitt då vi testade ordningen som monogame anropar våran applikation. Vi återkommer till det vartefter som det blir aktuellt att ta upp det.

Vi städar bort alla funktioner utom konstruktorn, Initialize, Update och Draw. Så kvar har vi följande kod:

  1.  
  2. using Microsoft.Xna.Framework;
  3. using Microsoft.Xna.Framework.Input;
  4.  
  5. namespace GameTutorial
  6. {
  7.     public class GameTutorialGame : Game
  8.     {
  9.         GraphicsDeviceManager graphics;
  10.  
  11.         public GameTutorialGame()
  12.         {
  13.             graphics = new GraphicsDeviceManager(this);
  14.         }
  15.  
  16.         protected override void Initialize()
  17.         {
  18.             base.Initialize();
  19.         }
  20.  
  21.         protected override void Update(GameTime gameTime)
  22.         {
  23.             if (Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit();
  24.             base.Update(gameTime);
  25.         }
  26.  
  27.         protected override void Draw(GameTime gameTime)
  28.         {
  29.             base.Draw(gameTime);
  30.         }
  31.     }
  32. }
  33.  


Vårat svarta fönster ska fortfarande köras om vi trycker på F5.


För att kunna rita nånting på skärmen så måste vi ha två saker. En Textur och en Batcher eller SpriteBatcher som det heter i Monogame.

En Textur är ett objekt som närmast kan beskrivas som ett genomskinligt papper. När vi sen kommer rita i fönstret så är det som att vi slänger på fler och fler av dessa Textur-papper. PÅ ett papper har vi ett träd, ett annat ett hus och kanske en bakgrund med lite berg. alla dessa papper bygger tillsammans upp en färdig bild som vi sedan vill visa upp i fönstret.

Och det är med spritebatchen som hjälper oss med just den delen att komponera ihop och lagra alla papper i ordning. För det är något som är ganska tidskrävande för en dator.

Vi börjar med att skapa en textur, (ett genomskinligt papper). Det gör vi genom att deklarera en variabel som vi döper till papper av typen Texture2D, Texture2D ligger i ett nytt namespace som heter Graphics under MonogameFramework. Så vi måste lägga till det namespacet också.
Vi ska prata mer om vad en texture faktiskt egentligen är senare någon gång, papper kanske inte är den bästa liknelsen, men det tar vi då.

När vi deklarerat våran texture utanför konstruktorn så behöver vi i initiera den. Det gör vi i funktionen Initialize genom att skriva:
papper = new Texture2D(GraphisDevice, 1,1); GraphicsDevice är ett objekt som vi får av Monogame, men om vi skriver den här koden i konstruktor istället så har inte graphicsdevice blivit initierad än och vi kommer få ett null exception error på graphicsdevice. Om ett objekt inte initierats så har det värdet null. Vilket innebär att det inte har något värde alls. det är som en tom ballong.

GraphicsDevice måste iallafall vara med för att hjälpa oss bygga upp texturen, 1, 1 säger att texturen ska vara en pixel bred och en pixel hög.

Vi ska nu ha följande kod.

  1.  
  2. using Microsoft.Xna.Framework;
  3. using Microsoft.Xna.Framework.Graphics;
  4. using Microsoft.Xna.Framework.Input;
  5.  
  6. namespace GameTutorial
  7. {
  8.     public class GameTutorialGame : Game
  9.     {
  10.         GraphicsDeviceManager graphics;
  11.         Texture2D papper;
  12.  
  13.         public GameTutorialGame()
  14.         {
  15.             graphics = new GraphicsDeviceManager(this);
  16.         }
  17.  
  18.         protected override void Initialize()
  19.         {
  20.             papper = new Texture2D(GraphicsDevice, 1, 1);
  21.             base.Initialize();
  22.         }
  23.  
  24.         protected override void Update(GameTime gameTime)
  25.         {
  26.             if (Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit();
  27.             base.Update(gameTime);
  28.         }
  29.  
  30.         protected override void Draw(GameTime gameTime)
  31.         {
  32.             base.Draw(gameTime);
  33.         }
  34.     }
  35. }
  36.  


Nu vill vi såklart rita ut vårat nya fina papper i fönstret. Det gör vi i Draw funktionen. Men först behöver vi ett nytt objekt. SpriteBatch som vi talade om tidigare. Det är ett ganska stort och invecklat objekt men vi tar en sak i taget utan att gå alldeles för långt ner på djupet.
Spritebatchen måste vi skapa sälva så vi deklarerar den tillsammans med vårat papper så här

  1.  
  2.         SpriteBatch spriteBatch;
  3.  

och i Initialize initierar vi den genom att skriva
  1.  
  2.         spriteBatch = new SpriteBatch(GraphicsDevice);
  3.  


Toppen, nu är allt klart för att börja rita.
Vi hoppar över till Draw funktionen och lägger till följande kod.

  1.  
  2.             spriteBatch.Begin();
  3.             spriteBatch.Draw(papper, new Rectangle(10,10,100,100), Color.White);
  4.             spriteBatch.End();
  5.  
  6.             base.Draw(gameTime);
  7.  


spriteBatch.Begin(); Säger att vi vill börja rita, talar vi inte omd et så kommer inte spritebatchen tillåta oss att rita nånting.
spriteBatch.Draw(papper, new Rectangle(10,10,100,100), Color.White); säger att vi vill rita vårat papper, vart den ska ritas anger vi med new Rectangel(10,10,100,100) vilket säger att vårat papper börjar 10 pixlar ifrån vänster och 10 pixlar ifrån toppen av vårat fönster. Sen säger den att vårat papper är 100 pixlar bred och 100 pixlar hög.


Rectangel(horisontellposition, vertikal position, bredd, höjd)



Color.White är lite krånglig, men den talar om vilken färg vi vill blanda med. Så den färgen vi skriver här kommer blanda sig med den färg som är på texturen som vi ritar. Det är också något som vi kommer till senare.

Men vad händer då om vi kör vårat program. Det händer ju ingenting..? Samma gamla svarta fönster... Som vi pratade om så är vårat papper genomskinligt och ett genomskinligt papper ska ju såklart vara osynligt att se. För att rita något på vårat papper så måste vi skriva en ny lite invecklad rad kod uppe i Initialize, efter att vi initierat vårat papper.


  1.  
  2. papper.SetData<Color>(new Color[] { Color.White });
  3.  


när vi initierade vårat papper sa vi att det skulle vara 1 pixel brett och 1 pixel högt. totalt består vårat papper av en pixel totalt och den pixeln fyller vi med fägen vit.
papper.SetData anger att vi vill ange färg som data för vad texturen ska inehålla. (För en textur kan vara och inehålla mycket annat än färg) sen anger vi en array av färger med endast en komponent, Vit.

Våran kod i sin helhet ser nu ut så här

  1.  
  2. using Microsoft.Xna.Framework;
  3. using Microsoft.Xna.Framework.Graphics;
  4. using Microsoft.Xna.Framework.Input;
  5. using System;
  6.  
  7. namespace GameTutorial
  8. {
  9.     public class GameTutorialGame : Game
  10.     {
  11.         GraphicsDeviceManager graphics;
  12.         SpriteBatch spriteBatch;
  13.         Texture2D papper;
  14.  
  15.         public GameTutorialGame()
  16.         {
  17.             graphics = new GraphicsDeviceManager(this);
  18.            
  19.         }
  20.  
  21.         protected override void Initialize()
  22.         {
  23.             spriteBatch = new SpriteBatch(GraphicsDevice);
  24.             papper = new Texture2D(GraphicsDevice, 1, 1);
  25.             papper.SetData<Color>(new Color[] { Color.White });
  26.  
  27.             base.Initialize();
  28.         }
  29.  
  30.         protected override void Update(GameTime gameTime)
  31.         {
  32.             if (Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit();
  33.             base.Update(gameTime);
  34.         }
  35.  
  36.         protected override void Draw(GameTime gameTime)
  37.         {
  38.             spriteBatch.Begin();
  39.             spriteBatch.Draw(papper, new Rectangle(10,10,100,100), Color.White);
  40.             spriteBatch.End();
  41.  
  42.             base.Draw(gameTime);
  43.         }
  44.     }
  45. }
  46.  
  47.  


Vi kör igen och se på tusan vår första vita rektangel är ritad...


Det var allt för den här delen, men jag lämnar lite kodändringar som kan vara spännade att testa och leka med för att se vad som händer.

  1.  
  2. papper.SetData<Color>(new Color[] { Color.Blue});
  3.  

  1.  
  2. papper.SetData<Color>(new Color[] { Color.Red});
  3.  

  1.  
  2. spriteBatch.Draw(papper, new Rectangle(200,200,100,100), Color.White);
  3.  

  1.  
  2. spriteBatch.Draw(papper, new Rectangle(0,10,800,10), Color.Orange);
  3.  

  1.  
  2. spriteBatch.Draw(papper, new Rectangle(10,10,100,100), Color.White);
  3. spriteBatch.Draw(papper, new Rectangle(300,10,100,100), Color.White);
  4. spriteBatch.Draw(papper, new Rectangle(150,200,100,100), Color.White);
  5.  

Lite mer avancerat, fundera kring vad som händer här...
  1.  
  2. papper = new Texture2D(GraphicsDevice, 2, 1);
  3. papper.SetData<Color>(new Color[] { Color.Red, Color.Blue });
  4.  

Monogame del 1

Monogame är ett .Net bibliotek för c# med stöd för dom flesta populära plattformarna idag. Så som Windows, mac, linux, Android, Ios, och även ett flertal konsoler. Jag kommer bara rikta mig till Windows men det är nog inte några större problem att porta över koden till någon av dom andra operativsystemen.

Jag kommer inte gå in på hur du installerar Visual Studio. Det finns en uppsjö andra guider som tar upp det förfarandet. Googla på på How to install Visual Studio så kommer du garanterat hitta en uppsjö guider. För stunden så är det Visual Studio Community 2019 som gäller.

När det är klart så kan vi installera Monogame, det görs numera genom något som heter Nugets. Starta Visual studio och klicka på "continue without code" i fönstret som öppnas. Efter det så öppnas Visual Studio i sin helhet utan att ha skapat någon kod. Där klickar vi på Extensions uppe i menyraden och sedan Manage Extensions. Då öppnas ett nytt fönster och uppe i högra hörnet av fönstret så finns en liten sökruta som vi skriver monogame i och trycker enter.

Ett extension som heter monogame project templates dyker upp och på den klickar vi install.
När det är klart kan vi stänga det fönstret och sedan klicka på File-> New->Project...



Så öppnas ett nytt fönster. I den listan letar vi upp Monogame Windows Project och trycker Next. Här får vi namnge vårat projekt, jag väljer att döpa mitt till GameTutorial och sedan klickar vi på Create. På så vis så skapas det två basklasser och lite annat åt oss så att vi snabbt kan komma igång och skriva spel.

När vi trycker på F5 så kommer ett blått fönster att öppnas vilket innebär att allt fungerar som det ska.
Låt oss grotta ner oss i den här koden lite.


Två klasser är skapta i vårat projekt, en heter Program.cs och ser ut så här

  1.  
  2. using System;
  3.  
  4. namespace GameTutorial
  5. {
  6. #if WINDOWS || LINUX
  7.     /// <summary>
  8.     /// The main class.
  9.     /// </summary>
  10.     public static class Program
  11.     {
  12.         /// <summary>
  13.         /// The main entry point for the application.
  14.         /// </summary>
  15.         [STAThread]
  16.         static void Main()
  17.         {
  18.             using (var game = new GameTutorialGame())
  19.                 game.Run();
  20.         }
  21.     }
  22. #endif
  23. }
  24.  


  1.  
  2. static void Main()
  3.  

Här börjar vårat program genom att skapa en ny klass av typen GameTutorialGame. Den här typen är genererad utifrån vad vi döpte vårat projekt. Sedan körs game.Run() och vårat program kör igång.
Det är inte så mycket att tala om i den här klassen så vi hoppar över till den dåligt döpta klassen GameTutorialGame klassen.
Men först så tänkte jag att vi gör en lite lite ändring här i program.cs.

  1.  
  2. using System;
  3.  
  4. namespace GameTutorial
  5. {
  6. #if WINDOWS || LINUX
  7.     /// <summary>
  8.     /// The main class.
  9.     /// </summary>
  10.     public static class Program
  11.     {
  12.         /// <summary>
  13.         /// The main entry point for the application.
  14.         /// </summary>
  15.         [STAThread]
  16.         static void Main()
  17.         {
  18.             using (var game = new GameTutorialGame())
  19.             {
  20.                 game.IsFixedTimeStep = false;
  21.                 game.Run();
  22.             }
  23.         }
  24.     }
  25. #endif
  26. }
  27.  


Den raden måste till för att vi ska kunna forcera våran main loop att snurra fortare än 60 frames per sekund. Det är möjligtvis inget som rekomenderas, men det kan vara bra medans man bygger för att testa prestanda och så att allt flyter på i rätt takt. Men vi kommer till det senare. Nu hoppar vi över till Game klassen eller GameTutorialGame som den heter för mig.

Den här klassen har betydligt mer kod i sig, uppemot 100 rader. Men mycket kod är kommentarer, jag kan rekomendera att läsa igenom alla kommentarer och när du är klar med det så tar vi och markerar all kod och trycker delete. Sådär... Då kan vi börja bygga upp klassen igen och gå igenom dom funktioner som fins. Vilka vi behöver kommer vi till senare och vilka som är viktiga.

Vi börjar med att lägga till namespace för klassen, så att klassen tillhör rätt namespace. En namespace är som en virtuell mapp, alla klasser som skapas och använder sig av samma namespace kommer att hitta varandra. Men om du skapar en klass med ett annat namespace så måste du referera till den klassen genom att ange namespace antingen via deklareringen eller med using statement längst upp i klassen.
eftersom det här är en basklass som ligger längst ner i namespace hierarkin så får den här namespacet samma namn som projektet. så i mitt fall GameTutorial.
Så nu har vi följande kod.


  1.  
  2. namespace GameTutorial
  3. {
  4.  
  5. }
  6.  


Fint, glöm inte hak-klammrarna. Då är vi redo att skapa klassen, det gör vi genom att skriva


  1.  
  2. namespace GameTutorial
  3. {
  4.     public class GameTutorialGame
  5.     {
  6.     }
  7. }
  8.  


Att klassen deklareras som publik gör att man kan anropa klassen från ett annat projekt, det är inget vi kommer göra i den här guiden, men det kan vara fint att känna till.
Men nu blir Visual Studio lite sur och kastar några fel från Program klassen som vi tittade på tidigare. Det är för att våran klass måste ärva lite funktioner från en basklass som heter Game. För att fixa det så lägger vi till : Game efter vårat klassnamn. Men för att det ska funka så måste vi lägga till vårat första using statement längst upp i klass filen.

Så nu ser våran fil ut så här och vi borde vara fria från felmeddelanden.

  1.  
  2. using Microsoft.Xna.Framework;
  3.  
  4. namespace GameTutorial
  5. {
  6.     public class GameTutorialGame : Game
  7.     {
  8.     }
  9. }
  10.  

när vi skriver using Microsoft.Xna.Framework; så talar vi om för klassen att den behöver använda kod ifrån det här kodbiblioteket. Det här är kod som på ett eller annat sätt ligger utanför vårat projekt och det måste finnas externa referenser till det för att fungera. Det är ett lite mer avancerat ämne så vi kan tala om det senare. Vi litar på att Visual Studio gör sitt jobb och lägger till dom referenser som behövs.

Vi har inga felmeddelanden, men om vi provar att köra programmet så kommer det att krascha. Så vi måste skriva lite till innan vi kan köra programmet igen.

Nu är det dax för oss att lägga till en klass konstuktor. Det är den första funktionen som anropas när en klass skapas med "new" Om du mins i program klassen så stog det

  1.  
  2. using (var game = new GameTutorialGame())
  3.  

när man skriver new GameTutorialGame() så kommer lämplig konstruktor att anropas.
I det här fallet en konstruktor utan argument, så då vet vi hur våran konstruktor måste se ut.

  1.  
  2. public GameTutorialGame()
  3. {
  4. }
  5.  


Så nu har vi följande kod

  1.  
  2.  
  3. using Microsoft.Xna.Framework;
  4.  
  5. namespace GameTutorial
  6. {
  7.     public class GameTutorialGame : Game
  8.     {
  9.         public GameTutorialGame()
  10.         {
  11.  
  12.         }
  13.     }
  14. }
  15.  



Om vi kör programmet så kraschar det och säger att vi inte har någon "Graphics Device Service" Det löser vi lätt genom att deklarera en variabel som heter graphics av typen GraphicsDeviceManager och den måste vi initiera i konstuktorn av klassen.
Så nu ska koden se ut så här

  1.  
  2. using Microsoft.Xna.Framework;
  3.  
  4. namespace GameTutorial
  5. {
  6.     public class GameTutorialGame : Game
  7.     {
  8.         GraphicsDeviceManager graphics;
  9.  
  10.         public GameTutorialGame()
  11.         {
  12.             graphics = new GraphicsDeviceManager(this);
  13.         }
  14.  
  15.     }
  16. }
  17.  
  18.  


Trycker vi på F5 nu så körs vårat program utan fel och ett svart fönster öppnas. Inte illa med 17 rader kod.

Iomed att vi ärver våran klass från Game så finns det ett antal funktioner som vi kan skriva över, eller "överrida" ( override)
om vi skriver "protected override" så kommer Visual Studio visa ett antal funktioner som kan överridas, låt oss lägga till allihopa.

Jag har även lagg till en rad i varje funktion som skriver ut till terminalen varje gång funktionen anropas.
På så vis kan vi få lite klarhet vad respektive funktion gör och när den anropas.

Koden ser nu ut så här:


  1.  
  2. using Microsoft.Xna.Framework;
  3. using System;
  4.  
  5. namespace GameTutorial
  6. {
  7.  
  8.     public class GameTutorialGame : Game
  9.     {
  10.         GraphicsDeviceManager graphics;
  11.  
  12.         public GameTutorialGame()
  13.         {
  14.             graphics = new GraphicsDeviceManager(this);
  15.             Console.WriteLine("Konstuktor");
  16.         }
  17.  
  18.         protected override void Initialize()
  19.         {
  20.             Console.WriteLine("Initialize");
  21.             base.Initialize();
  22.         }
  23.  
  24.         protected override void LoadContent()
  25.         {
  26.             Console.WriteLine("LoadContent");
  27.             base.LoadContent();
  28.         }
  29.  
  30.         protected override void UnloadContent()
  31.         {
  32.             Console.WriteLine("UnloadContent");
  33.             base.UnloadContent();
  34.         }
  35.  
  36.         protected override void OnActivated(EventArgs args)
  37.         {
  38.             Console.WriteLine("OnActivated");
  39.             base.OnActivated(args);
  40.         }
  41.  
  42.         protected override void OnDeactivated(EventArgs args)
  43.         {
  44.             Console.WriteLine("OnDeactivated");
  45.             base.OnDeactivated(args);
  46.         }
  47.  
  48.         protected override void OnExiting(EventArgs args)
  49.         {
  50.             Console.WriteLine("OnExiting");
  51.             base.OnExiting(args);
  52.         }
  53.  
  54.         protected override void Dispose(bool disposing)
  55.         {
  56.             Console.WriteLine("Dispose");
  57.             base.Dispose(disposing);
  58.         }
  59.  
  60.         protected override void BeginRun()
  61.         {
  62.             Console.WriteLine("BeginRun");
  63.             base.BeginRun();
  64.         }
  65.  
  66.         protected override void Update(GameTime gameTime)
  67.         {
  68.             Console.WriteLine("Update");
  69.             base.Update(gameTime);
  70.         }
  71.  
  72.         protected override void EndRun()
  73.         {
  74.             Console.WriteLine("EndRun");
  75.             base.EndRun();
  76.         }
  77.  
  78.         protected override bool BeginDraw()
  79.         {
  80.             Console.WriteLine("BeginDraw");
  81.             return base.BeginDraw();
  82.         }
  83.  
  84.         protected override void Draw(GameTime gameTime)
  85.         {
  86.             Console.WriteLine("Draw");
  87.             base.Draw(gameTime);
  88.         }
  89.  
  90.         protected override void EndDraw()
  91.         {
  92.             Console.WriteLine("EndDraw");
  93.             base.EndDraw();
  94.         }
  95.     }
  96. }
  97.  
  98.  

Det börjar bli mycket nu, Console raderna kan vi ta bort senare men om vi kör programmet nu så kan vi se vilka funktioner som blivit anropade och i vilken ordning. Tryck på stopp knappen ganska omgående efter att du startat programmet annars kommer vissa funktioner spamma ut information och du kommer få svårt att se vad som händer i början.

Vi ser iallafall att följande funktioner anroppas i tur och ordning.
Konstuktor
OnActivated
Initialize
LoadContent
BeginRun
Update
Update
BeginDraw
Draw
EndDraw


Sedan körs update, begindraw Draw, endDraw i en oändlig loop.
Om vi lägger till följande kod utan någon närmare förklaring för tillfället

if (Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit();

i Update(GameTime gameTime)
så kan vi avsluta programmet på ett korrekt sätt och se vad som händer när vi avslutar. Vi tar bort Consol -rader som vi redan sett anropas.
Den raden kräver att vi ladar in ett nytt namespace så om inte visula studio redan gjort det automatiskt så lägger vi till using Microsoft.Xna.Framework.Input; längst upp.

  1.  
  2. using Microsoft.Xna.Framework;
  3. using Microsoft.Xna.Framework.Input;
  4. using System;
  5.  
  6. namespace GameTutorial
  7. {
  8.  
  9.     public class GameTutorialGame : Game
  10.     {
  11.         GraphicsDeviceManager graphics;
  12.  
  13.         public GameTutorialGame()
  14.         {
  15.             graphics = new GraphicsDeviceManager(this);
  16.             Console.WriteLine("Konstuktor");
  17.         }
  18.  
  19.         protected override void Initialize()
  20.         {
  21.             Console.WriteLine("Initialize");
  22.             base.Initialize();
  23.         }
  24.  
  25.         protected override void LoadContent()
  26.         {
  27.             Console.WriteLine("LoadContent");
  28.             base.LoadContent();
  29.         }
  30.  
  31.         protected override void UnloadContent()
  32.         {
  33.             Console.WriteLine("UnloadContent");
  34.             base.UnloadContent();
  35.         }
  36.  
  37.         protected override void OnActivated(EventArgs args)
  38.         {
  39.             Console.WriteLine("OnActivated");
  40.             base.OnActivated(args);
  41.         }
  42.  
  43.         protected override void OnDeactivated(EventArgs args)
  44.         {
  45.             Console.WriteLine("OnDeactivated");
  46.             base.OnDeactivated(args);
  47.         }
  48.  
  49.         protected override void OnExiting(EventArgs args)
  50.         {
  51.             Console.WriteLine("OnExiting");
  52.             base.OnExiting(args);
  53.         }
  54.  
  55.         protected override void Dispose(bool disposing)
  56.         {
  57.             Console.WriteLine("Dispose");
  58.             base.Dispose(disposing);
  59.         }
  60.  
  61.         protected override void BeginRun()
  62.         {
  63.             Console.WriteLine("BeginRun");
  64.             base.BeginRun();
  65.         }
  66.  
  67.         protected override void Update(GameTime gameTime)
  68.         {
  69.             if (Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit();
  70.             base.Update(gameTime);
  71.         }
  72.  
  73.         protected override void EndRun()
  74.         {
  75.             Console.WriteLine("EndRun");
  76.             base.EndRun();
  77.         }
  78.  
  79.         protected override bool BeginDraw()
  80.         {
  81.             return base.BeginDraw();
  82.         }
  83.  
  84.         protected override void Draw(GameTime gameTime)
  85.         {
  86.             base.Draw(gameTime);
  87.         }
  88.  
  89.         protected override void EndDraw()
  90.         {
  91.             base.EndDraw();
  92.         }
  93.     }
  94. }
  95.  
  96.  


Kör vi nu så får vi följande resultat
Konstuktor
OnActivated
Initialize
LoadContent
BeginRun

> Loopen startas här och följande funktioner körs tills programmet avslutas
Update
BeginDraw
Draw
EndDraw

> Här trycker vi på escape

OnDeactivated
EndRun
OnExiting
UnloadContent
Dispose


OnActivate och OnDeactivate anroppas var gång fönstret tappar och/eller återfår focus. Det kan vara bra att hålla reda på framöver ifall användaren tabbar ut från programmet, eller av annan aledning förlorar fokus från spelet, så man kan forcera in spelet i pause läge.
Det börjar bli ett jäkla längt inlägg nu så jag tror jag stannar här så kanske vi kan komma till lite mer roliga saker i nästa inlägg. Förhoppningsvis har vi lite koll på hur monogame anropar vårat spel. Inästa inlägg så ska vi se till att rita nånting i fönstret och kanske ta lite mer konfigurering av monogame.

Tack för visat intresse.