فقط حول الإعدادات الداخلية والخارجية للتطبيق في Unity3D

مقدمة


مرحبًا أيها القراء الأعزاء ، أود أن أتحدث في مقالة اليوم عن إعدادات وتكوين تطبيقات الألعاب التي تم إنشاؤها في بيئة Unity3d.

حسب التقاليد ، سأبدأ بالخلفية. أثناء العمل في صناعة الألعاب ، قمت بتطوير العديد من المشاريع بدرجات متفاوتة من التعقيد وقمت بزيارة معسكر تصميم الألعاب ومعسكر المبرمجين (حيث أنا حتى هذا اليوم). ليس سراً أن أي تطبيق يتطلب عددًا كبيرًا من بيانات التكوين والإعدادات المختلفة. في النموذج الكلاسيكي ، نسبيًا Unity3d ، يتم وضع هذه الإعدادات في الجزء المرئي من المفتش ، حيث يتم إدخال بعض الأرقام ، إلخ. أعتقد أنه لا يستحق الحديث عن مدى ملاءمة هذا النهج ، إنه حتى إذا تم استبعاده ، فإن المشهد الذي توجد فيه فئة MonoBehaviour محجوب للتغييرات من قِبل مطورين آخرين ، في وقت توليفه . لذلك ، بعد سلسلة من أنواع المحن المختلفة في الماضي ، قررت أن أكتب شيئًا بسيطًا وفعالًا من شأنه أن يجعل الحياة أسهل للجميع ويبسط العمل مع هذه البيانات التي أريد مشاركتها معك.

ملاحظة : تنطبق جميع الشفرة الموضحة أدناه على Unity الإصدار 2018.3+ وتستخدم مترجم Roslyn (إصدار لغة C # 7+).

الإعدادات الداخلية


للبدء ، فكر في الإعدادات الداخلية للمشروع ، والتي تشمل ثوابت متعددة وروابط ومعرفات SDKs خارجية ومفاتيح وما إلى ذلك. أشياء بما في ذلك إعدادات اللعب العالمية والمحلية. بشكل عام ، يمكن تقسيم كل هذه البيانات إلى أربعة أنواع:

  • سلسلة
  • كثافة العمليات
  • تعويم
  • منطقي

يمكن تخزين جميع البيانات الأخرى بسهولة ، ومع مراعاة السلسلة ، يمكنك تخزين أي شيء باستخدام تسلسل JSON. سوف نستخدم ScriptableObject كأساس ، وهو مناسب لحل هذه المهمة مثل أي شخص آخر.

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

لذلك ، في قاعدة البيانات ، لدينا مجموعة من القيم التي هي:

  • اسم المعلمة
  • نوع المعلمة
  • قيم المعلمة كسلسلة

ملاحظة : لماذا خطوط؟ بدا لي أكثر ملاءمة من تخزين 4 متغيرات من أنواع مختلفة.

للاستخدام في التعليمات البرمجية ، نضيف طرقًا مساعدة وقاموسًا يخزن القيم المحولة في نموذج محاصر.
 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; } 


تتم التهيئة في OnEnable . لماذا لا في مستيقظا ؟ لا يتم استدعاء هذه الطريقة للمثيلات المخزنة كأصول (تسمى في وقت CreateInstance ، والتي لا نحتاج إليها). عند بدء تشغيل تطبيق الأصول ، يتم استدعاء ScriptableObject أولاً ، OnDisable (في المحرر فقط) ، ثم OnEnable . وأيضًا ، حتى في المحرر أثناء كل عملية إعادة تجميع وفتح عملية تهيئة المشروع لا تعمل ، تحتاج إلى إضافة توجيهات precompilation ، وإدراجها في بداية الملف:
 #if UNITY_EDITOR using UnityEditor; #endif 

سنحتاج إلى طريقة GetValue أكثر من ذلك ، وبالنسبة للإعدادات الداخلية ، تقوم ببساطة بإرجاع القيمة الافتراضية.

طريقة GetParameterValue هي طريقتنا الرئيسية للوصول إلى المعلمات. تجدر الإشارة إلى أنه على الرغم من إلغاء وضع القيم في القيم ، فإن المعلمات المخزنة في الإعداد هي في بعض الأحيان ثوابت ، لذلك يجب أخذها عند تهيئة المشاهد. لا تستدعي الطريقة في التحديث .

مثال للاستخدام:

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

