Learning Day 5


Its been 5 days since my learning journey with MonoGame began and I still haven't worked on any gameplay. This is one of the reasons I started out with a few learning projects before jumping into the game I want to make. I am still figuring out best practices and how I like to structure my games.

That and I really want to stream my games development from the beginning. I have a few things going on right now that would make streaming difficult. So I am hoping to start my dream project and streaming in April.

Removed Change Scene

My idea for a change scene function stemmed from my lack of knowledge early on. It seemed the next logical step to do away with that completely. Now objects are responsible for calling adding themselves to the current scent and cleaning themselves up when done.

I am not sure how I will handle display layers, or if that will even be an issue. I did learn that adding the component has to be the last thing I do after initializing it.

In the following code, if you move the game.Components.Add(this) to the top of the constructor instead of the bottom then many of the references in LoadContent() will fail. This is because they try to get used before they even exist.

Background.cs

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using TargetPractice.Tools;
namespace TargetPractice.Objects;
public class Background : DrawableGameComponent
{
    private SpriteAtlas _spriteAtlas;
    private ContentManager _sceneContent;
    private SpriteBatch _spriteBatch;
    private Texture2D stall_sheet;
    public Background(Game game) : base(game)
    {
        _sceneContent = new ContentManager(Game.Services, Game.Content.RootDirectory);
        _spriteAtlas = new SpriteAtlas(_sceneContent);
        game.Components.Add(this);
    }
    public override void Initialize()
    {
        _spriteBatch = new SpriteBatch(GraphicsDevice);
        base.Initialize();
    }
    protected override void LoadContent()
    {
        _spriteAtlas.LoadSheet("spritesheet_stall");
        stall_sheet = _spriteAtlas.GetSpriteSheet("spritesheet_stall");
        base.LoadContent();
    }
    public override void Update(GameTime gameTime)
    {
        base.Update(gameTime);
    }
    public override void Draw(GameTime gameTime)
    {
        _spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend);
        var backgroundImage = Settings.GetSetting("Background");
        var background = _spriteAtlas.GetSpriteRectangle("spritesheet_stall", backgroundImage);
        var bgWidth = background.Value.Width;
        var bgHeight = background.Value.Height;
        var screenWidth = GraphicsDevice.Viewport.Width;
        var screenHeight = GraphicsDevice.Viewport.Height;
        for (var x = 0; x < screenWidth; x += bgWidth)
        {
            for (var y = 0; y < screenHeight; y += bgHeight)
            {
                _spriteBatch.Draw(stall_sheet, new Vector2(x, y), background, Color.White, 0f, Vector2.Zero, 1f, SpriteEffects.None, 0f);
            }
        }
        _spriteBatch.End();
        base.Draw(gameTime);
    }
}

Settings.cs

Settings has evolved over to a singleton. Being a singleton allows me to call it from anywhere in the code and ensure that there is only ever one instance of it. Lets take a look at how that works.

First in the games main constructor we call initialize it.

public TargetPractice()
    {
        _graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
        IsMouseVisible = true;
        Settings.GetInstance(this);
    }

