반응형
Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

코딩하기 좋은날

Android Google Play Game 연동하기(네이티브) 본문

Android

Android Google Play Game 연동하기(네이티브)

huiung 2021. 11. 5. 05:45
반응형

게임용 앱을 만들때 업적관리, 로그인, 리더보드 등의 기능을 쉽게 이용하기 위해서 Google Play Game을 연동하여 사용 할 수 있습니다. 따로 위의 기능들을 직접 구현할 필요가 없기 때문에 + 유저들이 친숙하기 때문에 편하게 사용 할 수 있는데요.

 

보통 게임을 만들땐.. Unity를 많이 사용하셔서 Unity에서 연동하는 방법은 많이 있는데.. 네이티브로 연동하는 것에 대해서는 정보가 많이 없고 문서도 조금(?) 불친절 하더라구요. 특히 firebase와 함께 연동하는 부분에서 삽질을 조금 하였는데.. 기록용 + 다른분들이 삽질을 조금 줄였으면 하는 마음에 연동하는 방법에 대한 글을 써보려고 합니다.

 

우선 네이티브 앱에서 Google Play Game을 연동하기 위해서는 아래와 같은 과정을 거쳐야 합니다.

1. 구글 플레이 콘솔 앱 생성 및 Firebase project 생성(+ Android Studio와 연동)

2. Google Cloud Platform에서 OAuth 2.0 클라이언트 ID를 생성하고 Google Play Game Console에 이를 추가 해줍니다.

3. 테스터 등록 및 debug용 SHA-1, SHA-256키를 firebase에 등록해 줍니다. + authentication의 sign-in method에서 Google Play Game을 사용함으로 설정해줍니다.

 

위의 과정들은 따로 설명은 하지 않고 저는 로그인과 관련된 코드에 대해서만 설명을 하도록 하겠습니다.

 

https://developers.google.com/games/services/android/signin -- 구글 플레이게임 로그인 연동 공식문서

https://firebase.google.com/docs/auth/android/play-games -- firebase와 구글 플레이게임 연동 공식문서

 

아래는 제가 구글 플레이게임 로그인을 구현한 Fragment의 코드입니다. 우선 onViewCreated에서 FirebaseAuth의 instance를 얻어오고 GoogleSignInOptions를 만들고 있습니다. 이때 저희는 구글 플레이 게임 로그인을 이용하므로 DEFAULT_GAMES_SIGN_IN 옵션을 사용하여야 하며, 웹서버 client id를 requestServerAuthCode() 에 넘겨줍니다.

companion object {

        private const val RC_SIGN_IN = 9001

        @JvmStatic
        fun newInstance() =
            MainFragment().apply {

            }

    }

    private lateinit var auth: FirebaseAuth
    private lateinit var gso: GoogleSignInOptions

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)       
        // Initialize Firebase Auth
        auth = FirebaseAuth.getInstance()

        gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
            .requestServerAuthCode((getString(R.string.default_web_client_id)))
            .build()

    }

 

onStart에서 FirebaseAuth Instance의 currentUser 값을 updateUI함수에 넘겨줍니다. 만약 해당 디바이스에서 이미 로그인이 되어 있는 유저라면 onStart단계에서 빠르게 UI를 업데이트 할 수 있습니다. onResume()에서는 signInSilently()라는 함수를 실행시켜 줍니다. 이름 처럼 조용히 로그인을 진행하는 동작을 합니다. 다른 화면을 왔다갔다 하면서, 로그인 상태에 대한 문제가 발생할 수 있기 때문에, 해당 함수는 onResume()에서 호출하는 것이 안전하다고 공식문서에서 말하고 있습니다.

 

override fun onStart() {
        super.onStart()
        // Check if user is signed in (non-null) and update UI accordingly.
        val currentUser = auth.currentUser
        updateUI(currentUser)
    }

    override fun onResume() {
        super.onResume()
        signInSilently()
    }
    
  private fun signInSilently() {
        activity ?: return
        // Haven't been signed-in before. Try the silent sign-in first
        val signInClient = GoogleSignIn.getClient(activity as Activity, gso)

        signInClient
            .silentSignIn()
            .addOnCompleteListener(
                activity as Activity
            ) { task ->
                if (task.isSuccessful) {
                    // The signed in account is stored in the task's result.
                    val signedInAccount = task.result
                    signedInAccount?.let {
                        firebaseAuthWithPlayGames(it)
                    }
                } else {
                    // Player will need to sign-in explicitly using via UI.
                    // See [sign-in best practices](http://developers.google.com/games/services/checklist) for guidance on how and when to implement Interactive Sign-in,
                    // and [Performing Interactive Sign-in](http://developers.google.com/games/services/android/signin#performing_interactive_sign-in) for details on how to implement
                    // Interactive Sign-in.
                    startSignInIntent()
                }
            }
    }

 

