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
public static List
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.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
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
Awesome work! Thanks for your sharing!
ReplyDeleteAh...Ha...
ReplyDeleteWithout 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. ^^
That's awesome, thanks!
ReplyDeleteJust bought your game and will rate 5 stars!
I downloaded your app and it was great!!
ReplyDeleteAnd I have a question Simon.
Didn't you have any memory leak problem when admob
refreshes its ad (loadRequest) in iphone version??
plz post some sample code for jni connection
ReplyDeleteI dun understand ur code. It is very complicated to integrate OF to Cocos2d-x. Do u have any step by step guide.
ReplyDeleteYour code giving me error on super.setPackageName this is not a method of COcos2dxActivity..
ReplyDeletehi... 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.
ReplyDeleteI want to integrate admobs for cocos2dx windows game.
ReplyDeleteHow to integrate it?
Great post much appreciate the time you took to write this.
ReplyDelete