Fast interne und externe Einstellungen für die Anwendung in Unity3D

Einführung


Hallo liebe Leser, im heutigen Artikel möchte ich über die Einstellungen und die Konfiguration von Spieleanwendungen sprechen, die in der Unity3d-Umgebung erstellt wurden.

Traditionell beginne ich mit dem Hintergrund. Während meiner Arbeit in der Spielebranche entwickelte ich verschiedene Projekte mit unterschiedlicher Komplexität und besuchte sowohl das Game Design Camp als auch das Programmiercamp (wo ich bis heute bin). Es ist kein Geheimnis, dass eine Anwendung eine große Anzahl unterschiedlicher Konfigurationsdaten und Einstellungen erfordert. In der klassischen, relativ Unity3d-Form werden solche Einstellungen im sichtbaren Teil des Inspektors platziert, wo einige Zahlen usw. eingegeben werden. Ich denke, dass es sich nicht lohnt, über die Bequemlichkeit dieses Ansatzes zu sprechen, auch wenn dies ausgeschlossen ist, die Tatsache, dass zum Zeitpunkt der Optimierung die Szene, in der sich die MonoBehaviour- Klasse befindet, für Änderungen durch andere Entwickler blockiert ist. Nach einer Reihe verschiedener Prüfungen in der Vergangenheit habe ich mich daher entschlossen, etwas Einfaches und Effektives zu schreiben, das allen das Leben erleichtert und die Arbeit mit solchen Daten vereinfacht, die ich mit Ihnen teilen möchte.

Hinweis : Der gesamte unten beschriebene Code gilt für Unity Version 2018.3+ und verwendet den Roslyn-Compiler (Sprachversion C # 7+).

Interne Einstellungen


Berücksichtigen Sie zunächst die internen Einstellungen des Projekts, zu denen verschiedene Konstanten, Links, Kennungen externer SDKs, Schlüssel usw. gehören. Dinge wie globale und lokale Gameplay-Einstellungen. Im Allgemeinen können alle diese Daten in vier Typen unterteilt werden:

  • String
  • Int
  • Float
  • Bool

Alle anderen Daten können problemlos in ihnen gespeichert werden. Unter Berücksichtigung der Zeichenfolge können Sie mithilfe der JSON-Serialisierung alles speichern. Wir werden ScriptableObject als Basis verwenden, das wie kein anderer zur Lösung dieser Aufgabe geeignet ist.

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; } 

In der Datenbank gibt es also eine Reihe von Werten:

  • Parametername
  • Parametertyp
  • Parameterwerte als Zeichenfolge

Hinweis : Warum Linien? Es schien mir bequemer, als 4 Variablen verschiedener Typen zu speichern.

Zur Verwendung im Code fügen wir Hilfsmethoden und ein Wörterbuch hinzu, in dem die konvertierten Werte in einer Boxform gespeichert werden.
 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; } 


Die Initialisierung erfolgt in OnEnable . Warum nicht bei Awake ? Diese Methode wird nicht für Instanzen aufgerufen, die als Assets gespeichert sind (sie wird zum Zeitpunkt von CreateInstance aufgerufen , was wir nicht benötigen). Wenn die Anwendung für Assets gestartet wird, wird das ScriptableObject zuerst OnDisable (nur im Editor) und dann OnEnable aufgerufen . Damit der Editor bei jeder Neukompilierung und beim Öffnen der Projektinitialisierung nicht funktioniert, müssen Sie Vorkompilierungsanweisungen hinzufügen und am Anfang der Datei einfügen:
 #if UNITY_EDITOR using UnityEditor; #endif 

Wir werden die GetValue- Methode weiter benötigen und für interne Einstellungen einfach den Standardwert zurückgeben.

Die GetParameterValue- Methode ist unsere Hauptmethode für den Zugriff auf Parameter. Es ist zu berücksichtigen, dass die in Setting gespeicherten Parameter trotz des Auspackens der Werte in gewisser Weise Konstanten sind. Sie sollten daher beim Initialisieren der Szenen berücksichtigt werden. Rufen Sie die Methode nicht in Update auf .

Anwendungsbeispiel:

 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"); } } 

