本文总结了在windows上使用cocos compile
命令编译cocos2d-x安卓apk的基本用法,以及记录一个使用NDK-r9d(gcc 4.8)编译C++11的hash_map
遇到的一个问题: error: invalid use of incomplete type 'struct std::hash
。
从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选项指定了工程的目录地址。即可。自己打开瞧瞧它的内容就知道了。很简单。
使用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 \
我的cocos代码中使用了一个强类型枚举作为键值的unordered_map
, 如下:
enum class MessageType
{
kMessageTypeChangePage = 0,
kMessageTypePushPage,
kMessageTypePopPage,
kMessageTypeChangeBackground,
};
class MessageCenter : public Singleton {
// ......
private:
typedef std::vector MessageQueue;
MessageQueue messages_;
typedef std::set HandlerQueue;
typedef std::unordered_map 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'
using __ebo_h1 = _Hashtable_ebo_helper<1, _H1>;
^
编译器抱怨说 : struct std::hash是不完整类型。它为什么会这样认为?这是因为hash表的定义需要一个hash函数,对于内置的数据类型,C++标准要求标准库要提供预定义的hash函数,在
头文件里可以找到内置数据类型的hash函数定义:
// std::hash
// Defined in
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;
而对于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
{
typedef MessageType argument_type;
typedef size_t return_type;
return_type operator() (const argument_type &arg) const
{
return static_cast(arg);
}
};
}
为了快点通过编译在android上看到我的游戏测试结果,我定义了一个很简单的hash函数,直接把enum的值转为size_t。
有了这个hash函数,NDK编译通过了并且打包成功。
作者水平有限,对相关知识的理解和总结难免有错误,还望给予指正,非常感谢!
在这里也能看到这篇文章:github博客, CSDN博客, 欢迎访问