Cocos2d-x Android项目配置踩坑记录

Cocos2d-x项目配Android环境总是踩坑,这里记录一下完整流程和常见问题。

环境准备

开发环境要求

组件 推荐版本 说明
Cocos2d-x 3.17+ 稳定版本,支持Android Studio
Android Studio 3.0+ 官方推荐IDE
NDK r16b - r20 用于编译C++代码
JDK 1.8+ Java开发工具包
Python 2.7.x 用于运行setup脚本

NDK版本选择

Cocos2d-x对NDK版本有要求,太新或太旧都不行。

推荐版本:

  • Cocos2d-x 3.17.x: NDK r16b - r19c
  • Cocos2d-x 4.0+: NDK r20+

NDK下载地址:

1
https://developer.android.com/ndk/downloads/

项目结构配置

标准项目目录

一个标准的Cocos2d-x项目应该有以下目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
MyGame/
├── Classes/ # C++源代码
│ ├── AppDelegate.cpp
│ ├── AppDelegate.h
│ └── HelloWorldScene.cpp
├── Resources/ # 游戏资源文件
├── cocos2d/ # Cocos2d-x引擎代码
├── proj.android-studio/ # Android Studio项目
│ ├── app/
│ │ ├── build.gradle
│ │ ├── jni/
│ │ │ ├── Android.mk
│ │ │ └── Application.mk
│ │ └── src/
│ ├── build.gradle
│ ├── settings.gradle
│ └── gradle.properties
└── CMakeLists.txt

local.properties配置

在项目根目录创建或修改local.properties文件:

1
2
3
4
5
6
7
8
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
ndk.dir=C\:\\android-ndk-r16b
sdk.dir=C\:\\Users\\Administrator\\AppData\\Local\\Android\\Sdk

注意:

  • Windows路径用双反斜杠\\
  • 确保路径指向实际安装的目录

Gradle项目配置

settings.gradle配置

1
2
3
4
include ':libcocos2dx'
project(':libcocos2dx').projectDir = new File(settingsDir, '../cocos2d/cocos/platform/android/libcocos2dx')
include ':xxxGame'
project(':xxxGame').projectDir = new File(settingsDir, 'app')

重要提示:

  • libcocos2dx的路径必须使用相对路径
  • libcocos2dx版本不能太低,否则会出现nativeSetAudioDeviceInfo方法找不到的错误
  • xxxGame改为你的游戏项目名称

gradle.properties配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.

# Specifies the JVM arguments used for the daemon process.
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

# When configured, Gradle will run in incubating parallel mode.
org.gradle.parallel=true

# Cocos2d-x配置
PROP_TARGET_SDK_VERSION=14
PROP_APP_ABI=armeabi-v7a

支持的ABI架构:

ABI 说明
armeabi-v7a 32位ARM,兼容性最好
arm64-v8a 64位ARM,性能更好
x86 Intel x86架构,模拟器使用
x86_64 64位Intel架构

gradle-wrapper配置

1
2
3
4
5
6
#Mon Nov 19 16:06:44 GMT+08:00 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip

项目级build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

allprojects {
repositories {
jcenter()
google()
}
}

模块级build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import org.apache.tools.ant.taskdefs.condition.Os

apply plugin: 'com.android.application'

android {
compileSdkVersion 24
buildToolsVersion "26.0.2"

android{compileOptions.encoding="UTF-8"}

defaultConfig {
applicationId "com.xxxx.xxxGame"
minSdkVersion 10
targetSdkVersion 23
versionCode 1
versionName "1.0"

externalNativeBuild {
ndkBuild {
if (!project.hasProperty("PROP_NDK_MODE") || PROP_NDK_MODE.compareTo('none') != 0) {
// skip the NDK Build step if PROP_NDK_MODE is none
targets 'cocos2dcpp'
arguments 'NDK_TOOLCHAIN_VERSION=4.9'
arguments 'APP_PLATFORM=android-'+PROP_TARGET_SDK_VERSION

def module_paths = [project.file("../../cocos2d").absolutePath,
project.file("../../cocos2d/cocos").absolutePath,
project.file("../../cocos2d/external").absolutePath]
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
// Windows下使用分号分隔
module_paths = module_paths.collect {it.replaceAll('\\\\', '/')}
arguments 'NDK_MODULE_PATH=' + module_paths.join(";")
}
else {
// Linux/Mac下使用冒号分隔
arguments 'NDK_MODULE_PATH=' + module_paths.join(':')
}

arguments '-j' + Runtime.runtime.availableProcessors()
abiFilters.addAll(PROP_APP_ABI.split(':').collect{it as String})
}
}
}
}

sourceSets.main {
java.srcDir "src"
res.srcDir "res"
jniLibs.srcDir "libs"
manifest.srcFile "AndroidManifest.xml"
assets.srcDir "../../Resources"
}

externalNativeBuild {
ndkBuild {
if (!project.hasProperty("PROP_NDK_MODE") || PROP_NDK_MODE.compareTo('none') != 0) {
// skip the NDK Build step if PROP_NDK_MODE is none
path "jni/Android.mk"
}
}
}

