版权声明:本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名elloop(包含链接)
#前言
本文总结了在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 / 3 d / .. / renderer / CCTexture2D . h : 32 ,
from D : \ codes \ 3.9 \ PlayingWithCocos3D \ proj . android \ .. / cocos2d / cocos / 3 d / .. / base / CCProtocols . h : 34 ,
from D : \ codes \ 3.9 \ PlayingWithCocos3D \ proj . android \ .. / cocos2d / cocos / 3 d / .. / 2 d / CCNode . h : 34 ,
from D : \ codes \ 3.9 \ PlayingWithCocos3D \ proj . android \ .. / cocos2d / cocos / 3 d / .. / 2 d / CCScene . h : 32 ,
from D : \ codes \ 3.9 \ PlayingWithCocos3D \ proj . android \ .. / cocos2d / cocos / 3 d / .. / base / CCDirector . h : 37 ,
from D : \ codes \ 3.9 \ PlayingWithCocos3D \ proj . android \ .. / cocos2d / cocos / 3 d / .. / base / CCAsyncTaskPool . h : 29 ,
from D : \ codes \ 3.9 \ PlayingWithCocos3D \ proj . android \ .. / cocos2d / cocos / 3 d / .. / 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函数,在``头文件里可以找到内置数据类型的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博客 , 欢迎访问