cocos2d-x Android In App Billing Dungeons Sample Optimization and Intergration in External Jar Lib
제목이 거창하네요. 제가 현재 회사에서 하고 있는 Framework는 살짝 어떤 내용인지 몇번 언급한 적이 있었죠. 대략적으로 현 상황을 정리하자면, iOS딴은 xcode에서 mm으로 해주면 c++이든 Objective-C든 간에 소스가 짬뽕이 되어도 컴파일이 되서 하나의 static lib로 결과물을 만들 수 있습니다.
반면, Android에서는 예외가 발생하더군요. C++로 되어있는 Framework는 NDK를 통해 이상없이 컴파일이 되지만 최소한의 Java 코딩이 더 들어갑니다. 특히나 cocos2d-x를 활용한 순수 게임 코딩이 아닌 제가 지금것 포스팅했던 여러 외부 모듈들을 붙이다보면 어쩔수 없이 JNI와 Java딴 코딩이 들어가죠. 그동안은 cocos2d-x Android template로 자동 생성된 게임의 java파일 즉 GameMainActivity에 그냥 바로 샘플식으로 작업해왔지만 이렇게 하면 다른 팀원들과의 협업에 있어서 C++ lib와 java code 공유라는 반쪽짜리 Android Framework가 되어버리게 됩니다. C++로 된 것을 NDK로 컴파일한 Framework와 Java딴을 컴파일한 JAR static lib도 만들 수 밖에 없는 상황이 된거죠.
서론이 길었습니다. 이번 포스팅에서는 지난번 안드로이드 인앱 빌링 연동을 했을 때 언급했던 Dungeons Sample 최적화부분과 게임 App이 아닌 따로 만든 안드로이드 외부 Lib JAR 프로젝트에 인앱 빌링 기능을 통합하는 것을 정리해봅니다. 대부분은 위 링크들을 기준으로 선 작업이 되어 있어야하고, 기타 수정부분만을 언급하도록 하겠습니다.
먼저 링크를 참고하면서 안드로이드 외부 Lib 프로젝트에 구글 인앱 빌링 Dungeons Sample를 Import해줍니다. 단, 이번에는 play_billing 하위 폴더중 res는 빼고 src만 가져옵니다.
<activity android:name="com.example.dungeons.Dungeons"
android:configChanges="orientation" >
</activity>
개발중인 App의 AndroidManifest.xml에서 Dungeons의 Activity는 삭제합니다. 더이상 Dungeons는 Activity가 아닌 일반 class 객체로 할 것이기 때문이죠.
역시나 기존에 연동했을 때 가져왔던 Dungeons Sample의 Layout 관련 xml 4개도 모두 삭제합니다.
아래는 최적화? 한 Dungeons class 입니다. 게임용에 맞게 Activity를 상속하지 않고 일반 class로 만들어봤습니다. Restore관련 db와 onRestoreTransactionsResponse 정도와 불필요해 보이는 dialog등을 삭제했고 구독 아이템 이외 타입의 아이템 정도만 구입할 수 있게 정리해봤습니다. JniMapper는 C++ Framework와 통신하기 위해 만든 Java측 Framework에 있는 class입니다. 별것은 없고 각 게임에 맞게 Thread Safe하게 JNI 통신 처리와 Dungeons class의 init, destroy 해주시면 됩니다.
이제 작업했던 cocos2d-x c++ 부분을 NDK로 빌드하고 Java측 JAR Lib를 export하고 Android 게임 프로젝트에 다 적용하시면 이상없이 작동할 것입니다.
모든 예외 상황을 처리한 것도 아니고 구현 안된 부분도 있으므로 아직 추가 개발에 여지는 남아있습니다.
반면, Android에서는 예외가 발생하더군요. C++로 되어있는 Framework는 NDK를 통해 이상없이 컴파일이 되지만 최소한의 Java 코딩이 더 들어갑니다. 특히나 cocos2d-x를 활용한 순수 게임 코딩이 아닌 제가 지금것 포스팅했던 여러 외부 모듈들을 붙이다보면 어쩔수 없이 JNI와 Java딴 코딩이 들어가죠. 그동안은 cocos2d-x Android template로 자동 생성된 게임의 java파일 즉 GameMainActivity에 그냥 바로 샘플식으로 작업해왔지만 이렇게 하면 다른 팀원들과의 협업에 있어서 C++ lib와 java code 공유라는 반쪽짜리 Android Framework가 되어버리게 됩니다. C++로 된 것을 NDK로 컴파일한 Framework와 Java딴을 컴파일한 JAR static lib도 만들 수 밖에 없는 상황이 된거죠.
서론이 길었습니다. 이번 포스팅에서는 지난번 안드로이드 인앱 빌링 연동을 했을 때 언급했던 Dungeons Sample 최적화부분과 게임 App이 아닌 따로 만든 안드로이드 외부 Lib JAR 프로젝트에 인앱 빌링 기능을 통합하는 것을 정리해봅니다. 대부분은 위 링크들을 기준으로 선 작업이 되어 있어야하고, 기타 수정부분만을 언급하도록 하겠습니다.
먼저 링크를 참고하면서 안드로이드 외부 Lib 프로젝트에 구글 인앱 빌링 Dungeons Sample를 Import해줍니다. 단, 이번에는 play_billing 하위 폴더중 res는 빼고 src만 가져옵니다.
<activity android:name="com.example.dungeons.Dungeons"
android:configChanges="orientation" >
</activity>
개발중인 App의 AndroidManifest.xml에서 Dungeons의 Activity는 삭제합니다. 더이상 Dungeons는 Activity가 아닌 일반 class 객체로 할 것이기 때문이죠.
역시나 기존에 연동했을 때 가져왔던 Dungeons Sample의 Layout 관련 xml 4개도 모두 삭제합니다.
아래는 최적화? 한 Dungeons class 입니다. 게임용에 맞게 Activity를 상속하지 않고 일반 class로 만들어봤습니다. Restore관련 db와 onRestoreTransactionsResponse 정도와 불필요해 보이는 dialog등을 삭제했고 구독 아이템 이외 타입의 아이템 정도만 구입할 수 있게 정리해봤습니다. JniMapper는 C++ Framework와 통신하기 위해 만든 Java측 Framework에 있는 class입니다. 별것은 없고 각 게임에 맞게 Thread Safe하게 JNI 통신 처리와 Dungeons class의 init, destroy 해주시면 됩니다.
package com.example.dungeons; import com.example.dungeons.BillingService.RequestPurchase; import com.example.dungeons.BillingService.RestoreTransactions; import com.example.dungeons.Consts.PurchaseState; import com.example.dungeons.Consts.ResponseCode; import android.os.Handler; import android.util.Log; import xxxxx.framework.JniMapper; /** * A sample application that demonstrates in-app billing. */ public class Dungeons { private static final String TAG = "Dungeons"; private DungeonsPurchaseObserver mDungeonsPurchaseObserver; private Handler mHandler; private BillingService mBillingService; private boolean bBillingSupport = false; /** * A {@link PurchaseObserver} is used to get callbacks when Android Market sends * messages to this application so that we can update the UI. */ private class DungeonsPurchaseObserver extends PurchaseObserver { public DungeonsPurchaseObserver(Handler handler) { super(JniMapper.getOwnerActivity(), handler); } @Override public void onBillingSupported(boolean supported, String type) { if (Consts.DEBUG) { Log.i(TAG, "supported: " + supported); } if (supported) { bBillingSupport = true; } } @Override public void onPurchaseStateChange(PurchaseState purchaseState, String itemId, int quantity, long purchaseTime, String developerPayload) { if (Consts.DEBUG) { Log.i(TAG, "onPurchaseStateChange() itemId: " + itemId + " " + purchaseState); } if (purchaseState == PurchaseState.PURCHASED) { } } @Override public void onRequestPurchaseResponse(RequestPurchase request, ResponseCode responseCode) { if (Consts.DEBUG) { Log.d(TAG, request.mProductId + ": " + responseCode); } final String cppResultCode; if (responseCode == ResponseCode.RESULT_OK) { if (Consts.DEBUG) { Log.i(TAG, "purchase was successfully sent to server"); } cppResultCode = request.mProductId; } else if (responseCode == ResponseCode.RESULT_USER_CANCELED) { if (Consts.DEBUG) { Log.i(TAG, "user canceled purchase"); } cppResultCode = "cancelIAP"; } else { if (Consts.DEBUG) { Log.i(TAG, "purchase failed"); } cppResultCode = "faileIAP"; } ///< JNI 호출로 C++에게 결과를 알려준다. JniMapper.callCppPurchaseResponse( cppResultCode ); } @Override public void onRestoreTransactionsResponse(RestoreTransactions request, ResponseCode responseCode) { if (responseCode == ResponseCode.RESULT_OK) { if (Consts.DEBUG) { Log.d(TAG, "completed RestoreTransactions request"); } // Update the shared preferences so that we don't perform // a RestoreTransactions again. /* SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); SharedPreferences.Editor edit = prefs.edit(); edit.putBoolean(DB_INITIALIZED, true); edit.commit(); */ } else { if (Consts.DEBUG) { Log.d(TAG, "RestoreTransactions error: " + responseCode); } } } } public void init() { mHandler = new Handler(); mDungeonsPurchaseObserver = new DungeonsPurchaseObserver(mHandler); mBillingService = new BillingService(); mBillingService.setContext(JniMapper.getOwnerActivity()); // Check if billing is supported. ResponseHandler.register(mDungeonsPurchaseObserver); if (!mBillingService.checkBillingSupported()) { } /* if (!mBillingService.checkBillingSupported(Consts.ITEM_TYPE_SUBSCRIPTION)) { showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID); } */ } public void destroy() { ResponseHandler.unregister(mDungeonsPurchaseObserver); mHandler = null; mDungeonsPurchaseObserver = null; mBillingService.unbind(); mBillingService = null; } /** * 아이템 구매 요청 * @param strProductId */ public void requestPurchase( String strProductId ) { if (Consts.DEBUG) { Log.d(TAG, "requestPurchase: " + strProductId); } if( bBillingSupport == false ) { ///< JNI 호출로 C++에게 구매 못한다고 알려준다. JniMapper.callCppPurchaseResponse( "faileIAP" ); if (Consts.DEBUG) { Log.d(TAG, "requestPurchase: Bulling Not Support"); } return; } //if( mManagedType != Managed.SUBSCRIPTION ) { if( !mBillingService.requestPurchase(strProductId, Consts.ITEM_TYPE_INAPP, null) ) { //showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID); } //} ///< 구독 /* else { if( !mBillingService.requestPurchase(strProductId, Consts.ITEM_TYPE_SUBSCRIPTION, null) ) { //showDialog(DIALOG_SUBSCRIPTIONS_NOT_SUPPORTED_ID); } } */ } }
이제 작업했던 cocos2d-x c++ 부분을 NDK로 빌드하고 Java측 JAR Lib를 export하고 Android 게임 프로젝트에 다 적용하시면 이상없이 작동할 것입니다.
모든 예외 상황을 처리한 것도 아니고 구현 안된 부분도 있으므로 아직 추가 개발에 여지는 남아있습니다.
댓글
댓글 쓰기