Search code examples
c#xmllinqlinq-to-xmlmonogame

Monogame game settings


I'm developing a platformer using C# and monogame as a fun project to improve my skills. I want to add support for settings like audio, video, controls and more but I don't know the best way to do so.

I plan on having a XML file that stores all settings kind of like this:

<Settings>
  <Audio>
    <masterVolume>100</masterVolume>
    <musicVolume>40</musicVolume>
    <sfxVolume>90</sfxVolume>
  </Audio>
  <Video>
    <Resolution>
      <width>1920</width>
      <height>1080</height>
    </Resolution>
  </Video>
  <Controls>
    <forward>W</forward>
    <back>S</back>
    <left>A</left>
    <right>D</right>
  </Controls>
</Settings>

My plan was to use a Settings class that has static variables for audio, video and controls etc that are divided into separate classes so that I can access them through Settings.Audio.MasterVolume or Settings.Controls.Forward. I want to load the settings into these classes from the XML using Linq and I want to save the settings to the XML file when they are changed by the user.

I know that singletons can be used as well but as far as I can tell from the research I've done it has too many drawbacks. Is that true?

Is this a good way to do this or is there a better way that allows me to access the settings throughout the project without having everything static?

Thanks in advance!


Solution

  • Your method works. However, you limit the re-usage of your class.

    If you would describe MyClass to others, would you say that the method being used to read the configuration parameters is essential for MyClass? In other words: would most code of your class be meaningless if the configuration parameters wouldn't be read from a configuration file, but for instance from a CSV-file, or from the internet, or whatever method?

    You'll probably say no: most code of my class could be reused if the configuration parameters were to be provided differently.

    So why limit your class to a class that can only work if it reads the configuration parameters from a configuration file?

    I guess that you currently have something like:

    static class ConfigurationSettings
    {
        public static int LeftControl => ...
        public static int RighControl => ...
    }
    
    class MyClass
    {
        // MyClass uses configuration settings:
        public int LeftControl => ConfigurationSettings.LeftControl;
        public int RightControl => ConfigurationSettings.RightControl;
    
        ...
    }
    

    You can't instantiate two objects of MyClass, each with their own set of configuration parameters. You can't create a MyClass object that uses a different method of providing configuration values.

    One of the use cases I quite often need this is when unit testing: I want a quick way to be able to change the configuration settings without having to change the configuration file of the unit test project.

    An easy way to allow users of your class to provide different methods for setting is to create an interface with the setting and provide a constructor with this interface:

    interface IMySettings
    {
        int LeftControl {get;}
        int RightControl {get;}
        ...
    }
    
    class ConfigurationSettings : IMySettings
    {
         // ConfigurationSettings reads settings from the configuration file
    }
    
    class MyClass
    {
        // default constructor use the configuration file via ConfigurationSettings:
        public MyClass() : this (new ConfigurationSettings())
        {
        }
    
        // extra constructor for those who don't want to use configuration file
        public MyClass(IMySettings settings)
        {
            this.Settings = settings;
        }
    
        protected readonly IMySettings settings;
    
        // MyClass uses settings:
        public int LeftControl => this.settings.LeftControl;
        public int RightControl => this.settings.RightControl;
    }
    

    Users who want to use the settings from the configuration file can use the default constructors; uses who want specific settings use the alternative constructor. This alternative constructor is also used if you want two different MyClass objects, each with their own settings.

    At first glance it seems that with minimal changes you can still use the configuration file. However, to be able to implement the interface, the class that reads the configuration file (MyClass) can't be static.

    However, you were right: there is only one configuration file, hence there should be only one instance of the class that reads from the configuration file: create a singleton.

    In my experience, classes that represent a configuration, is one of the often used reasons to use a singleton instead of a static class