- 마지막 업데이트
SDK 추가 후 환경 구성·빌드 설정·SDK Config 등록을 안내해요.
개발 환경 설정
환경 정의
스토브 플랫폼은 개발·테스트용 Sandbox와 실제 서비스용 Live 두 가지 환경으로 구성돼요. 두 환경은 데이터·계정·콘솔이 모두 분리되어 있어 SDK도 각 환경에 맞춰 구성해야 해요.
| 구분 | Sandbox | Live |
|---|---|---|
| 용도 | 개발 · QA · 테스트 | 실제 서비스 |
| 파트너스 | partners.gate8.com | partners.onstove.com |
| API Host | api.gate8.com | api.onstove.com |
| PC 런처 | 런처 3.0 Sandbox + 개발자 모드 사용 가능 | 런처 3.0 Live |
| 계정 / 데이터 | 독립 운영. 실 서비스에 영향 없음 | 독립 운영. QA 완료 후 전환 |
환경별 파트너스 접근 안내
ㆍ 파트너스는 환경별로 접속 URL이 달라요. 각 환경 모두 VPN 계정을 통해서만 접근할 수 있어요.
ㆍ SDK Config·앱 등록 등 모든 등록 작업은 각 환경의 파트너스에서 별도로 진행해야 해요.
- 환경 전환
SDK가 어떤 환경을 호출할지는 클라이언트 빌드의 환경 설정 값으로 결정돼요.
일반적으로 빌드 타입(debug / release) 또는 SDK 초기화 옵션으로 분기해요.
- 개발·QA 빌드: Sandbox
- 스토어 배포 빌드: Live
환경 동기화 주의
ㆍ 클라이언트 환경 설정과 파트너스 SDK Config 등록 환경은 반드시 일치시켜야 해요.
ㆍ Sandbox 클라이언트가 Live SDK Config를 조회하면 SDK init이 실패해요.
플랫폼별 SDK 추가
플랫폼별로 SDK 모듈을 추가하고 빌드 환경을 구성하는 방법이에요. SDK 다운로드는 SDK 다운로드 및 설치 가이드를 먼저 참고해 주세요.
본인 환경 1개 섹션만 위에서 아래로 따라가면 돼요
ㆍ 본인 환경(MobileSDK / PC SDK)에 해당하는 섹션을 순서대로 적용하면 환경 설정이 끝나요. MobileSDK는 빌드 환경(Android / iOS / Unity / Unreal)에 따라 하위 섹션으로 세분화돼요.
ㆍ 예제 코드는 SDK 2.9.0 기준이에요. 최신 모듈 버전은 SDK 다운로드 및 설치에서 확인해 주세요.
ㆍ 인증 Provider별 키(facebook_app_id, google_web_client_id, naver_client_id 등)는 로그인 가이드에서 각 Provider별로 다뤄요.
모바일 SDK
모바일 환경(Android · iOS)에서 동작하는 SDK예요. 본인 빌드 환경에 맞는 하위 섹션을 따라가세요. 같은 모바일이라도 빌드 도구(네이티브 / Unity / Unreal)별로 설정 방식이 달라 4개로 분리되어 있어요.
Unity
.unitypackage 임포트 후 Unity 스토브 환경 설정 + Android Custom Gradle Template + UnityPlayerActivity 추가 설정 + iOS Xcode 후처리까지 진행해요. (Unity 2022.3.10f1 이상)
1) Unity 패키지 임포트
- SDK 다운로드 및 설치에서 받은
.unitypackage를 압축 해제 - Unity → Assets → Import Package → Custom Package…로 가져오기
2) 스토브 환경 설정 (Zone)
iOS Info.plist의 StoveEnvironment와 별개로, Unity는 에디터 Inspector에서 직접 환경을 설정해요.
- Unity → Stove → Edit Settings 클릭
- Inspector 창에서
Zone항목 설정 —live/sandbox
3) Android — Custom Gradle Template 5개 활성화
Project Settings → Player → Android → Publishing Settings에서 아래 5개를 모두 체크해요.
- Custom Main Gradle Template →
Assets/Plugins/Android/mainTemplate.gradle - Custom Launcher Gradle Template →
launcherTemplate.gradle - Custom Base Gradle Template →
baseProjectTemplate.gradle - Custom Gradle Properties Template →
gradleTemplate.properties - Custom Settings Gradle Template →
settingsTemplate.gradle
baseProjectTemplate.gradle — Plugin 버전 지정
plugins {
id 'com.android.application' version '7.1.2' apply false
id 'com.android.library' version '7.1.2' apply false
id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
//**BUILD_SCRIPT_DEPS**
}
task clean(type: Delete) {
delete rootProject.buildDir
}
gradleTemplate.properties — AndroidX / Jetifier 활성화
org.gradle.jvmargs=-Xmx**JVM_HEAP_SIZE**M
org.gradle.parallel=true
unityStreamingAssets=**STREAMING_ASSETS**
android.useAndroidX=true
android.enableJetifier=true
**ADDITIONAL_PROPERTIES**
settingsTemplate.gradle — STOVE / Huawei / OneStore Maven 저장소 등록
pluginManagement {
repositories {
**ARTIFACTORYREPOSITORY**
gradlePluginPortal()
google()
mavenCentral()
}
}
include ':launcher', ':unityLibrary'
**INCLUDES**
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
**ARTIFACTORYREPOSITORY**
google()
mavenCentral()
flatDir { dirs "${project(':unityLibrary').projectDir}/libs" }
maven { url "https://externalnexus.iam0.com/repository/mvp"; allowInsecureProtocol = true } // STOVE
maven { url "https://jitpack.io"; allowInsecureProtocol = true }
maven { url "https://developer.huawei.com/repo/"; allowInsecureProtocol = true } // Huawei
maven { url "https://repo.onestore.net/repository/onestore-sdk-public"; allowInsecureProtocol = true } // OneStore
}
}
mainTemplate.gradle — 스토브 의존성 + dataBinding + multiDexEnabled
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
**APPLY_PLUGINS**
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.stove:base:2.9.0'
implementation 'com.stove:log:2.9.0'
implementation 'com.stove:auth:2.9.0'
implementation 'com.stove:auth-apple:2.9.0'
implementation 'com.stove:auth-facebook:2.9.0'
implementation 'com.stove:auth-google:2.9.0'
implementation 'com.stove:auth-naver:2.9.0'
implementation 'com.stove:auth-line:2.9.0'
implementation 'com.stove:auth-steam:2.9.0'
implementation 'com.stove:auth-ui:2.9.0'
implementation 'com.stove:push:2.9.0'
implementation 'com.stove:push-firebase:2.9.0'
implementation 'com.stove:view:2.9.0'
implementation 'com.stove:iap:2.9.0'
implementation 'com.stove:iap-google:2.9.0'
implementation 'com.stove:gamingservices:2.9.0'
**DEPS**
}
android {
defaultConfig { multiDexEnabled true }
buildFeatures { dataBinding true }
}
launcherTemplate.gradle — Amazon Build 충돌 회피 + SDK 2.8.0 강제
configurations.all {
// Amazon Build에서 중복 의존성 제외
exclude(group: 'com.google.protobuf', module: 'protobuf-lite')
// SDK 2.8.0+ 사용 시 Android 7.x 지원 필수
resolutionStrategy {
force 'com.google.android.gms:play-services-ads-identifier:18.0.1'
}
}
android {
defaultConfig { multiDexEnabled true }
buildFeatures { dataBinding true }
}
4) UnityPlayerActivity 상속 시 추가 설정
UnityPlayerActivity를 상속받아 커스텀 Activity를 사용 중이라면, STOVE Push / View UI Overlay가 정상 동작하도록 아래 코드를 추가하고 manifest에서 hardwareAccelerated를 설정해야 해요.
class CustomActivity : UnityPlayerActivity() {
override fun onCreate(bundle: Bundle?) {
super.onCreate(bundle)
Push.handleIntent(applicationContext, intent) // Push 동작 추가
}
override fun onNewIntent(newIntent: Intent?) {
super.onNewIntent(newIntent)
Push.handleIntent(applicationContext, newIntent)
}
// View 2.8.2 이상 — UI Overlay 동작 추가
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 7585) ViewUI.handleIntent(requestCode, resultCode, data)
}
}
AndroidManifest 하드웨어 가속 설정 (View 2.8.2+ 필수)
<activity
android:name="com.stove.unity.StoveUnityPlayerActivity"
android:hardwareAccelerated="true"
android:exported="true">
<!-- ... -->
</activity>
5) iOS — Xcode 후처리 (PostProcessBuild 권장)
Unity는 빌드 시 Xcode 프로젝트를 생성해요. 매 빌드마다 수동으로 손대지 않도록 Assets/Editor/에 PostProcessBuild 스크립트를 두는 것을 권장해요. 자동화로 처리할 항목은 다음과 같아요(구체값은 위 iOS 섹션 참조).
- Podfile 작성 +
pod install Info.plist키 추가 (StoveEnvironment/LSApplicationQueriesSchemes=[mstove]/ URL Schemes / 권한 메시지)- entitlements 머지 (Capabilities 키)
- Library / Runpath Search Paths 추가 (Xcode 16.x / 26 분기)
AppDelegate.mm에SGSApplicationDelegate openURL위임 코드 삽입
Library / Runpath Search Paths — Xcode 버전 분기 실제 코드:
pbxProject.AddBuildProperty(targetGuid, "LIBRARY_SEARCH_PATHS", "$(SDKROOT)/usr/lib/swift");
pbxProject.AddBuildProperty(targetGuid, "LIBRARY_SEARCH_PATHS", "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)");
pbxProject.AddBuildProperty(targetGuid, "LIBRARY_SEARCH_PATHS", "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)");
pbxProject.AddBuildProperty(targetGuid, "LD_RUNPATH_SEARCH_PATHS", "/usr/lib/swift");
pbxProject.AddBuildProperty(targetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "YES");
// UnityFramework target에도 동일 적용
pbxProject.AddBuildProperty(frameworkTargetGuid, "LIBRARY_SEARCH_PATHS", "$(SDKROOT)/usr/lib/swift");
pbxProject.AddBuildProperty(frameworkTargetGuid, "LIBRARY_SEARCH_PATHS", "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)");
pbxProject.AddBuildProperty(frameworkTargetGuid, "LIBRARY_SEARCH_PATHS", "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)");
pbxProject.SetBuildProperty(frameworkTargetGuid, "LD_RUNPATH_SEARCH_PATHS", "/usr/lib/swift");
pbxProject.AddBuildProperty(frameworkTargetGuid, "LD_RUNPATH_SEARCH_PATHS", "$(inherited)");
pbxProject.AddBuildProperty(frameworkTargetGuid, "LD_RUNPATH_SEARCH_PATHS", "@executable_path/Frameworks");
pbxProject.AddBuildProperty(frameworkTargetGuid, "LD_RUNPATH_SEARCH_PATHS", "@loader_path/Frameworks");
pbxProject.AddBuildProperty(frameworkTargetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "NO");
수동 처리도 가능해요
자동화 스크립트를 두지 않는다면 매 빌드 후 Xcode를 열어 위 값을 직접 적용해도 동작에는 문제없어요. 다만 빌드가 잦으면 자동화를 권장해요.
6) iOS — SGSIAP2 적용 시 변경사항 (Unity 특유)
iOS Native와 달리 Unity는 SDK 내부 파일(IAPNativeInterface.mm)을 확인하는 절차가 추가돼요.
Assets/Stove/Plugins/iOS/IAPNativeInterface.mm파일 상단 import 변경objectivec#import <SGSIAP2/SGSIAP2.h> // before: #import <SGSIAP/SGSIAP.h>- Podfile 변경 —
pod 'SGSIAP2', '2.9.0' stove_iap_setListener함수 최하단에[SGSIAP prepareTransactionListener]호출 확인- Unity Plugin 2.8.2 이상에는 코드가 내부 포함되어 있어요. 누락 시 결제 시도가 사일런트로 실패하므로 직접 확인을 권장해요.
objectivecvoid stove_iap_setListener(const char *identifier) { NSString *nsIdentifier = StoveSDKMakeNSString(identifier); // ... 기존 코드 ... [SGSIAP prepareTransactionListener]; // ⚠️ 누락 시 결제 실패 }
7) Notification Service Extension (푸시 풍부형 사용 시)
iOS 푸시에서 이미지·액션을 사용하려면 NSE target을 추가하고 SGSPushExtension Pod을 의존성으로 넣어야 해요.
- Podfile에 NSE target 추가 (위 iOS → Podfile 참고)
- Xcode → File → New → Target → "Notification Service Extension"으로 target 생성
NSE target의 Build Settings — Xcode 버전 분기 실제 코드:
pbxProject.AddBuildProperty(notificationServiceTarget, "LIBRARY_SEARCH_PATHS", "$(SDKROOT)/usr/lib/swift");
pbxProject.AddBuildProperty(notificationServiceTarget, "LIBRARY_SEARCH_PATHS", "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)");
pbxProject.AddBuildProperty(notificationServiceTarget, "LIBRARY_SEARCH_PATHS", "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)");
pbxProject.SetBuildProperty(notificationServiceTarget, "LD_RUNPATH_SEARCH_PATHS", "/usr/lib/swift");
pbxProject.AddBuildProperty(notificationServiceTarget, "LD_RUNPATH_SEARCH_PATHS", "$(inherited)");
pbxProject.AddBuildProperty(notificationServiceTarget, "LD_RUNPATH_SEARCH_PATHS", "@executable_path/Frameworks");
pbxProject.AddBuildProperty(notificationServiceTarget, "LD_RUNPATH_SEARCH_PATHS", "@executable_path/../../Frameworks");
pbxProject.AddBuildProperty(notificationServiceTarget, "LD_RUNPATH_SEARCH_PATHS", "@loader_path/Frameworks");
Unreal
Plugins 폴더 배치 + Project.build.cs 의존성 + UPL(GamePlugin.xml)로 Android Gradle 의존성 주입 + Online Subsystem GooglePlay 비활성화까지가 기본이에요. iOS Capabilities(keychain·app group) 또는 LSSupportsOpeningDocumentsInPlace(네이버 로그인용)가 필요하면 커스텀 엔진 빌드 환경이 추가로 필요해요. (Unreal 5.44 이상)
1) 플러그인 배치 & 활성화
- 다운로드한 스토브 모듈을 프로젝트
Plugins폴더에 복사 .uproject또는 편집 → 플러그인 메뉴에서 활성화
2) Project.build.cs — 모듈 의존성
using System.IO;
public class Game : ModuleRules
{
public Game(ReadOnlyTargetRules Target) : base(Target)
{
PublicDependencyModuleNames.AddRange(new string[] {
"SGS_Base", "SGS_Log",
"SGS_Auth", "SGS_AuthUI",
"SGS_Push", "SGS_View",
"SGS_IAP", // 또는 "SGS_IAP_StoreKit2"
"SGS_GamingServices"
});
}
}
3) Android 설정
SDK Manager 버전 고정
- SDK Platform 34 설치
- SDK Tools 33.0.3 설치 (상위 버전은 제거 또는 체크 해제)
Gradle 7.5 설정 — Engine/Build/Android/Java/gradle/gradle/wrapper/gradle-wrapper.properties
distributionUrl=https://services.gradle.org/distributions/gradle-7.5-all.zip
Online Subsystem GooglePlay 비활성화 (필수) — 언리얼 기본 제공 플러그인의 Online Subsystem GooglePlay를 비활성화해서 스토브 플러그인의 GooglePlay 라이브러리와의 충돌을 방지해요.
Extra Manifest — 편집 → 프로젝트 세팅 → Android → 고급 APK 패키징
<meta-data android:name="com.stove.environment" android:value="live" /> <!-- live / sandbox -->
<meta-data android:name="com.stove.auth.ui.sanction_type" android:value="1" /> <!-- 선택 -->
UPL GamePlugin.xml — Unreal에서 Gradle 의존성을 주입하는 유일한 통로
<?xml version="1.0" encoding="utf-8"?>
<root xmlns:android="http://schemas.android.com/apk/res/android">
<init>
<log text="Android init"/>
</init>
<buildGradleAdditions>
<insert>
dependencies {
implementation 'com.stove:base:2.9.0'
implementation 'com.stove:log:2.9.0'
implementation 'com.stove:auth:2.9.0'
implementation 'com.stove:auth-apple:2.9.0'
implementation 'com.stove:auth-facebook:2.9.0'
implementation 'com.stove:auth-google:2.9.0'
implementation 'com.stove:auth-naver:2.9.0'
implementation 'com.stove:auth-line:2.9.0'
implementation 'com.stove:auth-steam:2.9.0'
implementation 'com.stove:auth-ui:2.9.0'
implementation 'com.stove:push:2.9.0'
implementation 'com.stove:push-firebase:2.9.0'
implementation 'com.stove:view:2.9.0'
implementation 'com.stove:iap:2.9.0'
implementation 'com.stove:iap-google:2.9.0'
implementation 'com.stove:gamingservices:2.9.0'
}
</insert>
</buildGradleAdditions>
</root>
Project.build.cs에 UPL 연결
public class Game : ModuleRules
{
public Game(ReadOnlyTargetRules Target) : base(Target)
{
// ...
if (Target.Platform == UnrealTargetPlatform.Android)
{
PrivateDependencyModuleNames.AddRange(new string[] { "Launch" });
string PluginPath = Utils.MakePathRelativeTo(ModuleDirectory, Target.RelativeEnginePath);
AdditionalPropertiesForReceipt.Add(
"AndroidPlugin",
Path.Combine(PluginPath, "GamePlugin.xml"));
}
}
}
이 UPL을 빼면 APK에 STOVE SDK가 안 들어가요
Unreal에서는 build.gradle을 직접 편집할 수 없고, UPL로만 dependency를 주입할 수 있어요.
4) iOS 설정 — 기본
Unreal 빌드 후 산출된 Xcode 프로젝트에 적용해요. 매 빌드마다 Xcode 설정이 초기화될 수 있으니 IPP 또는 Build.cs 후처리로 자동화를 권장해요. 처리할 항목:
- iOS SDK Framework를 Git 저장소에서 받아 Plugins 디렉토리에 배치
Info.plist—StoveEnvironment/LSApplicationQueriesSchemes=[mstove]/ URL Schemes / 권한 메시지 (구체값은 위 iOS 섹션 참고)- Embedded Framework 처리 — 각 플러그인이 가져가는
.framework/.bundle매핑은 아래 표 참고 - Build Phases → Embed Frameworks에서 Embed & Sign 설정
5) Unreal 플러그인 ↔ iOS SDK Framework 매핑
각 Unreal 플러그인이 어떤 framework / bundle을 가져가는지 매핑이에요. Embedded Framework 작업 시 참고하세요.
| 플러그인 | SDK Framework | Resources |
|---|---|---|
| SGS_Base | SGSBase.framework, SGSGamingServices.framework | SGSBaseResources.bundle |
| SGS_Log | SGSLog.framework | SGSLogResources.bundle |
| SGS_Auth | SGSAuth.framework, SGSMemberAuth.framework | SGSAuthResources.bundle, SGSMemberAuthResources.bundle |
| SGS_AuthUI | SGSAuthUI.framework | SGSAuthUIResources.bundle |
| SGS_Auth_Facebook | SGSAuthFacebook.framework, FBAEMKit.framework, FBSDKCoreKit.framework, FBSDKCoreKit_Basics.framework, FBSDKLoginKit.framework | SGSAuthFacebookResources.bundle |
| SGS_Auth_Google | SGSAuthGoogle.framework, GoogleSignIn.framework, AppAuth.framework, GTMAppAuth.framework, GTMSessionFetcher.framework | SGSAuthGoogleResources.bundle |
| SGS_Auth_Apple | SGSAuthApple.framework | SGSAuthAppleResources.bundle |
| SGS_Auth_Naver | SGSAuthNaver.framework, NaverThirdPartyLogin.framework | SGSAuthNaverResources.bundle |
| SGS_Auth_Line | SGSAuthLine.framework, LineSDK.framework | SGSAuthLineResources.bundle |
| SGS_Auth_Twitter | SGSAuthTwitter.framework | SGSAuthTwitterResources.bundle |
| SGS_Push | SGSPush.framework, SGSPushExtension.framework | — |
| SGS_View | SGSView.framework | SGSViewResources.bundle |
| SGS_IAP / SGS_IAP_StoreKit2 | SGSIAP.framework / SGSIAP2.framework | — |
6) iOS — SGSIAP2 적용 시 변경사항 (Unreal 특유)
iOS Native/Unity와 달리 Unreal은 플러그인을 통째로 교체해요. API 호출 코드(SGSIAPProduct, SGSIAPOptional 등)는 동일한 @objc 이름을 유지하므로 게임 코드 수정은 필요 없어요.
- SDK 다운로드 페이지에서
SGS_IAP_StoreKit2(iOS)항목 다운로드 - 프로젝트의
Plugins/SGS_IAP디렉토리를 다운로드한 플러그인으로 교체 - Unreal 프로젝트 재빌드
prepareTransactionListener 호출 등 내부 처리는 새 플러그인에 포함돼 있어 게임에서 추가 작업이 필요 없어요.
7) iOS — Capabilities 설정 (커스텀 엔진 빌드 필요)
Unreal 5.44 기본 엔진은 Push Notifications / Sign in with Apple / In-App Purchase만 Capabilities로 지원해요. STOVE SDK에 필요한 Keychain Sharing / App Group을 추가하려면 GitHub의 Unreal Engine 풀 소스로 빌드한 커스텀 엔진이 필요해요.
커스텀 엔진 빌드 환경에서만 적용 가능합니다
ㆍ Epic Games 계정과 GitHub 계정을 연결해 Unreal Engine GitHub에서 풀 소스 받기
ㆍ 빌드 절차는 Unreal Engine GitHub 다운로드 안내를 참고하세요
커스텀 엔진의 Engine/Source/Programs/UnrealBuildTool/Platform/IOS/IOSExports.cs의 WriteEntitlements() 함수에 스토브 키체인 설정을 추가해요.
// IOSExports.cs > WriteEntitlements() 함수 내에 추가
Text.AppendLine("\t<key>keychain-access-groups</key>");
Text.AppendLine("\t<array><string>$(AppIdentifierPrefix)com.stove.globaldata</string></array>");
키체인·앱 그룹이 필수가 아닌 게임은 기본 엔진으로 가능해요
토큰 영속 저장이나 NSE(푸시 확장) 등을 사용하지 않는 단순 연동 케이스라면 기본 엔진으로도 빌드할 수 있어요. 적용 가능 여부는 퍼블리싱 기술 담당자에게 확인해 주세요.
8) iOS — LSSupportsOpeningDocumentsInPlace 사용 (네이버 로그인 사용 시, 커스텀 엔진 빌드 필요)
네이버 로그인을 추가하면 deprecated된 OpenURL 메서드와 신규 메서드를 동시에 처리해야 해서 엔진 소스 수정이 필요해요. 이 작업도 커스텀 엔진 빌드 환경에서만 가능합니다.
배경: Unreal 엔진은 iOS의 deprecated된 OpenURL 함수를 사용하지만, 네이버 SDK는 신규 함수 형태를 사용해요. 두 형태를 모두 지원하려면 IOSAppDelegate에 application:openURL:options: 메서드 분기를 추가해야 해요.
수정 파일:
아래 예시 코드의 add - start 부터 add - end 까지 코드에 추가해 주세요
Engine/Source/Runtime/ApplicationCore/Public/IOS/IOSAppDelegate.h
DECLARE_MULTICAST_DELEGATE_FourParams(FOnOpenURL, UIApplication*, NSURL*, NSString*, id);
static FOnOpenURL OnOpenURL;
// add - start
DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnOpenURLwithOptions, UIApplication*, NSURL*, NSDictionary* );
static FOnOpenURLwithOptions OnOpenURLwithOptions;
// add - end
// -------------------------------------------//
// parameters passed from openURL
@property (nonatomic, retain) NSMutableArray* savedOpenUrlParameters;
// add - start
@property (nonatomic, retain) NSMutableArray* savedOpenUrlWithOptionsParameters;
// add - end
Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSAppDelegate.cpp
FIOSCoreDelegates::FOnOpenURL FIOSCoreDelegates::OnOpenURL;
// add - start
FIOSCoreDelegates::FOnOpenURLwithOptions FIOSCoreDelegates::OnOpenURLwithOptions;
// add - end
FIOSCoreDelegates::FOnWillResignActive FIOSCoreDelegates::OnWillResignActive;
FIOSCoreDelegates::FOnDidBecomeActive FIOSCoreDelegates::OnDidBecomeActive;
TArray<FIOSCoreDelegates::FFilterDelegateAndHandle> FIOSCoreDelegates::PushNotificationFilters;
// ----------------------------------------- //
@synthesize AccessibilityCacheTimer;
#endif
@synthesize savedOpenUrlParameters;
// add - start
@synthesize savedOpenUrlWithOptionsParameters;
// add - end
@synthesize BackgroundSessionEventCompleteDelegate;
// ----------------------------------------- //
GShowSplashScreen = false;
}, TStatId(), NULL, ENamedThreads::ActualRenderingThread);
}
// add - start
for (NSDictionary* openUrlParameter in self.savedOpenUrlWithOptionsParameters)
{
UIApplication* application = [openUrlParameter valueForKey : @"application"];
NSURL* url = [openUrlParameter valueForKey : @"url"];
NSDictionary<NSString*, id> * options = [openUrlParameter valueForKey : @"options"];
FIOSCoreDelegates::OnOpenURLwithOptions.Broadcast(application, url, options);
}
self.savedOpenUrlWithOptionsParameters = nil; // clear after saved openurl delegate running
// add - end
for (NSDictionary* openUrlParameter in self.savedOpenUrlParameters)
{
UIApplication* application = [openUrlParameter valueForKey : @"application"];
// ----------------------------------------- //
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
// save launch options
self.launchOptions = launchOptions;
#if PLATFORM_TVOS
self.bDeviceInPortraitMode = false;
#else
// use the status bar orientation to properly determine landscape vs portrait
self.bDeviceInPortraitMode = UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]);
printf("========= This app is in %s mode\n", self.bDeviceInPortraitMode ? "PORTRAIT" : "LANDSCAPE");
#endif
// check OS version to make sure we have the API
OSVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
if (!FPlatformMisc::IsDebuggerPresent() || GAlwaysReportCrash)
{
// InstallSignalHandlers();
}
self.savedOpenUrlParameters = [[NSMutableArray alloc] init];
// add - start
self.savedOpenUrlWithOptionsParameters = [[NSMutableArray alloc] init];
// add - end
self.PeakMemoryTimer = [NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(RecordPeakMemory) userInfo:nil repeats:YES];
#if !BUILD_EMBEDDED_APP
// ----------------------------------------- //
return YES;
}
// add - start
//### use option
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options
{
#if !NO_LOGGING
NSLog(@"%s", "IOSAppDelegate openURL options\n");
#endif
NSString* EncdodedURLString = [url absoluteString];
NSString* URLString = [EncdodedURLString stringByRemovingPercentEncoding];
FString CommandLineParameters(URLString);
// Strip the "URL" part of the URL before treating this like args. It comes in looking like so:
// "MyGame://arg1 arg2 arg3 ..."
// So, we're going to make it look like:
// "arg1 arg2 arg3 ..."
int32 URLTerminator = CommandLineParameters.Find( TEXT("://"), ESearchCase::CaseSensitive);
if ( URLTerminator > -1 )
{
CommandLineParameters.RightChopInline(URLTerminator + 3, false);
}
FIOSCommandLineHelper::InitCommandArgs(CommandLineParameters);
self.bCommandLineReady = true;
[self.CommandLineParseTimer invalidate];
self.CommandLineParseTimer = nil;
// Save openurl infomation before engine initialize.
// When engine is done ready, running like previous. ( if OnOpenUrl is bound on game source. )
if (bEngineInit)
{
FIOSCoreDelegates::OnOpenURLwithOptions.Broadcast(app, url, options);
}
else
{
#if !NO_LOGGING
NSLog(@"%s", "Before Engine Init receive IOSAppDelegate openURL\n");
#endif
NSDictionary* openUrlParameter = [NSDictionary dictionaryWithObjectsAndKeys :
app , @"application",
url, @"url",
options, @"options",
nil];
[savedOpenUrlWithOptionsParameters addObject : openUrlParameter];
}
return YES;
}
// add - end
FCriticalSection RenderSuspend;
- (void)applicationWillResignActive:(UIApplication *)application
{
구체 패치 코드가 필요하시면
엔진 버전별로 수정 위치가 다르므로, 상세 패치 스니펫은 스토브 퍼블리싱 기술 담당자에게 요청해 주세요.
Android
Gradle 의존성 + AndroidManifest.xml 메타데이터 + (SDK 2.8.0 이상 사용 시) Android 7.x 지원 강제 옵션을 설정해요.
1) Gradle 의존성 (app/build.gradle)
STOVE Maven 저장소를 등록하고 필요한 모듈을 의존성으로 추가해요.
buildscript {
ext.kotlin_version = '1.8.0'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
android {
dataBinding { enabled = true }
}
repositories {
google()
maven {
// SDK 2.6.1 이상 — 현재 저장소
url "https://externalnexus.iam0.com/repository/mvp"
allowInsecureProtocol = true // Android Gradle Plugin 7.0+ 필수
}
mavenCentral()
}
dependencies {
// 핵심
implementation 'com.stove:base:2.9.0'
implementation 'com.stove:log:2.9.0'
// 인증
implementation 'com.stove:auth:2.9.0'
implementation 'com.stove:auth-apple:2.9.0'
implementation 'com.stove:auth-facebook:2.9.0'
implementation 'com.stove:auth-google:2.9.0'
implementation 'com.stove:auth-naver:2.9.0'
implementation 'com.stove:auth-line:2.9.0'
implementation 'com.stove:auth-steam:2.9.0'
implementation 'com.stove:auth-ui:2.9.0'
// 그 외
implementation 'com.stove:push:2.9.0'
implementation 'com.stove:push-firebase:2.9.0'
implementation 'com.stove:view:2.9.0'
implementation 'com.stove:iap:2.9.0'
implementation 'com.stove:iap-google:2.9.0'
implementation 'com.stove:gamingservices:2.9.0'
}
SDK 2.6.1 미만을 사용 중이라면
저장소 위치가 달라요: http://e-nexus.iam0.net/content/repositories/mvp/
2) AndroidManifest.xml — 스토브 환경 설정 (필수)
com.stove.environment 메타데이터가 없으면 SDK 초기화 시 AuthConfigurationError(30001)가 발생해요.
<application>
<!-- 환경: live(라이브) / sandbox(테스트) -->
<meta-data
android:name="com.stove.environment"
android:value="live" />
</application>
3) (선택) AuthUI 옵션
필요한 경우 아래 메타데이터를 추가하세요. 미설정 시 괄호 안의 기본값으로 동작해요.
<application>
<!-- 제재 화면이 닫힐 때 동작: 미설정 시 앱 종료(default), 1로 설정 시 콜백 처리 -->
<meta-data android:name="com.stove.auth.ui.sanction_type" android:value="1" />
<!-- 약관 화면 색상 테마: orange(default) / black / LostarkM (AuthUI 2.6.2+) -->
<meta-data android:name="com.stove.auth.ui.theme" android:value="orange" />
<!-- 라이트/다크 모드: 0=시스템(default) / 1=light / 2=dark -->
<meta-data android:name="com.stove.auth.ui.appearance_mode" android:value="1" />
<!-- 약관 화면 크기: full(default) / small -->
<meta-data android:name="com.stove.auth.ui.display_size" android:value="small" />
<!-- 푸시 헤드업 알림: false(default) / true (AuthUI 2.9.0+) -->
<meta-data android:name="com.stove.push.heads_up_notification_enable" android:value="true" />
</application>
4) SDK 2.8.0 이상 — Android 7.x 지원 (필수)
play-services-ads-identifier를 18.0.1로 고정해야 Android 7.x 단말에서 런타임 크래시가 발생하지 않아요.
configurations.all {
resolutionStrategy {
force 'com.google.android.gms:play-services-ads-identifier:18.0.1'
}
}
5) 디버그 로그 확인
게임 개발·디버깅 시 stove-sdk 태그로 logcat에서 SDK 동작 로그를 볼 수 있어요.
adb shell setprop log.tag.stove-sdk VERBOSE
iOS
CocoaPods로 STOVE SDK를 추가하고 Info.plist / entitlements / Xcode Build Settings / AppDelegate까지 설정해야 정상 동작해요.
1) 사전 준비 — Specs 저장소 등록 (최초 1회)
STOVE SDK Pod는 internal repo에 호스팅돼요. GitLab 가입 후 로컬 CocoaPods에 저장소를 추가해요.
# 1) http://stove-developers-gitlab.sginfra.net 에 웹 회원가입 (별도 승인 절차 없음)
# 2) 터미널에서 repo 추가 (최초 1회 username/password 입력)
pod repo add Specs https://stove-developers-gitlab.sginfra.net/stove-sdk/Specs.git
GitLab 접근이 안 되시나요?
퍼블리싱 기술 담당자에게 문의해 주세요.
2) Podfile
아래 예시의 {ProjectTargetName}은 본인 Xcode 프로젝트의 main target 이름으로 바꿔 주세요.
source 'http://stove-developers-gitlab.sginfra.net/stove-sdk/Specs.git'
source 'https://github.com/CocoaPods/Specs.git'
target '{ProjectTargetName}' do
pod 'SGSBase', '2.9.0'
pod 'SGSLog', '2.9.0'
pod 'SGSAuth', '2.9.0'
pod 'SGSAuthApple', '2.9.0'
pod 'SGSAuthFacebook', '2.9.0'
pod 'SGSAuthGoogle', '2.9.0'
pod 'SGSAuthNaver', '2.9.0'
pod 'SGSAuthLine', '2.9.0'
pod 'SGSAuthSteam', '2.9.0'
pod 'SGSAuthUI', '2.9.0'
pod 'SGSPush', '2.9.0'
pod 'SGSView', '2.9.0'
pod 'SGSGamingServices', '2.9.0'
# 인앱결제 — 아래 중 하나만 선택 (동시 사용 불가)
pod 'SGSIAP', '2.9.0' # StoreKit 1 기반
# pod 'SGSIAP2', '2.9.0' # StoreKit 2 기반
end
# 푸시 확장 (Notification Service Extension) — 별도 target
target 'NotificationServiceExtension' do
pod 'SGSPushExtension', '2.9.0'
end
설치/업데이트 후 생성된 .xcworkspace로 빌드해요.
pod install
# 또는
pod update
3) (선택) Framework 수동 다운로드
CocoaPods를 사용할 수 없는 환경에서는 SDK 다운로드 및 설치 페이지에서 tag 이름으로 항목을 선택해 직접 다운로드한 뒤 Xcode 프로젝트에 추가하세요.
4) SGSIAP vs SGSIAP2 (인앱결제 모듈)
두 모듈은 동시 사용 불가. Podfile에서 하나만 활성화하세요.
| 항목 | SGSIAP (StoreKit 1) | SGSIAP2 (StoreKit 2) |
|---|---|---|
| Firebase Analytics 자동 수집 | 지원 | 미지원 — 리스너 기반 수동 연동 필요 |
| Singular 등 수동 전송 도구 | 변경 없음 | 변경 없음 |
SGSIAP → SGSIAP2 마이그레이션 시 필수 변경 3가지:
// ① import 변경 — SGSIAP를 import하는 모든 파일
#import <SGSIAP2/SGSIAP2.h> // before: #import <SGSIAP/SGSIAP.h>
// ② Podfile — pod 'SGSIAP2', '2.9.0' (SGSIAP에서 교체)
// ③ prepareTransactionListener 호출 추가 — 누락 시 결제 시도 즉시 errorListener 실패
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSNotification.SGSPurchasesUpdatedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updatedPurchasesNotification:)
name:NSNotification.SGSPurchasesUpdatedNotification object:nil];
[SGSIAP prepareTransactionListener]; // ⚠️ SGSIAP2에서 추가로 필요
호출 시점
결제 알림 observer를 등록한 직후 호출하세요. 미호출 시 결제 시도가 사일런트로 실패합니다.
5) Info.plist
<key>StoveEnvironment</key>
<string>live</string> <!-- live / sandbox -->
<!-- Base 2.2.0+ 필수 -->
<key>NSUserTrackingUsageDescription</key>
<string>로그 수집을 위해 필요합니다</string>
<!-- AuthUI 2.3.0+ 선택: 1=제재 화면 닫힘 시 콜백 처리 (default: 앱 종료) -->
<key>StoveSanctionType</key>
<integer>1</integer>
<!-- 커뮤니티/고객센터 사용 시 권한 메시지 (문구는 게임에 맞게 조정) -->
<key>NSCameraUsageDescription</key>
<string>커뮤니티 게시글/문의 시 사진·동영상 촬영에 사용됩니다</string>
<key>NSMicrophoneUsageDescription</key>
<string>음성이 포함된 동영상 촬영에 사용됩니다</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>기존 사진·동영상 첨부에 사용됩니다</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>사진·동영상을 디바이스에 저장하기 위해 사용됩니다</string>
<!-- 스토브 앱간 인증 — 미설정 시 인증 실패 -->
<key>LSApplicationQueriesSchemes</key>
<array>
<string>mstove</string>
</array>
<!-- URL Schemes — 외부 앱(Google/Apple/Naver/Line 등) 복귀에 사용 -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
</dict>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
6) Xcode Capabilities (.entitlements)
.entitlements 파일을 직접 편집하거나, Xcode → Signing & Capabilities → + Capability로 UI에서 토글해도 같은 결과예요.
<!-- APNS 푸시 사용 시 -->
<key>aps-environment</key>
<string>production</string>
<!-- StoveSDK 필수 — 미설정 시 토큰 저장 실패 -->
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.stove.globaldata</string>
</array>
<!-- 애플 로그인 사용 시 -->
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
<!-- 푸시 확장(Notification Service Extension) 사용 시 -->
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.stove.sdk</string>
</array>
7) Build Settings — Search Paths
Xcode 버전에 따라 분기해서 설정해요. 미설정 시 Swift 런타임 링크 또는 dylib not found 오류가 발생해요.
Library Search Paths — Xcode → Build Settings → Search Paths
$(SDKROOT)/usr/lib/swift
$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)
$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)
Xcode 26.0.x 사용 시 추가 경로$(TOOLCHAIN_DIR)가 Metal cryptex 경로로 잘못 resolve되는 버그가 있어요(26.2에서 수정됨). 26.0.x 환경에서는 아래 경로를 추가하세요.$(DEVELOPER_DIR)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$(PLATFORM_NAME)
Runpath Search Paths — Xcode → Build Settings → Linking (Xcode 16/26 공통, 아래 순서를 반드시 지켜서 추가)
/usr/lib/swift
$(inherited)
@executable_path/Frameworks
@loader_path/Frameworks
8) AppDelegate — Provider 로그인 위임 (필수)
Google/Apple/Facebook/Naver/Line OAuth 콜백이 SDK에 도달하려면 openURL을 위임해야 해요. 미설정 시 외부 로그인 자체가 실패해요.
#import <SGSAuth/SGSAuth.h>
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options
{
return [SGSApplicationDelegate application:app openURL:url options:options];
}
PC SDK
PC 빌드는 Mobile SDK와 별도의 패키지를 사용해요. 아래 3개 환경 중 게임에서 사용하는 엔진에 맞는 절차를 따라 주세요.
PCSDK3는 Windows 64bit 환경을 공식 지원해요.
ㆍ 32bit 또는 Mono(Unity) 지원이 필요하면 스토브 기술지원에 문의해 주세요.
ㆍ 모듈 의존성: 모든 모듈은 BaseSDK가 선행되어야 해요. IAPSDK/ViewSDK를 사용하는 경우 WebView2Loader.dll이 함께 포함되어야 해요.
- Native C/C++ 빌드 설정 (Visual Studio)
- 솔루션 디렉터리에
StovePCSDK3디렉터리를 생성하고 다운로드한 패키지의Include/Lib/Dll폴더를 복사해요. - 프로젝트 속성에서 다음과 같이 경로를 지정해요.
- 구성 속성 > C/C++ > 일반 > 추가 포함 디렉터리:
$(SolutionDir)StovePCSDK3\Include - 구성 속성 > 링커 > 일반 > 추가 라이브러리 디렉터리:
$(SolutionDir)StovePCSDK3\Lib - 구성 속성 > 링커 > 입력 > 추가 종속성: 사용할 모듈별
*.lib(예:BaseSDK.lib)
- 구성 속성 > C/C++ > 일반 > 추가 포함 디렉터리:
- 게임 빌드 출력 폴더에 사용할 모듈의
*.dll을 복사해 주세요. - 소스 코드에서
#include "BaseSDK.h"로 헤더를 포함해요.
- 솔루션 디렉터리에
- Unity 빌드 설정
Assets/Plugins/STOVEPCSDK3하위에Managed,Native디렉터리를 생성해요.- 다운로드한 패키지의
x86_64폴더에서 Managed 플러그인(*_NET.dll)과 Native 플러그인(*.dll)을 각각 위 디렉터리에 복사해요.- Native와 Managed 플러그인은 쌍을 이루므로 함께 포함해야 해요. (예: Base만 사용 →
BaseSDK.dll+BaseSDK_NET.dll) - IAP/View 사용 시
WebView2Loader.dll도 함께 복사해요.
- Native와 Managed 플러그인은 쌍을 이루므로 함께 포함해야 해요. (예: Base만 사용 →
- 각 dll Inspector → Platform settings에서
x86_64(또는x64) 선택 후 Apply 해요. - Player Settings → Configuration → Api Compatibility Level: .NET 4.x
- 빌드 시 Architecture는 시스템에 맞춰
x86_64로 지정해요. (Unity 에디터는 x64만 지원) - 코드에서
using static Stove.PCSDK.Base;와 같이 모듈별 네임스페이스를 추가해요.
!info향후 unitypackage 방식으로 제공 예정이에요.
ㆍ 현재는 dll을 직접 배치하는 수동 방식만 지원하지만, 차기 버전부터.unitypackage임포트 방식으로 간편하게 제공할 예정이에요.
ㆍ 적용 시점은 SDK 릴리즈 노트에서 안내드릴게요.
- Unreal 빌드 설정
- 프로젝트 루트의
ThirdParty/StovePCSDK디렉터리에 다운로드한 패키지의Include/Lib/Dll폴더를 배치해요. - 모듈 dll은
Binaries/Win64에도 함께 복사해요. (IAP/View 사용 시WebView2Loader.dll포함) - 모듈의
*.Build.cs파일에 다음과 같은 형태로 PublicIncludePaths / PublicAdditionalLibraries / RuntimeDependencies를 추가해요.
csstring ThirdPartyPath = Path.GetFullPath(Path.Combine(ModuleDirectory, "../../ThirdParty")); string StovePCSDKPath = Path.Combine(ThirdPartyPath, "StovePCSDK"); PublicIncludePaths.Add(Path.Combine(StovePCSDKPath, "Include")); string[] SDKNameList = { "BaseSDK", "IAPSDK", "ViewSDK" }; foreach(string SDKName in SDKNameList) { PublicAdditionalLibraries.Add(Path.Combine(StovePCSDKPath, "Lib", "x64", SDKName + ".lib")); RuntimeDependencies.Add( Path.Combine("$(BinaryOutputDir)", SDKName + ".dll"), Path.Combine(StovePCSDKPath, "Dll", "x64", SDKName + ".dll")); }- 소스 코드에서
#include "BaseSDK.h"로 헤더를 포함해요.
- 프로젝트 루트의
PCSDK3 모듈 초기화·정리 흐름은 SDK 기본 연동 가이드를 따라 주세요.
ㆍ Base SDK 초기화 후 다른 모듈을 초기화하고, 정리 시에는 역순으로 진행해야 해요.
ㆍ 로그는 C:\Users\{유저명}\AppData\Local\STOVEPCSDK3{Env}\logs\{GameID} 경로에 모듈별로 쌓여요.
SDK Config 설정
SDK Config는 SDK 동작에 필요한 설정값을 등록·관리하는 시스템이에요. SDK v2.x에서 제공하며, 파트너스를 통해 등록·관리해요.
SDK Config 특징
- SDK 기본 설정값 및 UI 노출 옵션, 플랫폼 URL 설정값을 관리해요.
- 클라이언트나 서버 변경 없이 서비스 설정값을 변경할 수 있어요.
- 국가별, 마켓별(패키지명 기준) 설정이 가능해 지역별 운영이 유연해요.
SDK Config 미등록 시 SDK init 실패
ㆍ SDK Config는 패키지별 / 클라이언트 버전별로 반드시 등록해야 해요. 등록되지 않은 버전은 SDK init이 실패해 게임 접속이 불가능해요.
ㆍ 버전 업데이트 시에도 Config 세팅 작업을 함께 진행해 주세요.
등록 위치
[파트너스 > GM > SDK Config 설정] 에서 등록·관리해요. 환경별로 별도 등록이 필요해요.
| 환경 | 접속 경로 |
|---|---|
| Sandbox | partners.gate8.com > GM > SDK Config 설정 |
| Live | partners.onstove.com > GM > SDK Config 설정 |
주요 등록 항목
| 항목 | 필수 여부 | 설명 |
|---|---|---|
| 패키지 / 클라이언트 버전 | 필수 | 패키지명 + 클라이언트 버전 단위로 Config 등록 |
| App ID / Client ID | 필수 | 스토브 플랫폼이 게임을 식별하는 키값 |
| 기본 정책 설정 | 필수 | 접속국가 GDS 설정, 약관 정책, 본인인증 정책 등 |
| 기능별 모듈 설정 | 선택 | 팝업, 푸시, 인앱 결제, 캐릭터, 기기 등록 등 사용 기능별 옵션 |
| UI / 노출 옵션 | 선택 | 로그인 UI Provider 노출 순서, 약관 동의 UI 옵션 등 |
iOS Unity UISceneDelegate 임시 조치
Unity가 특정 에디터 버전부터 iOS 빌드 시 UIScene 라이프사이클(SceneDelegate)을 기본 지원하도록 업데이트됐어요. 이로 인해 Xcode 프로젝트를 생성·빌드할 때 Info.plist에 UIApplicationSceneManifest 설정이 자동으로 삽입돼요.
STOVE SDK(iOS)는 AppDelegate 라이프사이클 콜백을 기반으로 동작해요. Info.plist에 UIApplicationSceneManifest가 존재하면 iOS가 SceneDelegate를 우선 호출하면서 AppDelegate의 일부 콜백이 호출되지 않아, 소셜 로그인·Push 등 SDK 기능 일부가 정상 동작하지 않아요.
적용 대상
아래 버전 이상을 사용 중인 경우에만 이 조치가 필요해요.
| Unity 버전 라인 | 해당 버전 |
|---|---|
| Unity 6.5 | 6000.5.0a3 이상 |
| Unity 6.4 | 6000.4.0b8 이상 |
| Unity 6.3 LTS | 6000.3.8f1 이상 |
| Unity 6.0 LTS | 6000.0.68f1 이상 |
| Unity 2022 xLTS | 2022.3.72f1 이상 |
영향 범위
Info.plist에 UIApplicationSceneManifest 설정이 남아 있으면 아래 기능에서 문제가 발생해요.
| 영역 | 관련 AppDelegate 콜백 | 증상 |
|---|---|---|
| 소셜 로그인(Auth) | application(_:open:options:) | Google · Facebook · Naver · LINE 등 3rd party 로그인 후 앱으로 돌아오는 리다이렉트 URL을 SDK가 수신하지 못해 로그인이 완료되지 않아요. |
왜 SceneDelegate로는 대체할 수 없나요?
ㆍ SceneDelegate(UIWindowSceneDelegate)에는 위 콜백이 존재하지 않아요.
ㆍ iOS가 SceneDelegate를 사용하도록 구성되면 앱 전역 콜백인 AppDelegate의 URL 처리·Push 콜백이 호출되지 않아요.
확인 방법
프로젝트에 아래 설정이 적용되어 있는지 확인해요.
- Xcode에서
Info.plist파일을 열기 - 아래 키가 존재하는지 확인 —
Application Scene Manifest(raw key:UIApplicationSceneManifest), 하위 항목Scene Configuration(raw key:UISceneConfigurations) - 소스 코드 형태로 확인하려면 Info.plist를 마우스 우클릭 → Open As → Source Code로 열어 아래와 유사한 항목이 있는지 확인
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict/>
</dict>
조치 방법
UIApplicationSceneManifest 키(및 하위 UISceneConfigurations 등)를 Info.plist에서 삭제해요. 삭제하면 iOS가 다시 AppDelegate 라이프사이클을 사용하게 되어 SDK가 정상 동작해요.
빌드 시 자동으로 다시 삽입될 수 있어요
ㆍ Unity 프로젝트는 빌드(Xcode 프로젝트 재생성) 시마다 해당 설정이 자동으로 다시 삽입될 수 있어요.
ㆍ 매 빌드 후 Info.plist를 다시 확인하거나, 빌드 후처리 스크립트(PostProcessBuild)로 해당 키를 자동 제거하는 방식을 권장해요.
SceneDelegate 지원 예정
- Apple 플랫폼 정책상 SceneDelegate(UIScene 라이프사이클) 지원은 iOS 26까지는 선택 사항이지만, iOS 27부터는 필수로 전환될 예정이에요.
- STOVE SDK는 iOS 27 정식 출시 이전까지 SceneDelegate 환경에 대응할 예정이에요. 대응이 완료되면 추가로 안내드릴게요. 그 전까지는 위 조치를 적용해 주세요.
참고
- Unity Discussions — Apple: Update your Editor to receive UIScene lifecycle support
- Apple Developer — TN3187: Migrating to the UIKit scene-based life cycle
iOS Firebase Analytics 결제 이벤트 연동
SGSIAP2를 사용하는 게임에서 Firebase Analytics에 결제 이벤트를 수동으로 연동하는 방법이에요. iOS 15 이상 + StoreKit 2 환경이 대상이에요.
개요
- iOS StoreKit 2 환경에서는 Firebase Analytics의 결제 자동 수집(
event_origin = auto)이 더 이상 동작하지 않아요. - 그래서 SGSIAP2 결제 리스너(
SGSPurchasesUpdatedNotification) 콜백 내부에서Analytics.logEvent("in_app_purchase", ...)를 직접 호출해야 해요. - 전송 파라미터 셋은 기존 StoreKit 1이 자동 적재하던 BigQuery 컬럼과 호환돼요.
iOS 전용 — Android는 플랫폼 가드로 감싸요
ㆍ Android는 현재 결제 이벤트 자동 수집이 지원돼요. 향후 수동 수집을 지원하는 Firebase Android SDK가 배포·적용되면 자동 수집분과 중복 집계될 수 있어요.
ㆍ 크로스플랫폼 엔진(Unity·Unreal)에서는 아래 예시 코드처럼 iOS 플랫폼 가드로 감싸 주세요.
참고 — Firebase 안내
Starting from Google Analytics for Firebase iOS SDK version 12.5.0, in_app_purchase events will no longer be reserved. Manually logged in_app_purchase events (via SDK or Measurement Protocol) will be counted in addition to those automatically collected by the SDK.
Android will follow in the coming months.
Firebase SDK 최소 버전
Firebase Analytics에서 예약 이벤트 in_app_purchase의 수동 전송이 지원되는 버전이에요.
| 플랫폼 | 최소 버전 |
|---|---|
| iOS Native (CocoaPods) | 12.5.0 이상 |
| Unity | 13.6.0 이상 |
| C++ (Cocos2d-x 등) | 13.3.0 이상 |
Firebase 공식 문서 (필독)
ㆍ Firebase iOS 인앱 구매 측정 문서를 함께 확인해 주세요. Firebase iOS 인앱 구매 측정
전송 파라미터 — 필수
결제 리스너 콜백에서 logEvent("in_app_purchase", ...)를 호출할 때 채우는 파라미터예요. 아래 표의 키·타입·단위 그대로 전송해요. 기존 StoreKit 1에서 자동 수집하던 컬럼과 동일한 형태예요.
| Firebase 키 | 전송 타입 | 예시 전송 값 | product 객체 필드 | BigQuery 저장값 |
|---|---|---|---|---|
| product_id | string | "item_001" | product.productIdentifier | 동일 |
| product_name | string | "1000엔 상품" | product.localizedTitle | 동일 |
| currency | string | "USD" | product.priceCurrencyCode | 동일 |
| price | double | 0.99 | product.priceAmountMicros / 1_000_000.0 | 990000 (Firebase 백엔드가 ×1,000,000 변환) |
| value | double | 0.99 | product.priceAmountMicros / 1_000_000.0 | 990000 (Firebase 백엔드가 ×1,000,000 변환) |
| quantity | long | 1 | 고정 (SGSIAP2는 단건 결제) | 1 |
| validated | long | 1 | 고정 (리스너는 검증 성공 시에만 호출) | 1 |
단위 주의 (중요)
ㆍ 예약 이벤트 in_app_purchase의 value·price는 Firebase 백엔드가 자동으로 ×1,000,000 하여 BigQuery(micros 정수)에 적재해요. 따라서 실제 통화 단위 Double(예: $0.99 → 0.99)로 보내야 기존 자동 전송 포맷의 990000 값과 일치해요.
ㆍ priceAmountMicros 정수(990000)를 그대로 전송하면 ×1,000,000이 한 번 더 적용돼 990000000000으로 과대 적재돼요. firebase_error 없이 "들어간 것처럼" 보이지만 매출 금액이 1백만 배로 왜곡되므로, 반드시 Double 단위로 전송해요.
전송 불필요 — Firebase 자동 부여
- 세션·화면 정보 등 시스템 파라미터는 Firebase SDK가 자동으로 부여하므로 전송 코드에 포함하지 않아요.
- 이벤트명은 반드시
in_app_purchase를 유지해요. - 필수 파라미터 이외의 커스텀 파라미터(
content_id,order_id,customer_user_id등)는in_app_purchase이벤트에 추가하지 않는 것을 권장해요. 게임사 자체 분석 데이터가 필요하면 별도 이벤트명(예:stove_purchase_detail)으로 분리 전송해 주세요.
SGSIAP2 리스너 페이로드 참조 (Product)
리스너 콜백의 result가 성공 상태인 경우에만 logEvent를 호출해요. Firebase 전송에는 product 필드만 사용해요.
| 필드 | 타입 | 용도 | 비고 |
|---|---|---|---|
| productIdentifier | String | Firebase product_id |
— |
| localizedTitle | String | Firebase product_name |
— |
| priceAmountMicros | Double | Firebase price 및 value |
⚠️ 단위 주의 (÷1,000,000 필요) |
| priceCurrencyCode | String | Firebase currency |
— |
엔진별 연동 코드
이미 등록·운영 중인 SGSIAP2 결제 리스너의 결제 성공 분기 안에 Firebase 전송 블록만 추가하는 형태예요. 리스너 등록 자체나 결제 성공·실패 분기 처리 로직을 새로 작성할 필요는 없어요. 추가 흐름은 3개 엔진 모두 동일해요 — 결제 성공 분기 진입 → product 필드에서 값 추출 → Firebase 파라미터 구성 → Analytics.logEvent("in_app_purchase", params) 호출.
작업 범위
ㆍ 아래 코드에서 // --------------- START --------------- ~ // --------------- END --------------- 사이 블록만 기존 리스너 성공 분기에 복사 추가하세요.
ㆍ 바깥쪽 리스너/조건 분기는 이미 존재하는 기존 코드의 위치를 보여주기 위한 것으로, 새로 작성·수정할 필요가 없어요.
Swift 프로젝트도 동일한 NotificationCenter API로 구독할 수 있어요. 기존 SGSPurchasesUpdatedNotification 옵저버 콜백의 결제 성공 분기 안에 아래 블록을 추가해요.
#import <FirebaseAnalytics/FirebaseAnalytics.h>
#import <SGSIAP2/SGSIAP2.h>
- (void)onSGSPurchasesUpdated:(NSNotification *)notification
{
SGSResult *result = [notification.userInfo objectForKey:@"result"];
if ([result isSuccessful]) {
// 게임 자체 결제 성공 처리 (아이템 지급, UI 업데이트 등)
// ...
// --------------- START: Firebase 결제 이벤트 전송 ---------------
SGSIAPProduct *product = [notification.userInfo objectForKey:@"product"];
// 단위: Firebase 가 예약 이벤트의 value/price 를 ×1,000,000 변환하므로
// 실제 통화 단위 Double 로 전송
double priceAmount = product.priceAmountMicros / 1000000.0;
NSDictionary *params = @{
@"product_id": product.productIdentifier ?: @"",
@"product_name": product.localizedTitle ?: @"",
@"currency": product.priceCurrencyCode ?: @"",
@"price": @(priceAmount),
@"value": @(priceAmount),
@"quantity": @1,
@"validated": @1
};
[FIRAnalytics logEventWithName:@"in_app_purchase" parameters:params];
// --------------- END ---------------
}
}
📖 Firebase iOS 이벤트 로깅 공식 문서: Log events
지원 엔진 요약
| 엔진 | 리스너 시그니처 | 비고 |
|---|---|---|
| iOS Native (Obj-C / Swift) | NotificationCenter의 SGSPurchasesUpdatedNotification 관찰 | Firebase iOS SDK 직접 호출 |
| Unity | IAP.SetListener(Action |
Firebase Unity SDK ↔ iOS CocoaPods 버전 매핑 주의 |
| Unreal (C++) | stove::IAP::SetListener(TFunction |
FString → UTF-8 변환 필요 |
| Cocos2d-x / 기타 C++ | iOS 네이티브 브릿지 직접 구성 | — |
적용 체크리스트
- Firebase SDK 버전 업데이트
- iOS Native: CocoaPods
12.5.0이상 - Unity:
13.6.0이상 (내부 iOS Pods12.6.0이상 자동 설치) - C++ (Cocos2d-x 등): Firebase C++
13.3.0이상 (내부 iOS Pods12.6.0이상)
- iOS Native: CocoaPods
- 엔진별 연동 코드 예시를 기존 리스너 성공 분기에 추가
결제 데이터 연동 확인
연동 후 Firebase DebugView에서 in_app_purchase 이벤트가 의도한 파라미터와 함께 실시간으로 집계되는지 확인할 수 있어요.
- 공식 가이드에 따라 테스트 빌드에 DebugView 활성화
- 테스트 결제 진행 (Apple Sandbox / TestFlight)
- Firebase 콘솔 Analytics → DebugView에서
in_app_purchase이벤트 발생 및 필수 파라미터 노출 확인
DebugView 참고
ㆍ DebugView로는 수동 전송 이벤트(event_origin = app)의 실시간 확인이 가능해요.
ㆍ Firebase DebugView 공식 가이드
참고
- Firebase iOS 인앱 구매 측정 — 파라미터 규격·단위 변환·집계 동작 정의 문서
- 엔진별 이벤트 로깅 공식 문서 — iOS · Unity · C++
- Firebase SDK Release Notes — iOS · Unity · C++
모듈 구성
STOVE SDK는 기능별로 모듈화되어 제공돼요. 게임에서 사용하는 기능에 맞춰 필요한 모듈만 추가해요.
모듈 구분
| 구분 | 모듈 | 역할 |
|---|---|---|
| 기본 (필수) | Base | SDK 초기화, 환경 설정, 공통 처리 |
| Log | 로그 수집, SDK 버전 정보 조회 | |
| 인증 | Auth | 로그인, 회원가입, 토큰 관리 |
| AuthUI / AuthGoogle / AuthApple / AuthFacebook / AuthNaver / AuthLine / AuthSteam |
통합 로그인 UI 및 Provider별 인증 모듈. 사용하는 Provider만 선택 | |
| 알림 / UI | Push | 푸시 알림 수신·처리 |
| View | 팝업, 공지, 커뮤니티 등 웹뷰 기반 UI | |
| 결제 | IAP / IAPGoogle / IAPHuawei / IAPOneStore / IAP_StoreKit2(iOS) |
인앱 결제. 사용하는 마켓 모듈만 선택 |
모듈 버전 정보 확인 방법
ㆍ 클라이언트에 적용된 SDK 모듈 버전은 Log.getSDKVersions()를 호출하면 JSON 형태로 확인할 수 있어요.
ㆍ QA 빌드 검증·디버깅·인게임 설정 화면 노출 등에 활용해요. 자세한 내용은 기능별 가이드 > 게임 정보를 참고해요.
버전별 외부 모듈 정보
- 3rd party 인증 Provider, 푸시(Firebase), 결제(StoreKit2) 등 외부 모듈은 SDK 버전에 따라 호환되는 외부 라이브러리 버전이 달라요.
- 외부 모듈을 추가할 때는 사용 중인 STOVE SDK 버전과 호환되는 외부 라이브러리 버전을 SDK 릴리즈 노트에서 확인해 주세요.
멀티플랫폼 고려사항
PC와 모바일을 동시에 서비스하는 멀티플랫폼 게임에서는 두 플랫폼 SDK를 모두 적용해야 해요. 이때 추가로 확인할 사항이에요.
SDK 적용
- PC와 모바일 SDK는 각각 별도 다운로드·적용
- 모바일 빌드: STOVE Mobile SDK (Android / iOS)
- PC 빌드: STOVE PC SDK (PCSDK3)
- 공통 식별자 일관성
- 게임 ID, App ID는 멀티플랫폼 게임 전체에서 동일하게 사용해요.
- 패키지명·번들 ID는 플랫폼별로 다르지만, 스토브 식별 키는 일관성을 유지해야 해요.
- 환경 동기화
- PC/모바일 빌드의 환경(Live/Sandbox)을 일치시켜야 해요.
- QA 시 PC = Sandbox, 모바일 = Live 같은 불일치 구성은 이용자 식별·결제 검증 오류로 이어져요.
이용자 식별
- MemberNo: 스토브 플랫폼 회원 식별자. PC/모바일 동일 회원의 경우 동일한 MemberNo를 사용해요.
- GUID: 게임 캐릭터 식별자. 멀티플랫폼 게임에서 PC/모바일 간 캐릭터 정보 공유 시 캐릭터 연동 가이드 참고
- 회원 타입 차이: 모바일은 정회원·게스트 모두 지원, PC는 정회원만 지원 (PC에서 게스트 로그인 불가)
빌드 분기 권장사항
- 빌드 환경별 분기: 클라이언트 코드에서 컴파일 분기(
#if UNITY_ANDROID등)로 PC/모바일 SDK 호출을 분리해요. - 공통 비즈니스 로직 추상화: 인증·결제 등 공통 흐름은 인터페이스로 추상화한 뒤 플랫폼별 구현체로 구분하는 것을 권장해요.