يوضّح هذا المستند كيفية نقل الألعاب الحالية من الإصدار v1 من حزمة SDK للألعاب إلى الإصدار v2 من حزمة SDK للألعاب. يستخدم الإصداران 10 والإصدارات الأقدم من مكوّن ألعاب Play الإضافي لمحرّك الألعاب Unity الإصدار v1 من حزمة SDK للألعاب.
قبل البدء
- تأكَّد من أنّك أعددت Play Console وثبّت Unity Editor.
تنزيل مكوّن ألعاب Google Play الإضافي لمحرّك الألعاب Unity
للاستفادة من أحدث الميزات في "خدمات ألعاب Play"، نزِّل أحدث إصدار من المكوّن الإضافي وثبِّته. يمكنك تنزيله من مستودع gitHub repository.
إزالة المكوّن الإضافي القديم
في Unity Editor، أزِل المجلدات أو الملفات التالية.
Assets/GooglePlayGames Assets/GeneratedLocalRepo/GooglePlayGames Assets/Plugins/Android/GooglePlayGamesManifest.androidlib Assets/Plugins/Android
استيراد المكوّن الإضافي الجديد إلى مشروع Unity
لاستيراد المكوّن الإضافي إلى مشروع Unity، اتّبِع الخطوات التالية:
- افتح مشروع لعبتك.
- في Unity Editor، انقر على مواد العرض (Assets) > استيراد حزمة (Import Package) > حزمة مخصّصة (Custom Package)
لاستيراد الملف الذي تم تنزيله
unitypackageإلى مواد عرض مشروعك. تأكَّد من أنّ منصة الإصدار الحالية مضبوطة على Android.
في القائمة الرئيسية، انقر على ملف (File) > إعدادات الإصدار (Build Settings).
اختَر Android وانقر على تبديل المنصة (Switch Platform).
من المفترض أن يظهر عنصر قائمة جديد ضمن نافذة (Window) > ألعاب Google Play (Google Play Games). إذا لم يظهر، أعِد تحميل مواد العرض بالنقر على مواد العرض (Assets) > إعادة تحميل (Refresh) ، ثم حاوِل ضبط منصة الإصدار مرة أخرى.
في Unity Editor، انقر على ملف (File) > إعدادات الإصدار (Build Settings) > إعدادات المشغّل (Player Settings) > إعدادات أخرى (Other Settings).
في مربّع مستوى واجهة برمجة التطبيقات المستهدَف ، اختَر إصدارًا.
في مربّع الواجهة الخلفية للبرمجة النصية ، أدخِل
IL2CPP.في مربّع البنيات المستهدَفة ، اختَر قيمة.
دوِّن اسم الحزمة package_name.يمكنك استخدام هذه المعلومات لاحقًا.
إعدادات المشغّل في مشروع Unity
مسارات نقل البيانات
يعتمد مسار نقل البيانات الصحيح للعبتك على كيفية تنفيذها للإصدار v1 من "خدمات ألعاب Play" ومعالجتها لهوية اللاعب. لضمان عملية نقل سلسة ومنع فقدان بيانات اللاعب، حدِّد السيناريو الذي يطابق إعدادك الحالي على أفضل وجه واتّبِع الخطوات المقابلة.
الخيار 1: للألعاب التي يكون فيها حساب اللاعب داخل اللعبة (IGA) مرتبطًا بمعرّف اللاعب في "خدمات ألعاب Play"
ينطبق هذا السيناريو على الألعاب التي استخدمت Player ID في "خدمات ألعاب Play" كمعرّف وحيد
لحساب اللاعب داخل اللعبة (IGA) ولم تطلب أو تخزِّن OpenID من قبل. التحدي الرئيسي هو ربط حساب اللاعب الحالي داخل اللعبة (IGA) بمعرّف أساسي (OpenID) بدون فقدان الاتصال بمستوى تقدّم اللاعب.
يتضمّن مسار نقل البيانات الخطوات التالية:
- عند تشغيل اللعبة، تصادق حزمة تطوير البرامج (SDK) للإصدار v2 من "خدمات ألعاب Play" على المنصة تلقائيًا وبدون أي إجراء من المستخدم.
تعرض اللعبة شاشة تسجيل الدخول. يجب أن تتضمّن هذه الشاشة زر تسجيل الدخول باستخدام حساب Google (SiWG) ، بدلاً من زر Google Play. لدمج الميزة:
نزِّل CredManBridge.java إلى مجلدك. يعمل صف Java هذا كجسر بين Unity ومكتبة
androidx.credentials.CredManBridge.java
package com.wickedcube.trivialkart; import android.accounts.Account; import android.content.Context; import android.util.Log; import android.os.CancellationSignal; import androidx.credentials.CredentialManager; import androidx.credentials.GetCredentialRequest; import androidx.credentials.GetCredentialResponse; import androidx.credentials.exceptions.GetCredentialException; import androidx.credentials.exceptions.NoCredentialException; import com.google.android.libraries.identity.googleid.GetGoogleIdOption; import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential; import com.google.android.gms.auth.api.identity.AuthorizationClient; import com.google.android.gms.auth.api.identity.AuthorizationRequest; import com.google.android.gms.auth.api.identity.AuthorizationResult; import com.google.android.gms.common.api.ApiException; import com.google.android.gms.auth.api.identity.Identity; import com.google.android.gms.common.api.Scope; import com.unity3d.player.UnityPlayer; import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.Executors;public class CredManBridge {
// --- MODE 1: SILENT SIGN-IN (Called on Awake) --- // Tries to auto-select an authorized account. If it fails, it does NOT show UI. public static void signInSilent(Context context, String webClientId) { CredentialManager credentialManager = CredentialManager.create(context); CancellationSignal cancellationSignal = new CancellationSignal(); Executor executor = Executors.newSingleThreadExecutor();
Log.d("CredMan", "Attempting Silent Sign-In...");
GetGoogleIdOption silentOption = new GetGoogleIdOption.Builder() .setFilterByAuthorizedAccounts(true) // Strict: Only authorized accounts .setServerClientId(webClientId) .setAutoSelectEnabled(true) // Auto-select if possible .build();
GetCredentialRequest silentRequest = new GetCredentialRequest.Builder() .addCredentialOption(silentOption) .build();
credentialManager.getCredentialAsync( context, silentRequest, cancellationSignal, executor, new androidx.credentials.CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() { @Override public void onResult(GetCredentialResponse result) { Log.d("CredMan", "Silent Sign-In Successful!"); handleSignInResult(context, result, webClientId); }
@Override public void onError(GetCredentialException e) { // Send a specific error code so Unity knows to just stay on the Start Screen Log.d("CredMan", "Silent sign-in failed. Keeping UI hidden."); UnityPlayer.UnitySendMessage("AuthManager", "OnSignInError", "SilentFailed"); } }); }
// --- MODE 2: INTERACTIVE SIGN-IN (Called on Button Click) --- // Forces the Account Selection / "Add Account" sheet to appear. public static void signInInteractive(Context context, String webClientId) { CredentialManager credentialManager = CredentialManager.create(context); CancellationSignal cancellationSignal = new CancellationSignal(); Executor executor = Executors.newSingleThreadExecutor();
Log.d("CredMan", "Starting Interactive Sign-In...");
GetGoogleIdOption interactiveOption = new GetGoogleIdOption.Builder() .setFilterByAuthorizedAccounts(false) // Show ALL accounts (and "Add Account") .setServerClientId(webClientId) .setAutoSelectEnabled(false) // Force the UI to show .build();
GetCredentialRequest interactiveRequest = new GetCredentialRequest.Builder() .addCredentialOption(interactiveOption) .build();
credentialManager.getCredentialAsync( context, interactiveRequest, cancellationSignal, executor, new androidx.credentials.CredentialManagerCallback<getcredentialresponse, getcredentialexception="">() { @Override public void onResult(GetCredentialResponse result) { Log.d("CredMan", "Interactive Sign-In Successful!"); handleSignInResult(context, result, webClientId); }</getcredentialresponse,>
@Override public void onError(GetCredentialException e) { Log.e("CredMan", "Interactive Sign-In Canceled or Failed", e); UnityPlayer.UnitySendMessage("AuthManager", "OnSignInError", "Canceled"); } }); }
private static void handleSignInResult(Context context, GetCredentialResponse result, String webClientId) { try { GoogleIdTokenCredential credential = GoogleIdTokenCredential.createFrom(result.getCredential().getData()); String email = credential.getId();
Account account = new Account(email, "com.google"); // Requesting GAMES_LITE scope to check for pre-existing V1 grants List<Scope> requestedScopes = Collections.singletonList(new Scope("https://www.googleapis.com/auth/games_lite")); AuthorizationRequest authRequest = new AuthorizationRequest.Builder() .setRequestedScopes(requestedScopes) .setAccount(account) .requestOfflineAccess(webClientId) .build(); AuthorizationClient authClient = Identity.getAuthorizationClient(context); authClient.authorize(authRequest) .addOnSuccessListener(authorizationResult -> { if (authorizationResult.getServerAuthCode() != null) { // CASE 1: RETURNING USER (Success) // The user has already granted GAMES_LITE in the past. // We got the code directly without showing UI. Log.i("CredMan", "PGS v1: Existing grant found. Returning user detected. Auth Code retrieved."); UnityPlayer.UnitySendMessage("AuthManager", "OnSignInSuccess", authorizationResult.getServerAuthCode()); } else if (authorizationResult.hasResolution()) { // CASE 2: NEW USER (PendingIntent) // The user has NOT granted GAMES_LITE before. The API returned a PendingIntent // (authorizationResult.getPendingIntent()) to show the consent screen. // As per your flow, we DISCARD this intent and do not show UI. Log.i("CredMan", "PGS v1: No existing grant (PendingIntent returned). This is a NEW user or they revoked access."); Log.i("CredMan", "PGS v1: Discarding PendingIntent. Proceeding as New User."); // Notify Unity that this is a "New User" so it can trigger V2 logic instead of failing UnityPlayer.UnitySendMessage("AuthManager", "OnSignInError", "NewUser_NoGrant"); } else { // Edge Case: No code and no resolution? Log.e("CredMan", "PGS v1: Authorization success but no Auth Code or Resolution returned."); UnityPlayer.UnitySendMessage("AuthManager", "OnSignInError", "No Auth Code returned"); } }) .addOnFailureListener(e -> { // CASE 3: GENERIC FAILURE Log.e("CredMan", "PGS v1: Authorization failed completely.", e); UnityPlayer.UnitySendMessage("AuthManager", "OnSignInError", "Authorization Failed: " + e.getMessage()); });} catch (Exception e) { UnityPlayer.UnitySendMessage("AuthManager", "OnSignInError", "Parsing Error: " + e.getMessage()); } } }
دمج "مدير بيانات الاعتماد":
- استخدِم
GetGoogleIdOptionمعsetFilterByAuthorizedAccounts(true)لعمليات تسجيل الدخول بدون أي إجراء من المستخدم لتسجيل دخول المستخدمين الذين سبق لهم منح الإذن للتطبيق فقط. - استخدِم
setFilterByAuthorizedAccounts(false)لعمليات تسجيل الدخول التفاعلية للسماح للمستخدمين باختيار حساب أو إضافة حساب جديد.
- استخدِم
طلب النطاق:
- بعد الحصول على بيانات اعتماد Google الأساسية، يتم إنشاء
AuthorizationRequestيطلب النطاق القديم المحدد: https://www.googleapis.com/auth/games_lite. - هذا النطاق مهم لأنه يمنح الخادم إذن البحث عن `معرّف اللاعب` القديم للمستخدم.
- بعد الحصول على بيانات اعتماد Google الأساسية، يتم إنشاء
معالجة النتائج:
- إذا منح المستخدم الإذن (أو سبق له منحه)، يعرض الجسر
ServerAuthCodeعلى Unity. - إذا لم يمنح المستخدم الإذن (سيناريو المستخدم الجديد)، تعرض واجهة برمجة التطبيقات
PendingIntent. في هذا المثال، يتم تجاهل الغرض، ويتم التعامل مع المستخدم كمستخدم جديد لتبسيط المسار.
- إذا منح المستخدم الإذن (أو سبق له منحه)، يعرض الجسر
لدعم Credential Manager وGoogle Identity services، تأكَّد من إضافة التبعيات التالية إلى إعدادات Gradle في
mainTemplate.gradle.dependencies { // Standard Unity dependencies implementation fileTree(dir: 'libs', include: ['*.jar']) // Credential Manager and Identity Libraries implementation 'androidx.credentials:credentials:1.3.0' implementation 'androidx.credentials:credentials-play-services-auth:1.3.0' implementation 'com.google.android.libraries.identity.googleid:googleid:1.1.1' // Play Services Auth for legacy scope handling implementation 'com.google.android.gms:play-services-auth:21.2.0' }
- مدير بيانات الاعتماد: يعالج التنسيق الأساسي للهوية وواجهة المستخدم لاختيار الحساب.
- مكتبة GoogleID: توفّر على وجه التحديد
GetGoogleIdOptionلـ استرداد رموزOpenIDConnect المميزة. - المصادقة في "خدمات Play": مطلوبة للحفاظ على التوافق وطلب
النطاق
GAMES_LITEلاستردادPlayer IDالقديم.
عندما ينقر اللاعب على زر "تسجيل الدخول باستخدام حساب Google" (SiWG) ويختار حسابًا على Google، يجب أن تسترد اللعبة معرّفَين مميّزَين:
OpenID، وهو المعرّف الأساسي لربط حساب اللاعب داخل اللعبة (IGA).Player IDفي "خدمات ألعاب Play"، الذي يتم استرداده باستخدام النطاقGAMES_LITE، للبحث عن حساب اللاعب داخل اللعبة (IGA) في نظامك الخلفي وإجراء عملية الربط.
في عمليات تشغيل اللعبة اللاحقة، يمكن للاعبين الوصول إلى حساباتهم داخل اللعبة (IGA) من خلال مسار "تسجيل الدخول باستخدام حساب Google" (SiWG)، بدون أن تضطر الألعاب إلى استخدام
Player IDكمعرّف أساسي.
يمكنك تنفيذ الخطوة 4 باستخدام عملية تنفيذ من جهة العميل للعبة.
- يستدعي المطوّر واجهة برمجة التطبيقات Android Credential Manager لتسجيل دخول المستخدم باستخدام حساب Google.
- بعد أن يكمل المستخدم عملية "تسجيل الدخول باستخدام حساب Google" (SiWG) ويختار حسابًا على Google، يتلقّى المطوّر عنصر نتيجة يحتوي على رمز التعريف، وعنوان البريد الإلكتروني.
- ينشئ المطوّر عنصر حساب من عنوان البريد الإلكتروني.
- يستدعي المطوّر واجهة برمجة التطبيقات Authorization API باستخدام النطاق
GAMES_LITEوالحساب. - إذا كان الحساب يتضمّن إذنًا حاليًا على النطاق
GAMES_LITE، تعرض واجهة برمجة التطبيقات Authorization API رمزًا مميزًا مباشرةً في عنصر الاستجابة.- استخدِم الرمز المميّز للاستجابة لاستدعاء خوادم "خدمات ألعاب Play" واسترداد "خدمات ألعاب Play"
Player ID. - يتأكّد المطوّر مما إذا كان
Player IDفي "خدمات ألعاب Play" مرتبطًا بحساب داخل اللعبة.- يعرف المطوّر أنّ هذا المستخدم المكرر الزيارة يعود إلى اللعبة من الإصدار v1 من "خدمات ألعاب Play".
- يمكن للمطوّر ربط معرّف gaia الجديد بالحساب السابق في الإصدار v1 من "خدمات ألعاب Play".
- استخدِم الرمز المميّز للاستجابة لاستدعاء خوادم "خدمات ألعاب Play" واسترداد "خدمات ألعاب Play"
- أو إذا لم يكن الحساب يتضمّن إذنًا حاليًا على النطاق
GAMES_LITE، تعرض واجهة برمجة التطبيقات Authorization API عنصر PendingIntent.- يعرف المطوّر أنّ المستخدم ليس لديه حساب حالي من الإصدار v1 من "خدمات ألعاب Play".
- يمكن للمطوّر تجاهل PendingIntent بأمان بدون عرض أي واجهة مستخدم.
الخيار 2: للألعاب التي تربط حساب اللاعب داخل اللعبة (IGA) بـ OpenID حاليًا
لدى المطوّرين في هذه المجموعة مسار نقل البيانات الأكثر وضوحًا. إذا كان حساب اللاعب داخل اللعبة مرتبطًا بشكل أساسي بـ OpenID، ما عليك سوى إجراء عملية نقل بيانات حزمة SDK الفنية العادية من الإصدار v1 إلى الإصدار v2 كما هو موضّح في الخطوات.
تعديل رمز تسجيل الدخول التلقائي
استبدِل فئة تهيئة PlayGamesClientConfiguration بفئة PlayGamesPlatform.Instance.Authenticate().
لا يلزم تهيئة وتفعيل
PlayGamesPlatform. يؤدي استدعاء PlayGamesPlatform.Instance.Authenticate() إلى جلب نتيجة تسجيل الدخول التلقائي.
لمزيد من المعلومات حول مسار المصادقة المقترَح مع عملية دمج الإصدار v2 من "خدمات ألعاب Play"
، اطّلِع على إرشادات تجربة المستخدم لمسار المصادقة المثالي.
#C
في Unity Editor، حدِّد موقع الملفات التي تحتوي على فئة PlayGamesClientConfiguration.
using GooglePlayGames;
using GooglePlayGames.BasicApi;
using UnityEngine.SocialPlatforms;
public void Start() {
PlayGamesClientConfiguration config =
new PlayGamesClientConfiguration.Builder()
// Enables saving game progress
.EnableSavedGames()
// Requests the email address of the player be available
// will bring up a prompt for consent
.RequestEmail()
// Requests a server auth code be generated so it can be passed to an
// associated backend server application and exchanged for an OAuth token
.RequestServerAuthCode(false)
// Requests an ID token be generated. This OAuth token can be used to
// identify the player to other services such as Firebase.
.RequestIdToken()
.Build();
PlayGamesPlatform.InitializeInstance(config);
// recommended for debugging:
PlayGamesPlatform.DebugLogEnabled = true;
// Activate the Google Play Games platform
PlayGamesPlatform.Activate();
}
وعِّدِلها إلى ما يلي:
using GooglePlayGames;
public void Start() {
PlayGamesPlatform.Instance.Authenticate(ProcessAuthentication);
}
internal void ProcessAuthentication(SignInStatus status) {
if (status == SignInStatus.Success) {
// Continue with Play Games Services
} else {
// Disable your integration with Play Games Services or show a login
// button to ask users to sign-in. Clicking it should call
// PlayGamesPlatform.Instance.ManuallyAuthenticate(ProcessAuthentication).
}
}
اختيار منصة تواصل اجتماعي
لاختيار منصة تواصل اجتماعي، اطّلِع على اختيار منصة تواصل اجتماعي.
استرداد رموز مصادقة الخادم
للحصول على رموز الوصول من جانب الخادم، اطّلِع على استرداد رموز مصادقة الخادم.
إزالة رمز تسجيل الخروج
أزِل رمز تسجيل الخروج. لم تعُد "خدمات ألعاب Play" تتطلب زر تسجيل خروج داخل اللعبة.
أزِل الرمز الموضّح في المثال التالي:
#C
// sign out
PlayGamesPlatform.Instance.SignOut();
اختبار لعبتك
تأكَّد من أنّ لعبتك تعمل على النحو المطلوب من خلال اختبارها. تعتمد الاختبارات التي تجريها على ميزات لعبتك.
في ما يلي قائمة بالاختبارات الشائعة التي يجب إجراؤها.
تسجيل الدخول بنجاح :
تعمل ميزة "تسجيل الدخول التلقائي". يجب أن يتم تسجيل دخول المستخدم إلى "خدمات ألعاب Play" عند تشغيل اللعبة.
يتم عرض النافذة المنبثقة الترحيبية.
مثال على نافذة منبثقة ترحيبية (انقر للتكبير) يتم عرض رسائل السجلّ التي تشير إلى النجاح. نفِّذ الأمر التالي في الوحدة الطرفية:
adb logcat | grep com.google.android.
في ما يلي مثال على رسالة سجلّ تشير إلى النجاح:
[
$PlaylogGamesSignInAction$SignInPerformerSource@e1cdecc number=1 name=GAMES_SERVICE_BROKER>], returning true for shouldShowWelcomePopup. [CONTEXT service_id=1 ]
ضمان اتّساق مكوّنات واجهة المستخدم :
تعرض النوافذ المنبثقة ولوحات الصدارة والإنجازات بشكل صحيح ومتّسق على أحجام الشاشات المختلفة وباتجاهات مختلفة في واجهة مستخدم "خدمات ألعاب Play".
لا يظهر خيار تسجيل الخروج في واجهة مستخدم "خدمات ألعاب Play".
تأكَّد من أنّه يمكنك استرداد "معرّف اللاعب" بنجاح، وإذا كان ذلك ممكنًا، تأكَّد من أنّ الإمكانات من جهة الخادم تعمل على النحو المتوقّع.
إذا كانت اللعبة تستخدم المصادقة من جهة الخادم، اختبِر مسار
requestServerSideAccessبدقة. تأكَّد من أنّ الخادم يتلقّى رمز التفويض ويمكنه استبداله برمز الدخول. اختبِر سيناريوهات النجاح والفشل لأخطاء الشبكة وسيناريوهات غير الصالحclient ID.
إذا كانت لعبتك تستخدم أيًا من الميزات التالية، اختبِرها للتأكّد من أنّها تعمل بالطريقة نفسها كما كانت قبل نقل البيانات:
- لوحات الصدارة: أرسِل النتائج واطّلِع على لوحات الصدارة. تحقَّق من الترتيب الصحيح وعرض أسماء اللاعبين ونتائجهم.
- الإنجازات: افتح الإنجازات وتأكَّد من تسجيلها بشكل صحيح وعرضها في واجهة مستخدم "ألعاب Play".
- حفظ التقدم في الألعاب: إذا كانت اللعبة تستخدم ميزة "حفظ التقدم في الألعاب"، تأكَّد من أنّ حفظ مستوى تقدّم اللعبة وتحميله يعملان بشكل سليم. من المهم بشكل خاص إجراء الاختبار على أجهزة متعدّدة وبعد تحديثات التطبيق.
مهام ما بعد نقل البيانات
أكمِل الخطوات التالية بعد نقل البيانات إلى الإصدار v2 من حزمة SDK للألعاب.