Programmering02 [prrprr02]

Tutorial - Hjälp och stöd

Introduktion

Så fort någon gör en tutorial så kan det bli fel, speciellt gäller detta i olika dataapplikationer där en programvara uppdateras och helt plötsligt så ser det annorlunda ut eller måste göras på något annat sätt. Denna hjälpsida handlar om att göra det enklare för dig som använder tutorialen och av olika anledningar får problem.
Min förhoppning är att vi skall kunna uppdatera denna sida gemensamt så att den fungerar som ett så bra stöd som det bara är möjligt.

Bokens tutorial och exempel baserar sig på Windowsmiljö och Visual Studio. När vi kör Mac så använder vi Xamarin, och vissa menyval och exakt hantering av applikationen skiljer sig åt. Här tänker jag mig att vi listar saker som avviker så mycket att det behövs ett stöd för att göra på rätt sätt.

[2017-11-09] Inför denna omgång av kursen så har det hänt en hel del som eventuellt kommer vålla oss problem. Vår huvudprogramvara, Xamarin, har slutat uppdateras och istället så har utvecklingen fortsatt genom Visual Studio for Mac men tyvärr är denna allt för buggig för tillfället och det går inte att skapa projekt som jobbar med MonoGame. Det skall tydligen gå att skapa projekt i Xamarin och sedan fortsätta att arbeta med dem i Visual Studio for Mac, detta har jag dock ej testat. Problemet blir då att vi ändå behöver två applikationer för att bygga våra spel så fram tills Visual Studio for Mac fungerar som det är tänkt så kör vi på Xamarin.

5. Installera utvecklingsmiljön (s. 23f)

För att kunna köra den första delen av kursen behöver vi installera några programvaror. Nedan finns den installation som fungerar för mig i november 2017 och som innehåller de senaste versionerna av de flesta applikationer. Under guiden så finns en tabell med alla programvaror som installerats och vilka versioner som används. Det kan säkert fungera med andra kombinationer men dessa fungerar för mig.

Installationen

Vissa av installationerna är väldigt stora och behöver mycket plats på din dator vid installationen. Alla versioner är senaste versionen eller den versionen som jag använder den 9 november 2017.

  1. Börja med att uppdatera till senaste versionen av operativsystemet. [MacOS 10.12.6 (Sierra)]
  2. Installera senaste versionen av Xcode från App Store. [Version 9.1 (9B55)]
  3. Dags att installera MonoGame. På sidan http://www.monogame.net/downloads/ väljer du senaste releasen, i skrivande stund är det 3-6, och välj sedan versionen för MacOS. Filnamnet är MonoGame.pkg. Installera sedan den. [Version: 3.6.0.1625]
  4. Dags att installera utvecklingsmiljön, Xamarin. Xamarin laddas ner från http://www.monodevelop.com/download/ och klicka på knappen Download Xamarin Studio då får du ner filen XamarinStudio-6.1.2.44.dmg. Installera sedan den. [Version: 6.1.2.44]
  5. Nu skall vi koppla MonoGame till Xamarin. Starta Xamarin och sedan skall du i menyn välja Xamarin Studio > Add-in Manager (tilläggshanterare på svenska). Välj sedan fliken Gallery och titta under meyvalet Game Development, där ligger MonoGame Addin, markera den och installera. [Version 3.6.0.906]
  6. En sista installation. Det finns ett program som heter Pipeline som vi senare skall använda för att göra sprites (bilder) tillgänglig i spelet. Det har redan installerats ett sådant program med version 3.5. Problemet är att detta är buggigt och kraschar hos mig. Jag har därför valt en tidigare version som fungerar. Gå till http://www.monogame.net/2016/03/17/monogame-3-5/ och klicka på länken för att ladda ner Pipeline GUI. Skriv över det gamla programmet med detta. [Version 3.3]
  7. Öppna slutligen Xamarin och kolla om det finns uppdateringar att göra. Det har gått bra att göra alla uppdateringar så du kan köra på.
  8. Nu skall allt vara på plats och du är förhoppningsvis redo att börja med bokens tutorial.

Sammanfattning

