Xcode로 빌드를 하다보면 각종 라이브러리 관련 error로 빌드 실패하는 경우가 상당히 많이있습니다. 

이는 보통 골칫거리가 아닐텐데요. CocoaPods 를 이용하면 라이브러리들을 쉽게 설치하고 관리할 수 있습니다.

이번 시간에는 Xcode에 CocoaPods를 설치하고 CocoaPods로 필요한 라이브러리들을 다운받아보도록 하겠습니다.


먼저, CocoaPods를 설치 할 Xcode 프로젝트를 준비합니다. 

제가 준비한 프로젝트 이름은 "CocoaPodsExample" 입니다.



그 다음 터미널을 실행시켜서 준비한 프로젝트가 있는 경로로 이동합니다.

(위 에서 3번째 줄에 cd CocoaPodsExample 한 것처럼 해당 프로젝트 폴더 안까지 이동하라는 뜻)

이동했다면 다음 명령어를 입력해줍니다.


명령어 : sudo gem install cocoapods 


그 다음 Password를(본인 컴퓨터의 비밀번호) 입력하면 자동으로 다운로드 받습니다.



1 gem installed 라는 메시지와 함께 

cocoapods 다운로드가 완료된 모습을 볼 수 있습니다.



이제 다운로드 받은 cocoapods 를 설치할 차례입니다.

다음 명령어를 입력합니다.


명령어 : pod setup


잠시 후 Setup completed 라는 메시지와 함께 설치가 완료됩니다.



본격적으로 라이브러리를 다운로드하기 위해서는 "podfile" 이라는 설정파일이 필요합니다.

podfile을 생성하기 위해 다음 명령어를 입력합니다.


명령어 : touch podfile



프로젝트 위치로 가보면 podfile 이 생성된 것을 확인할 수 있습니다.



이제 설정파일에 다운로드할 라이브러리 정보를 작성하기 위해 podfile 을 열어줍니다.


명령어 : open -e podfile


물론 podfile 을 더블클릭하여 직접 열어줘도 무방합니다만

간혹 시스템 설정에 따라 직접 열기가 안되는 경우가 있는데 그런 경우 위 명령어를 입력해서 열어주세요.



저는 프로젝트에 Firebase, AdMob, GooglePlayGames 를 연동하려했기 때문에 

필요한 라이브러리를 위와 같이 작성해주었습니다.


만약 ABCDEFG 라는 라이브러리가 필요하다면

대신에 pod 'ABCDEFG' 라고 작성해주면 됩니다.



podfile에 라이브러리 작성을 끝냈다면

작성한 라이브러리를 정말로 다운받기 위해 다음 명령어를 실행해줍니다.


명령어 : pod install



관련 라이브러리들이 다운로드된 모습입니다.

그리고 프로젝트 위치에 ~.xcworkspace 라는 파일이 새로 생성됩니다!



* 여기서 주의할 점!!!


- 혹시 CocoaPods 설치 진행 중에 Xcode 프로젝트가 열려있었다면 완전히 닫아줍니다.

- 앞으로는 ~.xcodeproj 파일 대신에 새로 생성된 ~.xcworkspace 라는 파일로 프로젝트를 열어줍니다. 무조건!


그래야 CocoaPods 로 다운로드한 라이브러리들이 정상적으로 적용됩니다.



Amazon AppStore에 App 등록하기


아마존 앱스토어 링크 : https://www.amazon.com/mobile-apps/b?ie=UTF8&node=2350149011

아마존 개발자 콘솔 링크 : https://developer.amazon.com



먼저, 아마존 개발자 콘솔로 이동합니다.





계정이 없는 경우 

[I am a new customer] 를 체크하고





정보를 입력 후 [Create account] 합니다.





계정생성을 완료한 후 or 계정이 이미 있는 경우

[I am a returning customer, and my password is] 를 체크하고

로그인 합니다.





로그인 후 사이트 상단의 [APPS & SERVICES] 로 이동한 다음

[Add a New App] 을 클릭합니다.





- Android

- Mobile Web

- PC & Mac


등록할 앱의 플랫폼을 선택하고 [Next] 합니다.





앱의 이름과 카테고리, 고객 서포트 관련 정보를 입력하고 [Save] 합니다.

여기서 [App SKU] 에는 해당 앱의 고유식별코드를 입력합니다. (본인이 알아볼 수 있게 입력, 중요하지 않음)





위 처럼, 앱이 추가된 모습을 볼 수 있습니다.

세부정보를 입력하기 위해 추가한 앱의 이름을 클릭합니다.





1. General Information

2. Availability & Pricing

3. Description

4. Images & Multimedia

5. Content Rating

6. Binary File(s)


총 6단계의 절차를 완료해야 앱을 업로드할 수 있습니다.


첫 번째 [General Information] 에는 아까 입력했던 정보가 나와있습니다.

수정할 게 있으면 [Edit] 을 눌러 수정할 수 있습니다.





두 번째 [Availability & Pricing] 에서는 출시관련 정보를 입력해야합니다.


- Where would you like this app to be available? 

앱을 출시할 국가를 정합니다. 

글로벌로 출시할 것이라면 [In all countries and regions where Amazon sells apps] 을 선택하고

특정 국가를 정하고 싶으면 [Only in the following countires and regions...] 를 선택하고 원하는 지역을 체크합니다.


- Are you charging for this app?

무료로 판매할 것이라면 [No, this is a free app] 을 선택합니다.

유료로 판매할 것이라면 [Yes, my base list price is...] 를 선택하고

출시할 지역별 판매가격을 책정합니다.


- Has this app already been released?

등록하려는 앱이 아직 출시된 앱이 아니라면 [No] 를 선택합니다.

등록하려는 앱이 구글 플레이 스토어나 애플 앱스토어 등에 이미 출시된 앱이라면 [Yes, it was first available on...] 를 선택하고

스토어와 출시일을 입력해줍니다.


- When would you like this app to be available on Amazon?

해당 앱을 아마존 스토어에 언제 출시할 것인지 입력해야합니다.

나중에 수정할 수 있으니 대략적인 날짜로 우선 설정합니다.


모두 완료하면 [Save] 합니다.





세 번째 [Description] 에서는 말 그대로, 해당 앱이 스토어에서 보여질 설명들을 입력해야합니다.


간단 설명, 자세한 설명, 한 줄 피쳐, 키워드를 입력하고 [Save] 합니다.



다음으로, 네 번째 [Images & Multimedia] 에서는 스토어에 보여질 앱 아이콘과 스크린샷, 영상 등을 입력해야합니다.




[General assets] 의 내용들을 아래와 같이 채워줍니다.



그 아래,



[Fire TV assets] 에 있는 내용들도 아래와 같이 채워줍니다.




모두 채우고 [Save] 합니다.






다섯 번째 [Content Rating] 입니다.

[Subject Matter][Additional Information] 에 있는 간단한 설문에 답하고 [Save] 합니다.




마지막 여섯 번째 [BInary File(s)] 에서는 

언어지원, 바이너리정보 등의 내용을 체크, 작성한 후 [UPLOAD BINARY] 버튼을 눌러 apk 파일을 업로드 합니다.

업로드가 완료되면 [Save] 합니다.





6가지 모든 과정을 마무리했다면 우측 상단에 있는 [Submit App] 을 눌러 앱을 제출합니다.


문제가 없다면 [Availability & Pricing] 에서 입력했던 출시예정 일자에 맞춰서 스토어에 업로드됩니다.



이상으로 포스팅을 마치겠습니다!



XCode 빌드 시


Use of '@import' when modules are disabled


위와 같은 에러가 발생할 때 해결 방법!





- [Build Settings] 으로 이동


- modules 검색


- Enable Modules (C and Objectivce-C) 옵션을 No 에서 Yes 로 변경



끝 입니다 ^^




Google Mobile Ads SDK (구글 애드몹) 를 사용하는 프로젝트를 XCode에서 빌드할 때


No visible @interface for 'GADUNativeCustomTemplateAd' declares the selector 'performClickOnAssetWithKey:customClickHandler:'


이런 에러가 발생한다면!?



GADUNativeCustomTemplateAd.h 파일로 이동합니다.


*nativeCustomTemplateAd 선언 부를 찾습니다.


@property(nonatomic, strong) GADUNativeCustomTemplateAd *nativeCustomTemplateAd;

위와 같이 되어있는 부분을


@property(nonatomic, strong) GADNativeCustomTemplateAd *nativeCustomTemplateAd;

이렇게 수정합니다. (GADU... -> GAD...)





다시 빌드하면 에러가 사라졌을 것입니다!






마이크로소프트가 맥용 비쥬얼스튜디오 출시를 발표했습니다.


이제 더 이상 Mac Unity에서 MonoDevelop이 아닌 VisualStudio를 사용할 수 있게 되었습니다!







다운로드 사이트 링크 : https://www.visualstudio.com/ko/vs/visual-studio-mac


저도 소식을 접하자마자 바로 다운받았는데요!





VisualStudio를 실행한 모습입니다 ㅠㅠ


이제 Mac 환경에서도 디버깅을 할 수 있게되어 기쁘네요.




* 주의 : 이 스크립트는 유니티 에디터에서만 작동하고 런타임 중에는 사용할 수 없습니다.



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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//This script will only work in editor mode. You cannot adjust the scale dynamically in-game!
using UnityEngine;
using System.Collections;
 
#if UNITY_EDITOR 
using UnityEditor;
#endif
 
[ExecuteInEditMode]
public class ParticleScaler : MonoBehaviour
{
    public float particleScale = 1.0f;
    public bool alsoScaleGameobject = true;
 
    float prevScale;
 
    void Start()
    {
        prevScale = particleScale;
    }
 
    void Update()
    {
#if UNITY_EDITOR
        //check if we need to update
        if (prevScale != particleScale && particleScale > 0)
        {
            if (alsoScaleGameobject)
                transform.localScale = new Vector3(particleScale, particleScale, particleScale);
 
            float scaleFactor = particleScale / prevScale;
 
            //scale legacy particle systems
            ScaleLegacySystems(scaleFactor);
 
            //scale shuriken particle systems
            ScaleShurikenSystems(scaleFactor);
 
            //scale trail renders
            ScaleTrailRenderers(scaleFactor);
 
            prevScale = particleScale;
        }
#endif
    }
 
    void ScaleShurikenSystems(float scaleFactor)
    {
#if UNITY_EDITOR
        //get all shuriken systems we need to do scaling on
        ParticleSystem[] systems = GetComponentsInChildren<ParticleSystem>();
 
        foreach (ParticleSystem system in systems)
        {
            system.startSpeed *= scaleFactor;
            system.startSize *= scaleFactor;
            system.gravityModifier *= scaleFactor;
 
            //some variables cannot be accessed through regular script, we will acces them through a serialized object
            SerializedObject so = new SerializedObject(system);
 
            //unity 4.0 and onwards will already do this one for us
#if UNITY_3_5
            so.FindProperty("ShapeModule.radius").floatValue *= scaleFactor;
            so.FindProperty("ShapeModule.boxX").floatValue *= scaleFactor;
            so.FindProperty("ShapeModule.boxY").floatValue *= scaleFactor;
            so.FindProperty("ShapeModule.boxZ").floatValue *= scaleFactor;
#endif
 
            so.FindProperty("VelocityModule.x.scalar").floatValue *= scaleFactor;
            so.FindProperty("VelocityModule.y.scalar").floatValue *= scaleFactor;
            so.FindProperty("VelocityModule.z.scalar").floatValue *= scaleFactor;
            so.FindProperty("ClampVelocityModule.magnitude.scalar").floatValue *= scaleFactor;
            so.FindProperty("ClampVelocityModule.x.scalar").floatValue *= scaleFactor;
            so.FindProperty("ClampVelocityModule.y.scalar").floatValue *= scaleFactor;
            so.FindProperty("ClampVelocityModule.z.scalar").floatValue *= scaleFactor;
            so.FindProperty("ForceModule.x.scalar").floatValue *= scaleFactor;
            so.FindProperty("ForceModule.y.scalar").floatValue *= scaleFactor;
            so.FindProperty("ForceModule.z.scalar").floatValue *= scaleFactor;
            so.FindProperty("ColorBySpeedModule.range").vector2Value *= scaleFactor;
            so.FindProperty("SizeBySpeedModule.range").vector2Value *= scaleFactor;
            so.FindProperty("RotationBySpeedModule.range").vector2Value *= scaleFactor;
 
            so.ApplyModifiedProperties();
        }
#endif
    }
 
    void ScaleLegacySystems(float scaleFactor)
    {
#if UNITY_EDITOR
        //get all emitters we need to do scaling on
        ParticleEmitter[] emitters = GetComponentsInChildren<ParticleEmitter>();
 
        //get all animators we need to do scaling on
        ParticleAnimator[] animators = GetComponentsInChildren<ParticleAnimator>();
 
        //apply scaling to emitters
        foreach (ParticleEmitter emitter in emitters)
        {
            emitter.minSize *= scaleFactor;
            emitter.maxSize *= scaleFactor;
            emitter.worldVelocity *= scaleFactor;
            emitter.localVelocity *= scaleFactor;
            emitter.rndVelocity *= scaleFactor;
 
            //some variables cannot be accessed through regular script, we will acces them through a serialized object
            SerializedObject so = new SerializedObject(emitter);
 
            so.FindProperty("m_Ellipsoid").vector3Value *= scaleFactor;
            so.FindProperty("tangentVelocity").vector3Value *= scaleFactor;
            so.ApplyModifiedProperties();
        }
 
        //apply scaling to animators
        foreach (ParticleAnimator animator in animators)
        {
            animator.force *= scaleFactor;
            animator.rndForce *= scaleFactor;
        }
#endif
    }
 
    void ScaleTrailRenderers(float scaleFactor)
    {
        //get all animators we need to do scaling on
        TrailRenderer[] trails = GetComponentsInChildren<TrailRenderer>();
 
        //apply scaling to animators
        foreach (TrailRenderer trail in trails)
        {
            trail.startWidth *= scaleFactor;
            trail.endWidth *= scaleFactor;
        }
    }
}
 
cs



ParticleScaler.cs


위 스크립트를 ParticleSystem 오브젝트에 갖다붙이고



    



인스펙터에서 ParticleScale 변수를 조절해주면


모든 설정값들이 스케일 1 기준으로 딱딱딱 바뀌는 것을 볼 수 있습니다.


왼쪽은 원본 파티클이고 오른쪽은 ParticleScaler.cs 로 스케일 2배한 파티클입니다.


참 편리한 녀석이죠?


유용하게 쓰세요!





코드 출처 : https://github.com/hypno2000/starcontrol/blob/master/Assets/ParticleScaler/ParticleScaler.cs




[Unity3D] 구글 플레이 게임 서비스 & 애플 게임 센터 연동 (1/2) #설정편 링크 : http://minhyeokism.tistory.com/70



* 본격적으로 진행하기 전에 한 가지 말씀드리자면 


UnityEngine.Social.localUser 는 Androiod와 iOS 두 플랫폼을 모두 지원하고


현재 앱의 Platform을 알아서 판단하여


Android는 구글 플레이 게임 서비스의 api를


iOS는 애플 게임선터의 api를 호출하도록 할 수 있습니다.


다시 말해,



1
2
3
4
    Social.localUser.Authenticate((bool success) =>
    {
 
    });
cs



위 처럼 Social.localUser.~~~ 를 하면 Android 든 iOS 든 알아서! 문제없이! 작동한다는 뜻입니다.


그러나 특별한 상황에서는 구분을 명확하게 해야할 필요가 있습니다. 그럴 때에는


- Android일 경우 PlayGamesPlatform. 을


- iOS일 경우 GameCenterPlatform. 을 


쓰시면 됩니다.


또한, iOS 앱에서도 PlayGamesPlatform.을 사용하면 게임센터가 아닌 구글 플레이에 로그인하도록 할 수도 있습니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    void Start()
    {
#if UNITY_ANDROID
 
        PlayGamesClientConfiguration config = new PlayGamesClientConfiguration.Builder()
            .EnableSavedGames()
            .Build();
 
        PlayGamesPlatform.InitializeInstance(config);
 
        PlayGamesPlatform.DebugLogEnabled = true;
 
        PlayGamesPlatform.Activate();
 
#elif UNITY_IOS
 
        GameCenterPlatform.ShowDefaultAchievementCompletionBanner(true);
 
#endif
    }
cs



제일 먼저 Initialization과 Configuration 부분입니다.


구글의 저장된 게임 기능을 사용할 것이라면 (for Android)


.EnableSavedGames() 를 config에 추가해주세요.


마지막 줄에 PlayGamesPlatform.Activate() 를 해줘야 모든 기능이 작동합니다.


iOS는 특별한 config 빌드가 없어도 됩니다.



* 한 가지 주의할 점!


iOS 앱에서 안드로이드 용인 PlayGamesPlatform.Activate() 가 호출되면


애플 게임센터 api가 제대로 작동하지 않는 것을 경험했습니다.


때문에 게임센터에 접근하려면 위 처럼 코드를 #if 와 #elif 로 나눠주는 것이 좋습니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    public void SignIn()
    {
        Social.localUser.Authenticate((bool success) =>
        {
            if (success)
            {
                // to do ...
                // 로그인 성공 처리
            }
            else
            {
                // to do ...
                // 로그인 실패 처리
            }
        });
    }
cs



Sign In 부분입니다.


Social.localUser로 Authenticate 하고 있기 때문에


Android와 iOS 둘 다 작동합니다.



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
32
33
34
35
36
    public void SignIn()
    {
#if UNITY_ANDROID
 
        PlayGamesPlatform.Instance.Authenticate((bool success) =>
        {
            if (success)
            {
                // to do ...
                // 구글 플레이 게임 서비스 로그인 성공 처리
            }
            else
            {
                // to do ...
                // 구글 플레이 게임 서비스 로그인 실패 처리
            }
        });
 
#elif UNITY_IOS
 
        Social.localUser.Authenticate((bool success) =>
        {
            if (success)
            {
                // to do ...
                // 애플 게임 센터 로그인 성공 처리
            }
            else
            {
                // to do ...
                // 애플 게임 센터 로그인 실패 처리
            }
        });
 
#endif
    }
cs



필요에 따라 이렇게 명확하게 구분하여 구현해도 됩니다.


(GameCenterPlatform 에는 Authenticate가 없기때문에 Social.localUser로 접근함)





플레이 게임 서비스 Sign Out 은 이렇게 하시면 됩니다.


(게임 센터 Sign Out api는 별도로 확인하지 못 함)



다음으로 업적과 리더보드를 구현해보겠습니다.



1
2
3
4
5
6
7
8
9
10
11
    public void UnlockAchievement(int score)
    {
        if (score >= 100)
        {
#if UNITY_ANDROID
            PlayGamesPlatform.Instance.ReportProgress(GPGSIds.achievement_100, 100f, null);
#elif UNITY_IOS
            Social.ReportProgress("Score_100", 100f, null);
#endif
        }
    }
cs



먼저, 업적을 잠금해제하는 코드 예시입니다.


Social.ReportProgress("업적ID", 0f~100f, callback);


첫 번째 파라미터에는 잠금해제 할 업적의 고유 ID를 입력해주고

(설정편에서 생성된 GPGSIds.cs에 있는 static 변수를 가져다 쓸 것)


두 번째 파라미터에는 해당 업적의 진행도(0f~100f) 를 넣어줍니다.

진행도가 존재하는 업적의 경우 진행도를 0과 100사이의 float형으로 환산하여 넣어주면 되고

단순히 조건을 충족하면 바로 잠금해제되는 업적일 경우 바로 100f를 넣어주면 됩니다.


세 번째 파라미터에는 callback 함수를 넣어줍니다.

콜백이 필요 없는 경우에는 null



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
    public void ShowAchievementUI()
    {
        // Sign In 이 되어있지 않은 상태라면
        // Sign In 후 업적 UI 표시 요청할 것
        if (Social.localUser.authenticated == false)
        {
            Social.localUser.Authenticate((bool success) =>
            {
                if (success)
                {
                    // Sign In 성공
                    // 바로 업적 UI 표시 요청
                    Social.ShowAchievementsUI();
                    return;
                }
                else
                {
                    // Sign In 실패 처리
                    return;
                }
            });
        }
 
        Social.ShowAchievementsUI();
    }
cs



업적 UI를 열려면 


Social.ShowAchievementsUI() 를 호출하면 됩니다.


* 소셜에 로그인이 되어있지 않다면 로그인 후 재요청하는 예외처리를 해주는 것이 좋습니다.



    



좌 : Android - Google Play Games Services 업적 UI 스크린샷

우 : iOS - Apple Game Center 목표 달성 UI 스크린샷



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
32
33
34
35
36
    public void ReportScore(int score)
    {
#if UNITY_ANDROID
 
        PlayGamesPlatform.Instance.ReportScore(score, GPGSIds.leaderboard_score, (bool success) =>
        {
            if (success)
            {
                // Report 성공
                // 그에 따른 처리
            }
            else
            {
                // Report 실패
                // 그에 따른 처리
            }
        });
 
#elif UNITY_IOS
 
        Social.ReportScore(score, "Leaderboard_ID", (bool success) =>
            {
                if (success)
                {
                    // Report 성공
                    // 그에 따른 처리
                }
                else
                {
                    // Report 실패
                    // 그에 따른 처리
                }
            });
        
#endif
    }
cs



다음은 리더보드에 점수를 기록하는 코드 예시입니다.


Social.ReportScore(점수, "리더보드 고유 ID", callback);


첫 번째 파라미터에는 플레이어가 기록한 점수를 넣어줍니다.


두 번째 파라미터에는 점수를 올릴 리더보드의 고유 ID를 넣어줍니다.

(설정편에서 생성된 GPGSIds.cs에 있는 static 변수를 가져다 쓸 것)


