最近在用Cocos Creator做项目,记录一下开发过程中遇到的各种坑和解决方法。
Android项目配置 项目结构 Creator构建Android项目后,目录结构大概是这个样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 proj.android-studio/ ├── app/ │ ├── build.gradle # 应用构建配置 │ ├── jni/ │ │ ├── Android.mk # NDK构建脚本 │ │ └── Application.mk # NDK应用配置 │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── assets/ # 游戏资源 │ └── java/ # Java源码 ├── build.gradle # 项目构建配置 ├── gradle.properties # Gradle属性配置 └── settings.gradle # 项目设置
Gradle配置 项目级build.gradle:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 buildscript { repositories { jcenter() google() } dependencies { classpath 'com.android.tools.build:gradle:2.3.3' } } 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 import org.apache.tools.ant .taskdefs.condition.Osapply plugin: 'com.android.application' android { compileSdkVersion 24 buildToolsVersion "26.0.2" defaultConfig { applicationId "com.game.xxx" minSdkVersion 10 targetSdkVersion 23 versionCode 1 versionName "1.0" externalNativeBuild { ndkBuild { if (!project .hasProperty("PROP_NDK_MODE" ) || PROP_NDK_MODE.compareTo ('none' ) != 0 ) { 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)) { module_paths = module_paths.collect { it.replaceAll('\\\\' , '/' ) } arguments 'NDK_MODULE_PATH=' + module_paths.join (";" ) } else { 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" } signingConfigs { release { if (project .hasProperty("RELEASE_STORE_FILE" )) { storeFile file (RELEASE_STORE_FILE) storePassword RELEASE_STORE_PASSWORD keyAlias RELEASE_KEY_ALIAS keyPassword RELEASE_KEY_PASSWORD } } } 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' } } } debug { externalNativeBuild { ndkBuild { arguments 'NDK_DEBUG=1' } } } } } dependencies { implementation fileTree (dir: 'libs' , include : ['*.jar' ]) implementation project (':libcocos2dx' ) }
常见编译错误 1 error: format not a string literal and no format arguments [-Werror=format-security]
解决方案:
在Application.mk中添加:
1 APP_CPPFLAGS += -Wno-error=format-security
CreateProcess参数错误 1 error: process_begin: CreateProcess(...) make (e=87): 参数错误
原因: Windows对命令行长度有限制,.mk文件里包含的文件太多。
解决:
在Application.mk中添加:
1 APP_SHORT_COMMANDS := true
malloc未声明 1 'malloc' was not declared in this scope
解决方案: 在源码中添加头文件包含:
void*转换错误 1 invalid conversion from 'void*' to 'char*'
解决方案: 在malloc调用前添加类型强转:
1 char * buffer = (char *)malloc (size);
armeabi不支持 1 2 3 Android NDK: INTERNAL ERROR: The armeabi ABI should have exactly one architecture definitions. Found: Error:ABIs [armeabi] are not supported for platform.
解决方案: 在gradle.properties中修改ABI配置:
1 2 3 4 5 PROP_APP_ABI =armeabi PROP_APP_ABI =armeabi-v7a
支持的ABI列表:
ABI
说明
armeabi-v7a
ARMv7及以上(推荐)
arm64-v8a
ARM64(高性能)
x86
Intel 32位模拟器
x86_64
Intel 64位模拟器
横竖屏切换踩坑 问题描述 从竖屏适配改为横屏时,可能出现以下问题:
界面整体显示为横屏
但Scene内容显示为竖屏
内容靠左,超出屏幕的部分被裁切
配置对比 错误配置:
1 2 3 4 5 6 7 8 9 10 { "orientation" : { "landscapeLeft" : true , "landscapeRight" : true , "portrait" : false , "upsideDown" : false } , "packageName" : "net.xxxxxxx" , "webOrientation" : "portrait" }
正确配置:
1 2 3 4 5 6 7 8 9 10 { "orientation" : { "landscapeLeft" : true , "landscapeRight" : true , "portrait" : false , "upsideDown" : false } , "packageName" : "net.xxxxxxx" , "webOrientation" : "landscape" }
关键发现: Android打包会受web-mobile配置里webOrientation参数影响,这是个坑!
代码层适配 AppDelegate.cpp适配:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) cocos2d::Size designResolutionSize = cocos2d::Size (800 , 480 ); glview->setDesignResolutionSize ( designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::FIXED_HEIGHT ); glview->setFrameSize (800 , 480 ); #else Size frameSize = glview->getFrameSize (); glview->setDesignResolutionSize ( frameSize.width, 480 , ResolutionPolicy::FIXED_HEIGHT ); #endif
DragonBones动画系统 动态加载DragonBones 本地资源加载方式:
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 onLoad ( ) { let animNode = new cc.Node (); animNode.parent = this .node ; this .armatureDisplay = animNode.addComponent (dragonBones.ArmatureDisplay ); this .armatureDisplay .addEventListener ( dragonBones.EventObject .COMPLETE , this .animationEventHandler , this ); cc.loader .loadResDir ('dragonbone/effect/txk' , (err, assets ) => { if (err || assets.length == 0 ) return ; for (let i = 0 ; i < assets.length ; i++) { if (assets[i] instanceof dragonBones.DragonBonesAsset ) { this .armatureDisplay .dragonAsset = assets[i]; } if (assets[i] instanceof dragonBones.DragonBonesAtlasAsset ) { this .armatureDisplay .dragonAtlasAsset = assets[i]; } } this .armatureDisplay .armatureName = "txk" ; this .armatureDisplay .playAnimation ("wait_1" , 1 ); }); }, animationEventHandler (event ) { if (event.type === dragonBones.EventObject .COMPLETE ) { this .armatureDisplay .playAnimation ("wait_1" , 1 ); } }
远程资源加载 从服务器加载:
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 let animNode = new cc.Node ();animNode.parent = cc.find ('Canvas' ); let dragonDisplay = animNode.addComponent (dragonBones.ArmatureDisplay );let image = 'http://localhost:7456/res/raw-assets/eee_tex-1529064342.png' ;let ske = 'http://localhost:7456/res/raw-assets/eee_ske-1529065642.json' ;let atlas = 'http://localhost:7456/res/raw-assets/eee_tex-1529065642.json' ;cc.loader .load (image, (error, texture ) => { cc.loader .load ({ url : atlas, type : 'txt' }, (error, atlasJson ) => { cc.loader .load ({ url : ske, type : 'txt' }, (error, dragonBonesJson ) => { let atlasAsset = new dragonBones.DragonBonesAtlasAsset (); atlasAsset.atlasJson = atlasJson; atlasAsset.texture = texture; let asset = new dragonBones.DragonBonesAsset (); asset.dragonBonesJson = dragonBonesJson; dragonDisplay.dragonAtlasAsset = atlasAsset; dragonDisplay.dragonAsset = asset; dragonDisplay.armatureName = 'box_anim' ; dragonDisplay.playAnimation ('box_anim' , 0 ); }); }); });
注意:循环播放可能会报错Cannot read property 'loopComplete' of null,建议用事件监听处理。
按钮事件系统 模拟点击 手柄适配时可能需要代码触发按钮点击:
1 2 this .node .getComponent (cc.Button ).clickEvents [0 ].emit (['click' ]);
事件绑定方式 方式一:EventHandler API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var clickEventHandler = new cc.Component .EventHandler ();clickEventHandler.target = this .node ; clickEventHandler.component = "MyComponent" ; clickEventHandler.handler = "callback" ; clickEventHandler.customEventData = "xxx" ; var button = this .node .getComponent (cc.Button );button.clickEvents .push (clickEventHandler); callback (event, customEventData ) { }
方式二:on API(推荐)
1 2 3 4 5 6 this .node .on ('click' , this .callback , this );callback (event ) { }
JNI跨平台调用 JS调用Android方法 Creator通过jsb.reflection提供JNI调用:
1 2 3 4 5 6 7 8 9 10 if (jsb) { let result = jsb.reflection .callStaticMethod ( "org/cocos2dx/javascript/AppActivity" , "pay" , "(Ljava/lang/String;)Ljava/lang/String;" , "1" ); cc.log (result); }
方法签名说明:
类型
签名
void
V
boolean
Z
int
I
long
J
float
F
double
D
String
Ljava/lang/String;
Object
L包名/类名;
String[]
[Ljava/lang/String;
Java方法定义 1 2 3 4 5 6 7 public class AppActivity extends Cocos2dxActivity { public static String pay (String index) { return "success" ; } }
完整JNI示例 C++层封装:
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 #include "platform/android/jni/JniHelper.h" #include <jni.h> #define PAGENAMEPATH "org/cocos2dx/cpp/AppActivity" std::string callJavaPay (const std::string& index) { JniMethodInfo minfo; bool isHave = JniHelper::getStaticMethodInfo ( minfo, PAGENAMEPATH, "pay" , "(Ljava/lang/String;)Ljava/lang/String;" ); if (!isHave) { CCLOG ("jni:no method" ); return "" ; } jstring idx = minfo.env->NewStringUTF (index.c_str ()); jstring result = (jstring)minfo.env->CallStaticObjectMethod ( minfo.classID, minfo.methodID, idx ); char * rtn = nullptr ; jclass clsstring = minfo.env->FindClass ("java/lang/String" ); jstring strencode = minfo.env->NewStringUTF ("utf-8" ); jmethodID mid = minfo.env->GetMethodID ( clsstring, "getBytes" , "(Ljava/lang/String;)[B" ); jbyteArray barr = (jbyteArray)minfo.env->CallObjectMethod ( result, mid, strencode ); jsize alen = minfo.env->GetArrayLength (barr); jbyte* ba = minfo.env->GetByteArrayElements (barr, JNI_FALSE); if (alen > 0 ) { rtn = (char *)malloc (alen + 1 ); memcpy (rtn, ba, alen); rtn[alen] = 0 ; } minfo.env->ReleaseByteArrayElements (barr, ba, 0 ); return std::string (rtn); }
第三方SDK集成 JAR包导入 步骤1: 在proj.android-studio/app/目录下创建libs文件夹
步骤2: 复制JAR包到libs目录
步骤3: 配置build.gradle
1 2 3 4 5 6 7 8 9 10 11 dependencies { implementation fileTree (include : ['*.jar' ], dir: 'libs' ) implementation fileTree ( include : ['*.jar' ], dir: 'F:/CocosCreator204/resources/cocos2d-x/cocos/platform/android/java/libs' ) implementation project (':libcocos2dx' ) }
Assets资源拷贝 SDK可能需要额外的资源文件,打包时自动拷贝:
1 2 3 4 5 6 7 8 9 10 11 android { } variant.mergeAssets.doLast { copy { from "${buildDir}/../../../../../treeSDKAssets" into "${buildDir}/intermediates/assets/${variant.dirName}/treeSDKAssets" } }
AndroidManifest配置 按SDK文档添加必要的权限和Activity:
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 <manifest xmlns:android ="http://schemas.android.com/apk/res/android" package ="com.yourcompany.yourapp" > <uses-permission android:name ="android.permission.INTERNET" /> <uses-permission android:name ="android.permission.ACCESS_NETWORK_STATE" /> <application android:label ="@string/app_name" android:icon ="@mipmap/ic_launcher" > <activity android:name ="com.thirdsdk.SDKActivity" /> <activity android:name ="org.cocos2dx.javascript.AppActivity" android:screenOrientation ="landscape" android:configChanges ="orientation|keyboardHidden|screenSize" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > </activity > </application > </manifest >
平台判断与音频适配 平台判断 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 if (cc.sys .os == cc.sys .OS_IOS ) { cc.log ("Running on iOS" ); } switch (cc.sys .os ) { case cc.sys .OS_IOS : break ; case cc.sys .OS_ANDROID : break ; case cc.sys .OS_WINDOWS : break ; }
7.2 音频格式适配 注意: iOS设备不支持OGG格式,建议统一使用MP3:
1 2 3 4 5 6 7 8 9 10 let audioUrl = cc.sys .os == cc.sys .OS_IOS ? "res/audio/sound.mp3" : "res/audio/sound.ogg" ; cc.loader .loadRes (audioUrl, cc.AudioClip , (err, clip ) => { if (!err) { cc.audioEngine .playEffect (clip, false ); } });
手机振动功能 检测与调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 supportsVibrate ( ) { return "vibrate" in navigator; }, startVibrate (duration = 1000 ) { if (navigator.vibrate ) { navigator.vibrate (duration); } else if (navigator.webkitVibrate ) { navigator.webkitVibrate (duration); } }, onTouchStart ( ) { if (this .supportsVibrate ()) { this .startVibrate (200 ); } }
总结一下 Cocos Creator跨平台开发方便是方便,但坑也不少。Android配置、横竖屏、DragonBones、JNI调用、SDK集成这些环节都得仔细处理。
核心要点:
Gradle配置要理解,常见错误有套路
横竖屏注意webOrientation配置
DragonBones本地/远程加载方式要区分
JNI方法签名要搞对
SDK集成记得配置资源拷贝
参考: