게임 프로그래밍 패턴
국내도서
저자 : 로버트 나이스트롬 (Robert Nystrom) / 박일역
출판 : 한빛미디어 2016.06.01
상세보기



최근에 코드를 짜면서 항상 뭔가 불만스럽고, 유지보수 측면이나, 세련됨? 같은 게 부족한 느낌이 많이 들었습니다. 객체지향을 제대로 활용하고 있는가하는 의구심도 들고, 그래서 설계나 리펙토링 측면에서 관련 도서를 찾다가 아는 형에게 추천받아 읽게 된 책입니다.


개인적으로 대만족입니다! 기존에 알고있던 패턴이래봤자 싱글톤, 감시자, 상태, 객체 풀 패턴 정도인데, 좀 더 다양한 패턴, 알고리즘, 자료구조, 발상을 접해서 설계시나 코딩시 고려요소를 늘리고 싶었거든요. 딱 원하던대로 입문하기에도 좋고, 중간 중간 심화 알고리즘을 제안해주어 파고들기도 좋은거 같습니다. 특히 접해보기 힘든 동떨어진 예시가 아닌 게임의 시스템을 기반으로 이야기를 하다보니 더 이해하기가 좋았던 듯 싶네요. 저처럼 디자인 패턴에 쉽게 입문하면서 다양한 발상을 접하고 싶고, 심화 알고리즘도 리스트업하고자 하시는 분들께는 강력 추천드립니다.


 다만, 바이트코드나 공간분할 등의 생소한 패턴들은 아직 익숙하지가 않고 머리 속에 "이 때는 이걸 활용해야겠군" 하는 개념이 잡히질 못한 느낌이여서 몇 번 더 보면서 활용을 해봐야 할 듯 싶네요. 아무래도 익숙해져야 필요할 때 딱딱 떠오를테니... 그리고 패턴에 대해 많이 무지했다고 느낀게 기존에 무심코 사용하던 방법들이 별도로 패턴이라는 이름으로 정의되어 있더군요. 무작정 목적을 위해 구현했던 것보다는 당연히 책에서 제시하는 방법들이 훨~씬 세련됐지만.... (그만큼 내공이 부족한거겠죠)


 덤으로 요즘에는 상속보다는 조합이 대세라는 식의 다른 프로그래머들은 어떻게 생각하는지 동향? 같은 것도 알 수 있어 좋았습니다. (개발자들은 이런 정보를 어디서 얻는 걸까요? 컨퍼런스 같은데 가면 알 수 있나? 발표자가 얘기했다고 대세라고 생각할 수 있나? 흠...) 컴포넌트 패턴이나 업데이트 패턴 같은 언리얼, 유니티 엔진의 근간을 이루는 패턴들의 발상과 구현도 다루어주고, 주석으로 여러 관련 사이트와 코드들을 던져주는 것도 좋았는데, 다만 이 사이트들은 어쩔수 없겠지만 영어라는 점이 ㅠㅠㅠㅠㅠㅠ (읽는데 한 세월이겠네)  매번 알고리즘이나 자료구조, 이번엔 패턴을 접하면서 느끼는거지만 세상엔 참 똑똑하신 분이 많은 것 같네요. 한정된 방법들로 발상과 아이디어를 코드화 시켜서 다듬어 낸다는게 참 대단한 것 같네요. 



 위 책을 사면서 리펙토링 (저자: 마틴 파울러) 책을 같이 샀는데, 첫번째 읽은 책이 대만족이다보니 이 책도 기대가 되는 한편, 용어들 때문에 살짝 두렵네요. 비교적 글이 적은 게임 프로그래밍 패턴에서도 글보다는 코딩 몇 줄을 보는게 더 이해하기 좋아서 빨리 코드보기를 기다렸거든요. 엮은분이 주석도 달아주시면서 잘 번역해주셨지만 개인적으로는 능력부족으로 용어들이 단 번에 연결되지 않을 때가 있어서 여러번 다시 읽어보고 이해해야 했는데, 리펙토링은 어떨런지...

Posted by 검은거북

 이미지 공유하기 기능을 구현하려다가 이미 구현되어있는게 있을법해서 찾아보니까 JNI로 깔끔하게 구현된게 있더군요! IOS까지!

https://github.com/tejas123/general-sharing-in-android-ios-in-unity


 문제는 Android N 부터는 정책이 달라져서 저 코드의 이미지 공유하기를 실행시키면,

FileUriExposedException: exposed beyond app through ClipData.Item.getUri()

이런 에러코드가 뜹니다. 



해결방법

1. 타겟 API를 안드로이드 6.0 이하로 내린다.

 - 아주아주 간단하지만, 찝찝합니다. 권장하지 않아요. 

 - 안드로이드에서는 앞으로 위 정책으로 진행할텐데 계속 유지보수하는한 언젠가는 결국 수정해야하잖아요?


