Tuesday, November 8, 2011

OpenFeint and Admob integrated with cocos2d-x...

Cocos2d is a great game framework on iOS, and I do implement a game using that on iOS.
My game "ChickenEggs" : http://itunes.apple.com/app/id446767688

Free Version:http://itunes.apple.com/app/id487067895

But after a while, my friends ask me, where is the android version of your game ?
Yeah, I told to myself, why not get a android version ?

I start to try to find a soultion for porting cocos2d game to android, and I found cocos2d-x - a c++ based cocos2d engine porting, supports multiple platforms including iOS/Android/Win32.
Well, it seems will be my perfect way to port iOS game to Android.
But after I start and almost finish the game logic part, I found suddenly that there is no Game Center on Android.

I start google again, and find a replacement named OpenFeint, but it comes with nightmare to me !!!
Because OpenFeint is a android jar package, and it based on java programming, the cocos2d-x is c++ based, and if you want they to communicate with each other, you need NDK/JNI.
I am not familiar with JNI at all, so I start google and google again, do some try and error to find the workable solution.

Finally, I get it works, and here is how it works.

BTW, you can direct use my code, it's a complete version.
I am lazy, so I always wish the code comes from internet I can use it right away...^^

First, write the native side functions.

Header

//
// OFJNIFunctions.h
// ChickenEggsX
//
// Created by Simon Lin on 2011/10/23.
// Copyright 2011年 __MyCompanyName__. All rights reserved.
//
/* DO NOT EDIT THIS FILE - it is machine generated */
#if 1
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)

#include "cocos2d.h"
#include
/* Header for class com_zhihmeng_ChickenEggsX_OFJNIFunctions */

#ifndef _Included_com_zhihmeng_ChickenEggsX_ChickenEggsX
#define _Included_com_zhihmeng_ChickenEggsX_ChickenEggsX
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_zhihmeng_ChickenEggsX_ChickenEggsX
* Method: nativeSetup
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_zhihmeng_ChickenEggsX_ChickenEggsX_init
(JNIEnv *, jobject,jobject);

class OFJNIFunctions
{
public:
static void showLeaderBoards();
static void showAchievements();
static void showDashBoards();
static void submitScores(int scores);
static void submitEggs(int eggs);
static void unlockAchievements(string achievementid);
static void openURL(string url);
};

#ifdef __cplusplus
}
#endif
#endif
#endif // CC_PLATFORM_ANDROID
#endif

CPP

//
// OFJNIFunctions.cpp
// ChickenEggsX
//
// Created by Simon Lin on 2011/10/23.
// Copyright 2011年 __MyCompanyName__. All rights reserved.
//
#if 1
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)

#include "OFJNIFunctions.h"

using namespace cocos2d;

JavaVM *gJavaVM;
jmethodID mid;
jclass mClass;
jobject mObject;

//--------------------------------------------------------------------

JNIEXPORT void JNICALL Java_com_zhihmeng_ChickenEggsX_ChickenEggsX_init(JNIEnv* env, jobject thiz, jobject weak_this)
{
jclass clazz = env->GetObjectClass(thiz);
mClass = (jclass)env->NewGlobalRef(clazz);
mObject = env->NewGlobalRef(weak_this);
return;
}

//--------------------------------------------------------------------

static const char *classPathName = "com/zhihmeng/ChickenEggsX/ChickenEggsX";

static JNINativeMethod methods[] = {
{"init", "(Ljava/lang/Object;)V",
(void *)Java_com_zhihmeng_ChickenEggsX_ChickenEggsX_init},
};

/*
* Register several native methods for one class.
*/
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;

