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

Welcome to our variety contact windows

Welcome to our variety contact windows:

Facebook:
http://www.facebook.com/MolioApp

Twitter:
http://www.twitter.com/MolioApp

微博:
http://www.weibo.com/MolioApp

現在可以升級為Android 4.0的台灣機種…

現在可以升級為Android 4.0的台灣機種…最有誠意的是Sony…2011年的都可以升…BTW…之前不是號稱可以有18個月的保證升級?哪去了?

Acer
Iconia Tab A100
Iconia Tab A200
Iconia Tab A500
Iconia Tab A501

Asus
Transformer Prime

HTC
HTC Sensation
HTC Sensation XL
HTC Sensation XE
HTC Rezound
HTC EVO 3D

LG
Optimus 2X

Motorola
Droid BIONIC
Droid RAZR
Xoom

Samsung
Nexus S
Galaxy S2
Galaxy Note
Galaxy tab 10.1
Galaxy Tab 8.9
Galaxy Tab 7.7
Galaxy Tab 7.0 Plus

Sony
Xperia arc
Xperia Play
Xperia neo V
Xperia Pro
Xperia Ray
Xperia mini
Xperia mini Pro

ChickenEggs(小雞爆蛋)有Android的版本了…華麗大旋轉中…

各位朋友們…有Android機器的…請在Market用英文查詢"ChickenEggs"或是中文查詢"小雞爆蛋"或是直接查我的名字"Simon Lin"…都可以找到ChickenEggs的Android版本哦…歡迎大家給予批評指教啦…可以的話…給個五顆星衝一下人氣吧…謝謝大家…啾咪…

https://market.android.com/details?id=com.zhihmeng.ChickenEggsX&feature=search_result#?t=W251bGwsMSwxLDEsImNvbS56aGlobWVuZy5DaGlja2VuRWdnc1giXQ