세 번째 파라미터에는 callback 함수를 넣어줍니다.

콜백이 필요 없는 경우에는 null


* ReportScore() 함수 내부에서 플레이어의 기록된 점수와 파라미터로 날아온 점수의 높낮이를 비교하고 필터링하기 때문에

 더 낮은 점수일 경우의 예외처리를 할 필요가 없습니다.



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
    public void ShowLeaderboardUI()
    {
        // Sign In 이 되어있지 않은 상태라면
        // Sign In 후 리더보드 UI 표시 요청할 것
        if (Social.localUser.authenticated == false)
        {
            Social.localUser.Authenticate((bool success) =>
            {
                if (success)
                {
                    // Sign In 성공
                    // 바로 리더보드 UI 표시 요청
                    Social.ShowLeaderboardUI();
                    return;
                }
                else
                {
                    // Sign In 실패 
                    // 그에 따른 처리
                    return;
                }
            });
        }
 
#if UNITY_ANDROID
        PlayGamesPlatform.Instance.ShowLeaderboardUI();
#elif UNITY_IOS
        GameCenterPlatform.ShowLeaderboardUI("Leaderboard_ID", UnityEngine.SocialPlatforms.TimeScope.AllTime);
#endif
    }
cs



리더보드의 UI를 열려면


Social.ShowLeaderboardUI()

PlayGamesPlatform.Instance.ShowLeaderboardUI()

GameCenterPlatform.ShowLeaderboardUI("순위표 고유 ID", TimeScope)


를 상황에 맞게 호출합니다.


* 마찬가지로, 소셜에 로그인이 되어있지 않다면 로그인 후 재요청하는 예외처리를 해주도록 합니다.





리더보드 UI를 열 때


위 처럼 TimeScope 를 정해서 넘겨주면 원하는 기간동안의 순위표를 보게할 수도 있습니다.


AllTime - 전체 기간 순위표

Today - 오늘 순위표

Week - 이번 주 순위표



    



Android - Google Play Games Services 리더보드 UI 스크린샷



    



iOS - Apple Game Center 순위표 UI 스크린샷




이상으로 포스팅을 마치겠습니다.


궁금한 점이 있다면 댓글 남겨주세요.



안녕하세요.


이번 포스팅의 주제는 Google Play Games 플러그인 연동하기 입니다!


유니티 프로젝트에


     Android - 구글 플레이 게임 서비스 (Google Play Games Services)

     iOS - 애플 게임 샌터 (Apple Game Center)


를 연동하고


1. 로그인(SignIn), 2. 로그아웃(SignOut), 3. 업적(Achievement), 4. 리더보드(Leaderboard) 


기능을 구현해보도록 하겠습니다.


물론 Google Play Games plugin for Unity - Github 사이트에 영문으로


업적, 리더보드, 저장된 게임, 퀘스트 등 다양한 기능 연동 가이드가 있습니다.


링크 : https://github.com/playgameservices/play-games-plugin-for-unity





우선 위 링크에서 [Clone or download] -> [Download.ZIP] 하여 플러그인을 다운로드합니다.


압축을 풀고 내용물을 보면





이렇게 되어있는데


맨 위 current-build 폴더에 최신 버전 플러그인.unitypackage가 들어있습니다.


(이 플러그인은 구글 플레이 게임서비스와 애플 게임센터 두 플랫폼 다 지원함)




유니티 프로젝트에 import 해둡니다.




[Android - 구글 플레이 게임 서비스 설정]



먼저, 구글 플레이 개발자 콘솔로 이동하여 (회원가입 후) 로그인 합니다.


플레이 콘솔 사이트 링크 : https://play.google.com/apps/publish/?hl=ko


(Google Play Developer Console의 탐색 기능과 디자인이 업데이트되었으며 이름이 Play Console로 변경되었습니다 - 17년 4월 기준)





우측에 [애플리케이션 만들기] 버튼을 클릭합니다. (플레이 콘솔에 앱을 추가하지 않은 경우만)





언어를 설정하고 제목을 입력한 후 [만들기] 하고


스토어 등록정보 등 기타 내용을 채워서 출시 조건을 맞춰줍니다.





애플리케이션 설정을 완료했다면 [게임 서비스]로 이동하여 [새 게임 추가]를 클릭합니다.





게임 이름을 입력하고 카테고리 선택 후 [계속] 합니다.


애플리케이션 설정과 마찬가지로 [게임 세부정보]에 내용을 채워줍니다.





그 다음 [연결된 앱]으로 이동하고 플랫폼을 선택합니다.





이전에 만들어 두었던 애플리케이션을 게임 서비스와 연결하는 과정입니다.


앱 이름과 패키지 이름, 기타 설정을 마치고 [저장하고 계속] 합니다.





연결이 정상적으로 완료된 모습입니다.







업적과 리더보드를 각각 추가해줍니다.


(참고로, 업적이나 리더보드 아이콘을 넣어주지 않으면 디폴트 아이콘이 보여집니다.)


그러고 나면 하단에 [리소스 받기] 가 보이실텐데요 한번 클릭해 봅시다.





위 처럼 [리소스 내보내기] 라고 뜨는데요


Android 앱은 [ANDROID] 탭을 눌러서 내용을 복사해둡니다.


(iOS 앱은 [OBJECTIVE-C] 탭을 눌러서 복사)


이제 유니티로 돌아옵니다.





플러그인이 정상적으로 임포트 되었다면 Window에 Google Play Games 메뉴가 생깁니다.


그럼 [Window] -> [Google Play Games] -> [Setup] -> [Android setup] 해줍니다.





