您的位置:首页 > 移动开发 > Cocos引擎

【cocos2d-x 3.x 学习与应用总结】4: 使用cocos compile编译apk

2016-01-24 23:27 661 查看

前言

本文总结了在windows上使用
cocos compile
命令编译cocos2d-x安卓apk的基本用法,以及记录一个使用NDK-r9d(gcc 4.8)编译C++11的
hash_map
遇到的一个问题:
error: invalid use of incomplete type 'struct std::hash<MessageType>'


cocos compile 基本用法, 编译debug版本的cpp-tests.

从2.x到3.x,cocos的辅助工具做的越来越完善了,2.x刚开始的时候编译apk是比较费劲的,除了最基本的安装JDK, NDK, Android SDK, Ant还要装cygwin,自己配环境变量,执行打包脚本等等。我最近使用的是cocos2d-x官方的github仓库中的默认v3分支最新代码(3.10), 以编译cpp-tests工程为android apk为例,使用最新的打包方式:cocos compile.

基本的环境准备

如今的cocos,大量使用了python作为其辅助工具,因此第一步就是装好python,推荐比较经典的2.7.x版本。

装好python之后就可以到引擎的根目录,执行setup.py来初始化cocos环境了,它会引导你配置好cocos命令行工具执行所需要的环境变量和工具。其中包括:

Setting up cocos2d-x...
->Check environment variable COCOS_CONSOLE_ROOT
->Search for environment variable COCOS_CONSOLE_ROOT...
->COCOS_CONSOLE_ROOT is found : D:\codes\cocos2d-x\tools\cocos2d-console\bin

->Check environment variable COCOS_X_ROOT
->Search for environment variable COCOS_X_ROOT...
->COCOS_X_ROOT is found : D:\codes

->Check environment variable COCOS_TEMPLATES_ROOT
->Search for environment variable COCOS_TEMPLATES_ROOT...
->COCOS_TEMPLATES_ROOT is found : D:\codes\cocos2d-x\templates

->Configuration for Android platform only, you can also skip and manually edit your environment variables

->Check environment variable NDK_ROOT
->Search for environment variable NDK_ROOT...
->NDK_ROOT is found : D:\Programs\android-ndk-r9d\

->Check environment variable ANDROID_SDK_ROOT
->Search for environment variable ANDROID_SDK_ROOT...
->ANDROID_SDK_ROOT is found : D:\Programs\adt-bundle-windows-x86_64-20131030\sdk

->Check environment variable ANT_ROOT
->Search for environment variable ANT_ROOT...
->ANT_ROOT is found : D:\programs\apache-ant-1.9.4\bin\


其中几个常用SDK和NDK的下载和安装不再细说。在配置好之后,能看到像我上面这样的输出就ok了,相对于2.x中要做的工作,这样的引导是安装已经很简单了。

在命令行执行打包命令

配置好环境之后,打包就非常简单了。对于cocos自带的cpp-tests项目,打包apk有两种操作方法:

方法1

在引擎根目录/build/目录下,执行
python android_build.py cpp-tests
即可。建议在cmd窗口中操作,可以看到真个过程,包括异常退出等情况。关于
android_build.py
的更多用法请参考其文件内容,里面有usage说明。

方法2

在引擎根目录/tests/cpp-tests/目录,执行cocos命令:

cocos compile -p android


其实,两种方法都是使用了
cocos compile
这个命令行工具。关于其完整的用法请在cmd中输入
cocos compile -h
即可查看帮助信息。第一种方式中的脚本
android_build.py
其实是使用
-s
参数来调用
cocos compile
命令,-s选项指定了工程的目录地址。即可。自己打开瞧瞧它的内容就知道了。很简单。

自己创建的项目打包apk

使用
cocos new
命令创建的工程如何打包成apk?

也很简单,跟cpp-tests是一样的过程,也是使用
cocos compile
命令。在执行命令之前,比cpp-tests多的一步操作是,添加自己写的C++源代码文件名称和包含路径到Android.mk.

假设项目名字叫”Dog”

第一步,添加文件包含路径和cpp文件名到Dog/proj.android/jni/Android.mk

例如下面的Android.mk文件是我的游戏目录下/proj.android/jni/Android.mk文件,其中那一堆cpp就是自己写的cpp源代码,要参与到二进制编译的cpp都要加进来。

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)

LOCAL_MODULE := cocos2dcpp_shared

LOCAL_MODULE_FILENAME := libcocos2dcpp

# 从hellocpp/main.cpp这行往下,是自己添加的cpp文件
LOCAL_SRC_FILES := hellocpp/main.cpp \
../../Classes\AppDelegate.cpp \
../../Classes\CCDirector.cpp \
../../Classes\customs\FlowLayout.cpp \
../../Classes\customs\Menu.cpp \
../../Classes\data_models\TestDataCenter.cpp \
../../Classes\LogicDirector.cpp \
../../Classes\message\Message.cpp \
../../Classes\message\MessageCenter.cpp \
../../Classes\PageManager.cpp \
../../Classes\pages\CameraTest.cpp \
../../Classes\pages\CCBPage.cpp \
../../Classes\pages\CCBTestPage.cpp \
../../Classes\pages\LittleTouchPage.cpp \
../../Classes\pages\MainPage.cpp \
../../Classes\pages\NodeTestPage.cpp \
../../Classes\pages\RootPage.cpp \
../../Classes\pages\ScrollviewTestPage.cpp \
../../Classes\pages\SuperPage.cpp \
../../Classes\pages\TestEntranceLayer.cpp \
../../Classes\pages\TouchTestPage.cpp \
../../Classes\util\CocosWindow.cpp \
../../Classes\util\cocos_util.cpp \
../../Classes\util\DrawNode3D.cpp \
../../Classes\util\StringUtil.cpp \
../../Classes\pages\ShaderTest.cpp

# 文件的包含路径
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../Classes

# _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


2. 在Dog/proj.android/目录下执行:cocos compile -p android

通过这两部就完成了自己创建项目的打包apk操作,可以注意到,比较复杂的就是第一步,在把自己的cpp文件加到Android.mk的时候,尤其是当你的cpp放的到处都是,像我的那样分散在多个子目录中。为了这一步的自动化,可以自己写个脚本。我这里分享一个很简单的小脚本,它可以遍历子目录,记录下所有的cpp文件。其实就是一个简单的类似unix命令tree的小脚本。

tree.py: 递归遍历子目录,统计所有.cpp文件,并写入完整路径到cpps.txt

# coding=utf-8

import os

def get_dir_tree(begin_dir, coll):
files = os.listdir(begin_dir)
for file_name in files:
full_path = os.path.join(begin_dir, file_name)
if os.path.isdir(full_path):
get_dir_tree(full_path, coll)
else:
coll.append(full_path)

if "__main__" == __name__:
file_tree = []
get_dir_tree(os.getcwd(), file_tree)

# write .cpp file names into file
with open("cpps.txt", "w") as f:
for name in file_tree:
if str(name).endswith(".cpp"):
f.write(name + " \\\n")


把这个tree.py脚本丢到: 项目根目录/Classes/目录下,执行即可生成一个cpps.txt, 里面记录了所有的cpp文件路径,然后按列选择
/Classes/..../xxxx.cpp \
, 复制粘贴到Android.mk即可。

cpps.txt

D:\codes\3.9\PlayingWithCocos3D\Classes\AppDelegate.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\CCDirector.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\cocos_src_modified\CCNodeLoader.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\customs\FlowLayout.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\customs\Menu.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\data_models\TestDataCenter.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\HelloWorldScene.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\LogicDirector.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\message\Message.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\message\MessageCenter.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\PageManager.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\CameraTest.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\CCBPage.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\CCBTestPage.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\LittleTouchPage.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\MainPage.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\NodeTestPage.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\RootPage.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\ScrollviewTestPage.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\ShaderTest.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\SuperPage.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\TestEntranceLayer.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\pages\TouchTestPage.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\util\CocosWindow.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\util\cocos_util.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\util\DrawNode3D.cpp \
D:\codes\3.9\PlayingWithCocos3D\Classes\util\StringUtil.cpp \


C++11中的强类型枚举(scoped enum)作为hash表键值导致的编译错误

我的cocos代码中使用了一个强类型枚举作为键值的
unordered_map
, 如下:

enum class MessageType
{
kMessageTypeChangePage = 0,
kMessageTypePushPage,
kMessageTypePopPage,
kMessageTypeChangeBackground,
};

class MessageCenter : public Singleton<MessageCenter> {

// ......

private:
typedef std::vector<Message*>                           MessageQueue;
MessageQueue                                            messages_;

typedef std::set<PriorityHandler*>                      HandlerQueue;
typedef std::unordered_map<MessageType, HandlerQueue>   HandlerMap;
HandlerMap                                              handlerMap_;
};


其中的HandlerMap类型就是以enum class MessageType来作为键值的hash表,在windows上我使用visual studio 2013编译项目,顺利通过。

编译apk时就出问题了。在使用
cocos compile -p android
命令,使用NDK对上面的代码进行编译的时候,会发现如下错误:

In file included from D:/programs/android-ndk-r9d/sources/cxx-stl/gnu-libstdc++/4.8/include/bits/hashtable.h:35:0,
from D:/programs/android-ndk-r9d/sources/cxx-stl/gnu-libstdc++/4.8/include/unordered_map:47,
from D:\codes\3.9\PlayingWithCocos3D\proj.android\../cocos2d/cocos/3d/../renderer/CCTexture2D.h:32,
from D:\codes\3.9\PlayingWithCocos3D\proj.android\../cocos2d/cocos/3d/../base/CCProtocols.h:34,
from D:\codes\3.9\PlayingWithCocos3D\proj.android\../cocos2d/cocos/3d/../2d/CCNode.h:34,
from D:\codes\3.9\PlayingWithCocos3D\proj.android\../cocos2d/cocos/3d/../2d/CCScene.h:32,
from D:\codes\3.9\PlayingWithCocos3D\proj.android\../cocos2d/cocos/3d/../base/CCDirector.h:37,
from D:\codes\3.9\PlayingWithCocos3D\proj.android\../cocos2d/cocos/3d/../base/CCAsyncTaskPool.h:29,
from D:\codes\3.9\PlayingWithCocos3D\proj.android\../cocos2d/cocos/3d/../cocos2d.h:41,
from jni/../../Classes/util/cocos_util.h:3,
from jni/../../Classes/cocos_include.h:4,
from jni/../../Classes/message/MessageCenter.h:4,
from jni/../../Classes\message\MessageCenter.cpp:1:
D:/programs/android-ndk-r9d/sources/cxx-stl/gnu-libstdc++/4.8/include/bits/hashtable_policy.h:1082:53: error: invalid use of incomplete type 'struct std::hash<MessageType>'
using __ebo_h1 = _Hashtable_ebo_helper<1, _H1>;
^


编译器抱怨说 : struct std::hash是不完整类型。它为什么会这样认为?这是因为hash表的定义需要一个hash函数,对于内置的数据类型,C++标准要求标准库要提供预定义的hash函数,在
<functional>
头文件里可以找到内置数据类型的hash函数定义:

// std::hash
// Defined in <funtional>
template<> struct hash<bool>;
template<> struct hash<char>;
template<> struct hash<signed char>;
template<> struct hash<unsigned char>;
template<> struct hash<char16_t>;
template<> struct hash<char32_t>;
template<> struct hash<wchar_t>;
template<> struct hash<short>;
template<> struct hash<unsigned short>;
template<> struct hash<int>;
template<> struct hash<unsigned int>;
template<> struct hash<long>;
template<> struct hash<long long>;
template<> struct hash<unsigned long>;
template<> struct hash<unsigned long long>;
template<> struct hash<float>;
template<> struct hash<double>;
template<> struct hash<long double>;
template< class T > struct hash<T*>;


而对于enum class类型C++标准并没有要求必须提供。windows上VC++编译器应该是提供了enum class的hash函数,因此编译能够通过。那么对于gcc编译器,要想解决这个问题就需要自己定义hash函数,针对我的enum class MessageType。

enum class MessageType
{
kMessageTypeChangePage = 0,
kMessageTypePushPage,
kMessageTypePopPage,
kMessageTypeChangeBackground,
};

namespace std
{
template <>
struct hash<MessageType>
{
typedef MessageType     argument_type;
typedef size_t          return_type;

return_type operator() (const argument_type &arg) const
{
return static_cast<return_type>(arg);
}
};
}


为了快点通过编译在android上看到我的游戏测试结果,我定义了一个很简单的hash函数,直接把enum的值转为size_t。

有了这个hash函数,NDK编译通过了并且打包成功。

作者水平有限,对相关知识的理解和总结难免有错误,还望给予指正,非常感谢!

在这里也能看到这篇文章:github博客, CSDN博客, 欢迎访问
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  cocos2d-x hash ndk gcc