2. FileProvider 방식으로 변경한다.

 - 안드로이드에서 권장하는 방법입니다. 

 - Android Native에서 구현 방법은 공식문서에서 이미 가이드를 하고 있으므로, 해당 가이드를 유니티에서 구현하도록 합니다.

 - https://developer.android.com/reference/android/support/v4/content/FileProvider

 - 가이드를 고대로 JNI로 unity에 구현하고, Manifest와 res만 수정하면 되겠네요.  아래부터 수정한 내역을 공유하도록 하겠습니다.


 - Code 수정 (GIT의 코드를 가이드에 맞게 일부 수정한 내용입니다.)

IEnumerator SaveAndShare () { yield return new WaitForEndOfFrame (); #if UNITY_ANDROID byte[] bytes = MyImage.EncodeToPNG(); string path = Application.persistentDataPath + "/MyImage.png"; File.WriteAllBytes(path, bytes); AndroidJavaClass intentClass = new AndroidJavaClass("android.content.Intent"); AndroidJavaObject intentObject = new AndroidJavaObject("android.content.Intent"); intentObject.Call<androidjavaobject>("setAction", intentClass.GetStatic<string>("ACTION_SEND")); intentObject.Call<androidjavaobject>("setType", "image/*"); intentObject.Call<androidjavaobject>("putExtra", intentClass.GetStatic<string>("EXTRA_SUBJECT"), "Media Sharing "); intentObject.Call<androidjavaobject>("putExtra", intentClass.GetStatic<string>("EXTRA_TITLE"), "Media Sharing "); intentObject.Call<androidjavaobject>("putExtra", intentClass.GetStatic<string>("EXTRA_TEXT"), "Media Sharing Android Demo"); AndroidJavaClass unity = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); AndroidJavaObject currentActivity = unity.GetStatic<androidjavaobject>("currentActivity"); //AndroidJavaClass uriClass = new AndroidJavaClass("android.net.Uri"); AndroidJavaClass uriClass = new AndroidJavaClass("android.support.v4.content.FileProvider"); AndroidJavaClass fileClass = new AndroidJavaClass("java.io.File"); AndroidJavaObject fileObject = new AndroidJavaObject("java.io.File", path);// Set Image Path Here AndroidJavaObject stringObject = new AndroidJavaObject("java.lang.String", "com.myproject.project.share.fileprovider");// Set Image Path Here //AndroidJavaObject uriObject = uriClass.CallStatic<androidjavaobject>("fromFile", fileObject); AndroidJavaObject uriObject = uriClass.CallStatic<androidjavaobject>("getUriForFile", currentActivity, stringObject, fileObject); // string uriPath = uriObject.Call<string>("getPath"); bool fileExist = fileObject.Call<bool>("exists"); Debug.Log("File exist : " + fileExist); if (fileExist) intentObject.Call<androidjavaobject>("putExtra", intentClass.GetStatic<string>("EXTRA_STREAM"), uriObject); currentActivity.Call("startActivity", intentObject); #endif }

  - 작은 설명을 붙이자면, 기존에 Uri.fromFile 함수를  FileProvider.getUriForFile 함수로 함수와 매개변수를 수정한게 끝입니다.  



 - AndroidManifest.xml 추가 

   - 경로 - \Assets\Plugins\Android\Share\AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.myproject.project.share" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="25" /> <application> <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.myproject.project.share.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" /> </provider> </application> </manifest>


 - res 추가

  - 경로 - \Assets\Plugins\Android\Share\res\xml\filepaths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
  <external-path name="storage/emulated" path="."/>
</paths>



진행과정에서 발생했던 에러

 android.content.pm.PackageItemInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object reference

  - 코드 상의 패키지 이름과 Provider의 authorities 오타 수정하여 통일

Posted by 검은거북

 매번 구글 플레이 서비스를 유니티에 연동할 때 단번에 성공한 적이 없는거 같네요... 시간낭비가 커서 이번에 파이어 베이스를 연동하며 같이 기록을 남기고자 합니다. 

 애초에 이런 기록을 남기는게 블로그 시작의 이유였는데... 이제서야 하다니.... 

 여기서는 구글 플레이 서비스 인증과 파이어 베이스 인증, 데이터 베이스 연동까지 진행하는 플로우와 발생했던 이슈들에 대해 포스팅하겠습니다.


우선 환경은 아래와 같습니다.

  1.  Unity 2017.4.1f1
  2.  GooglePlayGamesPlugin-0.9.50
  3.  firebase_unity_sdk_4.5.2


1.  구글 플레이 서비스 와 파이어 베이스 환경설정 

  1. Google Play Console
    1. 구글 콘솔에 새 프로젝트 생성.
    2. 해당 프로젝트의 패키지로 서명된 앱을 샘플앱으로 우선 등록.
      • 패키지 등록 및 SHA-1 값을 편하게 보기 위해 ㅎㅎ ( KeyTool로 해도 되지만 개인적으로 이게 편합니다.)
  2.  Console에 게임 서비스에 프로젝트 등록.
    1. 플레이 콘솔에서 등록했던 프로젝트를 게임서비스에 등록. (Android)
    2. 파이어 베이스에서 인증에 사용하기위해 웹 어플리케이션으로도 등록.
      1. 웹 어플리케이션에 등록하는 실행 URL은 파이어베이스의 URL을 사용.
        1. ex) myproject.firebaseapp.com
        2. 위의 url은 설정의 google-services.json에서도 확인할 수 있고, 웹설정에서도 확인 가능, Authentication의 로그인 방법에 승인된 도메인에서 확인가능.
  3. FireBase 프로젝트 등록.
    1. 생성시 위의 프로젝트에 연동을 하거나, 생성 후 설정에서 안드로이드 앱을 등록하여 연동.
      1. SHA 인증서 지문 = GooglePlayConsole에서 앱로드 증명서의 SHA-1 값
    2. Authentication 의 로그인 방법에 Play 게임 사용으로 설정.
      1. 이때 등록하는 클라이언트 ID와 보안 비밀은 아래 API console에서 웹 애플리케이션의 클라이언트 ID에서 구할 수 있습니다.
        1. 반드시 웹 애플리케이션의 클라이언트 ID와 비번이여야 합니다. 웹을 통해서 인증을 받아오기 때문에
  4. API Console 설정. ( https://console.developers.google.com/)
    1.   API는 이미 위에서 FireBase와 게임서비스가 연동되면서 클라이언트 ID들이 만들어져있습니다.
    2. Android 클라이언트 ID 서명 인증서 지문 확인.
      1. 이번에도 앱로드 증명서의 SHA-1 값을 등록합니다.
  5. Unity 환경 설정.
    1. Window - GooglePlayGames - Setup - Android Setup
      1. Resource Definition 과 web App client ID 등록.
        1. 리소스 정의는 게임 서비스의 리더보드나 업적에서 리소스 받기로 받을 수 있음. 만약에 두가지를 안쓴다면 밑의 기본 리소스로 등록.
        2. WebAppClientID는 게임서비스에 등록된 웹 어플리케이션 ID 등록.
      2. FireBase 프로젝트의 설정으로 들어가 google-services.json을 다운받아 유니티 프로젝트 내에 넣는다.

2. GooglePlayService 인증 연동하기 (코드)

 

    public void activePlayGame()
    {

        PlayGamesClientConfiguration config = new PlayGamesClientConfiguration.Builder()
                    .RequestServerAuthCode(false)
                    .Build();

        PlayGamesPlatform.InitializeInstance(config);
        // recommended for debugging:
        PlayGamesPlatform.DebugLogEnabled = true;
        // Activate the Google Play Games platform
        PlayGamesPlatform.Activate();

    }
    public void googleLogin()
    {
        Social.localUser.Authenticate((success, errorMessage) =>
        {
            if (success)
            {
                authCode = PlayGamesPlatform.Instance.GetServerAuthCode();
            }
            else
            {

               Debug.Log("google " + errorMessage);
            }

            Debug.Log("google " + success);
        });
    }
    public void googleLogout()
    {
        PlayGamesPlatform.Instance.SignOut();

    }



2. 파이어 베이스 인증 연동하기. (코드)

 - 공식 문서의 예제 코드를 거의 임용한 코드.

 - 사용 전에는 반드시 connectFirebase를 먼저 호출. 

public void connectFirebase(string _authCode) { FirebaseAuth auth = Firebase.Auth.FirebaseAuth.DefaultInstance; Firebase.Auth.Credential credential = Firebase.Auth.PlayGamesAuthProvider.GetCredential(_authCode); auth.SignInWithCredentialAsync(credential).ContinueWith(task => { if (task.IsCanceled) { Debug.LogError("SignInWithCredentialAsync was canceled."); return; } if (task.IsFaulted) { Debug.LogError("SignInWithCredentialAsync encountered an error: " + task.Exception); return; } Firebase.Auth.FirebaseUser newUser = task.Result; Debug.LogFormat("User signed in successfully: {0} ({1})", newUser.DisplayName, newUser.UserId); }); } public string getFirebaseUser() { Firebase.Auth.FirebaseUser user = auth.CurrentUser; string uid = null; if (user != null) { uid = user.UserId; } return uid; } public void firebaseLogout() { FirebaseAuth.DefaultInstance.SignOut(); }



3. 파이어 베이스 데이터 베이스(실시간) 연동하기. (코드)

  - 공식 문서의 예저코드를 거의 임용한 코드 ( 델리게이트 제외)

  - 반드시 connectDatabase를 먼저 호출

    public delegate void DelGetDB(object data);
    DatabaseReference reference;

    public void connectDatabase()
    {
        // Set up the Editor before calling into the realtime database.
        FirebaseApp.DefaultInstance.SetEditorDatabaseUrl("https://myproject.firebaseio.com/");

        // Get the root reference location of the database.
        reference = FirebaseDatabase.DefaultInstance.RootReference;
    }
     public void FuncGetDB(object _data)
    {
        Debug.Log("SUCESS " + _data.ToString());
    }
    public void setData(string _userId, string _dataName,int _value)
    {
        reference.Child("users").Child(_userId).Child(_dataName).SetValueAsync(_value);
    }
    
    public void getData(string _userId, string _dataName, DelGetDB _funcDB)
    {
        reference.Child("users").Child(_userId).Child(_dataName).GetValueAsync().ContinueWith(task => {
            if (task.IsCompleted)
            {
                DataSnapshot snapshot = task.Result;
                _funcDB(snapshot.Value);
            }
            else if (task.IsFaulted)
            {
                // Handle the error...
                Debug.Log("getData fail ");
            }else
            {
                Debug.Log("getData cancel ");
            }
        });
    }





* 발생 오류들과 해결과정

 - 저한테는 요게 이 포스팅의 핵심입니다 ㅎㅎ


1) duplicate files copied in APK ~~~ (빌드 에러)

  - 사실 이전에 다른 프로젝트하면서 구글 플레이 서비스 인증을 사용했던 적이 있던터라...패키지들을 한꺼번에 추가했다가 발생했던 오류입니다.

  - 원인 - 패키지 임포트 간의 다른 폴더에 중복된 파일 추가로 추정.

  - 해결 - GooglePlayGamesPlugin-0.9.46 에서의 버그로, 0.9.50에서 수정되었다하여 버전 업 후 전체 패키지 파일 제거하여 차례로 다시 import