위와 같은 팝업창이 뜨면 [Resources Definition]


아까 복사해 두었던 리소스를 붙여넣기 합니다.


[Web App Client ID]는 웹 게임용 변수이므로 패스하고 [Setup] 버튼을 누릅니다.





Setup이 완료되었습니다.


[OK]를 누르고 로딩을 기다리면





위 처럼 GPGSIds 라는 .cs 파일이 생성되었을 것입니다.





앞서 만들어둔 업적과 리더보드의 ID를 string 형식으로 담고있는 모습입니다.


업적을 잠금해제하거나 리더보드에 스코어를 올릴 때


static 클래스 GPGSIds의 string 변수를 가져다 쓸 것입니다.




[iOS - 애플 게임센터 설정]



아이튠즈 커넥트 사이트로 이동하여 회원가입 후 로그인 합니다.


iTunes Connect 사이트 링크 : https://itunesconnect.apple.com





로그인 후 메인 페이지에서 [나의 앱] 으로 이동합니다.





[나의 앱] 페이지 좌상단에 있는 [+ 버튼] 을 누른 후 [신규 앱] 을 클릭합니다.


이미 등록된 앱이 있다면 앱 아이콘을 클릭합니다.





플랫폼, 이름, 기본 언어, 번들 ID, SKU 를 입력하고 [생성] 합니다.





앱이 정상적으로 생성되었다면 앱 아이콘을 눌러 앱 페이지로 이동합니다.


그 다음 [앱 내 추가 기능] -> [Game Center] 메뉴로 이동합니다.





순위표와 목표 달성 (각각 리더보드와 업적을 뜻함) 이


비어있는 모습입니다.


먼저 순위표 옆에 있는 +버튼을 눌러줍니다.





개별 순위표를 [선택] 합니다.





개별 순위표 정보를 채워줍니다.


그리고 하단 순위표 현지화에 있는 [언어 추가] 를 눌러줍니다.





현지화 할 언어를 선택합니다.


나머지 정보를 입력하고 (이미지는 선택사항, 이미지를 올리지 않으면 디폴트 이미지가 보여짐) [Save] 합니다.


이제 리더보드가 준비되었습니다.


다음으로 업적을 추가해 봅시다.





목표 달성 옆에 있는 +버튼을 눌러서 업적을 생성합니다.


업적 정보를 입력하고 하단 목표 달성 현지화에 있는 [언어 추가] 를 눌러줍니다.





마찬가지로 현지화 할 언어를 선택하고 제목과 설명을 입력하고 [Save] 합니다.


위는, 업적 아이콘을 원하는 이미지로 직접 설정해 준 모습입니다.





다시 유니티로 돌아와서


[Window] -> [Google Plat Games] -> [Setup] -> [iOS setup...] 합니다.






팝업 창이 뜨면 플레이 콘솔 [리소스 내보내기] 에서 [OBJECTIVE-C] 를 선택하고 


복사 & 붙여넣기 합니다.


그리고 [Setup] 해줍니다.




자, 이제 정말로 연동할 준비가 다 되었습니다!


다음 편에 계속됩니다!


[Unity3D] 구글 플레이 게임 서비스 & 애플 게임 센터 연동 (2/2) #코드편 링크 : http://minhyeokism.tistory.com/72



이번에는 유니티에 구글 애드몹 SDK를 연동하여 [Banner 광고][Interstitial 광고]를 달아보도록 하겠습니다.



우선 AdMob 홈페이지에 들어가서 회원가입 후 로그인 합니다.


Google AdMob 사이트 링크 : http://www.google.co.kr/admob





홈페이지에서 [수익 창출] -> [+새로운 앱에서 수익 창출] 합니다.





1. 앱 선택


이미 스토어에 올라간 앱이라면 [앱 검색]


개발 중인 앱이라면 [앱 직접 추가]


이미 추가된 앱의 광고를 생성하려면 [추가한 앱 중에서 선택]


본 포스팅에서는 개발 중인 앱이라 가정하고 [앱 직점 추가] 로 진행


앱 이름과 플랫폼을 선택 후 [앱 추가] 를 누릅니다.





이제 ca-app-pub 로 시작하는 앱 ID가 생성되었음을 볼 수 있습니다.


2. 광고 형식 선택 및 광고 단위 이름 지정


먼저 [배너] 를 선택해주고


[광고 유형] [자동 새로고침] [텍스트 광고 스타일] [광고 단위 이름] 모두 설정해주고 [저장] 을 누릅니다.





앱 ID와 마찬가지로 [광고 단위 ID] 도 생성되었습니다.


3. Firebase 애널리틱스 설정(선택사항)


Firebase 애널리틱스 관련 내용은 다음에 포스팅 할 예정이니 [건너 뛰기] 누릅니다.





4. 구현 안내 보기


Banner 광고 생성을 완료하였으니


Interstitial 광고(전면 광고)도 생성하기 위해 [다른 광고 단위 생성] 합니다.





[전면] 을 선택하고 


[광고 유형][게재빈도 설정][광고 단위 이름] 을 설정하고 [저장] 합니다.





배너 광고 단위와 전면 광고 단위가 잘 생성되었음을 볼 수 있습니다.


이제 본격적으로 유니티에 연동할 차례입니다.





우선 아래 링크에서 AdMob Unity Plugin(GoogleMobileAds.unitypackage)을 다운로드 받습니다.


Google AdMob Unity Plugin(v3.4.0) 다운로드 링크 : https://github.com/googleads/googleads-mobile-unity/releases/tag/v3.4.0