Then we moved our Settings.cs to be a static object like this

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace TargetPractice.Tools;
public class Settings
{
    private static Settings _instance;
    private static GraphicsDeviceManager _graphics;
    private static DisplayMode _displayMode;
    private static Game _game;
    public static Dictionary<string, string> GlobalSettings;
    // Private constructor
    private Settings(Game game)
    {
        _graphics = (GraphicsDeviceManager)game.Services.GetService(typeof(IGraphicsDeviceManager));
        _displayMode = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode;
    }
    // Public static method to get instance
    public static Settings GetInstance(Game game)
    {
        if (_instance == null)
        {
            _game = game;
            _instance = new Settings(game);
            Initialize();
        }
        return _instance;
    }
    private static void Initialize()
    {
        Console.WriteLine("Initializing settings");
        var settings = ReadSettings();
        Console.WriteLine(settings);
        ApplyVideoSettings(settings);
    }
    private static Dictionary<string, string> ReadSettings()
    {
        var settings = new Dictionary<string, string>();
        var lines = File.ReadAllLines("Content/settings.ini");
        foreach (var rawLine in lines)
        {
            var line = rawLine.Split('#', 2)[0].Trim();
            if (string.IsNullOrWhiteSpace(line)) continue;
            var keyValue = line.Split('=', 2);
            if (keyValue.Length == 2)
            {
                settings[keyValue[0].Trim()] = keyValue[1].Trim();
            }
        }
        GlobalSettings = settings;
        return settings;
    }
    private static void ApplyVideoSettings(Dictionary<string, string> settings)
    {
        switch (settings["Mode"])
        {
            case "FullScreen":
                ConfigureGraphicsFullScreen();
                break;
            case "BorderlessFullScreen":
                ConfigureBorderlessFullScreen();
                break;
            case "Windowed":
                ConfigureGraphicsWindowed(int.Parse(settings["Width"]), int.Parse(settings["Height"]));
                break;
            default:
                ConfigureGraphicsWindowed();
                break;
        }
    }
    private static void ConfigureGraphicsFullScreen()
    {
        Console.WriteLine("Configuring full screen");
        _game.Window.AllowUserResizing = false;
        _graphics.IsFullScreen = true;
        _graphics.PreferredBackBufferWidth = _displayMode.Width;
        _graphics.PreferredBackBufferHeight = _displayMode.Height;
        _graphics.ApplyChanges();
    }
    private static void ConfigureBorderlessFullScreen()
    {
        Console.WriteLine("Configuring borderless full screen");
        _game.Window.AllowUserResizing = false;
        _game.Window.IsBorderless = true;
        _graphics.IsFullScreen = true;
        _graphics.PreferredBackBufferWidth = _displayMode.Width;
        _graphics.PreferredBackBufferHeight = _displayMode.Height;
        _graphics.ApplyChanges();
    }
    private static void ConfigureGraphicsWindowed(int width = 800, int height = 480)
    {
        Console.WriteLine("Configuring windowed");
        _game.Window.AllowUserResizing = true;
        _graphics.IsFullScreen = false;
        _graphics.PreferredBackBufferWidth = width;
        _graphics.PreferredBackBufferHeight = height;
        _graphics.ApplyChanges();
    }
    public static string GetSetting(string key, string defaultValue = "")
    {
        if (GlobalSettings.TryGetValue(key, out string value))
        {
            return value;
        }
        return defaultValue;
    }
    public static void UpdateSetting(string key, string newValue)
    {
        GlobalSettings[key] = newValue;
        var lines = File.ReadAllLines("Content/settings.ini");
        bool keyFound = false;
        for (int i = 0; i < lines.Length; i++)
        {
            var line = lines[i].Split('#', 2);
            var keyValue = line[0].Trim().Split('=', 2);
            if (keyValue.Length == 2 && keyValue[0].Trim() == key)
            {
                lines[i] = $"{keyValue[0].Trim()}={newValue}" + (line.Length > 1 ? $" # {line[1].Trim()}" : "");
                keyFound = true;
                break;
            }
        }
        if (keyFound)
        {
            File.WriteAllLines("Content/settings.ini", lines);
        }
    }
}

If you look up at our Background.cs you can see how we call items from our .ini file using the GetSettings function.

I have also expanded it to allow us to update settings and have them written back to the .ini file. I have not tested that yet so that will be a project for later today.

Final Thoughts

That is all I have for today from coding yesterday. One thought I have had is if my game got larger I might want to change how the SpriteAtlas works. Every scene creates a new instance of this so if multiple scenes need the same SpriteSheet then it would be loaded in multiple times.

I need to think more on how I want to handle this. That is all and its time for me to get to the rest of my day. Feel free to leave any feedback or questions you may have.

Leave a comment

Log in with itch.io to leave a comment.