Wir haben die Stiftung geschrieben und brauchen jetzt einen Editor, da das Hauptziel für uns nur die Bequemlichkeit für diejenigen war, die mit diesen Einstellungen arbeiten.

Um ein Menüelement hinzuzufügen, um ein Asset erstellen zu können, können Sie das folgende Attribut verwenden:

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

Jetzt schreiben wir einen benutzerdefinierten Inspektor, mit dem Sie Daten zum Asset anzeigen und einen externen Editor starten können.
 [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; } } } 


So wird es aussehen:

Bild

Jetzt brauchen wir einen Editor der Parameter selbst und ihrer Werte, dafür verwenden wir ein benutzerdefiniertes Fenster.
 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(); } } } } 


Ich werde den Code nicht viel erklären, hier ist alles einfach. Ich stelle nur fest, dass Sie mit dem Editor optional alle Assets vom Typ Einstellung bearbeiten können. Zu diesem Zweck finden wir sie beim Öffnen des Fensters im Projekt mithilfe der AssetDatabase.FindAssets- Methode ("t: {0}". Fmt (typeof (Setting) .Name)) . Die Bearbeitung des Parameternamens erfolgt ebenfalls über die Schaltfläche, um eine versehentliche Änderung auszuschließen.

So sieht der Editor aus:

Bild

Wir haben die in der Anwendung verwendeten Einstellungen untersucht und werden nun einen spezifischeren Fall betrachten.

Externe Einstellungen


Stellen Sie sich eine Situation vor, in der wir in einem bereits laufenden Spiel plötzlich bestimmte Werte ändern mussten, um das Gameplay anzupassen. In der primitiven Version ändern wir dies im Build, sammeln solche Änderungen, nehmen ein Update vor und senden es an die Filialen. Danach warten wir auf die Bestätigung usw. Aber wie wäre es mit denen, die die Anwendung nicht aktualisieren? Und was ist, wenn dringend Änderungen vorgenommen werden müssen? Um dieses Problem zu lösen, gibt es einen Mechanismus wie Remote-Einstellungen . Dies ist keine neue Erfindung und wird beispielsweise in vielen SDKs von Drittanbietern für Analysen usw. verwendet - in Firebase , GameAnalytics sowie in Unity Analytics . Letzteres werden wir verwenden.

Hinweis : Im Allgemeinen gibt es keinen Unterschied zwischen all diesen Systemen. Sie sind ähnlich und verwenden dieselben Prinzipien.

Lassen Sie uns näher darauf eingehen, was Remote-Einstellungen in Unity Analytics sind und was sie bewirken können.

Damit diese Funktionalität im Projekt verfügbar wird, müssen Sie die Analyse im Projekt auf der Registerkarte Dienste aktivieren.

Bild

Danach müssen Sie sich bei Ihrem Unity3d-Konto anmelden und dort Ihr Projekt finden und dem Link zum Analysebereich folgen, wo Sie links im Menü Remote-Einstellungen auswählen.

Bild

Alle Einstellungen sind in diejenigen unterteilt, die im Entwicklungsmodus verwendet werden, und diejenigen, die in einer bereits veröffentlichten Anwendung verwendet werden.

Bild

Um einen Parameter hinzuzufügen, wählen Sie das entsprechende Element aus und geben Sie den Namen, den Typ und den Wert des Parameters ein.

Bild

Nachdem wir alle erforderlichen Parameter hinzugefügt haben, benötigen wir Unterstützung im Code, um mit ihnen arbeiten zu können.

Hinweis : Die Schaltfläche Synchronisieren synchronisiert die Einstellungen mit der Anwendung. Dieser Vorgang findet nicht sofort statt. In dem Moment, in dem die Parameter in der Anwendung aktualisiert werden, werden entsprechende Ereignisse ausgelöst, über die wir später sprechen werden .

Um mit Remote-Einstellungen zu arbeiten, benötigen Sie keine zusätzlichen SDKs. Aktivieren Sie einfach die Analyse, wie oben beschrieben.

Wir werden eine Klasse für die Arbeit mit Remote-Einstellungen schreiben. Dazu verwenden wir die oben beschriebene Setting-Klasse als Basis.
 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; } } } 