다운로드한 unitypackage를 유니티 프로젝트에 import 합니다.



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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
using UnityEngine;
using System;
using GoogleMobileAds.Api;
 
public class AdMobManager : MonoBehaviour
{
    public string android_banner_id;
    public string ios_banner_id;
 
    public string android_interstitial_id;
    public string ios_interstitial_id;
 
    private BannerView bannerView;
    private InterstitialAd interstitialAd;
 
    public void Start()
    {
        RequestBannerAd();
        RequestInterstitialAd();
 
        ShowBannerAd();
    }
 
    public void RequestBannerAd()
    {
        string adUnitId = string.Empty;
 
#if UNITY_ANDROID
        adUnitId = android_banner_id;
#elif UNITY_IOS
        adUnitId = ios_bannerAdUnitId;
#endif
 
        bannerView = new BannerView(adUnitId, AdSize.SmartBanner, AdPosition.Top);
        AdRequest request = new AdRequest.Builder().Build();
 
        bannerView.LoadAd(request);
    }
 
    private void RequestInterstitialAd()
    {
        string adUnitId = string.Empty;
 
#if UNITY_ANDROID
        adUnitId = android_interstitial_id;
#elif UNITY_IOS
        adUnitId = ios_interstitialAdUnitId;
#endif
 
        interstitialAd = new InterstitialAd(adUnitId);
        AdRequest request = new AdRequest.Builder().Build();
 
        interstitialAd.LoadAd(request);
 
        interstitialAd.OnAdClosed += HandleOnInterstitialAdClosed;
    }
 
    public void HandleOnInterstitialAdClosed(object sender, EventArgs args)
    {
        print("HandleOnInterstitialAdClosed event received.");
 
        interstitialAd.Destroy();
 
        RequestInterstitialAd();
    }
 
    public void ShowBannerAd()
    {
        bannerView.Show();
    }
 
    public void ShowInterstitialAd()
    {
        if (!interstitialAd.IsLoaded())
        {
            RequestInterstitialAd();
            return;
        }
 
        interstitialAd.Show();
    }
 
}
 
cs



AdMobManager.cs를 생성하고 위 코드를 복사 붙여넣기 합니다.


빈 게임 오브젝트를 생성하고 AdMobManager.cs를 Add Componet 해줍니다.





플랫폼 별로, banner_id 와 interstitial_id 변수 값에 앞서 얻은 id를 각각 입력해줍니다.


AdMob은 RequestAd() 로 광고를 요청하고 요청에 성공하면 ShowAd()로 광고를 송출하는 형식입니다.


배너광고는 이니셜라이즈 시 Request 하고 바로 Show 합니다.


전면광고는 미리 Request 로 캐싱해 두다가 원하는 상황에 Show 한 후 광고가 닫히면 재요청하는 구조입니다.


메모리 누수를 방지하기 위해


HandleOnInterstitialAdClosed 콜백에서 interstitialAd.Destroy(); 를 해주도록 합니다.





BannerView를 생성할 때 파라미터로 사이즈와 위치를 조절할 수 있는데요


AdSize.SmartBanner 로 설정해주면 디바이스의 해상도에 맞춰서 자동으로 크기 조절이 되고


AdPosition 파라미터로 배너의 위치를 조절할 수 있습니다.





위 스크린샷은 배너가 AdPosition.Top 으로 설정된 모습입니다.


전면 광고는 게임 오버나 버프 지급 등 특정 상황에 송출하도록 합니다.


RequestInterstitialAd() 로 캐싱해 두었던 전면광고를


원하는 상황에 ShowAd() 해줌으로써 광고를 송출시킵니다.


광고 시청이 완료되면 HandleOnInterstitialAdClosed 콜백이 호출되는데


콜백 안에 광고 시청에 대한 보상을 지급하고 


RequestInterstitialAd() 로 소모된 광고를 미리 재 요청해놓습니다.





게임 내 애드몹 전면광고가 송출된 모습입니다.


이상으로 포스팅을 마치도록 하겠습니다.



이번에는 Unity 프로젝트에 AdColony SDK 연동을 해보겠습니다! (역시 매우 간단)


AdColony 홈페이지 링크 : https://www.adcolony.com

AdColony SDK 다운로드 링크 : https://github.com/AdColony/AdColony-Unity-SDK-3



위의 AdColony GitHub 링크로 가서


[Clone or download -> Download ZIP]


한 후, 다운로드 받은 애드콜로니 유니티패키지를 프로젝트에 import 합니다.





다음으로 진행하기 전에 기본적인 세팅이 필요한데요


우선 애드콜로니에 회원가입하고 로그인 합니다.


그 다음 [MONETIZATION] 탭에서 [Setup New App] 을 클릭합니다





App의 Platform과 국가, 이름


광고를 Skip 가능하게 할 지와 


13세 미만 어린이에게 지도 감독이 필요한 앱인지 여부를 체크합니다.


그리고 앱의 특성이나 출시 국가에 따라 다르겠지만 웬만한 정책위반을 피하기 위해


되도록이면 [Customize Your Ads] 부분에 정치, 종교, 성인 등의 광고 타입은 체크해제 해주는 것이 좋습니다. 


모든 세팅이 완료되면 [Create] 합니다.





아래는 광고 App의 기본적 Setup이 완료된 모습입니다.


[AdColony App ID] 는 스크립트를 작성할 때 필요하므로 잘 기억해둡니다.


