Strategy for storing application-wide constants

I would like to have some application-wide (i.e. all scenes) settings (such as color palettes, default fonts, etc.), editable in the inspector, that I can refer to from code in various components.

The settings don’t need to be modifiable at runtime (it’s not a problem if they are; it’s just not a requirement); just a design time thing to make it easier for me to tweak and preview common settings during development, or to change them if necessary in the futue.

What’s my best option?

Unity 2020.3.16f1 LTS.

ScriptableObjects are a decent solution tailored to this type of situation.

2 Likes

agreed, scriptableobjects work good for this

Every one of my games tends to get a SO called “GameSettings”:

I’ll implement it as a Singleton, and I tend to use it as the “startup” section of my game. Like so:

using UnityEngine;
using System.Collections.Generic;

using com.spacepuppy;
using com.spacepuppy.Project;
using com.spacepuppy.Scenes;
using com.spacepuppy.SPInput;
using com.spacepuppy.Utils;

using com.vivarium.UserInput;

namespace com.vivarium
{

    public class Game : GameSettings
    {

        public const float GRAV = -20f;
        public const string MAIN_INPUT = "Main.Input";

        public const string SCRN_START = "StartScreen";

        public static float DEFAULT_TIMEINDAY = SPTimePeriod.SECONDS_IN_DAY;
        public static int DEFAULT_DAYSINSEASON = 24;

        #region Singleton Access

        private static Game _instance;
        private static System.Action<Game> _onBeforeInitializeCallback;

        public static bool Initialized { get { return _instance != null; } }

        public static Game Settings { get { return _instance; } }

        public static void Init(System.Action<Game> onBeforeInitializeCallback = null)
        {
            _onBeforeInitializeCallback = onBeforeInitializeCallback;
            _instance = GameSettings.GetGameSettings<Game>();
        }

        #endregion

        #region Fields

        [Header("Resources")]
        [SerializeField()]
        public com.vivarium.Factories.VivariumFactoryConfiguration DefaultFactoryConfiguration;

        [Header("Default Transition Settings")]
        [SerializeField()]
        public float DefaultTransitionDuration = 1f;
        [SerializeField()]
        public Color DefaultTransitionColor = Color.black;

        [Header("Falling & Gravity")]
        [SerializeField()]
        public float GravityMagnitude = 20.0f;
        [SerializeField()]
        public float TerminalVelocity = -30f;
        [SerializeField()]
        public float TerminalDepth = -100.0f;

        [Header("Drag & Drop Settings")]
        public float DragInitiationMouseDownDuration = 1f;

        [Header("Tank Settings")]
        [SerializeField()]
        [TimeUnitsSelector("Hours")]
        public float DefaultStartTime;
        [Range(Constants.TEMPERATURE_MIN, Constants.TEMPERATURE_MAX)]
        public float DefaultTemperature = 80f;
        [Range(0f, 1f)]
        public float DefaultHumidity = 0.5f;

        [Header("Character Settings")]
        [FixedPercent.Config(0f, 1f, true)]
        public FixedPercent CriticalEnergy = 0.2M;
        [FixedPercent.Config(0f, 1f, true)]
        public FixedPercent CriticalEnergyBuffer = 0.1M;

        [FixedPercent.Config(0f, 1f, true)]
        public FixedPercent CriticalHunger = 0.2M;
        [FixedPercent.Config(0f, 1f, true)]
        public FixedPercent CriticalHungerBuffer = 0.1M;

        [FixedPercent.Config(0f, 1f, true)]
        public FixedPercent CriticalHealth = 0.2M;
        [FixedPercent.Config(0f, 1f, true)]
        public FixedPercent CriticalHealthBuffer = 0.1M;

        [FixedPercent.Config(0f, 1f, true)]
        public FixedPercent CriticalHappiness = 0.2M;
        [FixedPercent.Config(0f, 1f, true)]
        public FixedPercent CriticalHappinessBuffer = 0.1M;

        [FixedPercent.Config(0f, 1f, true)]
        public FixedPercent CriticalProductivity = 0.2M;
        [FixedPercent.Config(0f, 1f, true)]
        public FixedPercent CriticalProductivityBuffer = 0.1M;


