Unity 멤버십
Unity에서 Hybe Game Platform Service(이하, 플랫폼, Platform) 연동을 위한 SDK 사용법에 대해서 설명합니다.
요구사항
Unity용 Platform SDK를 사용하기 위한 개발 환경은 아래와 같습니다.
최소 사양 : 2021.3.1f
OS : Windows, Mac OS x86/silicon
환경 구성
SDK 설치
Unity에서 Platform SDK를 사용하기 위한 프로젝트를 구성하는 방법은 다음과 같습니다.
지정된 메뉴에서 SDK를 다운로드 합니다.
Unity에서 Assets > Import Package > Custom Package... 메뉴를 차례로 선택한 후 HyPlatformPlugin.unitypackage 파일을 선택합니다.
Import 팝업에서 [import] 버튼을 클릭하여 SDK를 현재 프로젝트에 설치합니다.
샘플 실행하기
SDK와 함께 제공된 샘플 프로젝트(HyPlatform-Starter) 실행 방법에 대해서 설명합니다.
샘플 디렉토리 구조
Platform SDK 에 포함된 구성은 다음과 같습니다.
Editor Default Resources
Firebase에서 사용하는 이미지 리소스
ExternalDependencyManager
Android, iOS 외부 모듈 종속성(dependency)를 체크하는 DLL. Unity 에디터의 Assets/External Depency Manger 하위에 OS별 Dependency 체크 메뉴에 관련 기능이 표시됩니다.
Firebase
Firebase 라이브러리 파일
HyPlatform
Platform 플러그인 DLL 파일. Android, iOS, Windows로 각각의 DLL이 위치합니다.
Resources/Platform/dev
실행환경(dev)에 디폴트 설정 파일(PlatformConfig.json)이 위치합니다.
HyPlatformSample
SDK를 이용한 플랫폼 로그인 샘플
- Scene/LoginScene : 샘플 Scene
로그인 Flow
플랫폼 초기화
연동 환경 Configuration 준비
Platform에 연동하기 위해서는 전달 받은 실행 환경 JSON을 준비합니다.
게임 플랫폼에 따라서 세부 항목이 달라질 수 있습니다. 지원되는 세부 항목은 Platform Configuration을 참고하세요.
{
"BuildEnv": "dev",
"CustomScheme": "platformstarter"
}
플랫폼 초기화
플랫폼 플러그인 초기화(Mount)를 수행합니다.
Firebase 초기화
푸시 초기화
접속 국가 정보 조회 등
Mount 호출 전에 SDK 실행 과정에서 보관되는 데이터를 저장할 경로를 지정해야 합니다.
Mount 비동기 함수로 호출 결과를 리턴 합니다.
//1. SDK 접근 가능한 스토리지 경로를 설정한다.
PlatformSupervisor.SetWritableStorageDir(GetDownloadPath());
//2. 플랫폼 SDK를 초기화 한다.
PlatformSupervisor.Mount("dev").ContinueWithOnMainThread( task =>
{
// 초기화 실패
if(task.Result == false)
return;
}
Auth 이벤트 리스너 등록
플랫폼 로그인 연동 과정에서 발생하는 이벤트를 수신하기 위해 Auth 이벤트 리스너 및 웹뷰 이벤트 리스너를 등록합니다.
PlatformSupervisor.Mount("dev").ContinueWithOnMainThread( task =>
{
if(task.Result == false)
return;
//1. Auth 이벤트 리스너 등록
PlatformSupervisor.SetAuthEventDelegate(OnLoginSuccess, OnLoginFailure);
PlatformSupervisor.SetLogoutEventDelegate(OnLogoutSuccess, OnLogoutFailure, OnWithdraw);
PlatformSupervisor.SetMessagingEventDelegate(OnMessageReceived);
//웹뷰 Agent를 등록한다.
PlatformSupervisor.SetWebviewAgent(new StarterWebviewAgent());
});
플랫폼 로그인(공통)
플랫폼 로그인은 3가지 단계로 수행됩니다.
구글/애플/이메일 등 인증 수단을 이용한 Signin
회원 가입
로그인
Auth 이벤트 리스너 구현
로그인 결과를 수신하는 이벤트 핸들러를 구현합니다.
void OnLoginSuccess(string resultJson)
{
var res = LoginResult.FromJson(resultJson);
}
void OnLoginFailure(string resultJson)
{
var error = HyError.FromJson(resultJson);
}
void OnLogoutSuccess()
{
}
void OnLogoutFailure(string message)
{
}
void OnWithdraw()
{
}
웹뷰 Agent 추가
로그인 프로세스에서 필요한 회원 가입, 서비스 이용 동의, 본인 확인 등의 단계는 웹뷰를 통해 진행됩니다. SDK는 웹뷰가 필요한 경우에 등록된 웹뷰 Agent로 필요한 이벤트를 전달합니다.
플랫폼 SDK는 웹뷰를 직접 제공하지 않습니다.
개발사에서 웹뷰를 구현해야 합니다. SDK에서 지원되는 웹뷰는 아래와 같습니다.
PC : vuplex webview
Android, iOS : Gree webvie
지원되는 웹뷰 이외의 웹뷰를 사용하는 경우 기술 PM에게 문의하세요.
IWebviewAgent 인터페이스를 상속 받은 클래스를 제공해야 합니다.
아래 2개의 함수를 각 플랫폼(Windows, Android, iOS)에서 사용하는 Webview 플러그인을 지원하도록 제공해야 합니다.
class StarterWebviewAgent : IWebviewAgent
{
void CloseWebview() {};
void OnShowWebview(string url) {};
}
자세한 방법은 웹뷰 연동을 참고하세요
플랫폼 로그인
플랫폼 로그인 프로세스를 실행합니다.
PlatformSupervisor.Signin(SigninMethod.GOOGLE, false);
Signin() 메소드를 호출하여 로그인을 요청합니다. 로그인 결과는 플랫폼 초기화 단계에서 등록한 Auth 이벤트 리스너로 전달됩니다.
지원하는 로그인 수단은 다음과 같습니다.
Google, Apple, LINE, Steam, Email, Guest, Facebook
플랫폼 로그인(Windows)
플랫폼 로그인(공통)과 기본 흐름은 동일하며 Windows 환경을 지원하는 웹뷰 Agent를 제공해야 합니다.
플랫폼 로그인(Steam)
플랫폼 로그인(Windows)와 기본 흐름은 동일하며, 스팀 연동에 필요한 추가 접속 환경을 지정해야 합니다.
플랫폼 로그인(Android)
플랫폼 로그인(공통)과 기본 흐름은 동일하며, 안드로이드 환경에 따른 추가 설정이 필요합니다.
Firebase 설정
CustomTab 지원 쿼리 설정
웹뷰 Agent 추가
Custom URI Scheme 설정
브라우저를 이용한 Apple, Line 등의 외부 인증 토큰을 수신할 CustomURISchem을 설정합니다.
아래 예제는 Custom URL Scheme 으로 "platformstarter"을 지정하는 intent-filter
예제입니다.
<activity>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<!-- Signin 리다이렉트 Custom uri -->
<data
android:host="signin"
android:scheme="platformstarter"/>
</intent-filter>
</activity>
CustomTab 지원 쿼리 설정
안드로이드 API 30 이상
버전을 타겟으로 할 경우 기본 브라우저가 CustomTab을 지원하는지 확인하기 위해 queries
설정을 추가 해야 합니다.
<manifest>
<queries>
<!-- intent에 action은 하나만 존재할 수 있으므로 이미 다른 intent가 있다면 따로 만들어줍니다. -->
<intent>
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
</queries>
</manifest>
플랫폼 로그인(iOS)
플랫폼 로그인(공통)과 기본 흐름은 동일하며, iOS 환경에 따른 추가 설정이 필요합니다.
Firebase 설정
웹뷰 Agent 추가
Custom URI Scheme 설정
브라우저를 이용한 Google, Line 등의 외부 인증 토큰을 수신할 CustomURISchem을 설정합니다.
Project Settings > Player > IOS > Other Settings > Configuraion > Supported URL Schemes
하위에 요소 추가 후 게임에 할당된 URL Scheme을 입력합니다. 아래는 Custom URL Scheme 으로 "hyplatform"을 지정하는 경우입니다.

게스트 로그인
Guest 계정은 임시 회원으로 특정 서비스만 사용할 수 있는 계정입니다.
PlatformSupervisor.Signin(SigninMethod.GUEST)
정회원 전환
게스트 로그인 수단으로 가입된 계정을 다른 인증 수단을 사용하는 정회원 계정으로 전환합니다.
정회원 전환은 오직 게스트 로그인 유저를 대상으로 지원하며, 그 외의 로그인 상태에서 API 호출시 실패를 리턴합니다. 정회원 전환이 성공적으로 완료되면 기존 게스트 계정은 삭제되고 연동한 로그인 수단으로만 로그인 할 수 있습니다.
게스트 계정으로 다시 로그인을 시도할 경우 새로운 게스트 계정을 생성하는 과정을 수행하게 됩니다.
PlatformSupervisor.ConvertMember(method, UserBirthDay.TryParse(userBirthDay))
.ContinueWith(task =>
{
if (task.Result.successful)
{
// 일반 계정 전환 완료
}
});
유저의 생일 정보 입력 요청
Task<string> PlatformSupervisor.ShowRequireGuestBirth()
웹뷰를 통해서 유저의 연령 정보를 입력 받을 수 있는 폼을 실행합니다. 실행이 완료되면 문자열(YYYYMMDD) 형태로 유저의 생일 값을 리턴합니다.
자동 로그인
플랫폼 로그인 단계에서 자동 로그인 지원 여부를 설정 할 수 있다.
bool bSupportAutoLogin = true;
PlatformSupervisor.Signin(SigninMethod.GOOGLE, bSupportAutoLogin);
Signin 메소드 호출 시 두 번째 인자로 true를 전달하면 로그인 정보가 저장되고, 플랫폼 초기화(Mount) 이후에 저장된 로그인 정보를 이용해서 자동 로그인을 수행할 수 있다.
PlatformSupervisor.DoAutoLogin((bTrying) =>
{
});
DoAutoLogin() 을 호출하여 자동 로그인을 시도합니다. 저장된 로그인 정보가 있는 경우 콜백으로 결과가 전달됩니다. 자동 로그인의 성공/실패는 등록한 로그인 핸들러(OnLoginSuccess, OnLoginFailure)로 전달됩니다
다음은 초기화(Mount) 결과가 전달되는 콜백 핸들러에서 자동 로그인을 요청하는 경우입니다.
// 플랫폼 SDK를 초기화 한다.
PlatformSupervisor.Mount("dev").ContinueWithOnMainThread( task =>
{
// 초기화 실패
if(task.Result == false)
return;
// 자동 로그인 시도
PlatformSupervisor.DoAutoLogin((bTrying) =>
{
// 저장된 자동 로그인 정보를 이용해서 로그인 시도
if(bTrying)
{
// 로그인 버튼 숨김 처리
// 로그인 실패 이벤트 수신 하는 경우 로그인 UI 팝업
}
});
}
자동 로그인 해제
Signout() 호출 시 자동 로그인 정보가 초기화 됩니다.
플랫폼 로그아웃
플랫폼 로그아웃을 요청합니다.
PlatformSupervisor.Signout();
다른 계정으로 로그인을 하기 위해서는 로그 아웃 이후에 다시 로그인을 요청할 수 있습니다.
로그 아웃 결과는 초기화(Mount) 단계에서 등록한 이벤트 핸들러로 전달된다.
로그아웃 성공 : OnLogoutSuccess
로그아웃 실패 : OnLogoutFailure
로그 아웃은 언제 호출하는가?
타이틀 이동 후 다른 계정으로 다시 로그인 하는 경우
자동 로그인 정보를 삭제하고자 하는 경우
탈퇴
계정 탈퇴를 요청합니다.
void ProcessWithDraw();
ProcessWithdraw를 호출하면 탈퇴 프로세스를 수행하는 웹뷰가 팝업되고, 결과는 SetLogoutEventDelegate에 등록한 이벤트 리스너 OnWithdraw()로 전달됩니다.
void OnWithDraw()
{
// 탈퇴된 계정에 대해서 ForceSignout()으로 로그인을 초기화 합니다.
PlatformSupervisor.ForceSignout();
}
탈퇴 완료 후 다른 계정으로 로그인을 다시 시도하기 위해서는 로그인 정보 초기화가 필요합니다. 탈퇴한 계정은 유효하지 않기 때문에 Signout()을 시도할 수 없습니다.
ForceSignout()으로 로그인 정보를 초기화할 수 있습니다.
Push 연동
Android
MessagingUnityPlayerActivity 설정
AndroidManifest.xml에 지정된 메인 엑티비티 클래스를 UnityPlayerActivity에서 MessagingUnityPlayerActivity으로 변경합니다.
<application>
<activity
android:name="com.google.firebase.MessagingUnityPlayerActivity"
-- 중략 --
>
<intent-filter> ... </intent-filter>
</activity>
<!-- [START fcm_disable_auto_init] -->
<meta-data
android:name="firebase_messaging_auto_init_enabled"
android:value="false" />
<meta-data
android:name="firebase_analytics_collection_enabled"
android:value="false" />
<!-- [END fcm_disable_auto_init] -->
</application>
MessagingUnityPlayerActivity를 메인 액티비티로 설정해야 하는 이유는?
Firebase Cloud Messaging과 Unity 간의 원활한 통합을 위해서입니다.
푸시 알림 처리 MessagingUnityPlayerActivity는 FCM으로부터 받은 푸시 알림을 Unity 환경으로 전달하는 브릿지 역할을 합니다. 기본 UnityPlayerActivity는 이러한 FCM 관련 기능이 구현되어 있지 않습니다.
인텐트 처리 앱이 백그라운드에 있을 때 푸시 알림을 탭하면 생성되는 인텐트를 적절히 처리하고, 이 데이터를 Unity 측으로 전달하는 로직이 MessagingUnityPlayerActivity에 이미 구현되어 있습니다.
푸시 수신 동의 팝업 띄우기
설치 후 인게임에서 앱 푸시 수신 동의 팝업을 띄우기 위해서는 SDK 초기화 후에 다음과 같이 Permission을 확인해야 합니다.
아래는 샘플에 적용된 예시입니다.
private void Awake()
{
PlatformSupervisor.Mount("dev").ContinueWithOnMainThread( task =>
{
-- 중 략 --
CheckPushPermission();
}
}
void CheckPushPermission()
{
// 안드로이드 api level을 구한다.
string androidInfo = SystemInfo.operatingSystem;
int apiLevel = int.Parse(androidInfo.Substring(androidInfo.IndexOf("-") + 1, 2));
// 디바이스 api level이 33 이상이면 퍼미션 요청
if (apiLevel >= 33 &&
!Permission.HasUserAuthorizedPermission("android.permission.POST_NOTIFICATIONS"))
{
Permission.RequestUserPermission("android.permission.POST_NOTIFICATIONS");
}
}
iOS
Xcode 설정
Unity-iPhone > General > Frameworks, libraries, And Embedded contents > [+]
UserNotifications.framework
UserNotificationsUI.framework
Unity-iPhone > Signing & Capabilities > 상단 라이브러리 추가 [+] 버튼
Push notification 추가
Background Modes
Unity-iPhone > Signing & Capabilities > Background Modes 선택
Remote notifications 체크
웹뷰 연동
Windows
웹뷰 SDK 연동
웹뷰 인스턴스(prefab) 생성 후에 웹뷰와 SDK가 데이터를 주고 받기 위해서 SDK를 연동해야 합니다.
var webview = mainWebViewPrefab.WebView as IWebView;
if (webview != null)
{
var Self = this;
//3.1 URL 변경 이벤트 핸들러
webview.UrlChanged += (sender, eventArgs) => {
// 웹뷰 URL이 변경되는 경우
if( ((IWebviewAgent)Self).OnUrlChanged(eventArgs.Url))
{
mainWebViewPrefab.Visible = false;
}
};
//3.2 웹뷰 콘솔 로그 이벤트 핸들러
// 웹뷰 console log를 WebviewAgent에게 전달한다. (옵션)
webview.ConsoleMessageLogged += (sender, eventArgs) => {
((IWebviewAgent)Self).OnReceiveConsoleMessage(eventArgs.Source, eventArgs.Line, eventArgs.Message);
};
//3.3 웹뷰(JavaScript)에서 유니티로 이벤트 전달 시 호출되는 이벤트 핸들러
// WebviewAgent에게 수신한 데이터를 전달한다.
webview.MessageEmitted += (sender, eventArgs) => {
((IWebviewAgent)Self).OnReceiveMessage(eventArgs.Val전ㅊue, (result) =>
{
if (!string.IsNullOrEmpty(result))
{
(mainWebViewPrefab.WebView as IWebView)?.PostMessage(result);
}
});
};
}
IWebviewAgent 이벤트를 구현한다.
class StarterWebviewAgent : IWebviewAgent
{
CanvasWebViewPrefab mMainWebViewPrefab; // Vuplex webview
public void CloseWebview()
{
if (mMainWebViewPrefab != null) //mMainWebViewPrefab is Vuplex
mMainWebViewPrefab.Visible = false;
// 웹뷰가 닫힐 때 SDK에 이벤트 전달해야 합니다.
((IWebviewAgent)this).OnWebviewClosed();
}
void OnShowWebview(string url)
{
// 웹뷰를 팝업합니다.
};
}
Android/iOS
웹뷰 SDK 연동
웹뷰와 SDK가 데이터를 주고 받기 위해서 SDK를 연동해야 합니다.
다음은 Gree webview에서 cb 파라미터에 적용한 예입니다.
webViewObject.Init(
cb: (msg) =>
{
//JavaScript -> Unity 이벤트 전달
var result = ActionDelegator.Instance.RunAction(msg);
if (string.IsNullOrEmpty(result) == false)
{
// SDK 수신 결과를 JavaScript에 전달
webViewObject.EvaluateJS($"window.unityEventHandler({result})");
}
},
전체 구현 코드 샘플은 다음과 같습니다.
public void ShowWebview(string url)
{
if(webViewObject != null)
{
webViewObject.SetVisibility(true);
webViewObject.LoadURL(url);
return;
}
webViewObject = (new GameObject("WebViewObject")).AddComponent<WebViewObject>();
webViewObject.Init(
cb: (msg) =>
{
//JavaScript -> Unity 이벤트 전달
var result = ActionDelegator.Instance.RunAction(msg);
if (string.IsNullOrEmpty(result) == false)
{
webViewObject.EvaluateJS($"window.unityEventHandler({result})");
}
},
err: (msg) =>
{
HyDebug.Log(string.Format("CallOnError[{0}]", msg));
},
started: (msg) => { HyDebug.Log($"WebView Started : {msg}"); },
hooked: (msg) => { HyDebug.Log($"WebView Hooked : {msg}"); },
enableWKWebView: true);
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
webViewObject.bitmapRefreshCycle = 1;
#endif
webViewObject.SetMargins(0, 0, 0, 0);
webViewObject.SetVisibility(true);
if (url.StartsWith("http"))
{
webViewObject.LoadURL(url.Replace(" ", "%20"));
}
}
#endif
}
웹뷰 사이즈 가이드
PC 웹뷰와 전체화면이 아닌 모바일 웹뷰는 웹뷰 내부 컨텐츠 내용을 정상적으로 표시 하기 위해 사이즈 가이드를 지켜 주셔야 합니다.
<Horizontal 및 PC 기준>
가로 (68%):
RectTransform
의 Width를 Anchor 기준68%
로 설정세로:
디바이스 전체 높이 - 홈 인디케이터 영역 - (16px * 2)
(상하 Gutter)
<Vertical 기준>
가로 (90%): Anchor 기준 Width를
90%
세로:
디바이스 전체 높이 - 상태 바 - 홈 인디케이터 영역 - (16px * 2) - 60px
(추가 마진)
float homeIndicatorHeight = 34f; // 홈 인디케이터 영역 높이, iPhone 기준 예시
float statusBarHeight = 44f; // 상태바 높이 (OS에 따라 다름)
Platform Configuration
플랫폼 초기화 함수(Mount)에 전달하는 실행 환경 JSON 정보는 다음과 같습니다.
{
"BuildEnv": "qa",
"ProjectId": "0000",
"SteamAppID": 00000000,
"SteamIdentity": "sample-qa-00000000",
"CustomScheme" : "hyplatform"
}
BuildEnv
개발 환경
qa, prod
ProjectId
기술PM에게 전달 받은 ProjectID
WebClientId
기술PM에게 전달 받은 구글 WebClientID
SteamAppID
기술PM에게 전달 받은 SteamAppID
SteamIdentity
기술PM에게 전달 받은 스팀 인증토큰
Custom URI Scheme
브라우저로 Google, Apple 같은 외부 인증을 시도하는 경우 인증 결과는 Custom URI Scheme 에 등록된 앱으로 전달됩니다. 인증 결과를 수신하기 위해서는 반드시 앱 별로 고유한 Custom URI Scheme을 설정해야 합니다.
사용가능 문자 : 영숫자, - (7days 와 같이 숫자로 시작할 수 없습니다.)
PlatformConfig.json 에 반드시 CustomScheme 속성을 설정해야 합니다.
{
-- 중 략 --
"CustomScheme": "hyplatform"
}
또한, OS 별로 추가 설정이 필요합니다.
Android
<activity>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<!-- Signin 리다이렉트 Custom uri -->
<data
android:host="signin"
android:scheme="hyplatform"/>
</intent-filter>
</activity>
iOS
Project Settings > Player > IOS > Other Settings > Configuraion > Supported URL Schemes
하위에 요소 추가 후 게임에 할당된 URL Scheme을 입력합니다. 아래는 Custom URL Scheme 으로 "hyplatform"을 지정하는 경우입니다.

API Reference
API는 PlatformSupervisor 클래스를 통해 제공됩니다. 설명하는 API는 특별한 설명이 없는 한 PlatformSupervisor의 메소드입니다.
로그인
Mount
플랫폼 SDK 초기를 수행합니다.
async Task<bool> Mount(string env)
파라미터
env 빌드 환경 ex) qa, prod
입력된 실행 환경 정보(env)는 실행 시 로딩하는 설정 파일(PlatformConfig.json)을 구분하는데 사용합니다. 자세한 내용은 Platform Configuration을 참고하세요.
Signin
플랫폼 로그인을 수행합니다.
void Signin(SigninMethod signinMethod, bool bEnableAutologin)
파라미터
signinMethod 로그인 종류, SigninMethod 참고
bEnableAutologin 자동 로그인 적용 여부, true 로 설정하면 로그인 정보가 저정됩니다. 자세한 정보는 자동 로그인을 참고하세요
SigninMethod
public enum SigninMethod
{
IM,
GOOGLE,
APPLE,
STEAM,
LINE,
X,
FACEBOOK,
GUEST,
}
public void OnClickGoogleLogin()
{
PlatformSupervisor.Signin(SigninMethod.GOOGLE, false);
}
DoAutoLogin
자동 로그인을 실행합니다.
void DoAutoLogin(Action<bool> callback)
파라미터
callback 자동 로그인 결과
Signin() 호출 시 자동 로그인 여부(bEnableAutologin)를 true로 설정하고 로그인을 완료한 경우, 다음에 플랫폼 초기화 후에 자동 로그인을 수행 할 수 있습니다. 자동 로그인 된 정보가 없으면 콜백으로 false를 전달합니다. 호출 전에 자동 로그인 가능 여부를 판단하는 기능을 제공하지 않습니다. DoAutoLogin을 호출하면 콜백으로 자동 로그인 시도 여부를 전달됩니다.
로그인 결과는 OnLoginSuccess로 전달됩니다.
PlatformSupervisor.DoAutoLogin((bTrying) =>
{
// 저장된 자동 로그인 정보를 이용해서 로그인 시도
if(bTrying)
{
// 로그인 버튼 숨김 처리
// 로그인 실패 이벤트 수신 하는 경우 로그인 UI 팝업
}
});
Signout
플랫폼 로그 아웃을 수행합니다.
void SignOut()
파라미터 : 없음
로그 아웃 결과는 초기화 시 등록한 이벤트 리스너로 전달된다.
로그아웃 성공 : OnLogoutSuccess
로그아웃 실패 : OnLogoutFailure
자동 로그인 정보가 초기화 됩니다.
ForceSignout
플랫폼 SDK의 로그인 정보를 초기화 합니다
void SignOut()
파라미터 : 없음
로그아웃 결과가 OnLogoutSuccess로 전달됩니다.
Signout은 로그인 정보를 이용해서 플랫폼 백엔드에 로그아웃을 요청하는 우아한 로그아웃이며, ForceSignout은 백엔드 연동없이 클라이언트의 로그인 정보를 즉시 초기화 합니다.
RefreshVerifyTokenAsync
플랫폼 로그인 성공 후에 전달 받은 로그인 토큰을 갱신합니다.
Task<string> RefreshVerifyTokenAsync()
파라미터 : 없음
리턴값
갱신된 LoginToken
로그인 성공 후 발급 받은 토큰은 1시간 동안 유효합니다. 로그인 토큰은 게임 서버로 전달되어 게임 서버가 플랫폼 백엔드를 통해 로그인 유저의 인증에 사용합니다.
다음 경우에 RefreshVerifyTokenAsync로 로그인 토큰을 갱신 해야 합니다.
로그인 성공 이후 게임 서버에 로그인 토큰을 즉시 전달하지 않는 경우
예를 들어, 로그인 후 [게임 시작] 클릭 후 게임 서버에 접속하는 경우 유저가 로그인 후 오랜 시간 자리를 비우고 [게임 시작]을 진행하면 만료된 로그인 토큰이 게임 서버에 전달되어 인증이 실패할 수 있습니다.
클라이언트가 게임 서버와 접속을 끊고 다시 게임 서버에 접속하는 경우
클라이언트가 비정상 상황에서 게임 서버와 접속이 강제로 끊긴 후 다시 게임 서버에 접속하는 경우
예를 들어, 로비로 튕긴 후 다시 게임 서버에 접속하는 경우
이벤트 리스너
SetAuthEventDelegate
플랫폼 인증 연동에 필요한 델리게이트(콜백)을 등록합니다.
void SetAuthEventDelegate(Action<string> onLoginSuccess,
Action<string> onLoginFailure)
파라미터
onLoginSuccess 로그인 성공 시 호출
onLoginFailure 로그인 실패 시 호출
SetLogoutEventDelegate
플랫폼 로그아웃 시 수신하는 델리게이트(콜백)을 등록합니다.
void SetLogoutEventDelegate(Action onLogoutSuccess,
Action<string> onLogoutFailure,
Action onWithDraw)
파라미터
onLogoutSuccess 로그 아웃 성공 시 호출
onLogoutFailure 로그 아웃 실패 시 호출
onWithDraw 계정 탈퇴 시 호출
onLoginSuccess
Signin 실행 성공 시 호출되는 이벤트 리스너
void OnLoginSuccess(string resultJson)
파라미터
resultJson 로그인 결과 JSON
{
"imid": "4M87F6H5GFFZDPY8QM97",
"loginToken": "xxx-xxx",
"countryCode" "KR"
}
imid 게임별로 부여되는 유저 ID
loginToken 로그인 토큰. 게임 서버에 전달하여 게임 플랫폼 백엔드에 접속한 유저의 유효성을 검사하는 용도로 사용됩니다. RefreshVerifyTokenAsync 을 호출하여 갱신할 수 있습니다.
countryCode 가입 국가 정보, ISO 3166-1 alpha-2 국가 코드( KR, US 등)
onLoginFailure
Signin() 실패 시 호출되는 이벤트 핸들러
void OnLoginFailure(string resultJson)
파라미터
resultJson 로그인 결과 JSON
Copy
{
"message":"Login Cancel",
"code":6
}
에러 코드는 아래와 같이 정의되어 있습니다.
public enum Code
{
UNKNOWN_ERROR = 0,
NO_INITIALIZE = 1,
SIGNIN_FAIL = 2, // Google/Apple Signin 실패
PLATFORM_LOGIN_FAIL = 3, // Platform login
AUTO_LOGIN_FAIL = 4, // Auto login fail
WITHDRAW_ACCOUNT = 5, // 계정 탈퇴 회원
CANCELED = 6, // 취소됨
}
에러 코드별 전달되는 상세 메시지는 다음과 같습니다.
SIGNIN_FAIL
google sigin failure
안드로이드에서 Google Signin 환경 에러
Apple SignIn failed
iOS Apple Signin 실패
Apple Signin Error
iOS Apple Signin 오류 발생
NOT_SUPPORTED_SIGNIN_PROVIDER
지원하지 않는 로그인 수단
Unknown login error
PLATFORM_LOGIN_FAIL
DORMANT_ACCOUNT
휴면유저
Already log in
로그아웃하지 않고 로그인 시도
Steam login not supported
스팀 로그인 미지원 환경에서 로그인 시도하는 경우
Platform login API Fail
게임 플랫폼 연동 에러 - 네트워크/서버 에러 등
Steam no prepare
스팀 연결 실패
Auth Fail
Google/Apple/Line 등 3rd Party Signin 에러
NOT_ALLOWED_AGE
일본에서 PC 런처 환경에서 미성년자(16세 이하)인 경우
FAIL_LOCK_REQUEST
짧은 기간 로그인 중복 요청
Unknown error
CANCELED
Login Cancel
외부인증 단계에서 취소
서비스 이용 동의 단계에서 취소
본인 인증 단계에서 취소
FAIL_LOCK_REQUEST
플랫폼 백엔드에 로그인 토큰을 짧은 시간 동안(1초 이내) 중복 요청하는 경우 발생합니다.
로그인 토큰을 요청하는 API는 아래와 같습니다.
Signin() : 인증 수단으로 플랫폼 로그인 시 로그인 토큰을 발급합니다.
RefereshVerifyTokenAsync() : 로그인 토큰 갱신을 요청합니다.
주로, Login() 호출 후 수신한 OnLoginSuccess 이벤트에서 RefereshVerifyTokenAsync() 을 호출하는 경우 발생합니다. OnLoginSuccess 수신한 LoginToken이 일정 시간 유효하기 때문에 RefereshVerifyTokenAsync()으로 갱신할 필요가 없습니다.
자세한 내용은 RefereshVerifyTokenAsync() 을 참고하세요.
onLogoutSuccess
Signout, ForceSignout 성공 시 호출되는 이벤트 핸들러
void OnLogoutSuccess()
리턴 없음
OnLogoutFailure
Signout 패 시 호출되는 이벤트 핸들러
void OnLogoutFailure(string message)
파라미터
message 로그아웃 실패 메시지
OnWithDraw
탈퇴(Withdraw) 요청 완료 시 호출되는 이벤트 핸들러
void OnWithDraw()
사용 예
void OnWithDraw()
{
// 탈퇴된 계정은 Signout()으로 로그아웃할 수 없다.
PlatformSupervisor.ForceSignout();
}
탈퇴 /계정전환
ProcessWithdraw
계정 탈퇴를 요청합니다.
void ProcessWithDraw()
파라미터
없음
탈퇴 프로세스를 수행하는 웹뷰가 팝업되고, 결과는 초기화 단계에서 등록한 이벤트 리스너 OnWithdraw() 로 전달됩니다. 탈퇴 프로세스 도중 웹뷰를 강제로 닫는 경우 취소 이벤트가 별도로 전달되지는 않습니다. OnWithdraw()를 수신하지 않는 경우 탈퇴 진행이 되지 않았습니다.
void OnWithDraw()
{
// 탈퇴된 계정은 Signout()으로 로그아웃할 수 없다.
PlatformSupervisor.ForceSignout();
}
탈퇴 완료 후 다른 계정으로 로그인을 다시 시도하기 위해서는 ForceSignout()으로 로그인 정보 초기화를 해야 합니다. 탈퇴한 계정은 유효하지 않기 때문에 Signout()을 시도할 수 없습니다.
ConvertMember
게스트 회원을 일반 회원으로 전환합니다.
async Task<ConvertMemberResult> ConvertMember(
SigninMethod method,
UserBirthDay userBirthDay = null)
파라미터
signinMethod 로그인 종류.
userBirthDay (optional) 생년월일. 게스트에 대한 생년월일을 저장하고 있는 경우 제공합니다.
리턴
회원 전환 결과 (ConvertMemberResult)
struct ConvertMemberResult
{
public readonly bool successful; // 정회원 전환 성공 여부
public readonly string errorMessage; // 실패시 애러 메시지
public readonly string resultCode; // 결과 코드
}
ShowRequireGuestBirth
유저의 생년월일 정보를 입력 받고 생년월일 정보를 리턴합니다.
Task<string> PlatformSupervisor.ShowRequireGuestBirth()
웹뷰를 통해서 유저의 연령정보를 입력받을 수 있는 폼을 실행합니다. 실행이 완료되면 string (YYYYMMDD) 형태로 유저의 생일 값을 리턴합니다.
기타
GetLastSignInTag
유저의 마지막 로그인 정보를 조회합니다. Mount가 완료된 이후에 사용 할 수 있습니다.
SignInTag? GetLastSignInTag()
public struct SignInTag
{
// 유저의 이메일 데이터입니다. 일부 Method(GUEST 등)의 경우 이메일이 비어있을 수 있습니다.
public string MaskedEmail { get; }
// 로그인 수단 데이터입니다.
public string Method { get; }
public SigninMethod MethodEnum { get; }
}
GetLocationCode
현재 사용자의 접속 국가 코드를 반환합니다
string GetLocationCode()
파라미터 : 없음
리턴
ISO 3166-1 alpha-2 국가 코드, ex) KR, US
SetClientLanguage
게임 클라이언트에서 사용하는 언어를 SDK에 전달합니다.
설정된 언어 정보를 이용해서 플랫폼 연동 과정에서 출력 되는 웹뷰의 다국어가 적용됩니다.
void SetClientLanguage(string language)
파라미터
language ISO 639-1 언어코드( ko, en, ja, zh-tw). 웹뷰에 보여지는 컨텐츠는 지원 국가 외에는 영문(en)으로 출력 됩니다.
SetWritableStorageDir
SDK에서 로그인 및 결제 과정에서 로컬에 데이터를 저장한다.
클라이언트는 반드시 초기화(Mount) 전에 쓰기 가능한 경로를 반드시 설정해야 합니다.
bool SetWritableStorageDir(string dir)
파라미터
dir 쓰기 가능한 경로
지정된 경로에 자동 로그인, 결제 정보 등이 저장됩니다.
//1. SDK 접근 가능한 스토리지 경로를 설정한다.
PlatformSupervisor.SetWritableStorageDir(Application.persistentDataPath);
//2. 플랫폼 SDK를 초기화 한다.
PlatformSupervisor.Mount("dev").ContinueWithOnMainThread( task =>
{
}
GetAgreementStatus
async Task<AgreementPayload> GetAgreementStatus()
마케팅 수신 동의 정보를 조회합니다.
리턴
AgreementPayload 플랫폼에 등록된 약관 동의 결과
public class AgreementPayload
{
public bool advertising; // 마케팅 수신 동의
public bool adPush; // 앱 푸시 수신 동의
public bool adNightPush; // 야간 푸시 수신 동의
}
UpdateAgreement
약관 동의 정보를 업데이트합니다.
async Task<AgreementPayload> UpdateAgreements(AgreementPayload agreementPayload )
파라미터
agreementPayload 약관 정보(AgreementPayload)
리턴
AgreementPayload 업데이트된 약관 동의 정보 수신
업데이트 요청한 약관 정보와 백엔드에 반영된 약관 동의 정보를 다를 수 있습니다.
예를 들어, 마케팅 정보 동의(advertising) 속성이 false 로 되어 있으면 유저가 설정한 푸시(adPush), 야간 수신(adNightPush) 동의 정보는 설정에 관계없이 항상 false로 처리됩니다.
ShowReview
void ShowReview()
iOS, Android 에서 제공하는 리뷰 팝업이 노출됩니다. 각 OS 별 리뷰 팝업 정책은 아래를 참고하세요.
OS별 리뷰 팝업 정책에 따라 실제 리뷰 팝업이 노출되는지 테스트가 쉽지 않습니다.
1회 팝업 이후에는 팝업 특정 조건이 충족되지 않으면 리뷰 팝업창이 나타나지 않습니다.
안드로이드 개발 환경에서 아래 로그가 출력 되면 정상 동작입니다.
review popup
review completed
iOS는 개발 환경에서 리뷰 팝업을 항상 보여줍니다. 앱 심사 완료 되지 않으면 평점 선택 후 [보내기] 버튼이 항상 비활성 상태입니다.
우수한 사용자 환경을 제공하기 위해 Google Play는 사용자에게 리뷰 대화 상자를 표시할 수 있는 빈도에 관한 시간 제한 할당량을 적용합니다. 이 할당량으로 인해 짧은 기간(예: 1개월 미만)
launchReviewFlow
메서드를 두 번 이상 호출할 경우 대화 상자가 표시되지 않을 수도 있습니다.참고: 구체적인 할당량 값은 구현 세부 정보이며 Google Play에서 예고 없이 변경할 수 있습니다.
개발 단계에서 리뷰 테스트를 하기 위해서는 반드시 gmail 계정으로 로그인 된 안드로이드 디바이스에서 테스트를 해야 합니다. 그렇지 않으면 리뷰 팝업이 나오지 않을 수 있습니다.
SKStoreReviewController API를 사용하면 사용자가 손쉽게 앱에 대한 피드백을 제공할 수 있습니다. 1년에 최대 3번까지 평가를 요청할 수 있습니다. 사용자는 표준 프롬프트를 통해 평가를 제출하며 앱에서 나가지 않고도 리뷰를 작성하고 제출할 수 있습니다. 개발 모드에서 항상 팝업 됩니다.
ListenReturnToGameEvent
지정된 시간(초) 동안 ReturnToGame 이벤트 수신을 대기한다.
이벤트가 수신되면 게임이 최상단으로 활성화됩니다.
static void ListenReturnToGameEvent(int timeoutSecond)
파라미터
timeoutSecond 이벤트 수신 대기 시간
주로 외부 브라우저로 결제를 진행할 때 사용합니다.
엑솔라 결제와 같은 경우 외부 브라우저에서 결제를 진행합니다. 브라우저에서 결제 완료 후 게임으로 돌아오는 기능을 제공하기 위해 사용합니다.
스팀
GetSteamInfo
연동된 스팀 정보를 조회합니다.
SteamInfo GetSteamInfo()
리턴
SteamInfo 스팀 연동 정보
public class SteamInfo
{
public ulong Id; // User ID
public string Language; // (결제)스팀 API 호출시 사용되는 언어코드(ISO 639-1)
public string Currency; // (결제)통화코드 ex) KRW, USD
}
IsSteamOverlaySupport
bool IsSteamOverlaySupport()
스팀에서 실행되는 경우 스팀의 오버레이 기능이 활성화 되어 있는지 체크합니다.
스팀 결제 기능을 사용하기 위해서는 스팀 앱에서 오버레이 기능이 활성화 되어 있어야 합니다.

IsSteamPlayMode
스팀 연동으로 플레이되는 환경인지 조회합니다.
멀티 플랫폼을 지원하는 게임의 경우에 스팀 플레이 환경인지 체크할 때 사용할 수 있습니다.
bool IsSteamPlayMode()
리턴
SteamInfo 스팀 연동 정보
배너, 게시판
GetBanners
async Task<HyResult<Banner[]>> PlatformSupervisor.GetBanners(string mappingId,
string fallbackLanguage = null)
배너 구좌(mappingId)에 대한 배너 목록을 요청합니다.
mappingId
배너 구좌 ID (담당 기술 PM에게 문의)
fallbackLanguage
현재 사용중인 언어(SetClientLanguage로 설정된)에 대한 배너가 없을 시 사용될 대체 언어 설정
SetClientLanguage로 설정된 사용 중인 언어를 지원하는 배너가 없는 경우 사용될 대체 언어를 설정할 수 있습니다.
public readonly struct Banner
{
public readonly uint bannerId;
public readonly uint bannerInfoId;
public readonly string defaultLanguageCode;
public readonly string[] platforms;
public readonly ulong exposureStartAt;
public readonly ulong? exposureEndAt;
public readonly int? exposeOrder;
public readonly ulong createdAt;
public readonly ulong? updatedAt;
public readonly BannerContent content;
}
public readonly struct BannerContentImageInfo
{
public readonly string key;
public readonly string url;
public readonly int no;
}
public readonly struct BannerContent
{
public readonly uint bannerContentId;
public readonly string languageCode;
public readonly string title;
public readonly string subTitle;
public readonly string landingType;
public readonly string landingUrl;
public readonly BannerContentImageInfo[] imageInfos;
}
Banner 객체의 주요 멤버는 다음과 같습니다.
title 배너 타이틀, 웹뷰 타이블 또는 별도 UI 구성시 타이틀 영역에 출력
exposureStartAt 배너 노출 시작 시간, unix UTC timestamp 클라이언트의 로컬 시간으로 변환한 후에 적용해야 합니다.
exposureEndAt 배너 노출 종료 시간, unix UTC timestamp 클라이언트의 로컬 시간으로 변환한 후에 적용해야 합니다.
content 배너에 대한 컨텐츠 정보를 담고 있습니다.
BannerContent 객체의 주요 멤버는 다음과 같습니다.
imageInfos 배너 이미지 URL
landingType
NONE : 설정 안함
DEEPLINK : 딥링크
CURRENT_TAB : 현재창 이동
NEW_TAB : 새창 이동
landingUrl 이미지 클릭시 이동되는 URL
GetBoardUrl
통합CMS에서 설정한 게시판의 URL을 요청합니다.
string GetBoardUrl(string boardId)
파라미터
boardId 게시판을 구분하는 ID
리턴
boardId에 해당되는 게시판을 제공하는 URL
boardId는 각 게임 별로 지원하는 ID 목록을 기술PM이 사전에 제공합니다.
GetUrl
string GetUrl(string urlType, string language = null)
인게임 사용하는 다양한 URL을 요청합니다.
urlType
URL 종류, 종류는 아래 표를 참고하세요.
language (optional)
게임 플레이 시 선택된 언어.
디폴트로 language를 지정하지 않고 사용하면 SetClientLanguage()에 사용한 언어 정보를 기준으로 URL을 리턴 합니다.
URL 종류
SDK에서 미리 정의한 상수를 사용하세요.
개인정보 처리방침
privacy_policy
HyString.PRIVACY_POLICY_URL
계정 센터(마이 페이지)
mypage
HyString.MYPAGE_URL
약관 보기
terms
HyString.TERMS_URL
고객센터
customer_center
HyString.CS_URL
커뮤니티
homepage
미정의
일본 연령 확인
birth_required
미정의
쿠폰 등록
coupon
미정의
거래 내역
transaction_history
미정의
확률형 아이템 고지
probability
미정의
string communityUrl = PlatformSupervisor.GetUrl("homepage"); // 커뮤니티
웹뷰
SetWebviewAgent
웹뷰 Agent를 등록합니다.
void SetWebviewAgent(IWebviewAgent webviewAgent)
서비스 이용 동의, 이메일 인증, 약관 동의, 탈퇴 등의 연동 과정에서 웹뷰가 사용됩니다. 웹뷰 Agent는 연동과정에서 필요한 웹뷰 팝업/닫기 등의 이벤트를 처리하고 웹뷰에서 수신한 이벤트를 SDK에 전달하기 위해서 사용됩니다.
IWebviewAgent
public interface IWebviewAgent
{
void CloseWebview();
void OnShowWebview(string url);
void OnWebviewClosed();
bool OnUrlChanged(string url)
void OnReceiveMessage(string actionJson, Action<string> callback)
void OnReceiveConsoleMessage(string source, int line, string message)
}
CloseWebview
void CloseWebview()
SDK 연동 과정에서 웹뷰가 닫혀야 되는 경우에 호출되는 핸들러입니다.
게임 클라이언트에서 IWebveiwAgent를 상속받은 클래스에서 웹뷰를 닫는 작업을 반드시 구현해야 합니다.
OnShowWebview
SDK에서 웹뷰 팝업이 필요한 경우에 호출되는 핸들러입니다.
게임 클라이언트에서 IWebveiwAgent를 상속받은 클래스에서 반드시 구현해야 합니다.
public void OnShowWebview(string url)
OnWebviewClosed
웹뷰가 닫혔을 때 클라이언트에서 호출해줘야 하는 함수입니다.
클라이언트가 함수를 호출함으로써 SDK에게 웹뷰가 닫혔음을 알려주면 SDK에서 로그인 취소와 같은 프로세스가 처리되도록 할 수 있습니다.
OnUrlChanged
bool OnUrlChanged(string url)
웹뷰 URL이 변경될 때 SDK에 변경된 URL 정보를 전달해야 합니다.
리턴
SDK가 URL을 처리한 경우 true, 그렇지 않으면 false를 리턴합니다.
CustomURIScheme 등 SDK에서 처리하도록하기 위해서는 반드시 호출되어야 합니다
OnReceiveMessage
void OnReceiveMessage(string actionJson, Action<string> callback)
웹뷰에서 Unity로 이벤트를 전송하는 경우 수신한 데이터를 SDK에 데이터를 전달해야 합니다.
자세한 사용법은 제공된 샘플을 참고하세요.
파라미터
actionJson 웹뷰에서 전달한 데이터
callback SDK에서 수신한 데이터를 처리한 결과.
SDK에서 리턴한 데이터를 다시 웹뷰에 전달해야합니다
OnReceiveConsoleMessage
void OnReceiveConsoleMessage(string source, int line, string message)
웹뷰 콘솔 로그를 SDK에 전달 해야 합니다. (optional)
자세한 사용법은 제공된 샘플을 참고하세요.
클라이언트 이벤트 로그 연동
SendEvent
void SendEvent(Dictionary<string, object> clientEvent)
void SendEvent(Dictionary<string, object>[] clientEvents)
유저 행동 로그 수집을 위해 클라이언트 사이드 이벤트 로그를 전송 합니다.
유저 행동 로그 중 클라이언트 로그는 유실/중복/위변조 등 이슈로 무결성을 보장하지 않습니다.
중요 지표에 활용되는 로그는 서버 로그를 권장하며, 서버 사이드 전송이 불가능하거나 로그 결함이 지표에 큰 영향을 주지 않는 일부 로그에 대해서 만 활용 합니다.
파라미터
clientEvent 단일 이벤트를 전송할 파라미터를 포함하는 Dictionary
clientEvents 여러 개 이벤트를 전송할 파라미터를 포함하는 Dictionary 한번에 최대 100개 까지 전송할 수 있다. (권장하지는 않음)
적용 예)
var clientEvent = new Dictionary<string, object>()
{
["srl"] = "external_device_id",
["event_name"] = "00_060_FirstLogin",
["external_device_id"] = "deviceUniqueId",
["external_device_type"] = 0,
["service_id"] = "100200200"
};
PlatformSupervisor.SendEvent(clientEvent);
이벤트 및 파라미터 세부 스펙은 별도로 전달 드리는 로그 명세서를 참고하세요.
log_type,
os_type, cre_time은 SDK 에서 생성하여 전달합니다.
퍼널 이벤트 연동
퍼널 이벤트에 대한 구체적인 처리 방법은 기술PM과 논의하여 관련 가이드를 따라주시기 바랍니다.
SetFunnelEventDelegate
// 이벤트 delegate 정의
delegate void FunnelEventListener(string eventName, IReadOnlyDictionary<string, string> parameters);
// 이벤트 핸들러 등록을 위한 함수
void PlatformSupervisor.SetFunnelEventDelegate(FunnelEventListener eventListener)
퍼널 이벤트 핸들링을 위한 리스너를 등록합니다. 등록된 리스너는 SDK 영역에서 퍼널 이벤트가 발생 시 호출됩니다.
eventName
이벤트 이름
parameters
발생한 이벤트에 대한 추가 정보를 갖는 Key-Value 데이터셋 입니다. 모든 Value는 string 타입으로 전달 되므로 필요에 따라 알맞은 타입으로 캐스팅이 필요합니다.
퍼널 이벤트 종류
PLATFORM_CERT_REQ
서비스 이용 동의 화면이 노출 될 때 발생하는 이벤트
{ "detail": "game" | "service", }
PLATFORM_CERT_COMP
서비스 이용 동의를 완료하면 발생하는 이벤트
빌드
Android 빌드하기
Project Settings > Other Settings
Scripting Backend : IL2CPP
Project Settings > Publishing Settings
Custom Keystore : Keystore Manager로 생성한 Keystore 정보 입력
Assert/google-services.json 파일 확인
iOS 빌드하기
Project Settings > Other Settings
Identification > bundle Identifier : Apple Developer Center에서 등록시 사용한 번들 ID
Assert/GoogleService-Info.plist 파일 확인
Unity-iPhone > Signing & Capabilities > 상단 라이브러리 추가 [+] 버튼
Sign in with Apple 추가
In-App Purchase 추가
Push Notifications 추가
Unity-iPhone > Build Settings > Build Options > Enable Bitcode : No 설정
UnityFramework > Build Settings > Build Options > Enable Bitcode : No 설정
UnityFramework > Link Binary With Libraries > SafariServices.framework 확인 (없다면 추가)
UnityFramework > Link Binary With Libraries > WebKit.framework 확인 (없다면 추가)
Troubleshooting
iOS
함께 볼만한 문서
Last updated