clazz = env->FindClass(className);
if (clazz == NULL) {
// LOGE("Native registration unable to find class '%s'", className);
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { // LOGE("RegisterNatives failed for '%s'", className); return JNI_FALSE; } return JNI_TRUE; } static int registerNatives(JNIEnv* env) { if (!registerNativeMethods(env, classPathName, methods, sizeof(methods) / sizeof(methods[0]))) { return JNI_FALSE; } return JNI_TRUE; } //-------------------------------------------------------------------- jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv *env; gJavaVM = vm; int result; // LOGI("JNI_OnLoad called"); if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
// LOGE("Failed to get the environment using GetEnv()");
return -1;
}

if (registerNatives(env) != JNI_TRUE) {
// LOGE("ERROR: registerNatives failed");
goto bail;
}

result = JNI_VERSION_1_6;

bail:
return result;
}

void OFJNIFunctions::showLeaderBoards()
{
int status;
JNIEnv *env;
bool isAttached = false;

status = gJavaVM->GetEnv((void **) &env, JNI_VERSION_1_6);
if(status < 0) { // LOGE("callback_handler: failed to get JNI environment, " // "assuming native thread"); status = gJavaVM->AttachCurrentThread(&env, NULL);
if(status < 0) { // LOGE("callback_handler: failed to attach " // "current thread"); return; } isAttached = true; } //----------------------------------------------------------- mid = env->GetStaticMethodID(mClass, "showLeaderBoards", "()V");
if (mid!=0)
env->CallStaticVoidMethod(mClass, mid);
//-----------------------------------------------------------

if(isAttached)
gJavaVM->DetachCurrentThread();
return;
}

void OFJNIFunctions::showAchievements()
{
int status;
JNIEnv *env;
bool isAttached = false;

status = gJavaVM->GetEnv((void **) &env, JNI_VERSION_1_6);
if(status < 0) { // LOGE("callback_handler: failed to get JNI environment, " // "assuming native thread"); status = gJavaVM->AttachCurrentThread(&env, NULL);
if(status < 0) { // LOGE("callback_handler: failed to attach " // "current thread"); return; } isAttached = true; } //----------------------------------------------------------- mid = env->GetStaticMethodID(mClass, "showAchievements", "()V");
if (mid!=0)
env->CallStaticVoidMethod(mClass, mid);
//-----------------------------------------------------------

if(isAttached)
gJavaVM->DetachCurrentThread();
return;
}

void OFJNIFunctions::showDashBoards()
{
int status;
JNIEnv *env;
bool isAttached = false;

status = gJavaVM->GetEnv((void **) &env, JNI_VERSION_1_6);
if(status < 0) { // LOGE("callback_handler: failed to get JNI environment, " // "assuming native thread"); status = gJavaVM->AttachCurrentThread(&env, NULL);
if(status < 0) { // LOGE("callback_handler: failed to attach " // "current thread"); return; } isAttached = true; } //----------------------------------------------------------- mid = env->GetStaticMethodID(mClass, "showDashBoards", "()V");
if (mid!=0)
env->CallStaticVoidMethod(mClass, mid);
//-----------------------------------------------------------

if(isAttached)
gJavaVM->DetachCurrentThread();
return;
}

void OFJNIFunctions::submitScores(int scores)
{
int status;
JNIEnv *env;
bool isAttached = false;

status = gJavaVM->GetEnv((void **) &env, JNI_VERSION_1_6);
if(status < 0) { // LOGE("callback_handler: failed to get JNI environment, " // "assuming native thread"); status = gJavaVM->AttachCurrentThread(&env, NULL);
if(status < 0) { // LOGE("callback_handler: failed to attach " // "current thread"); return; } isAttached = true; } //----------------------------------------------------------- mid = env->GetStaticMethodID(mClass, "submitScores", "(I)V");
if (mid!=0)
env->CallStaticVoidMethod(mClass, mid, scores);
//-----------------------------------------------------------

if(isAttached)
gJavaVM->DetachCurrentThread();
return;
}

void OFJNIFunctions::submitEggs(int eggs)
{
int status;
JNIEnv *env;
bool isAttached = false;

status = gJavaVM->GetEnv((void **) &env, JNI_VERSION_1_6);
if(status < 0) { // LOGE("callback_handler: failed to get JNI environment, " // "assuming native thread"); status = gJavaVM->AttachCurrentThread(&env, NULL);
if(status < 0) { // LOGE("callback_handler: failed to attach " // "current thread"); return; } isAttached = true; } //----------------------------------------------------------- mid = env->GetStaticMethodID(mClass, "submitEggs", "(I)V");
if (mid!=0)
env->CallStaticVoidMethod(mClass, mid, eggs);
//-----------------------------------------------------------

if(isAttached)
gJavaVM->DetachCurrentThread();
return;
}

void OFJNIFunctions::unlockAchievements(string achievementid)
{
int status;
JNIEnv *env;
bool isAttached = false;

status = gJavaVM->GetEnv((void **) &env, JNI_VERSION_1_6);
if(status < 0) { // LOGE("callback_handler: failed to get JNI environment, " // "assuming native thread"); status = gJavaVM->AttachCurrentThread(&env, NULL);
if(status < 0) { // LOGE("callback_handler: failed to attach " // "current thread"); return; } isAttached = true; } //----------------------------------------------------------- mid = env->GetStaticMethodID(mClass, "unlockAchievements", "(Ljava/lang/String;)V");
if (mid!=0)
{
CCLog("achievementid=%s",achievementid.c_str());
env->CallStaticVoidMethod(mClass, mid, env->NewStringUTF(achievementid.c_str()));
}
//-----------------------------------------------------------

if(isAttached)
gJavaVM->DetachCurrentThread();
return;
}

void OFJNIFunctions::openURL(string url)
{
int status;
JNIEnv *env;
bool isAttached = false;

status = gJavaVM->GetEnv((void **) &env, JNI_VERSION_1_6);
if(status < 0) { // LOGE("callback_handler: failed to get JNI environment, " // "assuming native thread"); status = gJavaVM->AttachCurrentThread(&env, NULL);
if(status < 0) { // LOGE("callback_handler: failed to attach " // "current thread"); return; } isAttached = true; } //----------------------------------------------------------- mid = env->GetStaticMethodID(mClass, "openURL", "(Ljava/lang/String;)V");
if (mid!=0)
{
CCLog("url=%s",url.c_str());
env->CallStaticVoidMethod(mClass, mid, env->NewStringUTF(url.c_str()));
}
//-----------------------------------------------------------

if(isAttached)
gJavaVM->DetachCurrentThread();
return;
}

#endif // CC_PLATFORM_ANDROID
#endif

Second, the Java side:

PS:I modify some static string settings, because they are my OpenFenit environment variables, you need to use yours. ^^

Java Side

public class ChickenEggsX extends Cocos2dxActivity{
private Cocos2dxGLSurfaceView mGLView;
private AdView mAdView;

public static List achievements = null;
public static List leaderboards = null;

static final String gameName = "XXXXXXXX";
static final String gameID = "XXXXXXXX";
static final String gameKey = "XXXXXXXX";
static final String gameSecret = "XXXXXXXX";
static final String leaderboardScoresID = "XXXXXXXX";
static final String leaderboardEggsID = "XXXXXXXX";

protected static final int showLeaderBoardsHandler = 0x101;
protected static final int showAchievementsHandler = 0x102;
protected static final int showDashBoardsHandler = 0x103;
protected static final int submitScoresHandler = 0x104;
protected static final int submitEggsHandler = 0x105;
protected static final int unlockAchievementsHandler = 0x106;
protected static final int openURLHandler = 0x107;
private static Handler OFHandler;

private AdView adView;

private native void init(Object wadk_this);

private static void showLeaderBoards() {
Message message = new Message();
message.what = ChickenEggsX.showLeaderBoardsHandler;
OFHandler.sendMessage(message);
}

private static void showAchievements() {
Message message = new Message();
message.what = ChickenEggsX.showAchievementsHandler;
OFHandler.sendMessage(message);
}

private static void showDashBoards() {
Message message = new Message();
message.what = ChickenEggsX.showDashBoardsHandler;
OFHandler.sendMessage(message);
}

private static void submitScores(int scores) {
Message message = new Message();
message.what = ChickenEggsX.submitScoresHandler;
message.arg1 = scores;
OFHandler.sendMessage(message);
}

private static void submitEggs(int eggs) {
Message message = new Message();
message.what = ChickenEggsX.submitEggsHandler;
message.arg1 = eggs;
OFHandler.sendMessage(message);
}

private static void unlockAchievements(String achievementid) {
Message message = new Message();
message.what = ChickenEggsX.unlockAchievementsHandler;
message.obj = achievementid;
OFHandler.sendMessage(message);
}

private static void openURL(String url) {
Message message = new Message();
message.what = ChickenEggsX.openURLHandler;
message.obj = url;
OFHandler.sendMessage(message);
}

protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);

// get the packageName,it's used to set the resource path
String packageName = getApplication().getPackageName();
super.setPackageName(packageName);
Map options = new HashMap();
options.put(OpenFeintSettings.SettingCloudStorageCompressionStrategy, OpenFeintSettings.CloudStorageCompressionStrategyDefault);
// use the below line to set orientation
options.put(OpenFeintSettings.RequestedOrientation, android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
OpenFeintSettings settings = new OpenFeintSettings( gameName, gameKey, gameSecret, gameID);

OpenFeint.initializeWithoutLoggingIn(this, settings, new OpenFeintDelegate() { });

init(new WeakReference(this));
OFHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case ChickenEggsX.showLeaderBoardsHandler:
Dashboard.openLeaderboards();
break;
case ChickenEggsX.showAchievementsHandler:
Dashboard.openAchievements();
break;
case ChickenEggsX.showDashBoardsHandler:
Dashboard.open();
break;
case ChickenEggsX.submitScoresHandler:
long scores = msg.arg1;
Score scoresScore = new Score(scores);
Leaderboard scoresLeaderboard = new Leaderboard(leaderboardScoresID);
scoresScore.submitTo(scoresLeaderboard, new Score.SubmitToCB() {
@Override public void onSuccess(boolean newHighScore) { // sweet, score was posted
ChickenEggsX.this.setResult(Activity.RESULT_OK);
}
@Override public void onFailure(String exceptionMessage) {
ChickenEggsX.this.setResult(Activity.RESULT_CANCELED);
}
});
break;
case ChickenEggsX.submitEggsHandler:
long eggs = msg.arg1;
Score eggsScore = new Score(eggs);
Leaderboard eggsLeaderboard = new Leaderboard(leaderboardEggsID);
eggsScore.submitTo(eggsLeaderboard, new Score.SubmitToCB() {
@Override public void onSuccess(boolean newHighScore) { // sweet, score was posted
ChickenEggsX.this.setResult(Activity.RESULT_OK);
}
@Override public void onFailure(String exceptionMessage) {
ChickenEggsX.this.setResult(Activity.RESULT_CANCELED);
}
});
break;
case ChickenEggsX.unlockAchievementsHandler:
new Achievement((String)msg.obj).unlock(new Achievement.UnlockCB () {
@Override public void onSuccess(boolean newUnlock) {
ChickenEggsX.this.setResult(Activity.RESULT_OK);
}
@Override public void onFailure(String exceptionMessage) {
ChickenEggsX.this.setResult(Activity.RESULT_CANCELED);
}
});
break;
case ChickenEggsX.openURLHandler:
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse((String)msg.obj));
startActivity(i);
break;
}
super.handleMessage(msg);
}
};

setContentView(R.layout.game_demo);
mGLView = (Cocos2dxGLSurfaceView) findViewById(R.id.game_gl_surfaceview);
mGLView.setTextField((EditText)findViewById(R.id.textField));

// Get the size of the mGLView after the layout happens
mGLView.post(new Runnable() {

@Override
public void run() {
Cocos2dxActivity.screenHeight = mGLView.getHeight();
Cocos2dxActivity.screenWidth = mGLView.getWidth();
}
});

// find AdView
mAdView = (AdView)this.findViewById(R.id.adView);
mAdView.setVisibility(AdView.VISIBLE);
}

@Override
protected void onPause() {
super.onPause();
mGLView.onPause();
}

@Override
protected void onResume() {
super.onResume();
mGLView.onResume();
}

static {
System.loadLibrary("cocos2d");
System.loadLibrary("cocosdenshion");
System.loadLibrary("game");
}
}

That's all, hope this will help someone, because I got stunk in these for days.
If you have some troubles like me, maybe this simple tour will give you a light.




















One more thing :

If your plan is selling your game free and want to earns some money by Admob.
You may encounter another trouble with surfaceView and AdView.
I am experienced them, so I want to share how I solved this problem.
I will skip all the setup steps, again, I am lazy, and you can find them by google.
In a short word, what I post above contains the Admob code on Activity side.
What you need to add is the layout.
Here we go !

First, make sure you have upgraded your android sdk to 3.2 at least.
I am not sure why, but after I upgraded, the Admob shows up.

Second, modify your res/layout/game_demo.xml like below:

?xml version="1.0" encoding="utf-8"?
LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ads="http://schemas.android.com/apk/lib/com.google.ads"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" android:layout_gravity="bottom"

EditText android:id="@+id/textField" android:layout_height="wrap_content" android:layout_weight="0" android:layout_width="fill_parent" /EditText

RelativeLayout
android:id="@+id/ADLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

org.cocos2dx.lib.Cocos2dxGLSurfaceView
android:id="@+id/game_gl_surfaceview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/

com.google.ads.AdView
android:id="@+id/adView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
ads:adUnitId="XXXXXXXXXX"
ads:adSize="BANNER"
ads:loadAdOnCreate="true"/
/RelativeLayout


/LinearLayout

PS1: Please add "<" and ">" by yourself, because when I add these symbols, it will be recognized as XML and doesn't show.
PS2: don't forget to modify the adUnitId to yours. ^_^
PS3: If you know the detail about the layout, you can modify it by yourself. What I post is just a example.

Here all you are.
Hope you guys love this simple guide.

Again, you can find the game "ChickenEggs" based on this guide here :

iOS Version : http://itunes.apple.com/app/id446767688
iOS Free Version : http://itunes.apple.com/app/id487067895
Android Version : https://market.android.com/details?id=com.zhihmeng.ChickenEggsX&feature=search_result#?t=W251bGwsMSwxLDEsImNvbS56aGlobWVuZy5DaGlja2VuRWdnc1giXQ

And, welcome to talk with me here :

MolioApp : http://www.molioapp.com
Facebook : http://www.facebook.com/MolioApp
Twitter : http://www.twitter.com/MolioApp
Weibo : http://www.weibo.com/MolioApp
Sina Blog : http://blog.sina.com.cn/molioapp

9 comments:

  1. Awesome work! Thanks for your sharing!

    ReplyDelete
  2. Ah...Ha...

    Without your porting of Cocos2d-x, I have no way to write this guide.

    But what I really want is you can add the gamecenter/openfeint to your cocos2d-x package. ^^

    ReplyDelete
  3. That's awesome, thanks!
    Just bought your game and will rate 5 stars!

    ReplyDelete
  4. I downloaded your app and it was great!!

    And I have a question Simon.

    Didn't you have any memory leak problem when admob
    refreshes its ad (loadRequest) in iphone version??

    ReplyDelete
  5. plz post some sample code for jni connection

    ReplyDelete
  6. I dun understand ur code. It is very complicated to integrate OF to Cocos2d-x. Do u have any step by step guide.

    ReplyDelete
  7. Your code giving me error on super.setPackageName this is not a method of COcos2dxActivity..

    ReplyDelete
  8. hi... can't understand your tutorial.. i want to intrigate admob with my cocos2dx android game. so please give me step by step instruction asa possible.

    ReplyDelete
  9. I want to integrate admobs for cocos2dx windows game.
    How to integrate it?

    ReplyDelete