Posts
Search
Contact
Cookies
About
RSS

Using MonoGame without an IDE

Added 21 Oct 2021, 2:32 p.m. edited 18 Jun 2023, 1:12 a.m.

While many people favour an IDE (Integrated Development Environment) there are a few reasons you might prefer using a simple editor and terminal, to say the least less hunting through a GUI hierarchy for the functionality you want, and a better understanding of the development environment.

C Sharp uses what it calls "assemblies" to provide libraries (such as MonoGame) and due to the fact that the mono compiler can't manage partial compilation (for later linking). You might find you want to make your own assemblies at a later date to separate out, for example, game logic and utility functions, however in this initial example I'll keep things simple.

I won't go into a great amount of detail about how the example is coded, as we're more interested in demonstrating that a project can be compiled and run. We first need to grab the MonoGame assembly, the file you're looking for is

MonoGame.Framework.DesktopGL.3.8.0.1641.nupkg 

The version number isn't critical, and don't worry about the nupkg file extension its nothing special other than a zip file, just extract the whole contents into your project directory giving it its own directory name (DesktopGL for example)

Here's some source code to put into your project's "src" directory

Program.cs
using System;

namespace App
{
    public static class Program
    {
        [STAThread]
        static void Main()
        {
            using (var game = new AppGame())
                game.Run();
        }
    }
}
AppGame.cs
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Collections;

namespace App
{
    public class AppGame : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Texture2D texture;
        ArrayList smiles;

        const int MaxSmiles = 20;

        public AppGame()
        {
            graphics = new GraphicsDeviceManager(this);
            //Content.RootDirectory = "data";
        }

        protected override void Initialize()
        {
            Smile.Size = new Vector2(64,64);
            Smile.Range = new Vector2(640, 480);

            graphics.PreferredBackBufferWidth = (int)Smile.Range.X;
            graphics.PreferredBackBufferHeight = (int)Smile.Range.Y;
            graphics.IsFullScreen = false;
            graphics.ApplyChanges();

            smiles = new ArrayList();
            for (int i = 0; i < MaxSmiles; i++) {
                var s = new Smile();
                s.Randomise();
                smiles.Add(s);
            }

            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            texture = Texture2D.FromFile(GraphicsDevice, "data/smile.png");
        }

        protected override void UnloadContent()
        {
            texture.Dispose();
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed
                    || Keyboard.GetState().IsKeyDown(Keys.Escape)) {
                        Exit();
            }

            for (int i = 0; i< MaxSmiles; i++)
            {
                var s = (Smile)smiles[i];
                s.Update(gameTime);
            }

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();
            for (int i = 0; i < MaxSmiles; i++) {
                var s = (Smile)smiles[i];
                spriteBatch.Draw(texture, s.Pos, Color.White);

            }
            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}
Smile.cs
using Microsoft.Xna.Framework;
using System;

public class Smile
{
    private Vector2 pos;
    private Vector2 vel;
    private static Vector2 size;
    private static Vector2 range;

    public Vector2 Pos {
        get { return pos; }
        set { pos = value; }
    }

    public Vector2 Vel {
        get { return vel; }
        set { vel = value; }
    }

    public static Vector2 Size {
        get { return size; }
        set { size = value; }
    }

    public static Vector2 Range {
        get { return range; }
        set { range = value; }
    }

    public void Randomise()
    {
        var rnd = new Random();
        pos = new Vector2( rnd.Next((int)(range.X - size.X)),
                            rnd.Next((int)(range.Y-size.Y)));
        vel = new Vector2( (float)(rnd.NextDouble()*2.0-1.0),
                            (float)(rnd.NextDouble()*2.0-1.0));
    }

    public void Update(GameTime gameTime)
    {
        pos = pos + ( vel * gameTime.ElapsedGameTime.Ticks/16000 );
        if (pos.X < 0 || pos.X > 640 - size.X) vel.X = -vel.X;
        if (pos.Y < 0 || pos.Y > 480 - size.Y) vel.Y = -vel.Y;
    }
}

A trivial example to be sure, but let look at the meat of this post and look at the Makefile

APPNAME:=$(shell basename `pwd`).exe
SRC:=$(wildcard src/*.cs)
ASMB:=DesktopGL/lib/net452/MonoGame.Framework.dll
CSC:=mcs

export MONO_PATH=DesktopGL/lib/net452/


run: $(APPNAME)
    mono $(APPNAME)

$(APPNAME): $(SRC)
 $(CSC) $(SRC) -r:$(ASMB) -out:$(APPNAME)

clean:
    rm -f $(APPNAME)

As its set up the makefile will compile the project (automagically finding any source files in the src directory) and then assuming it compiles okay will run it. One thing to note setting the environment variable MONO_PATH allows the MonoGame assembly to be found at runtime.

And that's just about all there is to it, a very simple Makefile and an extracted nuget package.

Next time I think I might have to look at 3D content

Enjoy!