Applikation Version Not
Mac OS X 10.12.6 (Sierra)
XCode Version 9.1 (9B55)
Xamarin Studio 6.3 (build 864)
MonoGame 5.4.1.6
MonoGame AddIn (Xamarin) 3.6.0.1625
MonoGame Pipeline 3.3

6.1 Att skapa ett projekt (s. 30)

Börja med att välja MonoGame Mac Application (Xamarin.Mac CLassic)

Skärmdump

Döp projektet till något bra, undvik svenska tecken och mellanslag i projektnamnet. Se också till att projektet hamnar i en mapp som säkerhetskopieras.

Skärmdump

6.3 Kodens uppbyggnad (s. 32f)

Alla metoder finns inte på plats vid start, iaf inte på det sätt som jag skapat projektet, bla saknas metoden UnloadContent(), skriv dit den om du vill. Program1.cs heter Main.cs istället.
Tänk på att boken är skapad för Visual Studio i windowsmiljö. Dessutom inte den senaste versionen.

6.5 Lägg till en sprite (s. 40)

I filmen så får du se hur du skapar ett projekt och hur du lägger till Sprites (bilder) till spelet. Detta görs på ett annorlunda sätt sedan version 5.3 av MonoGame och det följer inte alls boken.

6.5.4 Animation via Update() (s. 45)

När du skall göra så att rymdskeppet skall röra sig så kan det bli så att skeppet direkt försvinner från rutan. Det kan vara en felkodning som gör det svårt att förstå när du inte kan följa skeppets rörelse. För att få skeppet att gå långsammare kan du ändra följande kod;

ship_vector.X += ship_vector.X;
ship_vector.Y += ship_vector.Y;

till detta

ship_vector.X += 0.5f;//ship_vector.X;
ship_vector.Y += 0.5f;//ship_vector.Y;

Om du fortfarande inte kan se rymdskeppet är det dags att börja felsöka på riktigt.
Tänk på att alla fel inte är syntaxfel, ibland skriver man logiska fel också, alltså händelser som inte överrensstämmer med vad man avsett.

6.6 Läsa av tangenttryckningar (s. 47)

Här gäller det att kommentera bort den delen som handlar om hur rymdskeppet automatisk flyttar sig annars kommer du få ett problem där du både styr och får hjälp/stjälp av den automatiska rörelsen. Att du behöver ta bort den koden är inte tydligt i boken.

7.x Skriva ut text i MonoGame (s. 68)

Properties får du fram genom att "högerklicka" på en fil, då visar den sig upp på höger sida av Xamarin.