하단에 디폴트로 생선된 Zone [Ad Zone #1] 이 보일텐데 Zone 세팅을 위해 클릭해줍니다.





[Zone is active] - Yes 해주고


[Zone ID] 도 [AdColony App ID]  마찬가지로 스크립트 작성에 필요하므로 잘 기억해둡니다.


Zone Name과 Creative Type을 설정해줍니다. (우리는 비디오 광고를 연동할 것이기 때문에 Video로 설정)





그 다음 [Zone Type] 을 아래와 같이 


Value Exchange/V4VC 로 설정해줍니다.


모든 설정을 마치면 [Save] 합니다.





여기까지 되었다면 다시 유니티로 돌아와서


AdColonyManager 라는 스크립트를 만들고


아래의 코드를 복사 붙여넣기 합니다.


애드콜로니 사이트에서 App 세팅할 때 보았던


[AdColony App ID] 와 [Zone ID] 를 각각 플랫폼별 id 변수에 넣어줍니다.


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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
using UnityEngine;
using System.Collections.Generic;
 
public class AdColonyManager : MonoBehaviour
{
    private const string android_appId = "xxxxxxxxxxxxxxxxxxx";
    private const string android_zoneId = "xxxxxxxxxxxxxxxxxxx";
 
    private const string ios_appId = "xxxxxxxxxxxxxxxxxxx";
    private const string ios_zoneId = "xxxxxxxxxxxxxxxxxxx";
 
    private string appId = string.Empty;
    private string zoneId = string.Empty;
 
    private AdColony.InterstitialAd ad = null;
 
    void Start()
    {
        Initialize();
    }
 
    private void Initialize()
    {
#if UNITY_ANDROID
        this.appId = android_appId;
        this.zoneId = android_zoneId;
#elif UNITY_IOS
        this.appId = ios_appId;
        this.zoneId = ios_zoneId;
#endif
 
        AdColony.Ads.OnConfigurationCompleted += (List<AdColony.Zone> zones_) =>
        {
            Debug.Log("AdColony.Ads.OnConfigurationCompleted called");
 
            if (zones_ == null || zones_.Count <= 0)
            {
                Debug.Log("Configure Failed");
            }
            else
            {
                Debug.Log("Configure Succeeded.");
            }
        };
 
        AdColony.Ads.OnRequestInterstitial += (AdColony.InterstitialAd ad_) =>
        {
            Debug.Log("AdColony.Ads.OnRequestInterstitial called");
 
            ad = ad_;
 
            // to do ...
            // 광고 요청에 성공했을 때 처리
 
            ShowAd();
        };
 
        AdColony.Ads.OnRequestInterstitialFailed += () =>
        {
            Debug.Log("AdColony.Ads.OnRequestInterstitialFailed called");
 
            // to do ...
            // 광고 요청에 실패했을 때 처리
        };
 
        AdColony.Ads.OnOpened += (AdColony.InterstitialAd ad_) =>
        {
            Debug.Log("AdColony.Ads.OnOpened called");
        };
 
 
        AdColony.Ads.OnClosed += (AdColony.InterstitialAd ad_) =>
        {
            Debug.Log("AdColony.Ads.OnClosed called, expired: " + ad_.Expired);
        };
 
        AdColony.Ads.OnExpiring += (AdColony.InterstitialAd ad_) =>
        {
            Debug.Log("AdColony.Ads.OnExpiring called");
        };
 
        AdColony.Ads.OnRewardGranted += (string zoneId, bool success, string name, int amount) =>
        {
            Debug.Log(string.Format("AdColony.Ads.OnRewardGranted called\n\tzoneId: "
                + "{0}\n\tsuccess: {1}\n\tname: {2}\n\tamount: {3}",
                zoneId, success, name, amount));
 
            if (success)
            {
                // to do ...
                // 광고 시청이 완료되었을 때 처리
                // 광고 시청에 대한 보상 지급 등 ...
            }
        };
 
        AdColony.AppOptions appOptions = new AdColony.AppOptions();
        appOptions.AdOrientation = AdColony.AdOrientationType.AdColonyOrientationAll;
 
        AdColony.Ads.Configure(this.appId, appOptions, this.zoneId);
    }
 
    public void RequestAd()
    {
        Debug.Log("**** Request Ad ****");
 
        AdColony.AdOptions adOptions = new AdColony.AdOptions();
        adOptions.ShowPrePopup = false;
        adOptions.ShowPostPopup = false;
 
        AdColony.Ads.RequestInterstitialAd(this.zoneId, adOptions);
    }
 
    public void ShowAd()
    {
        Debug.Log("**** Show Ad ****");
 
        if (this.ad != null)
        {
            AdColony.Ads.ShowAd(this.ad);
        }
    }
 
}
 
cs



RequestAd() 함수로 광고 요청을 보낸 후


요청에 성공했다면 OnRequestInterstitial 콜백을 타고


ShowAd() 함수를 호출되는 구조입니다. (요청에 실패하면 OnRequestInterstitialFailed 콜백)


그리고 ShowAd() 로 광고 시청이 완료되면 


OnRewardGranted 콜백이 호출되므로 (success == true)


콜백 안에 광고 시청에 대한 보상을 지급하는 코드를 작성해주면 됩니다.



* 광고 procedure


Initialize() -> RequestAd() -> OnRequestInterstitial -> ShowAd() -> OnRewardedGranted



위와 같은 절차로 여러분의 프로젝트에 코드를 녹여주시고 


OnRequestInterstitialFailed 나 OnExpiring 등 기타 콜백함수도 입맛에 맞게 정의해주시면 되겠습니다.


참고로 AdColony는 UnityAds와 다르게 Editor 상에서는 테스트가 불가능하므로 모바일에서 테스트해보시길 바랍니다!



+ Recent posts