Category: MonoGame

An example of what you can do with MonoGame

An example of what you can do with MonoGame

OpenVIII screenshotMonoGame is not just for mobile, as I’ve been doing. Open VIII is a Final Fantasy VIII game engine written in C#/MonoGame and currently works on Windows and Linux (not sure about Mac). Other games in the series have been ported to other platforms but not FFVIII, so that’s why the project was started.

The instructions for Open VIII on Windows suggest Visual Studio 2017 but I imagine 2019 might also work as MonoGame 3.8 has templates for it.  As the project says “OpenVIII is an open-source complete Final Fantasy VIII game engine rewrite from scratch powered in OpenGL making it possible to play the vanilla game on wide variety of platforms including Windows 32/64 bitLinux 32/64 bit and even mobile!

As with virtually all open source reimplementations, you will have to provide your own game assets such as images and sound. You can do this apparently by buying the original game on Steam. I took a look and sure enough it’s there and there’s an official remastering by Square Enix. I’m not sure why the Steam search brings up FF VII as well but hey that’s search for you… I’ve added a permanent link to the C#/MonoGame links page.

Steam Final Fantasy VIII

Monogame 3.8 is now out

Monogame 3.8 is now out

Monogame 3.8 releasedThese don’t come that often and this one has quite a few changes. You can read all about it on their What New page.  The main changes as far as I am concerned is the .NET Core support, templates in VS 2019 (finally!) and distribution via Nuget.

Interesting though that the Pipeline tool is dead, Long live the Pipeline! It’s now called the MCGB tool. I think it’s more a rebranding than completely new or maybe it is (apologies if that’s the case). And I notice that text is a resource type that you can add BUT on Android, no point in compressing things because apks are compressed anyway. Here’s a big secret, apk files, like Java .jar files are just zip files.  Yes you can view their contents with WinZip!

If you are a 3.7 MonoGame user (as I am), you should probably readthe migrating from 3.7 web page.

 

MonoGame – How to make a clickable button

MonoGame – How to make a clickable button

Clear Button with one card clickedI’m using this in Android games but the principle applies to any MonoGame game. Here clickable and touchable mean the same.

You need three things to make something clickable.

  1. The area. For simplicity sake this will always be rectangular.
  2. The Action. When you touch (or click it), you want it to run code.
  3. An id of sorts. If you have multiple buttons etc, you meed to distinguish them.

We’ll use an int for 3. Use const ints to give it a name rather than being a magic number.

 

When you click whatever, the Event should include the ide so let’s define the event Type first.

public class ButtonClickedEventArgs : EventArgs
{
    public int Id { get; set; }
}

Not rocket science is it!

Now lets define a ClickButton class.

public class ClickButton
{
    public int Id { get; set; }
    public Rectangle TouchArea { get; set; }
    public Action<object, ButtonClickedEventArgs> AnAction { get; set; } = null;

    public ClickButton(int id)
    {
        Id = id;
        TouchArea = new Rectangle(0, 0, 0, 0);
    }
}

Again nothing too complicated. There’s an int Id which which is set in the Constructor, a Rectangle TouchArea and an Action which is defined to include our ButtonClickedEventArgs.

Here;’s some example code. I’ve defined a graphic for a Clear button in my game and this is loaded as usual in the LoadContent().  I’ve also defined the button in Class declaration.

ClickButton ClearButton;

In LoadControl(), this is also initialised. Here’s it’s two line. It could be done in one, by altering the constuctor.

ClearButton = new ClickButton(1);  // 1 is the id
ClearButton.AnAction += ClearCards;

ClearCards is a method that does something when the button is cleared. This matches our Event handler as it has the usal two parameters for an Event except the args is our ButtonClickedEventArgs.

private void ClearCards(object sender, ButtonClickedEventArgs args)  {
   // all the code here, not shown
}

Where is the TouchArea defined?

So are we all setup ready to rock’n’roll? Well not quite. We haven’t said exactly where the TouchArea is. That of course depends on where we draw the button. In my game, the ClearButton moves across. It’s absent when all six cards are present but as soon as one has been touched and a back card touched to move it, the Clear Card appears. In the top image you can see it to the right of the five top cards. I had clicked the seven of clubs when it was in the top row then the first back on the top row of the three rows.

For my next move I clicked the ten of diamonds and the second back on that first row where the ten of diamonds no wit. It now looks like this and the Clear Button has moved over.  If I click the Clear button it will move the seven clubs and ten diamonds back to the top row and replace them in the first row with backs.

Second clickTo set the TouchArea of the ClearButton, I do it in the Draw method. It’s as simple as this:

if (hand.Count < 6) // Draw ClearButton on end
  {
    spriteBatch.Draw(clearButton, new Rectangle(x, y, cardWidth, cardHeight), 
        new Rectangle(0, 0, cardWidth, cardHeight), Color.White);
    ClearButton.TouchArea = new Rectangle(x, y, cardWidth, cardHeight);
  }

There’s actually a small bug visible. When you click a top row card, the game highlights it by reducing its opacity to 0.5. If you look at the ten of diamonds, you can see it still has that reduced opacity; it should have been restored to 1.0 when it was moved to the first row. The same is true in the top picture for the seven clubs! Easily overlooked but just a one-line fix.

Some Touchy Code