لقد كتبنا الأساس والآن نحن بحاجة إلى محرر ، لأن الهدف الرئيسي بالنسبة لنا كان مجرد الراحة لأولئك الذين يعملون مع هذه الإعدادات.

لإضافة عنصر قائمة حتى تتمكن من إنشاء مادة عرض ، يمكنك استخدام السمة:

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

سنقوم الآن بكتابة مفتش مخصص يتيح لك عرض البيانات على الأصل وإطلاق محرر خارجي.
 [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; } } } 


هكذا سيبدو:

الصورة

نحتاج الآن إلى محرر للمعلمات نفسها وقيمها ، لذلك نستخدم نافذة مخصصة.
 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(); } } } } 


لن أشرح الكود كثيرًا ، كل شيء بسيط هنا. ألاحظ فقط أن المحرر يسمح لك بتحرير جميع أصول نوع الإعداد اختيارياً. للقيام بذلك ، عند فتح النافذة ، نجدهم في المشروع باستخدام طريقة AssetDatabase.FindAssets ("t: {0}". Fmt (typeof (Setting) .Name)) . ويتم أيضًا تحرير اسم المعلمة من خلال الزر لاستبعاد التغيير العرضي.

هذا ما يشبه المحرر:

الصورة

لقد فحصنا الإعدادات المستخدمة داخل التطبيق ، والآن سننظر في حالة أكثر تحديدًا.

الإعدادات الخارجية


تخيل موقفًا في لعبة قيد التشغيل بالفعل ، نحتاج فجأة إلى تغيير قيم معينة لضبط طريقة اللعب. في الإصدار البدائي ، نقوم بتغيير هذا في البنية ، ونجمع هذه التغييرات ، ونحدث تحديثًا ونرسله إلى المتاجر ، وبعد ذلك ننتظر التأكيد ، إلخ. ولكن ماذا عن أولئك الذين لا يقومون بتحديث التطبيق؟ وماذا لو كانت التغييرات بحاجة إلى إجراء عاجل؟ لحل هذه المشكلة ، هناك آلية مثل الإعدادات عن بعد . هذا ليس اختراعًا جديدًا ويستخدم في العديد من أدوات تطوير البرامج (SDK) الخاصة بجهات خارجية للتحليلات ، وما إلى ذلك ، على سبيل المثال - في Firebase و GameAnalytics وكذلك في Unity Analytics . هذا هو الأخير الذي سوف نستخدمه.

ملاحظة : بشكل عام ، لا يوجد فرق بين جميع هذه الأنظمة ، فهي متشابهة وتستخدم نفس المبادئ.

دعنا نتحدث عن ماهية الإعدادات عن بعد في Unity Analytics وما الذي يمكن أن تفعله.

لكي تصبح هذه الوظيفة متوفرة في المشروع ، تحتاج إلى تمكين التحليلات في المشروع في علامة التبويب " الخدمات" .

الصورة

بعد ذلك ، تحتاج إلى تسجيل الدخول إلى حساب Unity3d الخاص بك والعثور على مشروعك هناك واتبع الرابط إلى قسم التحليلات ، حيث على اليسار في القائمة حدد الإعدادات عن بُعد .

الصورة

تنقسم جميع الإعدادات إلى تلك المستخدمة في وضع التطوير وتلك التي سيتم استخدامها في تطبيق تم إصداره بالفعل.

الصورة

لإضافة معلمة ، حدد العنصر المناسب وأدخل اسم المعلمة ونوعها وقيمتها.

الصورة

بعد أن قمنا بإضافة جميع المعلمات اللازمة ، نحتاج إلى دعم في الكود للعمل معهم.

ملاحظة : يقوم الزر Sync بمزامنة الإعدادات مع التطبيق. لا تحدث هذه العملية على الفور ، ولكن في الوقت الذي يتم فيه تحديث المعلمات في التطبيق ، سيتم تشغيل الأحداث المقابلة ، وسوف نتحدث عنها لاحقًا .

للعمل مع "الإعدادات عن بُعد" ، لا تحتاج إلى أي أدوات SDK إضافية ، ما عليك سوى تشغيل التحليلات ، كما كتبت أعلاه.

سنكتب فصلًا للعمل مع الإعدادات عن بُعد ، ولهذا نستخدم فئة الإعداد الموضحة أعلاه كقاعدة.
 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; } } } 


