Solo acerca de la configuración interna y externa de la aplicación en Unity3D

Introduccion


Hola queridos lectores, en el artículo de hoy me gustaría hablar sobre la configuración y la configuración de las aplicaciones de juegos creadas en el entorno Unity3d.

Por tradición, comenzaré con el fondo. Mientras trabajaba en la industria del juego, desarrollé varios proyectos con diversa complejidad y visité tanto el campamento de diseño de juegos como el campamento de programadores (donde estoy hasta el día de hoy). No es ningún secreto que cualquier aplicación requiere una gran cantidad de datos y configuraciones diferentes. En la forma clásica, relativamente Unity3d, tales configuraciones se colocan en la parte visible del inspector, donde se ingresan algunos números, etc. Creo que no vale la pena hablar de la conveniencia de este enfoque, incluso si se excluye, el hecho de que, en el momento del ajuste, la escena en la que se encuentra la clase MonoBehaviour está bloqueada por otros desarrolladores. Por lo tanto, después de una serie de varios tipos de pruebas en el pasado, decidí escribir algo simple y efectivo que facilitará la vida de todos y simplificará el trabajo con esos datos, que quiero compartir con ustedes.

Nota : todo el código descrito a continuación es aplicable a la versión Unity 2018.3+ y utiliza el compilador Roslyn (versión en lenguaje C # 7+).

Ajustes internos


Para comenzar, considere la configuración interna del proyecto, que incluye varias constantes, enlaces, identificadores de SDK externos, claves, etc. cosas que incluyen configuraciones de juego globales y locales. En general, todos estos datos se pueden dividir en cuatro tipos:

  • Cadena
  • Int
  • Flotador
  • Bool

Todos los demás datos pueden almacenarse fácilmente en ellos, y teniendo en cuenta la cadena, puede almacenar cualquier cosa utilizando la serialización JSON. Utilizaremos ScriptableObject como base, lo cual es adecuado para resolver esta tarea como nadie más.

public class Setting : ScriptableObject { public enum ParameterTypeEnum { Float, Int, String, Bool } [Serializable] public class ParameterData { public string Name => _name; public ParameterTypeEnum ParameterType => _parameterType; public string DefaultValue => _defaultValue; [SerializeField] private string _name; [SerializeField] private ParameterTypeEnum _parameterType; [SerializeField] private string _defaultValue; } [SerializeField] protected ParameterData[] Parameters; } 

Entonces, en la base de datos, tenemos una matriz de valores que son:

  • Nombre del parámetro
  • Tipo de parámetro
  • Valores de parámetros como una cadena

Nota : ¿por qué líneas? Me pareció más conveniente que almacenar 4 variables de diferentes tipos.

Para usar en el código, agregamos métodos auxiliares y un diccionario que almacenará los valores convertidos en un cuadro.
 protected readonly IDictionary<string, object> settingParameters = new Dictionary<string, object>(); [NonSerialized] protected bool initialized; private void OnEnable() { #if UNITY_EDITOR if (EditorApplication.isPlayingOrWillChangePlaymode) { Initialization(); } #else Initialization(); #endif } public virtual T GetParameterValue<T>(string name) { if (settingParameters.ContainsKey(name)) { var parameterValue = (T)settingParameters[name]; return parameterValue; } else { Debug.Log("[Setting]: name not found [{0}]".Fmt(name)); } return default; } protected virtual void Initialization() { 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; case ParameterTypeEnum.Int: { if (!int.TryParse(parameter.DefaultValue, out int value)) { value = default; } parameterValue = GetValue(parameter.Name, value); } break; case ParameterTypeEnum.String: { parameterValue = GetValue(parameter.Name, parameter.DefaultValue); } break; case ParameterTypeEnum.Bool: { if (!bool.TryParse(parameter.DefaultValue, out bool value)) { value = default; } parameterValue = GetValue(parameter.Name, value); } break; } settingParameters.Add(parameter.Name, parameterValue); } initialized = true; } protected virtual object GetValue<T>(string paramName, T defaultValue) { return defaultValue; } 


La inicialización se realiza en OnEnable . ¿Por qué no en Despertar ? Este método no se llama para instancias almacenadas como activos (se llama en el momento de CreateInstance , que no necesitamos). Cuando se inicia la aplicación para activos , primero se llama al ScriptableObject , OnDisable (solo en el editor), luego OnEnable . Además, para que en el editor durante cada recompilación y apertura de la inicialización del proyecto no funcione, debe agregar directivas de precompilación e insertar al principio del archivo:
 #if UNITY_EDITOR using UnityEditor; #endif 

Necesitaremos más el método GetValue , y para configuraciones internas, simplemente devuelve el valor predeterminado.

El método GetParameterValue es nuestro método principal para acceder a los parámetros. Vale la pena considerar que a pesar del desempaquetado de los valores, los parámetros almacenados en Configuración son de alguna manera constantes, por lo que deben tomarse al inicializar las escenas. No llame al método en Actualización .

Ejemplo de uso:

 public class MyLogic : MonoBehaviour { [SerializeField] private Setting _localSetting; private string _localStrValue; private int _localIntValue; private float _localFloatValue; private bool _localBoolValue; private void Start() { _localStrValue = _localSetting.GetParameterValue<string>("MyStr"); _localIntValue = _localSetting.GetParameterValue<int>("MyInt"); _localFloatValue = _localSetting.GetParameterValue<float>("MyFloat"); _localBoolValue = _localSetting.GetParameterValue<bool>("MyBool"); } } 

Escribimos la fundación y ahora necesitamos un editor, ya que el objetivo principal para nosotros era solo la conveniencia para aquellos que trabajan con esta configuración.

Para agregar un elemento de menú para poder crear un activo, puede usar el atributo:

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

Ahora escribiremos un inspector personalizado que le permitirá mostrar datos sobre el activo e iniciar un editor externo.
 [CustomEditor(typeof(Setting), true)] public class SettingCustomInspector : Editor { private GUIStyle _paramsStyle; private GUIStyle _paramInfoStyle; private const string _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>"; public override void OnInspectorGUI() { 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(); } } private void PrepareGUIStyle() { 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; } } } 


Así es como se verá:

imagen

Ahora necesitamos un editor de los propios parámetros y sus valores, para esto usamos una ventana personalizada.
 public class SettingEditorWindow : EditorWindow { public Setting SelectedAsset; private int _currentSelectedAsset = -1; private readonly List<string> _assetNames = new List<string>(); private readonly IList<SerializedObject> _settingSerializationObjects = new List<SerializedObject>(); private readonly IList<T> _assets = new List<T>(); private readonly 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; private const string _SELECTED_ASSET_STR = "SettingSelected"; public static void Show(Setting asset) { var instance = GetWindow<Setting>(true); instance.title = new GUIContent("Settings Editor", string.Empty); instance.SelectedAsset = asset; } private void OnEnable() { 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")); } private void OnDisable() { PlayerPrefs.SetInt(_SELECTED_ASSET_STR, _currentSelectedAsset); } private void PrepareGUIStyle() { 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; } } private void OnGUI() { 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(); } private void Draw(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) { case 0: { if (!float.TryParse(parameterDefaultValueProperty.stringValue, out float value)) { value = default; } value = EditorGUILayout.FloatField(value, GUILayout.ExpandWidth(true)); parameterDefaultValueProperty.stringValue = value.ToString(); } break; case 1: { if (!int.TryParse(parameterDefaultValueProperty.stringValue, out int value)) { value = default; } value = EditorGUILayout.IntField(value, GUILayout.ExpandWidth(true)); parameterDefaultValueProperty.stringValue = value.ToString(); } break; case 2: parameterDefaultValueProperty.stringValue = EditorGUILayout.TextField(parameterDefaultValueProperty.stringValue, GUILayout.ExpandWidth(true)); break; case 3: { if (!bool.TryParse(parameterDefaultValueProperty.stringValue, out bool value)) { 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(); } } } } 


No explicaré mucho el código, todo es simple aquí. Solo noto que el editor le permite editar opcionalmente todos los activos de tipo Configuración . Para hacer esto, al abrir la ventana, los encontramos en el proyecto utilizando el método AssetDatabase.FindAssets ("t: {0}". Fmt (typeof (Setting) .Name)) . Y también se edita el nombre del parámetro a través del botón para excluir su cambio accidental.

Así es como se ve el editor:

imagen

Examinamos la configuración utilizada dentro de la aplicación, ahora consideraremos un caso más específico.

Configuraciones externas


Imagine una situación en la que, en un juego que ya se está ejecutando, de repente necesitamos cambiar ciertos valores para ajustar la jugabilidad. En la versión primitiva, cambiamos esto en la compilación, acumulamos dichos cambios, hacemos una actualización y la enviamos a las tiendas, luego de lo cual esperamos la confirmación, etc. ¿Pero qué hay de aquellos que no actualizan la aplicación? ¿Y si los cambios deben hacerse con urgencia? Para resolver este problema, existe un mecanismo como la configuración remota . Este no es un invento nuevo y se usa en muchos SDK de terceros para análisis, etc., por ejemplo, está en Firebase , en GameAnalytics , así como en Unity Analytics . Es lo último que usaremos.

Nota : en general, no hay diferencia entre todos estos sistemas, son similares y usan los mismos principios.

Analicemos qué es la configuración remota en Unity Analytics y qué puede hacer.

Para que esta funcionalidad esté disponible en el proyecto, debe habilitar el análisis en el proyecto en la pestaña Servicios .

imagen

Después de eso, debe iniciar sesión en su cuenta de Unity3d y encontrar su proyecto allí y seguir el enlace a la sección de análisis, donde a la izquierda en el menú seleccione Configuración remota .

imagen

Todas las configuraciones se dividen en las que se usan en el modo de desarrollo y las que se usarán en una aplicación ya lanzada.

imagen

Para agregar un parámetro, seleccione el elemento apropiado e ingrese el nombre, tipo y valor del parámetro.

imagen

Después de haber agregado todos los parámetros necesarios, necesitamos soporte en el código para trabajar con ellos.

Nota : El botón Sincronizar sincroniza la configuración con la aplicación. Este proceso no ocurre instantáneamente, sin embargo, en el momento en que se actualizan los parámetros de la aplicación, se activarán los eventos correspondientes, hablaremos de ellos más adelante .

Para trabajar con la configuración remota, no necesita ningún SDK adicional, solo active el análisis, como escribí anteriormente.

Escribiremos una clase para trabajar con configuraciones remotas, para esto usamos la clase de configuración descrita anteriormente como base.
 public sealed class RemoteSetting : 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; } protected override object GetValue<T>(string paramName, T defaultValue) { switch(defaultValue) { case float f: return RemoteSettings.GetFloat(paramName, f); case int i: return RemoteSettings.GetInt(paramName, i); case string s: return RemoteSettings.GetString(paramName, s); case bool b: return RemoteSettings.GetBool(paramName, b); default: return default; } } } 


Como puede ver, redefinimos el método GetValue y agregamos un nuevo método que le permite obtener una lista de parámetros modificados, lo necesitaremos más adelante.

Arriba, escribimos un ejemplo de uso de Configuración en el código, es bastante simple, pero no tiene en cuenta la presencia de configuraciones remotas, por lo tanto, para unificar el acceso a todas las configuraciones en una sola clave, escribiremos un administrador que ayudará en esto.

Código de administrador de configuración
 public class SettingsManager : MonoBehaviourSingleton<SettingsManager> { public Setting this[string index] => GetSetting(index); [SerializeField] private Setting[] _settings; private readonly IDictionary<string, Setting> _settingsByName = new Dictionary<string, Setting>(); public void ForceUpdate() { RemoteSettings.ForceUpdate(); } private void Start() { 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); return null; } } private void OnRemoteSettingBeforeUpdate() { RemoteSettingBeforeUpdate.Call(); } private void OnRemoteSettingsUpdated() { foreach (var setting in _settingsByName.Values) { if (setting is RemoteSetting) { var updatedParameter = remoteSetting.GetUpdatedParameter(); foreach (var parameterName in updatedParameter) { RemoteSettingUpdated.Call(parameterName); } } } } private void OnRemoteSettingCompleted(bool wasUpdatedFromServer, bool settingsChanged, int serverResponse) { RemoteSettingsCompleted.Call(wasUpdatedFromServer, settingsChanged, serverResponse); } private void OnDestroy() { RemoteSettings.BeforeFetchFromServer -= OnRemoteSettingBeforeUpdate; RemoteSettings.Updated -= OnRemoteSettingsUpdated; RemoteSettings.Completed -= OnRemoteSettingCompleted; } } 


El gerente se presenta en forma de un esqueleto, que vive solo en la escena. Esto se hizo para facilitar la referencia y para administrar fácilmente un conjunto de parámetros en cada escena (excluya los parámetros que no son requeridos por la lógica).

Como puede ver, RemoteSettings tiene tres eventos:

  1. Evento generado antes de que los valores de los parámetros se reciban del servidor remoto
  2. Evento de actualización de parámetros (llamado solo por el botón Sincronizar, sobre el que escribimos anteriormente), así como en el caso de actualización forzada de parámetros a través de la función ForceUpdate
  3. Evento activado cuando se reciben datos sobre la configuración remota del servidor. El código de respuesta del servidor también se emite aquí en caso de que ocurra algún error.

Nota : el código usa un sistema de eventos basado en tipos de datos, más sobre esto está escrito en otro artículo mío .

Nota : debe comprender cómo funciona RemoteSettings. Al principio, si hay acceso a Internet, descarga automáticamente datos sobre los parámetros y los almacena en caché, por lo que la próxima vez que inicie, si no hay Internet, los datos se tomarán del caché. La excepción es la situación cuando la aplicación se inicia inicialmente con el acceso a la red desactivado, en este caso, las funciones para obtener el valor del parámetro devolverán el valor predeterminado. En nuestro caso, estos son los que ingresamos en el editor.

Ahora cambiemos el ejemplo del uso de la configuración del código, teniendo en cuenta lo anterior.

 public class MyLogic : MonoBehaviour { private const string INGAME_PARAMETERS = "IngamgeParameters"; private const string REMOTE_RAPAMETERS = "RemoteParamteters"; private string _localStrValue; private int _localIntValue; private float _localFloatValue; private bool _localBoolValue; private string _remoteStrValue; private int _remoteIntValue; private float _remoteFloatValue; private bool _remoteBoolValue; private void Start() { 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"); } } 

Como puede ver, desde el código, no hay diferencia en el trabajo entre la configuración interna y externa, sin embargo, si es necesario, si la lógica lo requiere, puede suscribirse a eventos del administrador relacionados con la configuración remota.

Nota : si solo necesita parámetros remotos, puede descargar un complemento especial de AssetStore , que le permite trabajar con ellos de inmediato.

Conclusión


En este artículo, traté de mostrar cómo puede simplemente configurar una aplicación escrita en Unity3d utilizando configuraciones internas y remotas. Utilizo un enfoque similar en mis proyectos, y demuestra su efectividad. Incluso logramos usar nuestra configuración remota para implementar nuestro sistema de prueba A / B. Además, la configuración se usa ampliamente para almacenar varias constantes relacionadas con el SDK, los elementos del servidor, así como la configuración del juego, etc. El diseñador del juego puede crear un conjunto de parámetros por adelantado y describir cómo, para qué y dónde se usan, mientras que puede personalizar el juego sin bloquear la escena. Y debido al hecho de que usamos ScriptableObject y almacenamos parámetros como activos, se pueden cargar a través de AssetBundle , lo que amplía aún más nuestras capacidades.

Enlaces especificados en el artículo :

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

Source: https://habr.com/ru/post/es436634/


All Articles