För att skriva ut text i MonoGame gör du så här.

  1. Du behöver en font av typen xnb. Sök på nätet, det går också att skapa egna. I den blå rutan i högerspalten finns en zippad fil med några olika fonter du kan använda.
  2. Lägg in fonten under Content och döp eventuellt om fonten.
  3. Markera fonten och välj egenskaper/properties genom att högerlicka på den, sätt ”build action” till content, sätt också copy to output till Always copy eller Copy if newer för att tvinga fonten att läggas med i applikationen. Glömmer du detta så kommer det ett felmeddelande sedan att filen inte hittas.
  4. Deklarera en font av typen SpriteFont. [rad 33]
  5. Ladda fonten. [rad 57]
  6. Skriv ut texten med funktionen DrawString [rad 104]

                public class Game1 : Game
              	{
              		GraphicsDeviceManager graphics;
              		SpriteBatch spriteBatch;
              		SpriteFont font;
              
              protected override void LoadContent ()
          		{
          			// Create a new SpriteBatch, which can be use to draw textures.
          			spriteBatch = new SpriteBatch (graphics.GraphicsDevice);

          			// Load up the font
          			font = Content.Load<SpriteFont>("spriteFont");
          		}
            
              protected override void Draw (GameTime gameTime)
              {
                // Clear the backbuffer
                graphics.GraphicsDevice.Clear (Color.CornflowerBlue);

                spriteBatch.Begin ();

                // draw the strings
                spriteBatch.DrawString (font, "Utskriven text", new Vector2 (10, 10), Color.Black);

                spriteBatch.End ();

                //TODO: Add your drawing code here
                base.Draw (gameTime);
              }
              

7.4.7 Ta bort fiender(s. 77)

När du skriver raden ”foreach (Enemy e in enemies.ToList() )” får du ett error, det löser du på följande sätt; Högerklicka på To List och sedan Resolve -­‐> using System.Linq.
Då kommer "using System.Linq;" läggas till bland deklarationerna längst upp i filen.

Lösning av Ludvig Forsberg.

Kodhjälp kapitel 7

Eftersom det i boken inte finns någon samlad kod efter kapitel 7 så har jag lagt dessa här. Notera att det är den kod som jag har skrivit när jag själv har följt tutorial, vilket innebär att den är vädigt lik, men kanske inte identisk hela vägen.

Kodhjälp kapitel 8

Eftersom det i boken inte finns någon samlad kod efter kapitel 8 så har jag lagt dessa här. Notera att det är den kod som jag har skrivit när jag själv har följt tutorial, vilket innebär att den är vädigt lik, men kanske inte identisk hela vägen.

9. Skriva till och läsa från fil(s. 129)

Skapa ett nytt projekt som en konsollapplikation när du testar att skriva till och läsa från en fil. Det är svårare att styra med monogame var filen skall lagras, men det kommer ett exempel på hur man kan göra.

Här följer ett fullt exempel, textfilen skapas vid programstart och sedan skall läser vi från filen och skriver ut texten i spelfönstret.

#region Using Statements
using System;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Input;
using System.Reflection.Emit;
#endregion

namespace MonoFileExample
{
	/// <summary>
	/// This is the main type for your game.
	/// </summary>
	public class Game1 : Game
	{
		GraphicsDeviceManager graphics;
		SpriteBatch spriteBatch;
		SpriteFont font;
		string filename;

		public Game1 ()
		{
			graphics = new GraphicsDeviceManager (this);
			Content.RootDirectory = "Content";
			graphics.IsFullScreen = false;
		}

		/// <summary>
		/// Allows the game to perform any initialization it needs to
    /// before starting to run.
		/// This is where it can query for any required services and
    /// load any non-graphic related content.
    /// Calling base.Initialize will enumerate through any
    /// components and initialize them as well.
		/// </summary>
		protected override void Initialize ()
		{
			// TODO: Add your initialization logic here
			base.Initialize ();

			// Hämtar adressen till min hemkatalog
			var documents = Environment.GetFolderPath (
				Environment.SpecialFolder.Personal);

			// Filnamnet
			filename = "myFile.txt";

			// Lagra sökvägen till filen
			filename = Path.Combine (documents, filename);

			// Skapar en sträng som lagras i textfilen
			string text = "Denna text sparas i filen och skrivs ut.";
			SaveToFile (filename, text);
		}

		/// <summary>
		/// LoadContent will be called once per game and is the place
    /// to load all of your content.
		/// </summary>
		protected override void LoadContent ()
		{
			// Create a new SpriteBatch
			spriteBatch = new SpriteBatch (GraphicsDevice);

			// Load the font
			font = Content.Load<SpriteFont>("font");
		}

		/// <summary>
		/// Allows the game to run logic such as updating the world,
		/// checking for collisions, gathering input,
    /// and playing audio.
		/// </summary>
		/// <param name="gameTime">Provides a snapshot of
    /// timing values.</param>
		protected override void Update (GameTime gameTime)
		{
      // Ends game with Escape
			if (Keyboard.GetState ().IsKeyDown (Keys.Escape)) {
				Exit ();
			}

			base.Update (gameTime);
		}

		/// <summary>
		/// This is called when the game should draw itself.
		/// </summary>
		/// <param name="gameTime">Provides a snapshot of
    /// timing values.</param>
		protected override void Draw (GameTime gameTime)
		{
			graphics.GraphicsDevice.Clear (Color.CornflowerBlue);

			// Använd spriteBatch för att rita ut saker på skärmen
			spriteBatch.Begin();

			//Läser texten från filen och lagrar i text
			string text = ReadFromFile(filename);

			// Skriver ut text
			spriteBatch.DrawString(font, text, Vector2.Zero, Color.White);

			spriteBatch.End ();
			base.Draw (gameTime);
		}

		protected override void UnloadContent(){
		}

		/// <summary>
		/// Saves to file.
		/// </summary>
		/// <param name="filename">Filename.</param>
		/// <param name="text">Text.</param>
		public static void SaveToFile (string filename, string text)
		{
			StreamWriter sw = new StreamWriter (filename);
			sw.WriteLine (text);
			sw.Close ();
		}

		/// <summary>
		/// Reads from file.
		/// </summary>
		/// <returns>Text from file</returns>
		/// <param name="filename">Filename.</param>
		public static string ReadFromFile (string filename)
		{
			StreamReader sr = new StreamReader (filename);
			String text = sr.ReadToEnd();
			sr.Close ();
			return text;
		}
	}
}

9.3 Klassen HighScore(s. 133)

Författaren har skapat en klass som används för att lagra HighScore och skapa en topplista av dessa.

Att spara listan till fil

När du skall lagra till fil så är det lite svårt att styra i MonoGame vilket innebär att du inte kan placera filen där du vill ha den, alltså i samma katalog där själva programmet ligger. Däremot kan du placerad den i datoranvändarens hemkatalog vilket visas i exemplet nedan.

All nedanstående kod läggs i klassen Game1 vilket är den klass där själva programmet körs, övrig klass i projektet är bara HighScore. Den koden som redovisas här är det som avviker från den kod som presenteras i tutorialen för klassen HighScore.

En stringvariabel med namnet "filename" deklareras bland övriga variabler.

string filename;

I funktionen Initialize() så initierar vi filnamnet och den mapp där vi vill lägga filen. MonoGame gör det lite krångligt för oss och låter oss inte lägga filer precis där vi vill, därför visar jag här hur du kan lägga filen i användarens hemmapp.

              protected override void Initialize ()
              {
              	// Hämtar adressen till användarens hemkatalog
              	var folder = Environment.GetFolderPath (
              		Environment.SpecialFolder.Personal);

              	// Filnamnet
              	filename = "myHighScore.txt";

              	// Lagra sökvägen till filen
              	filename = Path.Combine (folder, filename);
              }
              

Innan base.Update() nästan längst ner i funktionen Update() så anropas funktionen som sparar HighScorelistan i filen.

              // Skriver ut hela HighScorelistan till filen i metoden Update()
              highscore.SaveToFile (filename);
              

Att ladda listan från fil

Funktionen LoadFromFile() skrivs som i tutorialen men sedan läggs anropet till LoadFromFile() sist i Game1.Initialize() istället för i Game1.LoadContent().

              // Läs in highscorelistan från den sparade filen.
              highscore.LoadFromFile (filename);
              

Nu skall du kunna läsa in highscorelistan från filen så att nya spelare kan matcha historiska highscore.

10. Databaser(s. 133)

XML

Här finns filen som bokens författare använder i sitt exempel.

Problem med System.Xml

Problem med att lägga till namnrymden "System.Xml" löser du på följande sätt.

Välj "Edit Reference..." under reference i solution explorer.

Lägg sedan till System.Xml bland valda referenser.

sqlite

Här kommer jag lägga upp ett exempel på hur du kan använda databasen sqlite för att lagra och läsa data.

              using System;
              using System.Data;
              using System.IO;
              using Mono.Data.Sqlite;

              class SqliteDemo {
              	static void Main (string [] args)
              	{
              		var connection = GetConnection ();
              		using (var cmd = connection.CreateCommand ()) {
              			connection.Open ();
              			cmd.CommandText = "SELECT * FROM People";
              			using (var reader = cmd.ExecuteReader ()) {
              				while (reader.Read ()) {
              					Console.Error.Write ("(Row ");
              					Write (reader, 0);
              					for (int i = 1; i < reader.FieldCount; ++i) {
              						Console.Error.Write(" ");
              						Write (reader, i);
              					}
              					Console.Error.WriteLine(")");
              				}
              			}
              			connection.Close ();
              		}
              	}

              	static SqliteConnection GetConnection()
              	{
              		// Hämtar adressen till min hemkatalog, där kommer databasen att läggas
              		var documents = Environment.GetFolderPath (
              			Environment.SpecialFolder.Personal);

              		// Skriv ut adressen till hemkatalogen
              		Console.WriteLine(documents.ToString ());

              		// Lagra sökvägen till filen
              		string db = Path.Combine (documents, "qwerty123.db3");

              		// Kolla om databasen existerar...
              		bool exists = File.Exists (db);

              		// ...annars skapa databasfilen.
              		if (!exists)
              			SqliteConnection.CreateFile (db);

              		// Skapa en uppkoppling mot databasfilen
              		var conn = new SqliteConnection("Data Source=" + db);

              		// Om filen inte fanns, skapa själva databasen, 3 attribut {PersonID, FirstName, LastName}
              		if (!exists) {
              			var commands = new[] {
              				"CREATE TABLE People (PersonID INTEGER NOT NULL, FirstName ntext, LastName ntext)",
              				// WARNING: never insert user-entered data with embedded parameter values
              				"INSERT INTO People (PersonID, FirstName, LastName) VALUES (1, 'Anders', 'Andersson')",
              				"INSERT INTO People (PersonID, FirstName, LastName) VALUES (2, 'Bosse', 'Larsson')",
              				"INSERT INTO People (PersonID, FirstName, LastName) VALUES (3, 'Chris', 'Cole')",
              			};

              			// Öppna kopplingen till databasfilen
              			conn.Open ();
              			// Loopa igenom arrayen commands och kör varje SQL-sats för sig
              			foreach (var cmd in commands) {
              				using (var c = conn.CreateCommand()) {
              					c.CommandText = cmd;
              					c.CommandType = CommandType.Text;
              					c.ExecuteNonQuery ();
              				}
              			}
              			// Stäng databaskopplingen
              			conn.Close ();
              		}
              		return conn;
              	}

              	static void Write(SqliteDataReader reader, int index)
              	{
              		Console.Error.Write("({0} '{1}')",
              			reader.GetName(index),
              			reader [index]);
              	}
              }
              

Uppdatera MonoGame.FrameWork.MacOS (3.4.0.459)

I mappen "Packages" så kan det dyka upp en förfrågan om att uppdatera MonoGame.FrameWork.MacOS (3.4.0.459) om du gör detta så kan du få problem med att följande kod ger ett error;

protected override void Update (GameTime gameTime)
{
  // For Mobile devices, this logic will close the Game when the Back button is pressed
  // Exit() is obsolete on iOS
  #if !__IOS__ &&  !__TVOS__
  if (GamePad.GetState (PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
      Keyboard.GetState ().IsKeyDown (Keys.Escape)) {
    Exit ();
  }
  #endif
  // TODO: Add your update logic here
  base.Update (gameTime);
}

Detta error handlar om hur applikationen skall avslutas när den körs i en mobilapp och eftersom vi inte skall köra appen på det sättet så kan vi kommentera bort detta som en temporär lösning.
Kommentera bort hela den delen med shift + cmd + 7

protected override void Update (GameTime gameTime)
{
//  // For Mobile devices, this logic will close the Game when the Back button is pressed
//  // Exit() is obsolete on iOS
//  #if !__IOS__ &&  !__TVOS__
//  if (GamePad.GetState (PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
//    Keyboard.GetState ().IsKeyDown (Keys.Escape)) {
//    Exit ();
//  }
//  #endif
  // TODO: Add your update logic here
  base.Update (gameTime);
}

Vill du istället fixa så att du kan stänga applikationen med "esc" så använder du följande kod.

protected override void Update (GameTime gameTime)
{
  // Stäng av applikationen med Escape
  if (keyboardState.IsKeyDown (Keys.Escape))
    this.Exit ();

  // TODO: Add your update logic here
  base.Update (gameTime);
}

Radera content

Ett problem jag stötte på under detta kapitel var att när man väl har lagt något i ”content” mappen så går det inte att ta bort utan att programmet kraschar. Jag löste detta genom att gå in i Finder och ta bort den därifrån. sedan tillbaka i xamarin försökte jag flytta den till en annan mapp. Den upptäckte då att den inte fanns och tog bort den ur projektet.
[OSX: 10.8, XCode: 5.1.1 Xamarin: 5.8?]

Lösning av Oscar Wolter, Länk

Öppna flera projekt

För att öppna flera samtidiga projekt i Xamarin så behöver du öppna flera instanser av Xamarin. Det gör du på följande sätt genom att i Terminalen skriva följande;

              open -n "/Applications/Xamarin Studio.app"