2) 구글 플레이 게임 로그인 시도시 Authentication fail 발생.

  - 솔직히 구글은 인증 실패시 원인같은거 안알려주고 error 코드로 Authentication fail로 알려줘서 이 부분은 원인과 해결방안이 굉장히 많겠지만... (Android Native로 개발해도 error code 하나 툭 던져주고 끝이라 고생했던 기억이...ㅠ)

  - 원인 - API 서비스의 Android Client ID의 서명 인증서가 앱 서명 인증서로 되어있었음.

  - 해결 - 업로드 인증서의 SHA-1 값으로 변경.


3) 구글 플레이 게임 로그인 시도시 Authentication fail 발생.

  - 네 위랑 똑같에요... 하지만 위와 상황은 달랐습니다.

  - 위는 순전히 구글 게임 인증만 시도했을 때고, 3)은 FireBase에서 사용하기 위한 AuthCode를 요청하는 코드를 추가했을 때 발생한 문제입니다. RequestServerAuthCode <- 요거

  - 원인 - Window - GooglePlayGames - Setup - Android Setup 의 웹 어플리케이션 ID에 오타가....

 - 해결 - 오타 수정.


4) FireBase 연동이 동작하지 않음

  - 원인 - 1)을 해결하며 가이드대로 Unity프로젝트에 넣었던 google-service.json을 같이 제거하고 다시 넣지 않음.

  - 해결 - 다시 추가.


5) DB에서 데이터를 가져올 때 제대로 데이터를 가져오지 못함.

  - 원인 - 가이드 그대로 코드했다가 별도 thread라는 걸 인지 못함. 지역 변수를 다른 스레드로 도는 함수내에서 조작하려 해서 발생.

  - 해결 - 델리게이트를 콜백 함수로 넘겨서 실행.


6) 리더보드 호출시 동작하지 않음

  - 상세 - 로그인과 리더보드 진입은 되나, 리더보드의 상세 등수를 보려하면 꺼짐.

  -  에러 코드 - There is no linked app associated with this client ID.

  - 원인 - 구글 콘솔 서비스(연결된 앱)의 안드로이드 클라이언트 ID와 API 서비스의 안드로이드 클라이언트 ID가 다름 ( 유니티에 ClientID를 잘못 등록해서 발생한걸로 추정)

  - 해결 - 콘솔 서비스의 연결된 앱에 새로 안드로이드를 등록하면 자동으로 API 서비스에 생성되니, 삭제 후 새로 생성.


Posted by 검은거북

블로그 이미지
프로그래밍 공부 요약 및 공부하며 발생한 궁금증과 해결과정을 포스팅합니다.
검은거북

공지사항

Yesterday
Today
Total

달력

 « |  » 2025.1
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

최근에 올라온 글

최근에 달린 댓글

글 보관함