The very last bit is to fire touch detection and this is done in the Update() method. When you touch the screen, calling Touchpanel.GetState() returns a collection of touches. This is my code. It cycles through the touches collection, gets the X, Y position of each touch and adjusts it to match the virtual screen coordinates. (Drawing to a virtual screen means this looks the same on every Android irrespective of its physical screen sizes and resolution).

touchState = TouchPanel.GetState();
foreach (var touch in touchState)
{
   if (touch.State == TouchLocationState.Pressed)
    {
         var x = touch.Position.X / xRatio; // adjust for virtual screen
         var y = touch.Position.Y / yRatio;
         CheckClickedTop(x, y, hand);
         if (pickedCard == null) return; // no point checking further...
         for (var i = 0; i < 3; i++)
            CheckClickedBottom(x, y, commonCards[i], true);
            if (hand.Count == 6) continue; // All top cards so no ClearButton visible
            // Check if Clear button
            if (ClearButton.TouchArea.Contains(x, y))
              {
                  ClearButton.AnAction(ClearButton, new ButtonClickedEventArgs() { Id = ClearButton.Id }); 
              }
      }
}

The top cards are held in a class called hand and the bottom three rows are held in CommonCards[3] each of which is a hand. The CheckClicked.. methods cycle through the cards in the hand comparing coordinates to see which card if at all has been clicked. If it has it calls the AnAction Event for the one clicked. The very last if shows how this works to check if the ClearButton was clicked. I’ll simplify this code by moving it into a ClickButton method.

An irritating bug with MonoGame/Android

An irritating bug with MonoGame/Android

Android Phone
Image by Iván Tamás from Pixabay

Bugs happen, it’s a way of life but when the bug affects you as a developer, it can be a tad annoying. I’ve found a bug and mentioned it in the MonoGame forums.

The bug occurs intermittently. I do a build then run the program on an Android phone. It starts loading, shows the splash screen then splat “The Content file was not found” message appears in Visual Studio.

This may be a bug in Xamarin or MonoGame. No doubt it will eventually get fixed but still a bit annoying. About the main workround I’ve found is rebuild everything. Start with the Pipeline tool- do a Build/Clean then a Build/Build. Then in the Project (in Visual Studio) do a Build/Clean then Build/Deploy which recompiles everything.

If the error still persists then it’s possible you have a typo; say a lowercase i instead of I in a filename. (I’ve done that once). Another fix though rather unsatisfactory is in the Project Properties, on the Options tab, right at the top is a checkbox Use Shared Runtime. If you untick that, deployment takes a bit longer. Instead of copying 3 MB which takes a second or so, it copies about 60 MB instead which takes a bit longer. I tried it but went back to the Clean and Deploy approach as copying was taking 10-15 seconds.

 

A test of resource loading in MonoGame

A test of resource loading in MonoGame

Ace of ClubsAce of DiamondsAce of HeartsAce of SpadesLoading resources into RAM on a smartphone can be a relatively slowish process. I’d been working on a card game and had both a set of 52 individual playing cards plus a sheet of 52 in one image. I have two Android phones so used the opportunity to test which is quicker loading 52 small images or one larger image. MonoGame ‘s Draw routines can copy from part of an image; you just specify the source.

In this case I was comparing loading 52 images, each 100 x 153 pixels (the four aces shown are the same images) or one image that is 1500 x 633. On the face of it, loading one image should be faster- there’s a certain overhead with loading each image so you get that x 52. But larger images can be problematic especially on devices like smartphones with less smaller amounts of RAM than desktops.

The two smartphones are both older, one is a BLU STUDIO ONE; the other a Chinese beast which shows up in Visual Studio as an Alps X27 Plus. It was bought on Ebay and claims to have 6 GB of RAm and 128 GB storage. In practice, when I tested the storage, it ran out after 12 GB! The phone only cost me £29 so I cn’t really complain…Although it claims to be Android 9.1 in the Developer information in Settings, it like the BLU seem to be running Android Lollipop. (5,1 – level 22) when plugged into my PC/Visual Studio.

I first ran the load code on the 52 images in Debug mode. Possibly not the best way to test code.

        private string LoadCards()

        {
            Stopwatch stopWatch = new Stopwatch();
            const string suits = "HCDS";
            const string ranks = "A23456789TJQK";
            stopWatch.Start();
            int i = 0;
            foreach (char c in suits)
            {
                int j = 0;
                foreach (char r in ranks)
                {
                    string s = @"Cards\"+r + c;
                    cards[i, j++] = Content.Load(s);
                }
                i++;
            }
            stopWatch.Stop();
            TimeSpan ts = stopWatch.Elapsed;

            string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
                ts.Hours, ts.Minutes, ts.Seconds,
                ts.Milliseconds / 10);
            return elapsedTime;
        }

That is the C# code to load the 52 images. The Alps which is a newer phone took 0.28 seconds to load them. The older BLU took 0.18. Neither are great times. To load the single image I commented the code out between the two stopWatch calls (Start() and Stop()) and then added this one line.

 allcards = Content.Load<Texture2D>("allcards");

The single image took 0.10 seconds on the Alps and 0.06 seconds on the BLU, both roughly three times faster loading one big image than loading all 52 images.  Information like this is useful and I’ll now create my own tools for building sprite sheets of multiple images.