signingConfigs {
release {
keyAlias 'xxx'
keyPassword 'xxxx'
storeFile file('E:/xxxx.keystore')
storePassword 'xxx'
}
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
if (project.hasProperty("RELEASE_STORE_FILE")) {
signingConfig signingConfigs.release
}
externalNativeBuild {
ndkBuild {
arguments 'NDK_DEBUG=0'
}
}
signingConfig signingConfigs.release
}
debug {
externalNativeBuild {
ndkBuild {
arguments 'NDK_DEBUG=1'
}
}
}
}
}

dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile project(':libcocos2dx')
}

NDK编译配置

Android.mk配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

$(call import-add-path,$(LOCAL_PATH)/../../../cocos2d)
$(call import-add-path,$(LOCAL_PATH)/../../../cocos2d/external)
$(call import-add-path,$(LOCAL_PATH)/../../../cocos2d/cocos)
$(call import-add-path,$(LOCAL_PATH)/../../../cocos2d/cocos/audio/include)

LOCAL_MODULE := cocos2dcpp_shared

LOCAL_MODULE_FILENAME := libcocos2dcpp


# 遍历所有源文件的宏定义
define walk
$(wildcard $(1)) $(foreach e, $(wildcard $(1)/*), $(call walk, $(e)))
endef

define uniq
$(eval seen :=) $(foreach _,$(1),$(if $(filter ${_},${seen}),,$(eval seen += ${_}))) ${seen}
endef


ALLFILES = $(call walk, $(LOCAL_PATH)/../../../Classes)

FILE_LIST := $(filter %.cpp, $(ALLFILES))
#FILE_LIST += $(filter %.c, $(ALLFILES))
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%)\
LOCAL_SRC_FILES += ../../../Classes/Libs/SQLite/sqlite3secure.c
#LOCAL_SRC_FILES += $(LOCAL_PATH)/hellocpp/main.cpp
$(info "UI path2:"$(LOCAL_SRC_FILES))

FILES_PATH := $(LOCAL_PATH)/../../../Classes
FILE_INCLUDES := $(dir $(foreach src_path,$(FILES_PATH), $(call walk,$(src_path),*/) ) )
FILE_INCLUDES := $(call uniq,$(FILE_INCLUDES))

LOCAL_C_INCLUDES := $(FILE_INCLUDES)

# _COCOS_HEADER_ANDROID_BEGIN
# _COCOS_HEADER_ANDROID_END


LOCAL_STATIC_LIBRARIES := cocos2dx_static

# _COCOS_LIB_ANDROID_BEGIN
# _COCOS_LIB_ANDROID_END

include $(BUILD_SHARED_LIBRARY)

$(call import-module,.)

# _COCOS_LIB_IMPORT_ANDROID_BEGIN
# _COCOS_LIB_IMPORT_ANDROID_END

Application.mk配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
APP_STL := gnustl_static

APP_CPPFLAGS := -frtti -DCC_ENABLE_CHIPMUNK_INTEGRATION=1 -std=c++11 -fsigned-char
APP_LDFLAGS := -latomic

APP_SHORT_COMMANDS := true
APP_CPPFLAGS += -Wno-error=format-security

ifeq ($(NDK_DEBUG),1)
APP_CPPFLAGS += -DCOCOS2D_DEBUG=1
APP_OPTIM := debug
else
APP_CPPFLAGS += -DNDEBUG
APP_OPTIM := release
endif

关键配置说明:

配置项 说明
APP_STL C++标准库类型,gnustl_static表示静态链接
APP_CPPFLAGS C++编译器选项,-std=c++11启用C++11
APP_LDFLAGS 链接器选项
APP_SHORT_COMMANDS 解决Windows命令行长度过长问题

AndroidManifest配置

游戏主模块配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.xxxx.xxxGame"
android:installLocation="auto">

<uses-feature android:glEsVersion="0x00020000" />

<application
android:label="@string/app_name"
android:icon="@drawable/icon">
<meta-data android:name="android.app.lib_name"
android:value="cocos2dcpp" />
<activity android:name="com.xxxx.xxxGame.AppActivity"
android:label="@string/app_name"
android:launchMode="singleTop"
android:screenOrientation="sensorPortrait"
android:configChanges="orientation|keyboardHidden"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

<supports-screens android:anyDensity="true"
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="true"/>

<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

libcocos2dx模块配置

1
2
3
4
5
6
7
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.cocos2dx.lib">

<application android:allowBackup="true">

</application>

</manifest>

自定义AppActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.gamedo.clonehero.pass;

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;

import org.cocos2dx.lib.Cocos2dxActivity;

