[Unity3D][AWS] Unity3D에서 AWS Cognito로 계정 인증하고 AWS Lambda 함수 호출하기

게임을 만들다보니 점점 욕심도 생기고 덩치가 커지면서 '서버'가 필요하게 되었습니다. 예전부터 그냥 C++로 서버 하나 뚝딱 만들면 되겠지... 라고 생각하고 있었는데, 클라우드 서버에서 처리량에 따라 서버의 개수 조절이라던가 여러가지 설정해 줘야 하는 부분들을 생각하니 머리가 아파졌습니다.

그래서 AWS의 Lambda를 이용하여 Serverless Mobile Game 시스템을 구축해보자는 생각으로 AWS를 조금씩 도입하기 시작했습니다.

그 첫번째 단계가 바로 Cognito를 이용한 계정의 인증입니다.
Cognito란 AWS에서 제공하는 계정 인증방식입니다. 기본적으로 Device 정보와 이것저것 조합하여 각 Device별로 고유한 ID를 생성하여 사용자를 인증하고 인증 정보를 토대로 AWS의 기능에 접근하기 위한 접근 권한을 얻어내기 위한 기능입니다. Cognito에서 권한을 얻으면 AWS SDK for Unity를 통해 S3, DynamoDB, Lambda, Notification 등 각종 서비스에 접근할 수 있게 됩니다.

Cognito의 권한은 '비인증 사용자'와 '인증 사용자'로 구분하여 서로 다른 권한을 부여할 수 있습니다. '비인증 사용자'는 말 그대로 어떠한 인증없이 그냥 접속한 Guest 유저를 말합니다. '인증 사용자'란 Facebook, google, twitter, Amazon, OpenID 등을 통해 인증을 받은 유저를 말합니다. Cognito에서 해당 인증 정보에서 뭔가 데이터를 얻어오진 않고 오직 인증을 하는데만 사용한다고 합니다.

이제 Unity에서 사용하기 위한 방법을 순서대로 따라가 보겠습니다.

첫번째 순서는 회원가입입니다. 가입 절차는 생략합니다. 일단 가입하면 무료로 1년간 AWS Free-tier 관련 기능들을 사용할 수 있습니다. 성능이 좀 좋다 싶으면 무료 사용 기간에도 돈을 내야하는 옵션이 있으니 테스트 할 때 무작정 좋은 옵션 선택하지말고 주의해야 합니다.

AWS Console에 접속을 했으면 'Region'을 선택해야 합니다. 최근 Seoul 리전이 생겨서 그걸 사용해 보려고 했으나 Seoul 리전에서는 Cognito 기능을 제공하지 않아서 그나마 가까운 Tokyo 리전을 선택했습니다.

Cognito를 설정해야하니 메뉴중 Cognito를 찾아서 클릭해 줍니다.

이미 만들어둔 Identity가 있어서 화면이 좀 다를 수 있겠으나 Create new Identity pool 버튼이 있을겁니다. 그걸 클릭해 줍니다.

이제 본격적으로 Identity Pool을 생성합니다. 계정의 인증 정보들이 잔뜩 담길 곳이라서 Pool 이라고 하나봅니다. Identity Pool Name 은 적당히 앱의 이름을 적어줍니다.

Unauthenticated Identities 옵션은 앞서 말했던 '비인증 사용자'를 허용할 것인지에 대한 옵션입니다. 체크를 해주면 인증하지 않은 사용자에게도 unique identifier를 생성해 줍니다. 게임 같은 경우에는 guest 입장이 안되면 피쳐드에 지장이 생겼던거 같으니 일단 저는 체크를 해줬습니다.

이제 문제는 Authentication providers에 대한 설정입니다.  위 스크린샷에 나와 있는것 처럼 각종 인증 제공자들에 대한 설정입니다. Facebook developer console, Google Developer Console을 통해서 API 접근을 위한 App ID를 얻어올 수 있습니다.

일단 Google 로그인을 위해 Google+ 의 Client ID를 얻어오는 방법을 알아보겠습니다.

이번에도 가입 절차를 생략하고 Google Developer Console에 접속합니다. 화면 상단에 '프로젝트 선택' -> '프로젝트 생성...' 을 클릭해 줍니다.

프로젝트 이름에 앱 이름을 입력해 줍니다.
그리고 약관에 동의를 하고 만들기를 클릭해 줍니다.

몇 초만 기다리면 프로젝트가 생성됩니다.
이제 내가 만든앱이 구글에 로그인을 하기위한 기본적인 준비가 되었습니다. 이제 로그인을 위한 API에 접근하기 위한 준비를 해야합니다. 붉은 사각형으로 표시된 "API 사용 설정 및 관리" 버튼을 클릭해 줍니다.

Android에서 사용할 클라이언트ID를 만들어 봅니다.
앞서 적은 글에서 Keystore의 SHA-1 finger print를 얻어서 적어줍니다.
패키지 이름은 Unity -> Player Setting -> Other Settings -> Bundle Identifier에 설정한 것을 기록해 줍니다.

이제 OAuth 2.0 클라이언트 ID가 발급되었습니다.  붉은 사각형 영역에 있는 클라이언트 ID를 복사해 줍니다.

이제 인증 사용자와 비인증 사용자 각각을 위한 권한을 설정해 줘야 합니다. 권한 설정은 AWS IAM을 이용하여 설정할 수 있습니다. 기본적으로 Create a new IAM Role이 설정되어 있고 해당 Identity Pool 이름이 붙어 있는 Role이 생성되게 되어 있습니다. 기본 생성되는 권한은 동일하게 인증을 얻는 것이고 '인증 사용자'에게는 Cognito Sync 기능을 사용할 수 있는 권한이 부여됩니다.

이제 Cognito를 사용하여 인증을 진행할 수 있는 준비가 되었습니다.
Unity에서 AWS SDK for Unity를 통해서 인증을 얻어와 봅시다.

http://docs.aws.amazon.com/mobile/sdkforunity/developerguide/

위의  링크에서 AWS SDK for Unity를 다운로드 하고 적당한 곳에 압축을 풀어줍니다.

압축을 풀어보면 unitypackages가 포함되어 있습니다. AWS에서 사용할 기능들에 맞춰서 필요한 package를 로드하면 됩니다. 일단 Lambda를 사용할 예정이므로 Lambda Package를 로드해 줍니다.

열기를 한 후 모든 파일을 import 해 줍니다.

AWSSDK/src/GameObjects/AWSPrefab을 Scene에 올려줍니다. 이 Prefab에 포함된 Unity Initializer 컴포넌트가 AWS SDK를 초기화 해주는 역할을 합니다. 이거 포함하지 않으면 기능이 제대로 동작하지 않습니다.

_credential = new CognitoAWSCredentials(
                    "identity Pool ID", // Identity Pool ID
                    RegionEndpoint.APNortheast1 // Region
                );

이제 타이밍상 적절한 곳에 위와같은 코드를 추가하여 CognitoAWSCredentials를 생성해 주기만하면 인증을 사용하기 위한 준비가 마무리 되었습니다. 그냥 이상태로 사용하면 비인증 사용자로 인증 권한을 받아옵니다.

    IAmazonLambda _lambda;
    IAmazonLambda LambdaClient
    {
        get
        {
            if (_lambda == null) {
                _lambda = new AmazonLambdaClient(Credential, RegionEndpoint.APNortheast1);
            }
            return _lambda;
        }
    }

    public void Invoke(string func, string eventText) {
        LambdaClient.InvokeAsync(new Amazon.Lambda.Model.InvokeRequest() {
            FunctionName = func,
            Payload = eventText
        },
        (response) => {
            if (response.Exception == null) {
                Debug.Log(Encoding.ASCII.GetString(response.Response.Payload.ToArray()));
            }
            else {
                Debug.Log(response.Exception);
            }
        });
    }

이제 람다를 사용하기 위해서 위의 코드처럼 AmazonLambdaClient를 생성합니다. 이때 아까 생성한 credential 정보를 인자로 넘겨줍니다.

이렇게 생성한 IAmazonLambda 인터페이스를 통해서 InvokeAsync로 람다 함수를 호출할 수 있습니다. 이제 호출을 위한 람다 함수를 만들어 줘야 합니다.

다시 AWS Console로 돌아갑니다.

이번에는 콘솔 메뉴에서 Lambda를 선택해 줍니다.

간단한 테스트용 함수를 만들거라서 Blueprint 중에 hello world를 선택했습니다. 언어로는 Java, Node.js, Python을 사용할 수 있는데 저는 Python을 선택했습니다. 편하신 언어로 선택하면 됩니다. 각 언어에서 제공하는 라이브러리들도 활용할 수 있습니다.

FunctionName은 호출에 사용될 함수의 이름입니다. 해당 함수명으로 호출하면 이 Python 파일이 실행되고 lambda_handler 함수가 호출됩니다. 테스트만 할꺼라서 함수명도 HelloWorld라고 간단히 지어주고 특별한 코드의 수정없이 Next를 클릭하여 함수를 생성해 줍니다.

이제 스크립트의 적절한 곳에서 아까 만들어둔 Invoke함수를 호출해 줍니다. 저는 Singleton 객체로 만들어두어서 이런식으로 호출했습니다.

AWSManager.instance.Invoke("HelloWorld", "{\"key1\":\"kwon\", \"key2\":\"sang\",\"key3\":\"goo\"}");

이제 Unity에서 게임을 실행하여 정상적으로 동작하는지 확인해봅시다. 아마 이상한 exception들이 나오면서 안될겁니다. 이유는 Identity pool을 생성할 때 자동으로 만들어진 IAM의 권한에 Lambda를 실행하기 위한 권할 설정이 되어 있지 않기 때문일겁니다. 다시 AWS Console로 가서 IAM을 찾아서 클릭해 줍니다.

붉게  사각형으로 표시한 "lambda:*" 를 권한에 추가하면 해당 권한을 얻은 사용자는 AWS의 모든 Lambda를 호출할 수 있는 권한을 얻게 됩니다. 나중에 적절한 수준의 권한으로 조절할 필요가 있겠네요.

이제 Unity에서 인증을 하고 Lambda 호출까지 수행할 수 있게 되었습니다.

[Unity3D] Keystore의 fingerprint 확인하기

keystore 파일은 안드로이드에 배포할 때 사용하는 인증서 파일입니다.

Unity의 Player Setting -> Publish Settings 에서 Create New Keystore를 선택하여 생성할 수 있습니다.

보통은 디버그용 기본 Keystore를 사용하기 때문에 디버깅을 하는데는 큰 문제가 없습니다. 하지만 앱스토어에 등록할 때는 이게 큰 문제가 될 수 있습니다.

정식으로 만든 Keystore로 앱스토어에 등록을 하는데 이 Keystore가 유실되면 APK가 등록이 안됩니다. ㄷㄷㄷ

그리고 Keystore에서 SHA1 fingerprint를 사용해야할 일이 있는데 그때는 JDK에 포함된 keytool을 사용하면 됩니다.

keytool -v -list -keystore <keystorefile>

[Unity3D] EditorGUI.Vector3Field의 형태를 EditorGUILayout.Vector3Field 와 똑같이 바꾸기

Unity의 Custom Editor에서 정말 많이 사용하는 함수 중 하나가 EditorGUILayout.Vector3Field입니다.

문제는 EditorGUILayout.Vector3Field는 한줄로 이렇게 나오는데,

EditorGUI.Vector3Field는 두줄로 이렇게 나옵니다.

그래서 EditorGUI.Vector3Field도 EditorGUILayout.Vector3Field 모양으로 쓸 수는 없나 싶어서 검색해보니 이런게 있었습니다.

http://docs.unity3d.com/ScriptReference/EditorGUIUtility-wideMode.html

[Unity3D] Start와 OnDestroy에서 event 등록 해제 할 때 주의하자

C#의 event를 사용할 때 delegate에 등록을 했으면 반대로 해제를 하는 과정이 필요합니다.

OnClick += OnClickButton;

OnClick -= OnClickButton;

 

그런데 Unity에서 이를 사용할 때 주의해야 할 점이 있습니다.
바로 Start(), OnDestroy() 함수에서 이벤트를 등록하는 것은 위험하다는 점입니다.

Start() 함수는 Object가 생성된 다음번 Update 함수가 호출되기 전에 호출됩니다.
OnDestroy() 함수는 Object가 소멸되기 직전에 호출됩니다.
그래서 Start() 함수에서 이벤트 등록을 하고 OnDestroy() 함수에서 이벤트 해제를 할 수도 있습니다.

여기서 문제는 OnDestroy 입니다.
Object가 생성되자마자 SetActive(false)를 호출 하면 Start 도 호출되지 않지만 그 상태로 Destroy 해버리면 OnDestroy도 호출되지 않습니다.

이렇게 되면 문제가 없지만 Start가 호출된 이후에 SetActive(false)를 호출한 뒤 Destroy를 해버리면 이벤트가 등록은 되지만 해제는 되지 않은 채 Object는 소멸되는 좋지 않은 상황이 발생해 버립니다. 그 상태에서 event가 발생하면 null 객체의 멤버 함수가 호출되는 최악의 상황이 발생합니다. C#인데도 메모리에 잘못접근하는 거 같아 보였어요. ㅠㅠ

이런 문제를 방지하기 위해서 C#의 이벤트를 사용할 때는 OnEnable과 OnDisable을 사용하기를 추천합니다.

[Unity3D] Destroy 함수와 OnDestroy

로직을 만들다보니 특정 조건일 때 Start() 함수에서 Destroy를 호출할 일이 생겼습니다. 그런데 문득 OnDestroy는 어느 타이밍에 호출되는 것인지 정확히 알고 싶어졌습니다.

그래서 아래와 같은 코드로 테스트를 해봤습니다.

using UnityEngine;
using System.Collections;

public class Test : MonoBehaviour {
    // Use this for initialization
    void Start () {
        Destroy(gameObject);
        Debug.Log("Test Start");
    }

    void OnDestroy() {
        Debug.Log("Test Destroy");
    }
}

 

Destroy 함수를 호출하여 Object를 제거하면 즉시 제거되는 것이 아니라 해당 Frame의 마지막에 제거됩니다. 이런식으로 동작하는 것은 복잡한 로직 처리 중 오브젝트가 제거되어 null 참조가 일어나는 일을 줄여줍니다. Destroy 호출하고나서 해당 객체를 참조하는게 잘못된게 아니냐고 볼 수도 있으나 Coroutine이나 Animator등 복잡하게 연계된 로직에서는 null참조를 없애는게 더 힘듧니다.

그러면 OnDestroy는 Destroy 함수를 호출했을 때 호출될지 아니면 실제로 소멸이 일어나는 타이밍에 호출될지 테스트를 해보기 위해서 위와같은 코드를 실행해 보니 콘솔창에 아래와 같이 출력되었습니다.

Test Start
Test Destroy

OnDestroy는 실제로 소멸이 일어나는 타이밍에 객체를 소멸시키기 직전에 호출하는 모양입니다.