Learning Day 4


I usually write my Dev Blogs the day after I do the coding. This gives me some time to decompress and process everything I worked on. Since yesterday was Sunday I took it easy and only implemented a few small features.

Settings

I decided to use a settings.ini to start with. I am sure there are better more modern ways, however, I love old games and I have fond memories of modifying .ini files to modify those games.

Right now its just video settings but I may expand this out to hold many settings for my game.

settings.ini

[Graphics]
Mode=Windowed # Windowed, FullScreen, BorderlessFullScreen
Width=800
Height=480

Here I am just reading the file in line by line and splitting the commands from the comments, then reading the commands into a dictonary.

settings.cs

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace TargetPractice.Tools;
public class Settings : DrawableGameComponent
{
    GraphicsDeviceManager _graphics;
    DisplayMode _displayMode;
    public Settings(Game game) : base(game)
    {
        _graphics = (GraphicsDeviceManager)game.Services.GetService(typeof(IGraphicsDeviceManager));
        _displayMode = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode;
    }
    public override void Initialize()
    {
        Console.WriteLine("Initializing settings");
        var settings = ReadSettings();
        Console.WriteLine(settings);
        ApplySettings(settings);
        base.Initialize();
    }
    public 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();
            }
        }
        return settings;
    }
    public void ApplySettings(Dictionary<string, string> settings)
    {
        Console.WriteLine(settings["Mode"]);
        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;
        }
    }
    public void ConfigureGraphicsFullScreen()
    {
        Console.WriteLine("Configuring full screen");
        Game.Window.AllowUserResizing = false;
        _graphics.IsFullScreen = true;
        _graphics.PreferredBackBufferWidth = _displayMode.Width;
        _graphics.PreferredBackBufferHeight = _displayMode.Height;
        _graphics.ApplyChanges();
    }
    public 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();
    }
    public 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();
    }
}

One thing I learned here is that when setting the graphics outside of the game constructor you need to call the `ApplyChanges()` function. When you are inside the games constructor the graphic device hasn't been initialized yet.

GraphicModes.cs

namespace TargetPractice.Enums;
public enum GraphicModes
{
    FullScreen,
    BorderlessFullScreen,
    Windowed
}

Just a small Enum to handle my Graphic Modes. Much better than magical numbers.

Sprite Atlas Update

I decided I did not like the sprite atlas having draw control over my objects.

The way it worked previously was a follows

  • Load the sprite sheet into memory
  • Load the rectangles of all individual sprites into memory
  • Accept instructions on drawing the image

This has one huge drawback, It did not return any data about the sprite so it could be positioned appropriately. Other unforeseen issues may have been caused by this in the future as well.

As such I removed the `Draw` function and replaced it with `GetSpriteSheet` and `GetSpriteRectangle`.

public Texture2D GetSpriteSheet(string spriteSheet)
    {
        if (_sheets.ContainsKey(spriteSheet))
        {
            return _sheets[spriteSheet];
        }
        return null;
    }
    public Rectangle? GetSpriteRectangle(string spriteSheet, string spriteName)
    {
        if (_sprites.ContainsKey(spriteSheet) && _sprites[spriteSheet].ContainsKey(spriteName))
        {
            return _sprites[spriteSheet][spriteName];
        }
        return null;
    }

Now when I can use it the way I need in my `MainMenu`

MainMenu.cs

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using TargetPractice.Tools;
namespace TargetPractice.Scenes;
public class MainMenuScene : DrawableGameComponent
{
    private SpriteBatch _spriteBatch;
    private ContentManager _sceneContent;
    private SpriteAtlas _spriteAtlas;
    private Texture2D stall_sheet;
    public MainMenuScene(Game game) : base(game)
    {
        _sceneContent = new ContentManager(Game.Services, Game.Content.RootDirectory);
        _spriteAtlas = new SpriteAtlas(_sceneContent);
    }
    public override void Initialize()
    {
        base.Initialize();
    }
    protected override void LoadContent()
    {
        _spriteAtlas.LoadSheet("spritesheet_hud");
        _spriteAtlas.LoadSheet("spritesheet_stall");
        stall_sheet = _spriteAtlas.GetSpriteSheet("spritesheet_stall");
        _spriteBatch = new SpriteBatch(GraphicsDevice);
        base.LoadContent();
    }
    public override void Update(GameTime gameTime)
    {
        base.Update(gameTime);
    }
    public override void Draw(GameTime gameTime)
    {
        _spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend);
        DrawBackground();
        _spriteBatch.End();
        base.Draw(gameTime);
    }
    protected override void UnloadContent()
    {
        _sceneContent.Unload();
        base.UnloadContent();
    }
    private void DrawBackground()
    {
        var background = _spriteAtlas.GetSpriteRectangle("spritesheet_stall", "bg_wood");
        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);
            }
        }
    }
}

As you can see I needed to get the height and width of the sprite so I could draw it as my background image.



The Future

I have a lot more to do in settings. First and foremost a way to update the settings in the game. Then adding in more system and game specific settings. Eventually I plan to place config information here so anyone who wants to "hack" the game by modifying the .ini file can.

Well that's all for today, I need to get to work. Feel free to leave me a message with any feedback or questions.

Leave a comment

Log in with itch.io to leave a comment.