كما ترى ، قمنا بإعادة تعريف طريقة GetValue وأضفنا طريقة جديدة تسمح لك بالحصول على قائمة بالمعلمات التي تم تغييرها ، وسوف نحتاج إليها لاحقًا.

أعلاه ، لقد كتبنا مثالًا على استخدام الإعداد في الكود ، إنه بسيط للغاية ، لكنه لا يأخذ في الاعتبار وجود الإعدادات عن بُعد ، لذلك ، لتوحيد الوصول إلى جميع الإعدادات في مفتاح واحد ، سنكتب مديرًا سيساعد في ذلك.

رمز مدير الإعدادات
 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; } } 


يتم تقديم المدير في شكل sigleton الذي يعيش فقط في مكان الحادث. تم ذلك لسهولة الرجوع إليه وبغية إدارة مجموعة من المعلمات في كل مشهد بسهولة (استبعد المعلمات غير المطلوبة بالمنطق).

كما ترون ، يحتوي RemoteSettings على ثلاثة أحداث:

  1. حدث مرفوع قبل استلام قيم المعلمات من الخادم البعيد
  2. حدث تحديث المعلمة (يطلق عليه فقط الزر Sync ، والذي كتبنا عنه سابقًا) ، وكذلك في حالة التحديث القسري للمعلمات من خلال وظيفة ForceUpdate
  3. حدث يتم تشغيله عند تلقي بيانات حول الإعدادات عن بُعد من الخادم. يتم أيضًا إصدار رمز استجابة الخادم هنا في حالة حدوث أي خطأ.

ملاحظة : يستخدم الكود نظام أحداث مبني على أنواع البيانات ، ومزيد من المعلومات عنه مكتوب في مقال آخر خاص بي .

ملاحظة : أنت بحاجة إلى فهم كيفية عمل RemoteSettings. في البداية ، إذا كان هناك وصول إلى الإنترنت ، فإنه يقوم تلقائيًا بتنزيل البيانات حول المعلمات وتخزينها مؤقتًا ، وبالتالي في المرة التالية التي تبدأ فيها ، إذا لم يكن هناك إنترنت ، فسيتم أخذ البيانات من ذاكرة التخزين المؤقت. الاستثناء هو الموقف عندما يتم تشغيل التطبيق في البداية مع إيقاف تشغيل الوصول إلى الشبكة ، وفي هذه الحالة ، فإن وظائف الحصول على قيمة المعلمة ستعيد القيمة الافتراضية. في حالتنا ، هذه هي تلك التي ندخلها في المحرر.

الآن دعنا نغير مثال استخدام الإعدادات من الكود ، مع مراعاة ما سبق.

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

كما ترون ، من خلال الكود ، لا يوجد فرق في العمل بين الإعدادات الداخلية والخارجية ، ولكن إذا لزم الأمر ، إذا كان المنطق يتطلب ذلك ، يمكنك الاشتراك في أحداث المدير المتعلقة بالإعدادات عن بُعد.

ملاحظة : إذا كنت تحتاج إلى معلمات عن بُعد فقط ، فيمكنك تنزيل مكون إضافي خاص من AssetStore ، مما يتيح لك العمل معهم على الفور.

الخاتمة


في هذه المقالة ، حاولت أن أوضح كيف يمكنك ببساطة تكوين تطبيق مكتوب بلغة Unity3d باستخدام كل من الإعدادات الداخلية والإعدادات البعيدة. أستخدم نهجًا مشابهًا في مشاريعي ، وهو يثبت فعاليته. لقد نجحنا في استخدام إعداداتنا عن بُعد لتطبيق نظام اختبار A / B الخاص بنا . بالإضافة إلى ذلك ، يتم استخدام الإعدادات على نطاق واسع لتخزين ثوابت متعددة متعلقة بـ SDK ، وأشياء الخادم ، فضلاً عن إعدادات اللعب ، إلخ. يمكن لمصمم اللعبة إنشاء مجموعة من المعلمات مقدمًا ووصف كيف ولماذا وأين يتم استخدامها ، بينما يمكنه تخصيص طريقة اللعب دون حظر المشهد. ونظرًا لحقيقة أننا استخدمنا ScriptableObject وتخزين معلمات مثل الأصول ، يمكن تحميلها من خلال AssetBundle ، مما يوسع من قدراتنا.

الروابط المحددة في المقال :

habr.com/en/post/282524
اصول / خدمة / حزم / مواد / خدمات / تحليلات / تحليلات - إعدادات - 89317

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


All Articles