레이블이 package인 게시물을 표시합니다. 모든 게시물 표시
레이블이 package인 게시물을 표시합니다. 모든 게시물 표시

2025년 10월 22일 수요일

Unity Google Sign In ...

Prepare your apps for Google Play’s 16 KB page size compatibility requirement

 

Deprecated 되었으나 로그인 관련 수정을 하고 싶지는 않았다 ㅠㅠ

 

1. Unity Engine support for 16 KB memory page sizes (Android 15+)


    
    Unity 2021.3.39f1 버전을 사용중이어서 위 링크의 내용을 얼핏 보고 2021.3.45f1 버전도
    지원하는 것으로 착각하고 열심히 버전을 올리고 스토어에 업로드 했는데 아니었다.
    Enterprise나 Industry 고객만 다운로드 할 수 있는 2021.3.48f1+만 지원 대상이었다.
    
    하는 수 없이 2022.3.62f1 버전으로 업데이트를 진행했다.
 

2. 유니티 엔진 업데이트로는 모두 다 해결되지 않는다.


    프로젝트에서 사용중인 외부 라이브러리도 모두 16 KB 페이지 사이즈를 사용하도록
    빌드 되어 있어야만 한다.

    아무것도 모르고 빌드가 성공하고 잠깐의 테스트가 성공해서 스토어에 배포를 
    시작했다가 무한 크래시로 유저들의 빗발치는 악담을 들은 건 넘어가기로 하고...

    문제는 Google Sign-In Unity Plugin은 구글이 이제 거의 7년을 방치해 놓고 있다는 사실.

3. 해결 방법.


    공식적으로 구글이 유니티 플러그인으로 공식 지원하는 건
    Google Play Games plugin for Unity 

    이걸 사용하려고 했다면 처음부터 그랬겠지만 이것 저것 덕지 덕지 붙어 있는 건 별로라.

    에셋 스토어에 있는 에셋부터 Google Sign-In Unity Plugin 컴파일까지 고려한 해결 
    방법은 많았지만 Google Sign-In 플러그인을 만들기로 했다. 
    아주 쉽게 Unity Android native plugin 방식으로.

    우선 Google Sign-In Unity Plugin 패키지를 지워준다.

    그리고, Java로 native plugin을 만든 뒤 /Assets/Plugins/Android/ 경로에 옮겨준다.
    동작이야 Google Sign-In Unity Plugin과 비슷하므로 자세한 설명은 생략하고
    코드만 정리해 놓는다.

   
package com.ships.googlesigninplugin;
 
import org.json.JSONObject;
import org.json.JSONException;
 
import android.app.Activity;
import android.content.Intent;
import android.util.Log;
import androidx.annotation.NonNull;
 
import com.google.android.gms.auth.api.signin.*;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.OnCompleteListener;
import com.unity3d.player.UnityPlayer;
 
public class GoogleSignInPlugin {
 
    private static final String TAG = "GoogleSignInPlugin";
    private static final int RC_SIGN_IN = 1002;
    private static GoogleSignInClient mGoogleSignInClient;
    private static String unityCallbackObject = "_GoogleManagers";
 
    public static void setWebClientId(String clientId) {
        Activity activity = UnityPlayer.currentActivity;
        Log.d(TAG, "setWebClientID - " + clientId);
        GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestEmail()
                .requestServerAuthCode(clientId)
                .build();
 
        mGoogleSignInClient = GoogleSignIn.getClient(activity, gso);
    }
 
    public static void signIn() {
        Activity activity = UnityPlayer.currentActivity;
 
        Intent signInIntent = mGoogleSignInClient.getSignInIntent();
        activity.startActivityForResult(signInIntent, RC_SIGN_IN);
    }
 
    public static void silentSignIn() {
        Activity activity = UnityPlayer.currentActivity;
 
        if (mGoogleSignInClient == null) {
            sendResult(falsenullnull"GoogleSignInClient not initialized");
            return;
        }
 
        Task<GoogleSignInAccount> task = mGoogleSignInClient.silentSignIn();
        if (task.isSuccessful()) {
            handleSignInResult(task);
        } else {
            task.addOnCompleteListener(activity, new OnCompleteListener<GoogleSignInAccount>() {
                @Override
                public void onComplete(@NonNull Task<GoogleSignInAccount> task) {
                    if (task.isSuccessful()) {
                        handleSignInResult(task);
                    } else {
                        // fallback to interactive
                        signIn();
                    }
                }
            });
        }
    }
 