내부적으로 silentSignIn()이라는 함수를 호출하며 성공 여부 콜백을 받습니다. 로그인에 성공한다면 firebaseAuthWithPlayGames()를 호출하고 실패한다면 startSignInIntent()를 호출합니다.

 

firebaseAuthWithPlayGames()는 주어진 GoogleSignInAccount 정보를 통해 firebase에 인증(?)을 하는 역할을 합니다. 마찬가지로 signInWithCredential() 호출 이후 성공 여부 콜백을 받으며 여기서 성공시 updateUI()를 호출해주게 됩니다.

 

// Call this both in the silent sign-in task's OnCompleteListener and in the
    // Activity's onActivityResult handler.
    private fun firebaseAuthWithPlayGames(acct: GoogleSignInAccount) {
        activity ?: return

        val auth = FirebaseAuth.getInstance()
        val credential = PlayGamesAuthProvider.getCredential(acct.serverAuthCode!!)
        auth.signInWithCredential(credential)
            .addOnCompleteListener(activity as Activity) { task ->
                if (task.isSuccessful) {
                    // Sign in success, update UI with the signed-in user's information
                    Log.d(TAG, "signInWithCredential:success")
                    val user = auth.currentUser
                    updateUI(user)
                } else {
                    // If sign in fails, display a message to the user.
                    Log.w(TAG, "signInWithCredential:failure", task.exception)
                    Toast.makeText(context, "Authentication failed.",
                        Toast.LENGTH_SHORT).show()
                    updateUI(null)
                }
            }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        data ?: return
        if (requestCode == RC_SIGN_IN) {
            val result = Auth.GoogleSignInApi.getSignInResultFromIntent(data)
            if (result?.isSuccess == true) {
                // The signed in account is stored in the result.
                result.signInAccount?.let {
                    firebaseAuthWithPlayGames(it)
                }
            } else {
                var message = result?.status?.statusMessage
                if (message == null || message.isEmpty()) {
                    message = getString(R.string.signin_other_error)
                }
                AlertDialog.Builder(context)
                    .setMessage(message)
                    .setNeutralButton(R.string.ok) { dialog, which -> activity?.finish() }
                    .show()
            }
        }
    }

    private fun startSignInIntent() {
        activity ?: return
        val signInClient = GoogleSignIn.getClient(
            activity as Activity,
            gso
        )
        val intent = signInClient.signInIntent
        startActivityForResult(intent, RC_SIGN_IN)
    }

    private fun updateUI(user: FirebaseUser?) {
        user ?: return
        binding.mainContainerLayout.visibility = View.VISIBLE
    }

silentSignIn()이 실패하게 되면 위의 startSignInIntent() 함수가 호출되게 되며, 사용자가 직접 로그인 할 수 있는 UI를 담은 Activity를 실행하게 됩니다. 이후 유저의 로그인 결과에 따라 onActivityResult 에서 requestCode를 통해 각각 처리가 됩니다. 예를 들면 디바이스에 여러개의 구글 계정이 존재한다면 어떤 계정을 사용해야 할지 알 수가 없으므로 silentSignIn()이 실패하게 될 것이고 명시적으로 계정을 선택하는 UI가 아래처럼 뜰 것 입니다.

 

 

만약 로그아웃이 필요하다면 아래와 같이 작성할 수 있겠습니다.

private fun signOut() {
        activity ?: return

        FirebaseAuth.getInstance().signOut()

        val signInClient = GoogleSignIn.getClient(
            activity as Activity,
            gso
        )

        signInClient.signOut().addOnCompleteListener(
            activity as Activity) {
            // at this point, the user is signed out.

            AlertDialog.Builder(context)
                .setMessage("로그아웃이 완료 되었습니다.")
                .setNeutralButton(R.string.ok, null)
                .show()
        }
    }

계정 변경이 필요한 경우.. 구글플레이 게임 앱에서 해당 앱을 선택하여 변경을 진행할 수 있기 때문에 굳이 로그아웃 기능을 추가할 필요가 있을까 싶은 생각이 들긴 합니다.

반응형