        //[System.NonSerialized()]
        //private SPSceneManager _sceneManager;
        //[System.NonSerialized()]
        //private VivariumUIController _ui;
        //[System.NonSerialized()]
        //private VivariumStats _vivar;
        //private PlantCareController _plantCare;

        [System.NonSerialized]
        private HashSet<string> _permanentTokenLedgerCategories = new HashSet<string>();

        #endregion

        #region Constructor

        protected override void OnInitialized()
        {
            _instance = this;
            GameLoop.Init();
            CustomTimeLayersData.RegisterTimeLayers();
            if (_onBeforeInitializeCallback != null)
            {
                var d = _onBeforeInitializeCallback;
                _onBeforeInitializeCallback = null;
                d(this);
            }

            //################
            // Initialize Services - this should always happen first, any given service shouldn't rely on accessing other services on creation
            //########
            {
                //create services
                var sceneManager = Services.Create<ISceneManager, SPSceneManager>(true);

                var inputManager = Services.Create<IInputManager, SPInputManager>(true);
                inputManager.Add(MAIN_INPUT, InputFactory.CreateInputDevice(MAIN_INPUT));

                Services.Create<ICameraManager, com.spacepuppy.Cameras.SPCameraManager>(true);
            }

            //load permanent ledgertoken categories
            _permanentTokenLedgerCategories.Clear();
            var txt = Resources.Load<TextAsset>("PermanentTokenCategories");
            if (txt != null)
            {
                using (var reader = new System.IO.StringReader(txt.text))
                {
                    string ln;
                    while ((ln = reader.ReadLine()) != null)
                    {
                        if (!string.IsNullOrEmpty(ln))
                        {
                            _permanentTokenLedgerCategories.Add(ln.Trim());
                        }
                    }
                }
            }

        }

        #endregion

        #region Properties

        public HashSet<string> PermanentTokenLedgerCategories
        {
            get { return _permanentTokenLedgerCategories; }
        }

        #endregion

        #region Static Methods

        public static void QuitGame()
        {
#if UNITY_EDITOR
            UnityEditor.EditorApplication.isPlaying = false;
#else
            Application.Quit();
#endif
        }

        #endregion

    }

}

Note - it may inherit from GameSettings, but that just inherits from ScriptableObject. It merely exists to contain some boilerplate that all my projects have.

using UnityEngine;

namespace com.spacepuppy.Project
{

    /// <summary>
    /// A singleton Game entry point
    /// </summary>
    public abstract class GameSettings : ScriptableObject
    {
       
        public const string PATH_DEFAULTSETTINGS = "GameSettings";
        public const string PATH_DEFAULTSETTINGS_FULL = @"Assets/Resources/GameSettings.asset";

        #region CONSTRUCTOR

        protected void Awake()
        {
            if (_instance != null && _instance != this)
                throw new System.InvalidOperationException("Attempted to create multiple GameSettings. Please get instances of the game settings via the static interface GameSettingsBase.GetGameSettings.");
        }

        protected abstract void OnInitialized();

        protected virtual void OnDestroy()
        {
            if (_instance == this)
                _instance = null;
        }

        #endregion

        #region Static Factory

        private static GameSettings _instance;

        public static GameSettings GetGameSettings(string path = null)
        {
            if(_instance == null)
            {
                if (path == null) path = PATH_DEFAULTSETTINGS;
                //_instance = Object.Instantiate(Resources.Load(path)) as GameSettings;
                _instance = Resources.Load(path) as GameSettings;
                if (_instance != null) _instance.OnInitialized();
                return _instance;
            }
            else
            {
                return _instance;
            }
        }

        public static T GetGameSettings<T>(string path = null) where T : GameSettings
        {
            if (_instance == null)
            {
                if (path == null) path = PATH_DEFAULTSETTINGS;
                //_instance = Object.Instantiate(Resources.Load(path)) as T;
                _instance = Resources.Load(path) as T;
                if (_instance != null) _instance.OnInitialized();
                return _instance as T;
            }
            else
            {
                return _instance as T;
            }
        }

        #endregion

    }

}
2 Likes