    public static void handleActivityResult(int requestCode, int resultCode, Intent data) {
        Log.d(TAG, "handleActivityResult");
        if (requestCode == RC_SIGN_IN) {
            Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
            handleSignInResult(task);
        } else {
            Log.d(TAG, "requestCode: " + requestCode + "resultCode: " + resultCode);
        }
    }
 
    public static void signOut() {
        if (mGoogleSignInClient != null) {
            mGoogleSignInClient.signOut();
        }
    }
 
    private static void handleSignInResult(Task<GoogleSignInAccount> completedTask) {
        try {
            GoogleSignInAccount account = completedTask.getResult(ApiException.class);
 
            String email = account.getEmail();
            String idToken = account.getServerAuthCode();
 
            sendResult(true, email, idToken, null);
        } catch (ApiException e) {
            Log.w(TAG, "signInResult:failed code=" + e.getStatusCode());
            sendResult(falsenullnull, e.getMessage());
        }
    }
 
    private static void sendResult(boolean success, String email, String idToken, String error) {
        try {
            JSONObject json = new JSONObject();
            json.put("success", success);
            json.put("email", email != null ? email : JSONObject.NULL);
            json.put("idToken", idToken != null ? idToken : JSONObject.NULL);
 
            Log.d(TAG, json.toString());
            UnityPlayer.UnitySendMessage(unityCallbackObject, "GoogleLoginResult", json.toString());
        } catch (Exception e) {
            Log.d(TAG, "Error sending result to Unity", e);
        }
    }
 
    private static void handleAccount(GoogleSignInAccount account) throws JSONException {
        JSONObject jsonObject = new JSONObject();
        if (account != null) {
            String email = account.getEmail();
            String idToken = account.getIdToken();
            String id = account.getId();
 
            jsonObject.put("success"true);
            jsonObject.put("email", email);
            jsonObject.put("idToken", idToken);
            Log.d(TAG, "Result: " + jsonObject.toString());
            UnityPlayer.UnitySendMessage(unityCallbackObject, "GoogleLoginResult", jsonObject.toString());
        } else {
            jsonObject.put("success"false);
            jsonObject.put("email""Account is null");
            Log.d(TAG, "Result: " + jsonObject.toString());
            UnityPlayer.UnitySendMessage(unityCallbackObject, "GoogleLoginResult", jsonObject.toString());
        }
    }
}
 
cs
 
    그리고 유니티에서 인증 결과를 받을 수 있도록 게임 오브젝트와 스크립트를 준비한다.

   
public class GoogleLoginResult
{
    public bool success;
    public string email;
    public string idToken;
}
 
public class GoogleAccountManager : MonoBehavior
{
    void Start()
    {
        _pluginClass = new AndroidJavaClass("com.ships.googlesigninplugin.GoogleSignInPlugin");
    }
 
    void SignIn()
    {
        _pluginClass?.CallStatic("setWebClientId", clientId);
    }
 
    public void GoogleLoginResult(string json)
    {
        GoogleLoginResult r = JsonUtility.FromJson<GoogleLoginResult>(json);
        if (r.success == true)
        {
            // 인증 검증.
        }
    }
}
cs


    이 후에 한참 디버깅 했는데, Main Activity에서 구글 로그인 결과를 다시 
    플러그인으로 넘겨줘야 한다.

   
public class MessagingUnityPlayerActivity extends UnityPlayerActivity {
    // 나머지 코드는 생략.
 
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        Log.d("MessagingUnityPlayerActivity""Override onActivityResult" + this);
        GoogleSignInPlugin.handleActivityResult(requestCode, resultCode, data);
        super.onActivityResult(requestCode, resultCode, data);
    }
}
cs

4. 결론.


    구글 정말 싫다~~~~~~

2020년 3월 16일 월요일

Unity Google Sign In ...

Google Sign-In Unity Plugin

 

하란대로 하면 안되서 정리한다 ㅠㅠ.

 

1. Plugin download.


   Google Sign-In Unity Plugin release에서 최신 버전을 다운로드 받는다.
   google-signin-plugin-1.0.4.unitypackage 및 GoogleSignIn-sample.unitypackage 파일을
   다운로드 받는다.

2. Unity 프로젝트 생성 및 패키지 임포트.

 


   작업에 사용한 Unity 버전은 2019.3.0f6 버전으로 다음 위치에서 패키지를 임포트합니다.


   하지만, 기대와 다르게 임포트하자마자 에러를 만나게 됩니다.


   에러 내용은 다음과 같습니다.

Library\PackageCache\com.unity.textmeshpro@2.0.1\Scripts\Editor\TMP_PackageUtilities.cs(310,17): error CS0433: The type 'Task' exists in both 'Unity.Tasks, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' and 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'

   해결 방법은 다음 링크에 나와있는대로 패키지의 parse 폴더를 제외합니다.
   error CS0433: The type 'Task' exists in both Unity.Tasks,....


   그리고 나면 또 다른 에러가 엄청나게 나옵니다.


   해당 에러에 대한 힌트는 다음 링크에서 확인 가능합니다.
   ArgumentException: Requested value 'x86' was not found

   간단하게 요약하면 unity-jar-resolver 패키지를 설치하면 됩니다.
   최신 버전은 v1.2.137 이고 zip 파일을 다운로드 하고 압축을 푼 뒤에
   play-services-resolver-1.2.137.0 패키지를  설치해주면 됩니다.

   다음 그림과 같이 버전이 낮은 파일이 이미 사용중일 수 있지만
   최신 버전으로 업데이트 합니다.


   그리고 나면 더이상 쓸모없는 파일을 삭제할 지 묻는 다이얼로그 윈도우가 뜨게 되는데,
   삭제하겠다고 선택하면 됩니다.


   그럼 더 이상 에러는 발생하지 않습니다.
   마지막으로 제일 처음 다운로드 받았던 샘플 패키지를 임포트하면 준비는 끝났습니다.

3. Keystore 생성.


   먼저 File - Build Settings - Platform을 Android로 변경한 뒤,
   App signing을 위해 키를 생성합니다.



   키 분실의 우려가 있으므로 프로젝트 아래 적당한 경로에 저장하고
   버전 관리 시스템을 사용할 수 있도록 합니다.

   구글 앱 서명 방식이 변경되어 앱 서명 인증서와  업로드 인증서로 나뉘고 구글에서 
   관리하도록 변경되어 분실을 하게 되더라도 복구할 방법이 현재는 존재합니다.


   위 그림과 같이 키스토어에 키를 하나 생성합니다.
   그리고 나면 최종적으로 다음과 같이 설정됩니다.


   그리고 Player - Other Settings에서 사용할 Package Name을 설정해 주면 됩니다.


4. Google APIs.


   Google Sign-In api 사용을 위해 사용자 인증 정보를 생성합니다.
   Google API console에서 사용자 인증 정보 - 사용자 인증 정보 만들기를 선택한 뒤,
   OAuth 클라이언트 ID를 선택하고 마지막으로 Android 선택 후 내용을 채워주면 됩니다.


   생성한 뒤 Unity에서 Web Client Id를 설정합니다.
   Canvas를 선택하고 Signin Sample Script 컴포넌트에 설정합니다.


   그리고 빌드한 뒤 apk 배포 후 테스트 해보면 역시나 안된다.


   이름도 거룩하다, DeveloperError ...

5. Google Sign-In for Android.


   Google Sign-In for Android 페이지에서 아래로 스크롤 하다 보면
   Configure a project 버튼을 찾을 수 있습니다.





   최종적으로 Web client (Auto-created for Google Sign-in) 이름으로 생성된
   OAuth 2.0 클라이언트 ID가 생기고 이 아이디를 사용해야 로그인이 됩니다.

   참고로, Google API console에서 클라이언트 ID를 생성할 때 Android가 아니라 
   웹 애플리케이션을 선택해서 만들어도 됩니다.

   이제 만들어진 클라이언트 ID로 빌드하게 되면 로그인이 드디어 됩니다.