Wie Sie sehen, haben wir die GetValue- Methode neu definiert und eine neue Methode hinzugefügt, mit der Sie eine Liste geänderter Parameter abrufen können. Wir werden sie später benötigen.

Oben haben wir ein Beispiel für die Verwendung von Einstellungen in Code geschrieben. Es ist recht einfach, berücksichtigt jedoch nicht das Vorhandensein von Remote-Einstellungen. Um den Zugriff auf alle Einstellungen in einem einzigen Schlüssel zu vereinheitlichen, schreiben wir einen Manager, der Ihnen dabei hilft.

Einstellungen Manager Code
 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; } } 


Der Manager wird in Form eines Sigleton dargestellt, der nur in der Szene lebt. Dies wurde durchgeführt, um die Referenz zu vereinfachen und um eine Reihe von Parametern in jeder Szene einfach verwalten zu können (schließen Sie Parameter aus, die von der Logik nicht benötigt werden).

Wie Sie sehen können, hat RemoteSettings drei Ereignisse:

  1. Ereignis, das ausgelöst wird, bevor Parameterwerte vom Remote-Server empfangen werden
  2. Parameteraktualisierungsereignis (wird nur über die Schaltfläche "Synchronisieren" aufgerufen, über die wir zuvor geschrieben haben) sowie im Fall einer erzwungenen Aktualisierung von Parametern über die ForceUpdate-Funktion
  3. Ereignis ausgelöst, wenn Daten zu Remote-Einstellungen vom Server empfangen werden. Hier wird auch der Server-Antwortcode ausgegeben, falls ein Fehler auftritt.

Hinweis : Der Code verwendet ein Ereignissystem, das auf Datentypen basiert. Weitere Informationen hierzu finden Sie in einem anderen Artikel von mir .

Hinweis : Sie müssen verstehen, wie RemoteSettings funktioniert. Wenn zu Beginn ein Internetzugang vorhanden ist, werden automatisch Daten zu den Parametern heruntergeladen und zwischengespeichert. Wenn Sie das nächste Mal starten und kein Internet vorhanden ist, werden die Daten aus dem Cache entnommen. Die Ausnahme ist die Situation, in der die Anwendung zum ersten Mal mit deaktiviertem Zugriff auf das Netzwerk gestartet wird. In diesem Fall geben die Funktionen zum Abrufen des Parameterwerts den Standardwert zurück. In unserem Fall sind dies diejenigen, die wir in den Editor eingeben.

Lassen Sie uns nun das Beispiel für die Verwendung von Einstellungen aus dem Code unter Berücksichtigung der obigen Punkte ändern.

 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"); } } 

Wie Sie dem Code entnehmen können, gibt es keinen Unterschied in der Arbeit zwischen internen und externen Einstellungen. Wenn es die Logik erfordert, können Sie jedoch bei Bedarf Managerereignisse abonnieren, die sich auf Remoteeinstellungen beziehen.

Hinweis : Wenn Sie nur Remote-Parameter benötigen, können Sie ein spezielles Plug-In aus AssetStore herunterladen , mit dem Sie sofort arbeiten können.

Fazit


In diesem Artikel habe ich versucht zu zeigen, wie Sie eine in Unity3d geschriebene Anwendung einfach mit internen und Remote-Einstellungen konfigurieren können. Ich verwende in meinen Projekten einen ähnlichen Ansatz, der seine Wirksamkeit unter Beweis stellt. Wir haben es sogar geschafft, unsere Remote-Einstellungen zur Implementierung unseres A / B-Testsystems zu verwenden . Darüber hinaus werden die Einstellungen häufig zum Speichern verschiedener Konstanten verwendet, die sich auf das SDK, Server-Dinge sowie Gameplay-Einstellungen usw. beziehen. Der Spieledesigner kann im Voraus eine Reihe von Parametern erstellen und beschreiben, wie, wofür und wo sie verwendet werden, während er das Gameplay anpassen kann, ohne die Szene zu blockieren. Aufgrund der Tatsache, dass wir ScriptableObject verwendet und Parameter wie Assets gespeichert haben , können diese über AssetBundle geladen werden, wodurch unsere Funktionen weiter erweitert werden.

Im Artikel angegebene Links :

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

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


All Articles