Just about the internal and external settings for the application in Unity3D

    Introduction


    Hello dear readers, in today's article I would like to talk about the settings and configuration of gaming applications created in the Unity3d environment.

    By tradition, I'll start with prehistory. During my work in the gaming industry, I developed various projects, with varying complexity, and visited both the game design camp and the programmers camp (where I am today). It's no secret that any application requires a large number of different configuration data and settings. In the classic, relatively Unity3d form, such settings are made in the visible part of the inspector, where some numbers are entered, etc. I think that talking about the convenience of this approach is not worth it, even if we exclude the fact that at the time of the setup, the scene in which MonoBehaviour is locatedthe class is blocked for changes by other developers. Therefore, after a series of all sorts of ordeals in the past, I decided to write something simple and effective that will make life easier for all and simplify work with such data, and I want to share it with you.

    Note : All of the code described below applies to Unity version 2018.3+ and uses the Roslyn compiler (C # 7+ language version).

    Internal settings


    To begin, consider the internal settings of the project, which include various constants, links, external SDK identifiers, keys, etc. things including global and local gameplay settings. In general, all such data can be divided into four types:

    • String
    • Int
    • Float
    • Bool

    All other data can be easily put into them, and taking into account the string, you can store anything using JSON serialization. Let's use ScriptableObject as a basis , which is suitable for solving this task like no one else.

    publicclassSetting : ScriptableObject
    {    
        publicenum ParameterTypeEnum
        {
            Float,
            Int,
            String,
            Bool
        }
        [Serializable]
        publicclassParameterData
        {
            publicstring Name => _name;
            public ParameterTypeEnum ParameterType => _parameterType;
            publicstring DefaultValue => _defaultValue;
            [SerializeField] privatestring _name;
            [SerializeField] private ParameterTypeEnum _parameterType;
            [SerializeField] privatestring _defaultValue;
        }
        [SerializeField] protected ParameterData[] Parameters;
    }
    

    So, in the database, we have an array of values ​​that represent:

    • Parameter name
    • Parameter type
    • Parameter Values ​​as Strings

    Note : why strings? It seemed to me more convenient than storing 4 variables of different types.

    For use in the code, we will add auxiliary methods and a dictionary that will store the converted values ​​in boxed form.
    protectedreadonlyIDictionary<string, object> settingParameters = newDictionary<string, object>();
    [NonSerialized]protectedboolinitialized;
        privatevoidOnEnable()
        {
    #if UNITY_EDITOR
            if (EditorApplication.isPlayingOrWillChangePlaymode)
            {
                Initialization();
            }
    #elseInitialization();
    #endif
        }
    publicvirtualTGetParameterValue<T>(stringname)
    {
        if (settingParameters.ContainsKey(name))
        {
            var parameterValue = (T)settingParameters[name];
            return parameterValue;
        }
        else
        {
            Debug.Log("[Setting]: name not found [{0}]".Fmt(name));
        }
        returndefault;
    }
    protectedvirtualvoidInitialization()
    {        
        if (initialized || Parameters == null) return;
        for (var i = 0; i < Parameters.Length; i++)
        {
            var parameter = Parameters[i];
            object parameterValue = null;
            switch (parameter.ParameterType)
            {
                case ParameterTypeEnum.Float:
                {
                     if (!float.TryParse(parameter.DefaultValue, out float value))
                     {
                         value = default;
                     }
                     parameterValue = GetValue(parameter.Name, value);
                }
                break;
                caseParameterTypeEnum.Int:
                    {
                        if (!int.TryParse(parameter.DefaultValue, out int value))
                        {
                            value = default;
                        }
                        parameterValue = GetValue(parameter.Name, value);
                    }
                    break;
                caseParameterTypeEnum.String:
                {
                     parameterValue = GetValue(parameter.Name, parameter.DefaultValue);
                }
                break;
                caseParameterTypeEnum.Bool:
                {
                     if (!bool.TryParse(parameter.DefaultValue, out bool value))
                     {
                          value = default;
                     }
                     parameterValue = GetValue(parameter.Name, value);
                 }
                 break;
             }
             settingParameters.Add(parameter.Name, parameterValue);
        }
        initialized = true;
    }           
    protectedvirtualobjectGetValue<T>(stringparamName, TdefaultValue)
    {
        return defaultValue;
    }
    


    Initialization is done in OnEnable . Why not in awake ? This method is not called for instances stored as an asset (it is called at the time of CreateInstance , which we do not need). At the moment of launching the application for assets, ScriptableObject is first called OnDisable (only in the editor), then OnEnable . Also, in the editor, each time you recompile and open a project, initialization does not work, you need to add precompilation directives and insert at the beginning of the file:
    #if UNITY_EDITORusing UnityEditor;
    #endif

    We need the GetValue method further, and for internal settings, it simply returns the default value.

    The GetParameterValue method is our main method for accessing parameters. It is worth considering here that despite the unboxing of values, the parameters stored in Setting are in some kind of constant, so you should take them when initializing the scenes. Do not call the method in Update .

    Example of use:

    publicclassMyLogic : MonoBehaviour
    {
        [SerializeField] private Setting _localSetting;
        privatestring _localStrValue;
        privateint _localIntValue;
        privatefloat _localFloatValue;
        privatebool _localBoolValue;
        privatevoidStart()
        {
            _localStrValue = _localSetting.GetParameterValue<string>("MyStr");
            _localIntValue = _localSetting.GetParameterValue<int>("MyInt");
            _localFloatValue = _localSetting.GetParameterValue<float>("MyFloat");
            _localBoolValue = _localSetting.GetParameterValue<bool>("MyBool");
        }
    }
    

    We wrote the foundation and now we need an editor, because the main goal for us was exactly the convenience for those who work with these settings.

    To add a menu item in order to be able to create an asset, you can use the attribute:

    CreateAssetMenu(fileName = "New Setting", menuName = "Setting")
    

    Now we will write a custom inspector, which will allow to display data on the asset and launch an external editor.
    [CustomEditor(typeof(Setting), true)]
    publicclassSettingCustomInspector : Editor
    {
        private GUIStyle _paramsStyle;
        private GUIStyle _paramInfoStyle;
        privateconststring _parameterInfo = "<color=white>Name</color><color=grey> = </color><color=yellow>{0}</color>  <color=white>Type</color><color=grey> = </color><color=yellow>{1}</color>  <color=white>Defualt Value</color><color=grey> = </color><color=yellow>{2}</color>";
        publicoverridevoidOnInspectorGUI()
        {
           if (GUILayout.Button("Edit Setting"))
           {
                SettingEditorWindow.Show(serializedObject.targetObject as Setting); 
           }
           EditorGUILayout.LabelField("Parameters:", _parametersStyle, GUILayout.ExpandWidth(true));
           var paramsProp = serializedObject.FindProperty("Parameters");
           for (var i = 0; i < paramsProp.arraySize; i++)
           {
                var paramProp = paramsProp.GetArrayElementAtIndex(i);
                var paramNameProp = paramProp.FindPropertyRelative("_name");
                var paramTypeProp = paramProp.FindPropertyRelative("_parameterType");
                var paramDefaultValueProp = paramProp.FindPropertyRelative("_defaultValue");
                EditorGUILayout.BeginHorizontal();
                EditorGUILayout.LabelField(_paramInfo.Fmt(
                                 paramNameProp.stringValue, 
                                 paramTypeProp.enumDisplayNames[paramTypeProp.enumValueIndex],
                                 paramDefaultValueProp.stringValue), 
                                 _paramInfoStyle);
                EditorGUILayout.EndHorizontal();
            }
        }
        privatevoidPrepareGUIStyle()
        {
            if (_parametersStyle == null)
            {
                _paramsStyle = new GUIStyle(GUI.skin.label);
                _paramsStyle.fontStyle = FontStyle.Bold;
                _paramsStyle.fontSize = 12;
                _paramsStyle.normal.textColor = Color.green;
                _paramInfoStyle = new GUIStyle(GUI.skin.label);
                _paramInfoStyle.richText = true;
             }
         }
    }
    


    This is how it will look like:

    image

    Now we need an editor of the parameters themselves and their values, for this we use a custom window.
    publicclassSettingEditorWindow : EditorWindow
    {
        public Setting SelectedAsset;
        privateint _currentSelectedAsset = -1;
        privatereadonly List<string> _assetNames = new List<string>();
        privatereadonly IList<SerializedObject> _settingSerializationObjects = new List<SerializedObject>();
        privatereadonly IList<T> _assets = new List<T>();
        privatereadonly IList<int> _editedNames = new List<int>();;
        private GUIContent _editNameIconContent;
        private GUIStyle _headerStyle;
        private GUIStyle _parametersStyle;
        private GUIStyle _parameterHeaderStyle;
        private GUIStyle _nameStyle;
        private Vector2 _scrollInspectorPosition = Vector2.zero;
        private Vector2 _scrollAssetsPosition = Vector2.zero;
        privateconststring _SELECTED_ASSET_STR = "SettingSelected";
        publicstaticvoidShow(Setting asset)
        {
            var instance = GetWindow<Setting>(true);
            instance.title = new GUIContent("Settings Editor", string.Empty);
            instance.SelectedAsset = asset;
        }
        privatevoidOnEnable()
        {
            var assetGuids = AssetDatabase.FindAssets("t:{0}".Fmt(typeof(Setting).Name));
            foreach (var guid in assetGuids)
            {
                var path = AssetDatabase.GUIDToAssetPath(guid);
                var asset = AssetDatabase.LoadAssetAtPath<T>(path);
                _assetNames.Add(path.Replace("Assets/", "").Replace(".asset", ""));
                _assets.Add(asset);
                _settingSerializationObjects.Add(new SerializedObject(asset));
            }
            _currentSelectedAsset = PlayerPrefs.GetInt(_SELECTED_ASSET_STR, -1);
            _editNameIconContent = new GUIContent(EditorGUIUtility.IconContent("editicon.sml"));
        }
        privatevoidOnDisable()
        {
            PlayerPrefs.SetInt(_SELECTED_ASSET_STR, _currentSelectedAsset);
        }
        privatevoidPrepareGUIStyle()
        {
            if (_headerStyle == null)
            {
                _headerStyle = new GUIStyle(GUI.skin.box);
                _headerStyle.fontStyle = FontStyle.Bold;
                _headerStyle.fontSize = 14;
                _headerStyle.normal.textColor = Color.white;
                _headerStyle.alignment = TextAnchor.MiddleCenter;
                _parametersStyle = new GUIStyle(GUI.skin.label);
                _parametersStyle.fontStyle = FontStyle.Bold;
                _parametersStyle.fontSize = 12;
                _parametersStyle.normal.textColor = Color.green;
            }
        }
        privatevoidOnGUI()
        {
            PrepareGUIStyle();
            if (SelectedAsset != null)
            {
                _currentSelectedAsset = _assets.IndexOf(SelectedAsset);
                SelectedAsset = null;
            }
            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.BeginVertical(GUI.skin.box, GUILayout.MinWidth(350f), GUILayout.ExpandHeight(true));
            _scrollAssetsPosition = EditorGUILayout.BeginScrollView(_scrollAssetsPosition, GUIStyle.none, GUI.skin.verticalScrollbar);
            _currentSelectedAsset = GUILayout.SelectionGrid(_currentSelectedAsset, _assetNames.ToArray(), 1);
            EditorGUILayout.EndScrollView();
            EditorGUILayout.EndVertical();
            EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
            var assetSerializedObject = (_currentSelectedAsset >= 0) ? _settingSerializationObjects[_currentSelectedAsset] : null;
            EditorGUILayout.Space();
            EditorGUILayout.LabelField((_currentSelectedAsset >= 0) ? _assetNames[_currentSelectedAsset] : "Select Asset...", _headerStyle, GUILayout.ExpandWidth(true));
            EditorGUILayout.Space();
            _scrollInspectorPosition = EditorGUILayout.BeginScrollView(_scrollInspectorPosition, GUIStyle.none, GUI.skin.verticalScrollbar);
            Draw(assetSerializedObject);
            EditorGUILayout.EndScrollView();
            EditorGUILayout.EndVertical();
            EditorGUILayout.EndHorizontal();
            assetSerializedObject?.ApplyModifiedProperties();
        }
        privatevoidDraw(SerializedObject assetSerializationObject)
        {
            if (assetSerializationObject == null) return;
            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Parameters:", _parametersStyle, GUILayout.Width(20f), GUILayout.ExpandWidth(true));
            var parametersProperty = assetSerializationObject.FindProperty("Parameters");
            if (GUILayout.Button("Add", GUILayout.MaxWidth(40f)))
            {
                if (parametersProperty != null)
                {
                    parametersProperty.InsertArrayElementAtIndex(parametersProperty.arraySize);
                }
            }
            EditorGUILayout.EndHorizontal();
            EditorGUILayout.Space();
            if (parametersProperty != null)
            {
                for (var i = 0; i < parametersProperty.arraySize; i++)
                {
                    var parameterProperty = parametersProperty.GetArrayElementAtIndex(i);
                    var parameterNameProperty = parameterProperty.FindPropertyRelative("_name");
                    var parameterTypeProperty = parameterProperty.FindPropertyRelative("_parameterType");
                    var parameterDefaultValueProperty = parameterProperty.FindPropertyRelative("_defaultValue");
                    EditorGUILayout.BeginHorizontal();
                    if (GUILayout.Button(_editNameIconContent, GUILayout.MaxWidth(25f), GUILayout.MaxHeight(18f)))
                    {
                        if (_editedNames.Contains(i))
                        {
                            _editedNames.Remove(i);
                        }
                        else
                        {
                            _editedNames.Add(i);
                        }
                    }
                    EditorGUILayout.LabelField("Name", _parameterHeaderStyle, GUILayout.MaxWidth(40f));
                    if (_editedNames.Contains(i))
                    {
                        parameterNameProperty.stringValue = EditorGUILayout.TextField(parameterNameProperty.stringValue, GUILayout.Width(175f));
                        var ev = Event.current;
                        if (ev.type == EventType.MouseDown || ev.type == EventType.Ignore || (ev.type == EventType.KeyDown && ev.keyCode == KeyCode.Return))
                        {
                            _editedNames.Remove(i);
                        }
                    }
                    else
                    {
                        EditorGUILayout.LabelField(parameterNameProperty.stringValue, _nameStyle, GUILayout.Width(175f));
                    }
                    EditorGUILayout.LabelField("Type", _parameterHeaderStyle, GUILayout.MaxWidth(40f));
                    parameterTypeProperty.enumValueIndex = EditorGUILayout.Popup(parameterTypeProperty.enumValueIndex,
                                                                                 parameterTypeProperty.enumDisplayNames,
                                                                                 GUILayout.Width(75f));
                    GUILayout.Space(20f);
                    EditorGUILayout.LabelField("DefaultValue", _parameterHeaderStyle, GUILayout.Width(85f));
                    switch (parameterTypeProperty.enumValueIndex)
                        {
                            case0:
                                {                                
                                    if (!float.TryParse(parameterDefaultValueProperty.stringValue, outfloatvalue))
                                    {
                                        value = default;
                                    }
                                    value = EditorGUILayout.FloatField(value, GUILayout.ExpandWidth(true));
                                    parameterDefaultValueProperty.stringValue = value.ToString();
                                }
                                break;
                            case1:
                                {
                                    if (!int.TryParse(parameterDefaultValueProperty.stringValue, outintvalue))
                                    {
                                        value = default;
                                    }
                                    value = EditorGUILayout.IntField(value, GUILayout.ExpandWidth(true));
                                    parameterDefaultValueProperty.stringValue = value.ToString();
                                }
                                break;
                            case2:
                                parameterDefaultValueProperty.stringValue = EditorGUILayout.TextField(parameterDefaultValueProperty.stringValue, GUILayout.ExpandWidth(true));
                                break;
                            case3:
                                {                                
                                    if (!bool.TryParse(parameterDefaultValueProperty.stringValue, outboolvalue))
                                    {
                                        value = default;
                                    }
                                    value = EditorGUILayout.Toggle(value, GUILayout.ExpandWidth(true));
                                    parameterDefaultValueProperty.stringValue = value.ToString();
                                }
                                break;
                        }
                    if (GUILayout.Button("-", GUILayout.MaxWidth(25f), GUILayout.MaxHeight(18f)))
                    {
                        if (_editedNames.Contains(i))
                        {
                            _editedNames.Remove(i);
                        }
                        parametersProperty.DeleteArrayElementAtIndex(i);
                    }
                    EditorGUILayout.EndHorizontal();
                }
            }
        }
    }
    


    I will not explain the code much, as a whole, everything is simple. I will only note that the editor allows you to edit by choice all assets of type Setting . To do this, when you open the window, we find them in the project using the AssetDatabase.FindAssets method ("t: {0}". Fmt (typeof (Setting). Name)) . As well as editing the name of the parameter is done through the button in order to prevent its accidental change.

    This is what the editor looks like:

    image

    We have considered the settings used inside the application, now we will consider a more specific case.

    External settings


    Imagine a situation that in an already running game, we suddenly needed to change certain values ​​in order to adjust the gameplay. In a primitive version, we change this in the build, accumulate such changes, do an update and send it to stores, then wait for confirmation, etc. But how about those who do not update the application? And what if changes need to be made urgently? To solve this problem, there is a mechanism such as Remote Settings . This is not a new invention and is used in many third-party SDKs for analytics, etc., for example, this is in Firebase , in GameAnalytics , and also in Unity Analytics . It is the last we will use.

    Note :In general, there is no difference between all these systems, they are similar and use the same principles.

    Let us dwell in more detail on what Remote Settings is in Unity Analytics and what it can do.

    In order for this functionality to be available in the project, you need to enable analytics in the project on the Services tab .

    image

    After that, you need to log into your Unity3d account and find your project there and follow the link to the analytics section, where in the menu on the left we select the item Remote Settings .

    image

    All settings are divided into those that are used in development mode and those that will be used in the already released application.

    image

    To add a parameter, select the corresponding item and enter the name, type and value of the parameter.

    image

    After we have added all the necessary parameters, we need support in the code to work with them.

    Note : The Sync button synchronizes the settings with the application. This process does not happen instantly, but at the moment when the parameters in the application are updated, the corresponding events will be triggered, we will talk about them later .

    To work with Remote Settings do not require any additional SDK, it is enough to enable analytics, as I wrote above.

    Let's write a class, for working with remote settings, for this we use the Setting class described above as the base.
    publicsealedclassRemoteSetting : Setting
    {     
        public IList<string> GetUpdatedParameter()
        {
            var updatedParameters = new List<string>();
            for (var i = 0; i < Parameters.Length; i++)
            {
                var parameter = Parameters[i];
                switch (parameter.ParameterType)
                {
                    case ParameterTypeEnum.Float:
                        {
                            var currentValue = Get<float>(parameter.Name);
                            var newValue = RemoteSettings.GetFloat(parameter.Name, currentValue);
                            if (currentValue != newValue)
                            {
                                settingParameters[parameter.Name] = newValue;
                                updatedParameters.Add(parameter.Name);
                            }
                        }
                        break;
                    case ParameterTypeEnum.Int:
                        {
                            var currentValue = Get<int>(parameter.Name);
                            var newValue = RemoteSettings.GetInt(parameter.Name, currentValue);
                            if (currentValue != newValue)
                            {
                                settingParameters[parameter.Name] = newValue;
                                updatedParameters.Add(parameter.Name);
                            }
                        }
                        break;
                    case ParameterTypeEnum.String:
                        {
                            var currentValue = Get<string>(parameter.Name);
                            var newValue = RemoteSettings.GetString(parameter.Name, currentValue);
                            if (string.Compare(currentValue, newValue, System.StringComparison.Ordinal) != 0)
                            {
                                settingParameters[parameter.Name] = newValue;
                                updatedParameters.Add(parameter.Name);
                            }
                        }
                        break;
                    case ParameterTypeEnum.Bool:
                        {
                            var currentValue = Get<bool>(parameter.Name);
                            var newValue = RemoteSettings.GetBool(parameter.Name, currentValue);
                            if (currentValue != newValue)
                            {
                                settingParameters[parameter.Name] = newValue;
                                updatedParameters.Add(parameter.Name);
                            }
                        }
                        break;
                }
            }
            return updatedParameters;
        }
        protectedoverrideobject GetValue<T>(string paramName, T defaultValue)
        {
            switch(defaultValue)
            {
                casefloat f:
                    return RemoteSettings.GetFloat(paramName, f);                
                caseint i:
                    return RemoteSettings.GetInt(paramName, i);                
                casestring s:
                    return RemoteSettings.GetString(paramName, s);                
                casebool b:
                    return RemoteSettings.GetBool(paramName, b);
                default:
                    returndefault;
            }        
        }
    }
    


    As you can see, we redefined the GetValue method and added a new method that allows us to get a list of changed parameters, we will need it later.

    Above, we wrote an example of using Setting in code, it is quite simple, but does not take into account the presence of remote settings, so to unify access to all settings in a single key, we will write a manager who will help with this.

    Settings Manager Code
    publicclassSettingsManager : MonoBehaviourSingleton<SettingsManager>
    {
        public Setting this[string index] => GetSetting(index);
        [SerializeField] private Setting[] _settings;
        privatereadonly IDictionary<string, Setting> _settingsByName = new Dictionary<string, Setting>();
        publicvoidForceUpdate()
        {
            RemoteSettings.ForceUpdate();
        }
        privatevoidStart()
        {
            foreach(var setting in _settings)
            {
                _settingsByName.Add(setting.name, setting);
            }
            RemoteSettings.BeforeFetchFromServer += OnRemoteSettingBeforeUpdate;
            RemoteSettings.Updated += OnRemoteSettingsUpdated;
            RemoteSettings.Completed += OnRemoteSettingCompleted;
        }
        private Setting GetSetting(string name)
        {
            if(_settingsByName.ContainsKey(name))
            {
                return _settingsByName[name];
            }else
            {
                Debug.LogWarningFormat("[SettingManager]: setting name [{0}] not found", name);
                returnnull;
            }
        }
        privatevoidOnRemoteSettingBeforeUpdate()
        {
            RemoteSettingBeforeUpdate.Call();
        }
        privatevoidOnRemoteSettingsUpdated()
        {
            foreach (var setting in _settingsByName.Values)
            {
                if (setting is RemoteSetting)
                {
                    var updatedParameter = remoteSetting.GetUpdatedParameter();
                    foreach (var parameterName in updatedParameter)
                    {
                        RemoteSettingUpdated.Call(parameterName);
                    }
                }
            }
        }
        privatevoidOnRemoteSettingCompleted(bool wasUpdatedFromServer, bool settingsChanged, int serverResponse)
        {
            RemoteSettingsCompleted.Call(wasUpdatedFromServer, settingsChanged, serverResponse);        
        }
        privatevoidOnDestroy()
        {
     RemoteSettings.BeforeFetchFromServer -= OnRemoteSettingBeforeUpdate;
            RemoteSettings.Updated -= OnRemoteSettingsUpdated;
            RemoteSettings.Completed -= OnRemoteSettingCompleted;
        }
    } 
    


    The manager is represented as a siglet, which lives only in the scene. This is done for ease of reference to it and in order to be able to easily manage the set of parameters in each scene (exclude parameters that are not required by logic).

    As you can see, RemoteSettings has three events:

    1. An event raised before parameter values ​​are retrieved from a remote server.
    2. Parameter update event (triggered just by the Sync button, which we wrote about earlier), as well as in the case of forced update of parameters via the ForceUpdate function
    3. Event triggered when remote settings are retrieved from the server. It also gives the server response code in case of any error.

    Note : the code uses an event system built on data types, more details are written about it in my other article .

    Note : you need to understand how RemoteSettings works. At the start, if you have access to the Internet, it automatically downloads data about the parameters and caches them, so the next time you start, if the Internet is not available, the data will be taken from the cache. An exception is the situation when the application is initially started with network access turned off, in this case, the function of getting the parameter value will return the default value. In our case, these are the ones that we enter in the editor.

    Now let's change the example of using the settings from the code taking into account the above.

    publicclassMyLogic : MonoBehaviour
    {
        privateconststring INGAME_PARAMETERS = "IngamgeParameters";
        privateconststring REMOTE_RAPAMETERS = "RemoteParamteters";
        privatestring _localStrValue;
        privateint _localIntValue;
        privatefloat _localFloatValue;
        privatebool _localBoolValue;
        privatestring _remoteStrValue;
        privateint _remoteIntValue;
        privatefloat _remoteFloatValue;
        privatebool _remoteBoolValue;
        privatevoidStart()
        {        
            var ingameParametes = SettingsManager.Instance[INGAME_PARAMETERS];
            var remoteParametes = SettingsManager.Instance[REMOTE_RAPAMETERS];
            _localStrValue = ingameParametes.GetParameterValue<string>("MyStr");
            _localIntValue = ingameParametes.GetParameterValue<int>("MyInt");
            _localFloatValue = ingameParametes.GetParameterValue<float>("MyFloat");
            _localBoolValue = ingameParametes.GetParameterValue<bool>("MyBool");
            _remoteStrValue = remoteParametes.GetParameterValue<string>("MyStr");
            _remoteIntValue = remoteParametes.GetParameterValue<int>("MyInt");
            _remoteFloatValue = remoteParametes.GetParameterValue<float>("MyFloat");
            _remoteBoolValue = remoteParametes.GetParameterValue<bool>("MyBool");
        }
    }
    

    As you can see, from the code, there is no difference in the work between the internal settings and the external ones; however, if necessary, if the logic requires it, you can subscribe to manager events related to remote settings.

    Note : if you only need remote settings, then you can download a special plugin from AssetStore , it allows you to work with them immediately.

    Conclusion


    In this article I tried to show how you can easily configure an application written in Unity3d using both internal settings and remote ones. I use a similar approach in my projects, and it proves its effectiveness. We even managed, using remote settings, to implement our A / B testing system . In addition, the settings are commonly used to store various constants associated with the SDK, with server things, as well as with setting up the gameplay, etc. A game designer can pre-create a set of parameters and describe how, and for what, and where they are used, while he can customize the gameplay without blocking the scene. And due to the fact that we used ScriptableObject and store such parameters as assets, they can be loaded viaAssetBundle , which further expands our capabilities.

    Links specified in the article :

    habr.com/en/post/282524
    assetstore.unity.com/packages/add-ons/services/analytics/unity-analytics-remote-settings-89317

    Also popular now: