يوضّح هذا المستند كيفية نقل الألعاب الحالية من الإصدار v1 من حزمة تطوير البرامج (SDK) الخاصة "بخدمات ألعاب Play" إلى الإصدار v2 من حزمة تطوير البرامج (SDK) الخاصة "بخدمات ألعاب Play".
قبل البدء
يمكنك استخدام أي بيئة تطوير متكاملة (IDE) مفضّلة، مثل "استوديو Android"، لنقل لعبتك. أكمِل الخطوات التالية قبل النقل إلى الإصدار v2 من "خدمات ألعاب Play":
- تنزيل "استوديو Android" وتثبيته
- يجب أن تستخدم لعبتك الإصدار v1 من حزمة تطوير البرامج (SDK) الخاصة "بخدمات ألعاب Play".
- يمكنك ترقية لعبتك لاستخدام الإصدار v1 من حزمة تطوير البرامج (SDK) الخاصة "بخدمات ألعاب Play" إلى
com.google.android.gms:play-services-games:24.0.0. لا ننصح بالترقية إلىcom.google.android.gms:play-services-games:25.0.0لأنّه تمت إزالة الإصدار v1 من واجهة برمجة التطبيقات الخاصة "بخدمات ألعاب Play".
تعديل التبعيات
في ملف
build.gradleالخاص بالوحدة، ابحث عن هذا السطر في التبعيات على مستوى الوحدة.implementation "com.google.android.gms:play-services-games:+"استبدِله بالرمز التالي:
implementation "com.google.android.gms:play-services-games-v2:version"استبدِل version بأحدث إصدار من حزمة تطوير البرامج (SDK) للألعاب.
بعد تعديل التبعيات، تأكَّد من إكمال جميع الخطوات الواردة في هذا المستند.
تحديد رقم تعريف المشروع
لإضافة رقم تعريف مشروع حزمة تطوير البرامج (SDK) الخاصة "بخدمات ألعاب Play" إلى تطبيقك، أكمِل الخطوات التالية:
في ملف
AndroidManifest.xml، أضِف عنصر<meta-data>والسمات التالية إلى عنصر<application><manifest> <application> <meta-data android:name="com.google.android.gms.games.APP_ID" android:value="@string/game_services_project_id"/> </application> </manifest>حدِّد مرجع مصدر السلاسل النصية
@string/game_services_project_idباستخدام رقم تعريف مشروع خدمات الألعاب لألعابك كقيمة. يمكن العثور على رقم تعريف مشروع "خدمات ألعاب Play" ضِمن اسم لعبتك في صفحة الإعداد على Google Play Console.في ملف
res/values/strings.xml، أضِف مرجع مورد سلسلة واضبط رقم تعريف مشروعك كقيمة. على سبيل المثال:<!-- res/values/strings.xml --> <resources> <!-- Replace 0000000000 with your game’s project id. Example value shown above. --> <string translatable="false" name="game_services_project_id"> 0000000000 </string> </resources>
مسارات نقل البيانات
يعتمد مسار نقل البيانات الصحيح للعبتك على كيفية تنفيذها للإصدار v1 من "خدمات ألعاب Play" ومعالجتها لهوية اللاعب. لضمان عملية نقل سلسة ومنع فقدان بيانات اللاعب، حدِّد السيناريو الذي يطابق إعدادك الحالي على أفضل وجه واتّبِع الخطوات المقابلة.
الخيار 1: للألعاب التي يكون فيها حساب اللاعب داخل اللعبة مرتبطًا برقم تعريف اللاعب في "خدمات ألعاب Play"
ينطبق هذا السيناريو على الألعاب التي استخدمت Player ID في "خدمات ألعاب Play" كمحدّد وحيد
لحساب اللاعب داخل اللعبة ولم تطلب أو تخزِّن OpenID من قبل. التحدي الرئيسي هو ربط حساب اللاعب الحالي داخل اللعبة بمحدّد أساسي (OpenID) بدون فقدان الاتصال بتقدّم اللاعب.
يتضمّن مسار نقل البيانات الخطوات التالية:
- عند تشغيل اللعبة، تُجري حزمة تطوير البرامج (SDK) للإصدار v2 من "خدمات ألعاب Play" عملية مصادقة تلقائية وصامتة للمنصة.
اعرض شاشة تسجيل دخول تتضمّن زر تسجيل الدخول باستخدام حساب Google، بدلاً من الزر Google Play. على سبيل المثال، اطّلِع على CredManBridge.java.
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); } @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()); }} }
استرجِع محدّدين مختلفَين عندما ينقر اللاعب على الزر تسجيل الدخول باستخدام حساب Google ويختار حسابًا على Google:
OpenID، وهو المحدّد الأساسي لربط حساب اللاعب داخل اللعبة.Player IDفي "خدمات ألعاب Play"، الذي يتم استرجاعه باستخدام النطاقGAMES_LITE، للبحث عن حساب اللاعب داخل اللعبة في نظامك الخلفي وإجراء عملية الربط. لمزيد من المعلومات، اطّلِع على استرجاعPlayer ID.
يمكنك الوصول إلى حساب اللاعب داخل اللعبة من خلال مسار تسجيل الدخول باستخدام حساب Google في عمليات تشغيل اللعبة اللاحقة، بدون أن تطلب الألعاب استخدام
Player IDكمحدّد أساسي.
استرجاع Player ID
يمكنك تنفيذ الخطوة 3 باستخدام عملية تنفيذ من جهة عميل اللعبة.
- استدعِ واجهة برمجة التطبيقات Android Credential Manager لتسجيل دخول المستخدم باستخدام حساب على Google.
- بعد أن يُكمل المستخدم مسار "تسجيل الدخول باستخدام حساب Google" ويختار حسابًا على Google، سيصلك عنصر نتيجة يحتوي على رمز تعريف و عنوان البريد الإلكتروني.
- أنشِئ عنصر حساب من عنوان البريد الإلكتروني.
- استدعِ واجهة برمجة التطبيقات Authorization باستخدام النطاق
GAMES_LITEوالحساب. - إذا كان الحساب يتضمّن إذنًا حاليًا على النطاق
GAMES_LITE، ستعرض واجهة برمجة التطبيقات Authorization رمزًا مميزًا مباشرةً في عنصر الردّ:- استخدِم الرمز المميّز للردّ لاستدعاء خوادم "خدمات ألعاب Play" واسترجاع
Player IDفي "خدمات ألعاب Play". - تأكَّد مما إذا كان
Player IDفي "خدمات ألعاب Play" مرتبطًا بحساب داخل اللعبة.- يشير ذلك إلى أنّ المستخدم المكرر الزيارة يعود من الإصدار v1 من "خدمات ألعاب Play".
- اربط رقم تعريف Gaia الجديد بالحساب السابق في الإصدار v1 من "خدمات ألعاب Play".
- استخدِم الرمز المميّز للردّ لاستدعاء خوادم "خدمات ألعاب Play" واسترجاع
- إذا لم يكن الحساب يتضمّن إذنًا حاليًا على النطاق
GAMES_LITE، ستعرض واجهة برمجة التطبيقات Authorization عنصرPendingIntent:- يشير ذلك إلى أنّ المستخدم ليس لديه حساب حالي من الإصدار v1 من "خدمات ألعاب Play".
- يمكنك تجاهل
PendingIntentبأمان بدون عرض أي واجهة مستخدم.
الخيار 2: للألعاب التي تربط حساب اللاعب داخل اللعبة بـ OpenID حاليًا
لدى المطوّرين في هذه المجموعة مسار نقل البيانات الأبسط. إذا كان حساب اللاعب داخل اللعبة مرتبطًا بشكل أساسي بـ OpenID، ما عليك سوى إجراء عملية نقل حزمة تطوير البرامج (SDK) الفنية العادية من الإصدار v1 إلى الإصدار v2 كما هو موضّح في الخطوات.
نقل البيانات من ميزة "تسجيل الدخول باستخدام حساب Google" التي تم إيقافها نهائيًا
استبدِل الفئة GoogleSignInClient
بالفئة GamesSignInClient.
Java
حدِّد موقع الملفات التي تتضمّن الفئة GoogleSignInClient.
import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
// ... existing code
@Override
public void onCreate(@Nullable Bundle bundle) {
super.onCreate(bundle);
// ... existing code
GoogleSignInOptions signInOption =
new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN).build();
// Client used to sign in to Google services
GoogleSignInClient googleSignInClient =
GoogleSignIn.getClient(this, signInOptions);
}
عدِّلها إلى ما يلي:
import com.google.android.gms.games.PlayGamesSdk;
import com.google.android.gms.games.PlayGames;
import com.google.android.gms.games.GamesSignInClient;
// ... existing code
@Override
public void onCreate(){
super.onCreate();
PlayGamesSdk.initialize(this);
// Client used to sign in to Google services
GamesSignInClient gamesSignInClient =
PlayGames.getGamesSignInClient(getActivity());
}
Kotlin
حدِّد موقع الملفات التي تتضمّن الفئة GoogleSignInClient.
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInClient
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
// ... existing code
val signInOptions = GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN
// ... existing code
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val googleSignInClient: GoogleSignInClient =
GoogleSignIn.getClient(this, signInOptions)
}
عدِّلها إلى ما يلي:
import com.google.android.gms.games.PlayGames
import com.google.android.gms.games.PlayGamesSdk
import com.google.android.gms.games.GamesSignInClient
// ... existing code
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
PlayGamesSdk.initialize(this)
// client used to sign in to Google services
val gamesSignInClient: GamesSignInClient =
PlayGames.getGamesSignInClient(this)
}
تعديل رمز GoogleSignIn
GoogleSignIn واجهة برمجة التطبيقات غير متاحة في الإصدار v2 من حزمة تطوير البرامج (SDK) الخاصة بالألعاب. استبدِل رمز واجهة برمجة التطبيقات GoogleSignIn بواجهة برمجة التطبيقات GamesSignInClient كما هو موضّح في المثال التالي.
لطلب رمز مميّز للوصول من جهة الخادم، استخدِم الطريقة
GamesSignInClient.requestServerSideAccess().
لمزيد من المعلومات، اطّلِع على
تعديل فئات الوصول من جهة الخادم.
Java
حدِّد موقع الملفات التي تتضمّن الفئة GoogleSignIn.
// Request code used when invoking an external activity.
private static final int RC_SIGN_IN = 9001;
private boolean isSignedIn() {
GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this);
GoogleSignInOptions signInOptions =
GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN;
return GoogleSignIn.hasPermissions(account, signInOptions.getScopeArray());
}
private void signInSilently() {
GoogleSignInOptions signInOptions =
GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN;
GoogleSignInClient signInClient = GoogleSignIn.getClient(this, signInOptions);
signInClient
.silentSignIn()
.addOnCompleteListener(
this,
task -> {
if (task.isSuccessful()) {
// The signed-in account is stored in the task's result.
GoogleSignInAccount signedInAccount = task.getResult();
showSignInPopup();
} else {
// Perform interactive sign in.
startSignInIntent();
}
});
}
private void startSignInIntent() {
GoogleSignInClient signInClient = GoogleSignIn.getClient(this,
GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN);
Intent intent = signInClient.getSignInIntent();
startActivityForResult(intent, RC_SIGN_IN);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_SIGN_IN) {
GoogleSignInResult result =
Auth.GoogleSignInApi.getSignInResultFromIntent(data);
if (result.isSuccess()) {
// The signed-in account is stored in the result.
GoogleSignInAccount signedInAccount = result.getSignInAccount();
showSignInPopup();
} else {
String message = result.getStatus().getStatusMessage();
if (message == null || message.isEmpty()) {
message = getString(R.string.signin_other_error);
}
new AlertDialog.Builder(this).setMessage(message)
.setNeutralButton(android.R.string.ok, null).show();
}
}
}
private void showSignInPopup() {
Games.getGamesClient(requireContext(), signedInAccount)
.setViewForPopups(contentView)
.addOnCompleteListener(
task -> {
if (task.isSuccessful()) {
logger.atInfo().log("SignIn successful");
} else {
logger.atInfo().log("SignIn failed");
}
});
}
عدِّلها إلى ما يلي:
private void signInSilently() {
gamesSignInClient.isAuthenticated().addOnCompleteListener(isAuthenticatedTask -> {
boolean isAuthenticated =
(isAuthenticatedTask.isSuccessful() &&
isAuthenticatedTask.getResult().isAuthenticated());
if (isAuthenticated) {
// Continue with Play Games Services
} else {
// If authentication fails, either disable Play Games Services
// integration or
// display a login button to prompt players to sign in.
// Use`gamesSignInClient.signIn()` when the login button is clicked.
}
});
}
@Override
protected void onResume() {
super.onResume();
// When the activity is inactive, the signed-in user's state can change;
// therefore, silently sign in when the app resumes.
signInSilently();
}Kotlin
حدِّد موقع الملفات التي تتضمّن الفئة GoogleSignIn.
// Request codes we use when invoking an external activity.
private val RC_SIGN_IN = 9001
// ... existing code
private fun isSignedIn(): Boolean {
val account = GoogleSignIn.getLastSignedInAccount(this)
val signInOptions = GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN
return GoogleSignIn.hasPermissions(account, *signInOptions.scopeArray)
}
private fun signInSilently() {
val signInOptions = GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN
val signInClient = GoogleSignIn.getClient(this, signInOptions)
signInClient.silentSignIn().addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// The signed-in account is stored in the task's result.
val signedInAccount = task.result
// Pass the account to showSignInPopup.
showSignInPopup(signedInAccount)
} else {
// Perform interactive sign in.
startSignInIntent()
}
}
}
private fun startSignInIntent() {
val signInClient = GoogleSignIn.getClient(this, GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
val intent = signInClient.signInIntent
startActivityForResult(intent, RC_SIGN_IN)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == RC_SIGN_IN) {
val result = Auth.GoogleSignInApi.getSignInResultFromIntent(data)
if (result.isSuccess) {
// The signed-in account is stored in the result.
val signedInAccount = result.signInAccount
showSignInPopup(signedInAccount) // Pass the account to showSignInPopup.
} else {
var message = result.status.statusMessage
if (message == null || message.isEmpty()) {
message = getString(R.string.signin_other_error)
}
AlertDialog.Builder(this)
.setMessage(message)
.setNeutralButton(android.R.string.ok, null)
.show()
}
}
}
private fun showSignInPopup(signedInAccount: GoogleSignInAccount) {
// Add signedInAccount parameter.
Games.getGamesClient(this, signedInAccount)
.setViewForPopups(contentView) // Assuming contentView is defined.
.addOnCompleteListener { task ->
if (task.isSuccessful) {
logger.atInfo().log("SignIn successful")
} else {
logger.atInfo().log("SignIn failed")
}
}
}عدِّلها إلى ما يلي:
private fun signInSilently() {
gamesSignInClient.isAuthenticated.addOnCompleteListener { isAuthenticatedTask ->
val isAuthenticated = isAuthenticatedTask.isSuccessful &&
isAuthenticatedTask.result.isAuthenticated
if (isAuthenticated) {
// Continue with Play Games Services
} else {
// To handle a user who is not signed in, either disable Play Games Services integration
// or display a login button. Selecting this button calls `gamesSignInClient.signIn()`.
}
}
}
override fun onResume() {
super.onResume()
// Since the state of the signed in user can change when the activity is
// not active it is recommended to try and sign in silently from when the
// app resumes.
signInSilently()
}إضافة رمز GamesSignInClient
إذا تمت مصادقة اللاعب بنجاح، أزِل زر تسجيل الدخول إلى "خدمات ألعاب Play" من لعبتك. إذا اختار المستخدم عدم المصادقة عند تشغيل اللعبة،
استمِر في عرض زر يتضمّن رمز "خدمات ألعاب Play"،
وابدأ عملية تسجيل الدخول باستخدام
GamesSignInClient.signIn().
Java
private void startSignInIntent() {
gamesSignInClient
.signIn()
.addOnCompleteListener( task -> {
if (task.isSuccessful() && task.getResult().isAuthenticated()) {
// sign in successful
} else {
// sign in failed
}
});
}Kotlin
private fun startSignInIntent() {
gamesSignInClient
.signIn()
.addOnCompleteListener { task ->
if (task.isSuccessful && task.result.isAuthenticated) {
// sign in successful
} else {
// sign in failed
}
}
}إزالة رمز تسجيل الخروج
أزِل رمز GoogleSignInClient.signOut.
أزِل الرمز الموضّح في المثال التالي:
Java
// ... existing code
private void signOut() {
GoogleSignInClient signInClient = GoogleSignIn.getClient(this,
GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN);
signInClient.signOut().addOnCompleteListener(this,
new OnCompleteListener() {
@Override
public void onComplete(@NonNull Task task) {
// At this point, the user is signed out.
}
});
} Kotlin
// ... existing code
private fun signOut() {
val signInClient = GoogleSignIn.getClient(this, GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
signInClient.signOut().addOnCompleteListener(this) {
// At this point, the user is signed out.
}
}التحقّق من نجاح المصادقة
أدرِج الرمز التالي للتحقّق مما إذا كنت قد أجريت عملية مصادقة تلقائية وأضِف المنطق المخصّص إذا كان متوفرًا.
Java
private void checkIfAutomaticallySignedIn() {
gamesSignInClient.isAuthenticated().addOnCompleteListener(isAuthenticatedTask -> {
boolean isAuthenticated =
(isAuthenticatedTask.isSuccessful() &&
isAuthenticatedTask.getResult().isAuthenticated());
if (isAuthenticated) {
// Continue with Play Games Services
// If your game requires specific actions upon successful sign-in,
// you can add your custom logic here.
// For example, fetching player data or updating UI elements.
} else {
// Show a login button to ask players to sign-in. Clicking it should
// call GamesSignInClient.signIn().
}
});
}
Kotlin
private void checkIfAutomaticallySignedIn() {
gamesSignInClient.isAuthenticated()
.addOnCompleteListener { task ->
val isAuthenticated = task.isSuccessful && task.result?.isAuthenticated ?: false
if (isAuthenticated) {
// Continue with Play Games Services
} else {
// Disable your integration or show a login button
}
}
}
تعديل أسماء فئات العميل والطرق
عند النقل إلى الإصدار v2 من "خدمات ألعاب Play"، تختلف الطرق المستخدَمة للحصول على أسماء فئات العميل.
استخدِم الطرق المقابلة
PlayGames.getxxxClient()
بدلاً من الطرق
Games.getxxxClient().
على سبيل المثال، بالنسبة إلى
LeaderboardsClient
استخدِم PlayGames.getLeaderboardsClient() بدلاً من الطريقة
Games.getLeaderboardsClient()
أزِل أي رمز مرتبط بالفئتَين GamesClient وGamesMetadataClient لأنّه ليس لدينا أي فئات بديلة في الإصدار v2 من "خدمات ألعاب Play".
Java
حدِّد موقع رمز LeaderboardsClient.
import com.google.android.gms.games.LeaderboardsClient;
import com.google.android.gms.games.Games;
@Override
public void onCreate(@Nullable Bundle bundle) {
super.onCreate(bundle);
// Get the leaderboards client using Play Games services.
LeaderboardsClient leaderboardsClient = Games.getLeaderboardsClient(this,
GoogleSignIn.getLastSignedInAccount(this));
}
عدِّلها إلى ما يلي:
import com.google.android.gms.games.LeaderboardsClient;
import com.google.android.gms.games.PlayGames;
@Override
public void onCreate(@Nullable Bundle bundle) {
super.onCreate(bundle);
// Get the leaderboards client using Play Games services.
LeaderboardsClient leaderboardsClient = PlayGames.getLeaderboardsClient(getActivity());
}
Kotlin
حدِّد موقع رمز LeaderboardsClient.
import com.google.android.gms.games.LeaderboardsClient
import com.google.android.gms.games.Games
// Initialize the variables.
private lateinit var leaderboardsClient: LeaderboardsClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
leaderboardsClient = Games.getLeaderboardsClient(this,
GoogleSignIn.getLastSignedInAccount(this))
}عدِّلها إلى ما يلي:
import com.google.android.gms.games.LeaderboardsClient
import com.google.android.gms.games.PlayGames
// Initialize the variables.
private lateinit var leaderboardsClient: LeaderboardsClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
leaderboardsClient = PlayGames.getLeaderboardsClient(this)
}وبالمثل، استخدِم الطرق المقابلة للعملاء التاليين: AchievementsClient أو EventsClient أو GamesSignInClient أو PlayerStatsClient أو RecallClient أو SnapshotsClient أو PlayersClient.
تعديل فئات الوصول من جهة الخادم
لطلب رمز مميّز للوصول من جهة الخادم، استخدِم الطريقة
GamesSignInClient.requestServerSideAccess()
بدلاً من الطريقة
GoogleSignInAccount.getServerAuthCode().
لمزيد من المعلومات، اطّلِع على إرسال رمز التفويض من جهة الخادم.
يوضّح المثال التالي كيفية طلب رمز مميّز للوصول من جهة الخادم.
Java
حدِّد موقع رمز الفئة GoogleSignInOptions.
private static final int RC_SIGN_IN = 9001;
private GoogleSignInClient googleSignInClient;
private void startSignInForAuthCode() {
/** Client ID for your backend server. */
String webClientId = getString(R.string.webclient_id);
GoogleSignInOptions signInOption = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
.requestServerAuthCode(webClientId)
.build();
GoogleSignInClient signInClient = GoogleSignIn.getClient(this, signInOption);
Intent intent = signInClient.getSignInIntent();
startActivityForResult(intent, RC_SIGN_IN);
}
/** Auth code to send to backend server */
private String mServerAuthCode;
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_SIGN_IN) {
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
if (result.isSuccess()) {
mServerAuthCode = result.getSignInAccount().getServerAuthCode();
} else {
String message = result.getStatus().getStatusMessage();
if (message == null || message.isEmpty()) {
message = getString(R.string.signin_other_error);
}
new AlertDialog.Builder(this).setMessage(message)
.setNeutralButton(android.R.string.ok, null).show();
}
}
}
عدِّلها إلى ما يلي:
private void startRequestServerSideAccess() {
GamesSignInClient gamesSignInClient = PlayGames.getGamesSignInClient(this);
gamesSignInClient
.requestServerSideAccess(OAUTH_2_WEB_CLIENT_ID,
/* forceRefreshToken= */ false, /* additional AuthScope */ scopes)
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
AuthResponse authresp = task.getResult();
// Send the authorization code as a string and a
// list of the granted AuthScopes that were granted by the
// user. Exchange for an access token.
// Verify the player with Play Games Services REST APIs.
} else {
// Authentication code retrieval failed.
}
});
}
Kotlin
حدِّد موقع رمز الفئة GoogleSignInOptions.
// ... existing code
private val RC_SIGN_IN = 9001
private lateinit var googleSignInClient: GoogleSignInClient
// Auth code to send to backend server.
private var mServerAuthCode: String? = null
private fun startSignInForAuthCode() {
// Client ID for your backend server.
val webClientId = getString(R.string.webclient_id)
val signInOption = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
.requestServerAuthCode(webClientId)
.build()
googleSignInClient = GoogleSignIn.getClient(this, signInOption)
val intent = googleSignInClient.signInIntent
startActivityForResult(intent, RC_SIGN_IN)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == RC_SIGN_IN) {
val result = Auth.GoogleSignInApi.getSignInResultFromIntent(data)
if (result.isSuccess) {
mServerAuthCode = result.signInAccount.serverAuthCode
} else {
var message = result.status.statusMessage
if (message == null || message.isEmpty()) {
message = getString(R.string.signin_other_error)
}
AlertDialog.Builder(this).setMessage(message)
.setNeutralButton(android.R.string.ok, null).show()
}
}
}
عدِّلها إلى ما يلي:
private void startRequestServerSideAccess() {
GamesSignInClient gamesSignInClient = PlayGames.getGamesSignInClient(this);
gamesSignInClient
.requestServerSideAccess(OAUTH_2_WEB_CLIENT_ID, /* forceRefreshToken= */ false,
/* additional AuthScope */ scopes)
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
AuthResponse authresp = task.getResult();
// Send the authorization code as a string and a
// list of the granted AuthScopes that were granted by the
// user. Exchange for an access token.
// Verify the player with Play Games Services REST APIs.
} else {
// Authentication code retrieval failed.
}
});
}
النقل من GoogleApiClient
بالنسبة إلى عمليات التكامل الحالية الأقدم، قد تعتمد لعبتك على الاختلاف في واجهة برمجة التطبيقات GoogleApiClient لحزمة تطوير البرامج (SDK) الخاصة "بخدمات ألعاب Play". تم إيقاف هذه الواجهة نهائيًا في أواخر عام 2017 واستبدالها بعملاء "غير متصلين".
لنقل البيانات، يمكنك استبدال الفئة GoogleApiClient بفئة مكافئة "غير متصلة".
يعرض الجدول التالي عمليات ربط الفئات الشائعة من الإصدار v1 من "خدمات ألعاب Play" إلى الإصدار v2 من "خدمات ألعاب Play":
| الإصدار الثاني من "خدمات ألعاب Play" (الحالي) | الإصدار الثاني من "خدمات ألعاب Play" (الحالي) |
|---|---|
| com.google.android.gms.games.AchievementsClient | com.google.android.gms.games.achievement.Achievements |
| com.google.android.gms.games.LeaderboardsClient | com.google.android.gms.games.leaderboard.Leaderboard |
| com.google.android.gms.games.SnapshotsClient | com.google.android.gms.games.snapshot.Snapshots |
| com.google.android.gms.games.PlayerStatsClient | com.google.android.gms.games.stats.PlayerStats |
| com.google.android.gms.games.PlayersClient | com.google.android.gms.games.Players |
| com.google.android.gms.games.GamesClientStatusCodes | com.google.android.gms.games.GamesStatusCodes |
إنشاء اللعبة وتشغيلها
لإنشاء اللعبة وتشغيلها على "استوديو Android"، اطّلِع على مقالة إنشاء تطبيقك وتشغيله.
اختبار لعبتك
تأكَّد من أنّ لعبتك تعمل على النحو المطلوب من خلال اختبارها. تعتمد الاختبارات التي تجريها على ميزات لعبتك.
في ما يلي قائمة بالاختبارات الشائعة التي يجب إجراؤها.
تسجيل الدخول بنجاح :
يعمل تسجيل الدخول التلقائي. يجب تسجيل دخول المستخدم إلى "خدمات ألعاب 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 من "خدمات ألعاب Play".
نشر اللعبة
أنشِئ حِزم APK وانشر اللعبة في Play Console.
- في قائمة "استوديو Android"، انقر على إنشاء > إنشاء حِزم APK/حِزم التطبيقات > إنشاء حِزم APK.
- انشر لعبتك. لمزيد من المعلومات، اطّلِع على مقالة نشر التطبيقات الخاصة من Play Console.