cocos2dx 文字按钮
在使用 Cocos2d-x 作为程序框架时,我之前一直偷懒使用 CCMessageBox 来实现提示框功能。然而,我忽略了一个问题:默认提示框的按钮文字是 “Ok”,在中文环境的应用中显得很不恰当。于是,我决定对其进行修改,并将过程记录下来,也许能帮助到有同样需求的开发者。我在 Google 上搜索相关内容时没有找到,可能是搜索关键字不准确。
修改方法
直接找到 Cocos2d-x 源码中的 org/cocos2dx/lib/Cocos2dxHandler.java 文件,将 showDialog 函数中的 “Ok” 字符串修改为 R.string.ok。以下是修改后的代码:
private void showDialog(Message msg) {
Cocos2dxActivity theActivity = this.mActivity.get();
DialogMessage dialogMessage = (DialogMessage)msg.obj;
new AlertDialog.Builder(theActivity)
.setTitle(dialogMessage.titile)
.setMessage(dialogMessage.message)
.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
}
}).create().show();
}
当然,在修改之前,需要导入 android.R 这个包。这样,CCMessageBox 在 Android 上就会根据操作系统的语言来显示按钮文字。在我的 S3 手机上测试,效果符合预期。
如何寻找到修改的地方
通过这次修改,我们可以深入了解 Cocos2d-x 中的 JNI 调用机制(虽然在实际开发中不一定需要手动调用,但可以了解引擎层提供的功能)。下面详细介绍如何找到需要修改的地方。
从 C++ 开始
CCMessageBox 函数定义在 cocos2dx/platform/CCCommon.h 中,其原型如下:
/**
* @brief Pop out a message box
*/
void CC_DLL CCMessageBox(const char * pszMsg, const char * pszTitle);
在 Win32 平台上,CCMessageBox 的实现很简单,直接调用了 MessageBox:
void CCMessageBox(const char * pszMsg, const char * pszTitle) {
MessageBoxA(NULL, pszMsg, pszTitle, MB_OK);
}
由于 CCMessageBox 是跨平台调用的函数,如果我们想在引擎层实现其他功能,可以仿照这种方式。接下来,我们重点关注 Android 层的实现(iOS 层的实现可以自行探索,原理应该类似)。
Android 层的实现文件是 cocos2dx/platform/android/CCCommon.cpp,代码如下:
#include "platform/CCCommon.h"
#include "jni/Java_org_cocos2dx_lib_Cocos2dxHelper.h"
#include <android/log.h>
#include <stdio.h>
#include <jni.h>
NS_CC_BEGIN
#define MAX_LEN (cocos2d::kMaxLogLen + 1)
void CCLog(const char * pszFormat, ...) {
char buf[MAX_LEN];
va_list args;
va_start(args, pszFormat);
vsnprintf(buf, MAX_LEN, pszFormat, args);
va_end(args);
__android_log_print(ANDROID_LOG_DEBUG, "cocos2d-x debug info", buf);
}
void CCMessageBox(const char * pszMsg, const char * pszTitle) {
showDialogJNI(pszMsg, pszTitle);
}
void CCLuaLog(const char * pszFormat) {
__android_log_print(ANDROID_LOG_DEBUG, "cocos2d-x debug info", pszFormat);
}
NS_CC_END
在这段代码中,CCMessageBox 调用了 showDialogJNI 函数,但在当前文件中并没有找到该函数的定义。注意到 include 部分有 jni/Java_org_cocos2dx_lib_Cocos2dxHelper.h,我们打开同目录下的 jni 目录中的 cpp 文件:
#include <stdlib.h>
#include <jni.h>
#include <android/log.h>
#include <string>
#include "JniHelper.h"
#include "cocoa/CCString.h"
#include "Java_org_cocos2dx_lib_Cocos2dxHelper.h"
#define LOG_TAG "Java_org_cocos2dx_lib_Cocos2dxHelper.cpp"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define CLASS_NAME "org/cocos2dx/lib/Cocos2dxHelper"
static EditTextCallback s_pfEditTextCallback = NULL;
static void * s_ctx = NULL;
using namespace cocos2d;
using namespace std;
string g_apkPath;
extern "C" {
JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxHelper_nativeSetApkPath(JNIEnv* env, jobject thiz, jstring apkPath) {
g_apkPath = JniHelper::jstring2string(apkPath);
}
JNIEXPORT void JNICALL Java_org_cocos2dx_lib_Cocos2dxHelper_nativeSetEditTextDialogResult(JNIEnv * env, jobject obj, jbyteArray text) {
jsize size = env->GetArrayLength(text);
if (size > 0) {
jbyte * data = (jbyte*)env->GetByteArrayElements(text, 0);
char * pBuf = (char*)malloc(size+1);
if (pBuf != NULL) {
memcpy(pBuf, data, size);
pBuf[size] = '\0';
// pass data to edittext's delegate
if (s_pfEditTextCallback) s_pfEditTextCallback(pBuf, s_ctx);
free(pBuf);
}
env->ReleaseByteArrayElements(text, data, 0);
} else {
if (s_pfEditTextCallback) s_pfEditTextCallback("", s_ctx);
}
}
}
void showDialogJNI(const char * pszMsg, const char * pszTitle) {
if (!pszMsg) {
return;
}
JniMethodInfo t;
if (JniHelper::getStaticMethodInfo(t, CLASS_NAME, "showDialog", "(Ljava/lang/String;Ljava/lang/String;)V")) {
jstring stringArg1;
if (!pszTitle) {
stringArg1 = t.env->NewStringUTF("");
} else {
stringArg1 = t.env->NewStringUTF(pszTitle);
}
jstring stringArg2 = t.env->NewStringUTF(pszMsg);
t.env->CallStaticVoidMethod(t.classID, t.methodID, stringArg1, stringArg2);
t.env->DeleteLocalRef(stringArg1);
t.env->DeleteLocalRef(stringArg2);
t.env->DeleteLocalRef(t.classID);
}
}
其中,CLASS_NAME 宏定义指定了要调用的 Java 类。showDialogJNI 函数直接调用了 CLASS_NAME 对应类中的 showDialog 函数。至此,C++ 层面的查找结束,接下来我们到 Java 层继续查找。
JAVA 层中找调用处
打开 Cocos2dxHelper.java 文件,找到 showDialog 函数,定义如下:
private static void showDialog(final String pTitle, final String pMessage) {
Cocos2dxHelper.sCocos2dxHelperListener.showDialog(pTitle, pMessage);
}
这是一个静态函数,调用了 sCocos2dxHelperListener 的 showDialog 函数。sCocos2dxHelperListener 是一个接口类型:
private static Cocos2dxHelperListener sCocos2dxHelperListener;
// ============================================================
// Inner and Anonymous Classes
// ============================================================
public static interface Cocos2dxHelperListener {
public void showDialog(final String pTitle, final String pMessage);
public void showEditTextDialog(final String pTitle, final String pMessage, final int pInputMode, final int pInputFlag, final int pReturnType, final int pMaxLength);
public void runOnGLThread(final Runnable pRunnable);
}
既然是接口,肯定有赋值的地方,并且有对应的类实现了这个接口。赋值函数如下:
public static void init(final Context pContext, final Cocos2dxHelperListener pCocos2dxHelperListener) {
final ApplicationInfo applicationInfo = pContext.getApplicationInfo();
Cocos2dxHelper.sContext = pContext;
Cocos2dxHelper.sCocos2dxHelperListener = pCocos2dxHelperListener;
Cocos2dxHelper.sPackageName = applicationInfo.packageName;
Cocos2dxHelper.sFileDirectory = pContext.getFilesDir().getAbsolutePath();
Cocos2dxHelper.nativeSetApkPath(applicationInfo.sourceDir);
Cocos2dxHelper.sCocos2dxAccelerometer = new Cocos2dxAccelerometer(pContext);
Cocos2dxHelper.sCocos2dMusic = new Cocos2dMusic(pContext);
Cocos2dxHelper.sCocos2dSound = new Cocos2dSound(pContext);
Cocos2dxHelper.sAssetManager = pContext.getAssets();
Cocos2dxBitmap.setContext(pContext);
}
实现该接口的类是 Cocos2dxActivity,我们查看 Cocos2dxActivity 类中的 showDialog 函数:
@Override
public void showDialog(final String pTitle, final String pMessage) {
Message msg = new Message();
msg.what = Cocos2dxHandler.HANDLER_SHOW_DIALOG;
msg.obj = new Cocos2dxHandler.DialogMessage(pTitle, pMessage);
this.mHandler.sendMessage(msg);
}
原来,最终是通过 mHandler 来处理的,而 mHandler 就是之前提到的 Cocos2dxHandler 类。
总结
这次查找修改位置的过程很有趣。我省略了 JniHelper 中与 Java 调用的部分,有兴趣的开发者可以自行研究,并不复杂。如果要实现一个跨平台的功能,可以参考这种方式,但由于涉及到修改源代码,不太推荐,因为引擎升级时会带来合并代码的麻烦。我自己使用 EasyNDK 来实现(不过 WIN32 平台没有实现)。另外,还有一种常用的带返回处理的 Dialog,可以判断用户按了确认还是取消,这在开发中也很实用。