public class AppActivity extends Cocos2dxActivity {

protected static Handler mUIHandler;

private Context mContext;
private static ImageView mWelcome = null;


// 创建一个ImageView,welcome是闪屏图片
protected ImageView createLaunchImage() {
mWelcome = new ImageView(mContext);
mWelcome.setImageResource(R.drawable.splash);
mWelcome.setScaleType(ImageView.ScaleType.CENTER_CROP);
return mWelcome;
}


// 当资源加载好了之后 删除之前创建的imageView 否则一直会在界面上显示
public static void removeLaunchImage() {
mUIHandler.post(new Runnable() {
@Override
public void run() {
if (mWelcome != null) {
mWelcome.setVisibility(View.GONE);
}
}
});
}

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

mContext = this;
// DO OTHER INITIALIZATION BELOW
mUIHandler = new Handler();
// 显示launch image 遮住「黑屏」
addContentView(createLaunchImage(),
new WindowManager.LayoutParams(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN));

new Thread() {
@Override
public void run() {
super.run();
try{
Thread.sleep(3000);//休眠3秒
}catch (Exception e){

}

/**
* 要执行的操作
*/
removeLaunchImage();
}
}.start();
}

static {
System.loadLibrary("cocos2dcpp");
}
}

常见问题与解决方案

nativeSetAudioDeviceInfo错误

错误信息:

1
No implementation found for void org.cocos2dx.lib.Cocos2dxHelper.nativeSetAudioDeviceInfo(boolean, int, int)

原因分析:

  1. 模拟器是x86架构,需要x86架构的so包
  2. Cocos2d-x版本过低,新版本中已移除该方法

解决方案1 - 添加x86支持:

修改jni/Application.mk文件:

1
APP_ABI := armeabi armeabi-v7a x86

解决方案2 - 升级Cocos2d-x:

更换为新版本Cocos2d-x,新版中Cocos2dxHelper里已经没有调用nativeSetAudioDeviceInfo方法。

dx.jar加载失败

错误信息:

1
Failed to load ..\sdk\build-tools\28.0.3\lib\dx.jar

原因分析:
加载dx.jar时默认使用最新版,但最新版一般都有兼容性问题。

解决方案:

  1. 打开%SDK_HOME%\build-tools目录
  2. 找一个老版本(如21.1.2)
  3. 打开lib文件夹,复制dx.jar
  4. 打开最新版本(如28.0.3)的lib文件夹
  5. 用复制的dx.jar替换

格式安全警告

错误信息:

1
format not a string literal and no format arguments [-Werror=format-security]

解决方案:

修改NDK目录下的build/core/default-build-commands.mk文件:

将:

1
TARGET_FORMAT_STRING_CFLAGS := -Wformat -Werror=format-security

修改为:

1
TARGET_FORMAT_STRING_CFLAGS := -Wformat   #-Werror=format-security

C++标准库函数缺失

错误信息:

1
'stoi' is not a member of 'std'

原因分析:
旧版本NDK对C++11支持不完整。

解决方案:
使用std::atoi替代std::stoi

malloc未声明

错误信息:

1
error: 'malloc' was not declared in this scope

解决方案:
在C++文件中添加头文件引用:

1
#include <stdlib.h>

MovieClip数据格式

标准数据格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
{
"mc": {
"mcName": {
"frameRate": 24,
"labels": [
{
"name": "fall",
"frame": 1,
"end": 3
},
{
"name": "move",
"frame": 4,
"end": 6
}
],
"events": [
{
"name": "@fallhalf",
"frame": 2
},
{
"name": "@movehalf",
"frame": 5
}
],
"frames": [
{
"res": "7ADF0F11",
"x": 224,
"y": 123
},
{
"res": "69D013A4",
"x": 228,
"y": 122
}
]
}
},
"res": {
"7ADF0F11": {
"x": 1,
"y": 87,
"w": 105,
"h": 80
},
"69D013A4": {
"x": 111,
"y": 1,
"w": 104,
"h": 85
}
}
}

字段说明

mc字段:

字段名 类型 说明
frameRate int 帧率,默认24,可选
labels array 帧标签列表,可选
events array 帧事件标签列表,可选
frames array 关键帧数据列表

res字段:

字段名 类型 说明
x int 资源在纹理集位置的x坐标
y int 资源在纹理集位置的y坐标
w int 资源宽度
h int 资源高度

性能优化

渲染合批

在Cocos2d-x中,可以通过设置实现自动渲染合批:

1
2
// 在Node节点的Mesh Renderer组件上
// 勾选enableAutoBatch属性

引擎会根据所用材质、Mesh的顶点格式、Primitive Type自动进行合批。

建议:

  • 只对可以被合批并且顶点数量较少的模型启用此选项
  • 相同材质的精灵尽量使用同一张纹理图集

总结一下

Cocos2d-x Android配置坑不少,主要注意NDK版本、Gradle配置和常见编译错误。

配置阶段 关键文件 注意事项
环境配置 local.properties NDK和SDK路径正确
Gradle配置 settings.gradle, build.gradle 模块依赖和版本
NDK配置 Android.mk, Application.mk 源文件路径和编译选项
Manifest配置 AndroidManifest.xml 权限和Activity
常见问题 - 版本兼容性和架构支持

参考: