cocos2dx使用汇总备查

2023/04/01 cocos 共 32060 字,约 92 分钟

版本说明

3.x的最新版本是 cocos2d-x 3.17.2 ,4.x的最新版本是 cocos2d-x4.0

3.x的架构图:

4.x的架构图:

可以明显地看出:从4.0开始,不再只是JavaScript引擎,也不再支持网页端,支持网页端的JavaScript部分被单独剥离出来,统一使用Cocos Creator来完成。因此定位就比较清晰了,Cocos Creator主打小游戏的开发,当然也支持网页端,cocos2dx4.0开始只支持原生端的Lua和C++。因为网页端的小游戏本身就可以在原生端运行,因此以后主推的开发平台就是Cocos Creator,并配合TypeScript语言进行开发。cocos2dx基本上已经成为了过去式,但是也不代表无法使用,如果需要,仍然可以使用该引擎进行开发,相信很多团队都有很多适合自己的版本。

环境配置

基础环境

官方的安装编译环境要求:Build Requirements - cocos2d-x

  • 下载解压 cocos2d-x 3.17.2 ,并把X:\cocos2d-x-3.17.2\tools\cocos2d-console\bin 设置到环境变量Path中,使得可以直接使用cocos命令。如果未设置到环境变量中,使用绝对路径也可以(推荐:warning: )。

  • Python 2.7.5+, Python 2,7.10 reccomended, NOT Python 3+

    • 使用pyenv安装一个2.7的最新版即可。
  • NDK r91c+ is required to build Android games (tested with r19c) May be called 19.2.xx from within Android Studio

  • Android Studio 3.4+ to build Android games (tested with 3.0)。 也可以不安装,不影响使用命令行打包。

  • Java8 Downloads - Oracle download java from oracle without login

  • 安装。执行:python setup.py,主要是按照提示设置两个路径:NDK路径和AndroidSDK路径,设置好后重新打开控制台终端。

  • 新建项目:cocos new HelloWorld -p com.xx.yy -l lua -d F:\CocosProjects ,提示是否同意发送数据时,可以选择N(不同意。)

  • 修改64位支持:修改gradle.propertiesPROP_APP_ABI=arm64-v8a

  • 运行项目

    • Windows版本:VisualStudio打开\frameworks\runtime-src\proj.win32 目录下的sln文件即可。

    • Android版本:进入frameworks\runtime-src\proj.android文件夹,运行如下命令:

      cocos run -p android
      

      gradle相关配置更新或下载完成,运行至真机/模拟器查看效果。或者直接用AndroidStudio打开frameworks\runtime-src\proj.android

Cocos Studio

当前能找到的最新的版本是cocos studio v3.10 ,下载地址:

默认使用的引擎版本是:cocos2d-x-3.10

认识Cocos Studio的文件:

  • Cocos.Launcher.Start:可以认为是dashboard,程序为Cocos.exe,通过它来管理创建的项目。
  • CocosStudio:开发IDE,程序为:CocosStudio.exe
  • simulator:模拟器,程序为:simulator.exe

随便创建个项目,然后运行项目,

发布项目成功
项目运行结束
Based on: cocos2d-x-3.10
编译模式:debug
正在编译...
要求的 VS 版本:[2012, 2013]
无法从注册表中找到可用的 VS 安装路径。

微软官方已经找不到可用的vs2013下载了,不过没有关系,vs强大之处就是高版本可以打开任意低版本项目,后面咱们直接上vs2022。

Cocos2dX新版搭配CocosStudio

截止目前为止,Cocos2d-X-3.x的最高版本是3.17.2,4.X的版本是4.0,我们以3.17.2版本为例来介绍,搭建一个开发环境。

  1. 安装cocos studio v3.10,例如安装在D:\Cocos

  2. 下载Cocos2d-X SDK包,由于Cocos2d源码比较庞大,GitHub上的文件不全,直接拉取的不能使用,从这里下载:https://cocos2d-x.org/download 。解压到cocos studio安装目录下,目录组织如下(cocos2d-x-3.10是cocos studio安装自带的):

    ├─Cocos Studio
    ├─Cocos2d-x
    │  ├─cocos2d-x-3.10
    │  ├─cocos2d-x-3.17.2
    │  └─cocos2d-x-4.0
    └─tools
    
  3. 使用用Cocos2d-X的命令行创建项目,进入目录:D:\Cocos\Cocos2d-x\cocos2d-x-3.17.2\tools\cocos2d-console\bin ,参考官方方式创建项目:

    # 需要Python2.7环境,推荐使用pyenv安装 
       
    # lua项目
    cocos new NewCocosProject -p com.your_company.mygame -l lua -d NEW_PROJECTS_DIR
       
    # c++项目
    cocos new NewCocosProject -p com.your_company.mygame -l cpp -d NEW_PROJECTS_DIR
    
  4. 使用cocos studio随便创建一个项目,复用如下几个文件,把它们复制到NEW_PROJECTS_DIR项目根目录下。

    NewCocosProject.ccs
    NewCocosProject.cfg
    NewCocosProject.udf
    
  5. 修改配置文件。主要是修改cfg文件,如果cocos创建的是cpp项目则需要将PublishDirectoryValue修改为Resources/res,这个主要是为了适应Cocos2d-X的资源目录组织形式,如果cocos创建的是lua项目则无须做任何修改。这里把全部的文件都贴上来,后面就不用再用CocosStudio创建项目了,直接复用这几个文件即可。

NewCocosProject.cfg

<SolutionConfig Version="3.10.0.0">
  <PublishDirectory Value="res" />
  <PackageDirectory Value="package/" />
  <PublishType Value="Reference" />
  <SolutionSize Value="960 * 640" />
  <ResolutionName Value="iPhone 4/4S" />
  <DefaultSerializer Value="Serializer_FlatBuffers" />
  <CustomSerializer Value="Serializer_FlatBuffers" />
  <IsNameStandardized Value="False" />
  <CustomProperties>
    <Item Key="CCS_CocosPropertis">
      <Value ctype="CocosProperties">
        <SolutionCodeType Value="Complete" />
        <ProgramLanguage Value="lua" />
        <CreateFrameworkVersion Value="cocos2d-x-3.10" />
        <CurrentFrameworkVersion Value="cocos2d-x-3.10" />
        <EngineType Value="Prebuilt" />
      </Value>
    </Item>
  </CustomProperties>
</SolutionConfig>

NewCocosProject.ccs

<Solution>
  <PropertyGroup Name="NewCocosProject" Version="3.10.0.0" Type="CocosStudio" />
  <SolutionFolder>
    <Group ctype="ResourceGroup">
      <RootFolder Name="." />
    </Group>
  </SolutionFolder>
</Solution>

NewCocosProject.udf

<UserData Version="3.10.0.0">
  <Properties>
    <Item Key="CocosRecentOperation">
      <Value ctype="CocosRecentOperation">
        <LastPublishType Value="Resource" />
        <IsLastPublish Value="True" />
        <LastRunType Value="Windows" />
      </Value>
    </Item>
    <Item Key="PackageParamsKey">
      <Value ctype="PackageParams">
        <LuaIsEncrypt Value="False" />
        <LuaEncryptKey Value="" />
        <LuaEncryptSign Value="" />
        <Android_PackageName Value="org.cocos.NewCocosProject" />
        <AndroidkeyStore Value="" />
        <AndroidVersion Value="" />
        <iOS_Target Value="NewCocosProject-mobile" />
        <iOS_BundleID Value="" />
        <Platform Value="None" />
        <RunPlatform Value="None" />
        <FrameworkVersion Value="cocos2d-x-3.10" />
        <IsDebugKeystore Value="True" />
        <KeystorePassword Value="" />
        <KeystoreAliasName Value="" />
        <KeystoreAliasPassword Value="" />
        <EnableSourceMap Value="False" />
        <EnableHTML5Advanced Value="False" />
      </Value>
    </Item>
    <Item Key="TabsParamsKey">
      <Value ctype="TabsInfo">
        <ActiveDocument />
      </Value>
    </Item>
  </Properties>
</UserData>
  1. 打开CocosStudio.exe(这里不能使用Cocos.Launcher.Start导入项目,会提示非法项目),打开「菜单」-「打开项目」,选择.ccs文件。这个时候UI的初始工程已经算完成了。
  2. 导入资源。在CocosStudio里可以导入资源。
  3. 发布资源。选择菜单:项目 - 发布与打包。资源会生成在对应目录下,后面cocos工程就可以访问到了。
  4. vs2022打开proj.win32\NewCocosProject.sln,编译运行。

推荐做法

  • 如果是团队组织,推荐CocosStudio的UI项目与cocos2dx工程项目分开,UI项目完工后发布资源给cocos2dx工程项目即可,将资源放到对应的目录下即可。
  • 如果是个人,UI项目可以跟cocos2dx工程项目是否放一起倒无所谓了。

首次编译

官方配置方式编译

以3.17.2为例说明,该版本编译Java端默认使用的是gradle-4.6 (查看生成的工程里面的配置文件可以得知,\frameworks\runtime-src\proj.android\gradle\wrapper\gradle-wrapper.properties),不过gradle-4.6 也是只能使用Java8编译的。

不要修改电脑的工具配置,直接使用项目frameworks\runtime-src\proj.android根目录下的gradlew工具进行编译。

如果电脑安装了多个Java版本,但默认版本并不是Java8,则可以直接修改gradlew.bat的内容,手动设定下JAVA_HOME

# 增加下面这句
set JAVA_HOME=D:\Java\jdk1.8.0_191

使用如下gradlew命令编译:

# 清理工程
./gradlew clean	

# 编译 Debug 版 APK
./gradlew assembleDebug	

# 编译 Release 版 APK
./gradlew assembleRelease	

# 清理并编译 Debug 版
./gradlew clean assembleDebug	

# 清理并编译 Release 版
./gradlew clean assembleRelease	

# 安装 Debug 版到设备
./gradlew installDebug	

# 生成 .aab 文件(Google Play 用)
./gradlew bundleRelease	

编译完成后,在app\build\outputs\apk目录下生成相应的debugrelease(未签名)的APK,其中debug版本的lua脚本是明文的,release版本的lua脚本被编译为luajit,文件扩展名为.luac。Lua编译工具使用的是tools\cocos2d-console\plugins\plugin_luacompilecocos2d-x-3.17.2使用的 LuaJIT 版本信息为:LuaJIT 2.1.0-beta2 ,GitHub镜像:https://github.com/LuaJIT/LuaJIT

AndroidStudio新版+Gradle新版(推荐)

其实使用新版本的AndroidStudio和gradle也是可以编译的,无非是Java的版本需要高一些,截止目前为止gradle需要Java17。

笔者使用的AndroidStudio版本信息如下:

Android Studio Meerkat | 2024.3.1
Build #AI-243.22562.218.2431.13114758, built on February 25, 2025
Runtime version: 21.0.5+-12932927-b750.29 amd64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.
Toolkit: sun.awt.windows.WToolkit
Windows 11.0
GC: G1 Young Generation, G1 Concurrent GC, G1 Old Generation
Memory: 8192M
Cores: 16
Registry:
  ide.instant.shutdown=false
  ide.experimental.ui=true
  i18n.locale=

配置gradle-wrapper.properties使用新版gradle:

#Mon Mar 24 14:00:20 CST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

build.gradle(project):

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        google()
        mavenCentral()
    }

    dependencies {
//        classpath 'com.android.tools.build:gradle:3.1.0'
        classpath 'com.android.tools.build:gradle:8.9.0'

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

allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

tasks.register('clean', Delete) {
    delete rootProject.buildDir
}

frameworks\runtime-src\proj.android目录下新配置一个local.properties,主要是指定下sdk目录:

## This file is automatically generated by Android Studio.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# 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.
#Wed Dec 06 10:53:55 CST 2017
sdk.dir=D\:\\android\\sdk

下面主要修改build.gradle内容。

增加namespace

配置NDK版本:

// 如果后面so文件通过外部工具进行编译,则该可以不用该配置
// 使用该配置后,可以无须再设置 ndk.dir 配置,使用AndroidStudio的SDKManager下载的NDK版本
android {
	ndkVersion "21.4.7075529"
}

配置lib的cpu架构:

// 可以这样配置,也可以设置 PROP_APP_ABI
android {
    defaultConfig {
        ndk {
            abiFilters = ["arm64-v8a"]
            //abiFilters.addAll(PROP_APP_ABI.split(':').collect { it as String })
        }
    }
}

设置cmake版本(后经测试发现,不设定也可以编译通过):

cmake {
    path "../../../../CMakeLists.txt"
    version "3.10.2"
}

配置res资源文件和lua脚本源码文件的中间目录,这里同时兼容了gradle4.x和8.x:

def mergedAssetsDir = variant.mergeAssetsProvider.get().outputDir.get().asFile
if (gradle.gradleVersion.startsWith("4.")) {
    mergedAssetsDir = layout.buildDirectory.get().asFile.toPath().resolve("intermediates/assets/${variant.dirName}")
}
println "Merged assets path: ${mergedAssetsDir}"

def srcTempDir = new File(mergedAssetsDir, "src").getPath()
def resTempDir = new File(mergedAssetsDir, "res").getPath()

配置cocos.bat为全路径,因为不希望影响全局系统变量:

def getCocosCommandPath() {
    if (OperatingSystem.current().isWindows()) {
        return 'D:\\Cocos\\Cocos2d-x\\cocos2d-x-3.17.2\\tools\\cocos2d-console\\bin\\cocos.bat'
    }

gradle.properties增加:

# 开发阶段设置为 0,表示不编译不加密脚本,提高开发速度,发布阶段设置一下
PROP_COMPILE_SCRIPT = 0
PROP_LUA_ENCRYPT = 0

最后修改一些其他因为迁移到高版本gradle出现的警告提示等,完整版的gradle代码见下。

Gradle完整代码

模板创建的build.gradle太复杂了,简单优化了下:

import org.gradle.internal.os.OperatingSystem

apply plugin: 'com.android.application'

android {
    compileSdkVersion PROP_COMPILE_SDK_VERSION.toInteger()
    namespace 'com.test.mygame'

    ndkVersion "21.4.7075529"

    defaultConfig {
        applicationId "com.test.mygame"
        minSdkVersion PROP_MIN_SDK_VERSION
        targetSdkVersion PROP_TARGET_SDK_VERSION
        versionCode 1
        versionName "1.0"

        externalNativeBuild {
            if (PROP_BUILD_TYPE == 'ndk-build') {
                ndkBuild {
                    targets 'cocos2dlua'
                    arguments 'NDK_TOOLCHAIN_VERSION=clang'
                    arguments '-j' + Runtime.runtime.availableProcessors()

                    def module_paths = [project.file("../../../cocos2d-x").absolutePath,
                                        project.file("../../../cocos2d-x/cocos").absolutePath,
                                        project.file("../../../cocos2d-x/external").absolutePath]
                    if (OperatingSystem.current().isWindows()) {
                        module_paths = module_paths.collect {it.replaceAll('\\\\', '/')}
                        arguments 'NDK_MODULE_PATH=' + module_paths.join(";")
                    }
                    else {
                        arguments 'NDK_MODULE_PATH=' + module_paths.join(':')
                    }
                }
            }
            else if (PROP_BUILD_TYPE == 'cmake') {
                cmake {
                    arguments "-DCMAKE_FIND_ROOT_PATH=", "-DANDROID_STL=c++_static", "-DANDROID_TOOLCHAIN=clang", "-DANDROID_ARM_NEON=TRUE"
                    cppFlags "-frtti -fexceptions -fsigned-char"
                }
            }
        }

        ndk {
            abiFilters = []
            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"
    }

    externalNativeBuild {
        if (PROP_BUILD_TYPE == 'ndk-build') {
            ndkBuild {
                path "jni/Android.mk"
            }
        }
        else if (PROP_BUILD_TYPE == 'cmake') {
            cmake {
                path "../../../../CMakeLists.txt"
				version "3.10.2"
            }
        }
    }

    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 {
            debuggable false
            jniDebuggable false
            renderscriptDebuggable false
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            if (project.hasProperty("RELEASE_STORE_FILE")) {
                signingConfig signingConfigs.release
            }

            externalNativeBuild {
                ndkBuild {
                    arguments 'NDK_DEBUG=0'
                }
            }
        }

        debug {
            debuggable true
            jniDebuggable true
            renderscriptDebuggable true
            externalNativeBuild {
                ndkBuild {
                    arguments 'NDK_DEBUG=1'
                }
            }
        }
    }
}

def getCocosCommandPath() {
    if (OperatingSystem.current().isWindows()) {
        return 'D:\\Cocos\\Cocos2d-x\\cocos2d-x-3.17.2\\tools\\cocos2d-console\\bin\\cocos.bat'
    }
    else {
        // on unix like system, can not get environments variables easily
        // so run a shell script to get environment variable sets by cocos2d-x setup.py
        new ByteArrayOutputStream().withStream { os ->
            def result = exec {
                executable = project.file('get_environment.sh')
                standardOutput = os
            }
            ext.console_path = os.toString().trim()
        }
        return new File(console_path + '/cocos').absolutePath;
    }
}

// a method used to invoke the cocos luaompile command
def compileLua(srcDir, dstDir, doCompile, is64bit, doEncrypt) {
    def compileArgs = ['luacompile', '-s', srcDir, '-d', dstDir]
    if (!doCompile) {
        compileArgs << '--disable-compile'
    } else if (is64bit) {
        compileArgs << '--bytecode-64bit'
    }

    if (doEncrypt) {
        compileArgs << '-e'
        compileArgs << '-k'
        compileArgs << project.property('PROP_LUA_ENCRYPT_KEY')
        compileArgs << '-b'
        compileArgs << project.property('PROP_LUA_ENCRYPT_SIGN')
    }

    // commandLine compileArgs
    println 'running command : ' + 'cocos ' + compileArgs.join(' ')
    exec {
        // if you meet problem, just replace `getCocosCommandPath()` to the path of cocos command
        executable getCocosCommandPath()
        args compileArgs
    }

    // remove the lua files in dstDir
    delete fileTree(dstDir) {
        include '**/*.lua'
    }
}

android.applicationVariants.configureEach { variant ->
    // 删除旧文件
    delete layout.buildDirectory.get().asFile.toPath().resolve("intermediates/assets/${variant.dirName}")

    variant.mergeAssetsProvider.get().doLast {
        println "Current Gradle version: ${gradle.gradleVersion}"

        def mergedAssetsDir = variant.mergeAssetsProvider.get().outputDir.get().asFile
        if (gradle.gradleVersion.startsWith("4.")) {
            mergedAssetsDir = layout.buildDirectory.get().asFile.toPath().resolve("intermediates/assets/${variant.dirName}")
        }
        println "Merged assets path: ${mergedAssetsDir}"

        def srcTempDir = new File(mergedAssetsDir, "src").getPath()
        def resTempDir = new File(mergedAssetsDir, "res").getPath()

        copy {
            def fromPath = layout.projectDirectory.dir("../../../../src").toString()
            println "Copying from: $fromPath -> $srcTempDir"
            from fromPath
            into srcTempDir
        }
        
        copy {
            def fromPath = layout.projectDirectory.dir("../../../../res").toString()
            println "Copying from: $fromPath -> $resTempDir"
            from fromPath
            into resTempDir
        }

        // 处理编译和加密逻辑
        def compileScript = variant.name == 'release'
        if (project.hasProperty('PROP_COMPILE_SCRIPT')) {
            compileScript = (PROP_COMPILE_SCRIPT.compareTo('1') == 0)
        }

        def encryptLua = project.hasProperty('PROP_LUA_ENCRYPT') && (PROP_LUA_ENCRYPT.compareTo('1') == 0)
        if (compileScript || encryptLua) {
            def buildType = -1
            if (compileScript) {
                def need64 = false
                def need32 = false
                android.defaultConfig.ndk.getAbiFilters().each { abi ->
                    if (abi == 'arm64-v8a') {
                        need64 = true
                    } else {
                        need32 = true
                    }
                }

                if (need64 && need32) {
                    buildType = 2
                } else if (need64) {
                    buildType = 1
                } else {
                    buildType = 0
                }
            }

            println "buildType is  ${buildType} "
            switch (buildType) {
                case -1:
                    compileLua(srcTempDir, srcTempDir, false, false, encryptLua)
                    break
                case 0:
                    compileLua(srcTempDir, srcTempDir, true, false, encryptLua)
                    break
                case 1:
                    compileLua(srcTempDir, srcTempDir, true, true, encryptLua)
                    break
                case 2:
                    compileLua(srcTempDir, "${srcTempDir}/64bit", true, true, encryptLua)
                    compileLua(srcTempDir, srcTempDir, true, true, encryptLua)
                    break
            }
        }
    }
}


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

缺失的类

LocalStorage

https://github.com/yangc999/jfclient/tree/master/frameworks/cocos2d-x/cocos/scripting/lua-bindings/manual/localstorage

打开VisualStudio工程,为libluacocos2d项目添加头文件和源文件,然后在 frameworks/cocos2d-x/cocos/scripting/lua-bindings/manual/lua_module_register.cpp 中添加:

#include "scripting/lua-bindings/manual/localstorage/lua_cocos2dx_localstorage_manual.h"

register_localstorage_module(L);

还缺少对Init函数的注册,需要添加:

int lua_cocos2dx_localstorage_Init(lua_State* L) {
	int argc = 0;
#if COCOS2D_DEBUG >= 1
	tolua_Error tolua_err;
	if (!tolua_isusertable(L, 1, "cc.LocalStorage", 0, &tolua_err)) goto tolua_lerror;
#endif

	argc = lua_gettop(L) - 1;
	if (argc == 1) {
#if COCOS2D_DEBUG >= 1
		if (!tolua_isstring(L, 2, 0, &tolua_err))
			goto tolua_lerror;
#endif
		std::string fullpath = tolua_tostring(L, 2, "");
		localStorageInit(fullpath);
		return 0;
	}
	return 0;

#if COCOS2D_DEBUG >= 1
	tolua_lerror:
	tolua_error(L, "#ferror in function 'lua_cocos2dx_localstorage_removeItem'.", &tolua_err);
	return 0;
#endif    
}

// 在 lua_register_cocos2dx_localstorage 函数中添加注册:
tolua_function(L, "init", lua_cocos2dx_localstorage_Init);

并修复lua_cocos2dx_localstorage_getItem一个BUG:

if (localStorageGetItem(key, &value)) {
	lua_pushlstring(L, value.c_str(), value.length());
	return 1;
}

完整代码如下。

//lua_cocos2dx_localstorage_manual.h

#ifndef COCOS_SCRIPTING_LUA_BINDINGS_LUA_COCOS2DX_LOCALSTORAGE_MANUAL_H
#define COCOS_SCRIPTING_LUA_BINDINGS_LUA_COCOS2DX_LOCALSTORAGE_MANUAL_H

#ifdef __cplusplus
extern "C" {
#endif
#include "tolua++.h"
#ifdef __cplusplus
}
#endif

TOLUA_API int register_localstorage_module(lua_State* L);

#endif // #ifndef COCOS_SCRIPTING_LUA_BINDINGS_LUA_COCOS2DX_CONTROLLER_MANUAL_H
//lua_cocos2dx_localstorage_manual.cpp

#include "scripting/lua-bindings/manual/localstorage/lua_cocos2dx_localstorage_manual.h"
#include "scripting/lua-bindings/manual/tolua_fix.h"
#include "scripting/lua-bindings/manual/LuaBasicConversions.h"
#include "storage/local-storage/LocalStorage.h"

int lua_cocos2dx_localstorage_setItem(lua_State* L)
{
	int argc = 0;
#if COCOS2D_DEBUG >= 1
    tolua_Error tolua_err;
    if (!tolua_isusertable(L,1,"cc.LocalStorage",0,&tolua_err)) goto tolua_lerror;
#endif

	argc = lua_gettop(L) - 1;
	if (argc == 2)
	{
#if COCOS2D_DEBUG >= 1
		if (!tolua_isstring(L, 2, 0, &tolua_err) ||
			!tolua_isstring(L, 3, 0, &tolua_err))
			goto tolua_lerror;
#endif
		std::string key = tolua_tostring(L, 2, "");
		//std::string value = tolua_tostring(L, 3, "");
		size_t size = 0;
		const char* data = (const char*)lua_tolstring(L, 3, &size);
		std::string value(data, size);
		localStorageSetItem(key, value);
		return 0;
	}
    return 0;

#if COCOS2D_DEBUG >= 1
tolua_lerror:
    tolua_error(L,"#ferror in function 'lua_cocos2dx_localstorage_setItem'.",&tolua_err);
    return 0;
#endif  
}

int lua_cocos2dx_localstorage_getItem(lua_State* L)
{
	int argc = 0;
#if COCOS2D_DEBUG >= 1
    tolua_Error tolua_err;
    if (!tolua_isusertable(L,1,"cc.LocalStorage",0,&tolua_err)) goto tolua_lerror;
#endif

	argc = lua_gettop(L) - 1;
	if (argc == 1)
	{
#if COCOS2D_DEBUG >= 1
		if (!tolua_isstring(L, 2, 0, &tolua_err))
			goto tolua_lerror;
#endif
		std::string key = tolua_tostring(L, 2, "");
		std::string value;
		if (localStorageGetItem(key, &value)) {
			lua_pushlstring(L, value.c_str(), value.length());
			return 1;
		}
	}
	return 0;

#if COCOS2D_DEBUG >= 1
tolua_lerror:
    tolua_error(L,"#ferror in function 'lua_cocos2dx_localstorage_getItem'.",&tolua_err);
    return 0;
#endif   
}

int lua_cocos2dx_localstorage_removeItem(lua_State* L)
{
	int argc = 0;
#if COCOS2D_DEBUG >= 1
    tolua_Error tolua_err;
    if (!tolua_isusertable(L,1,"cc.LocalStorage",0,&tolua_err)) goto tolua_lerror;
#endif

	argc = lua_gettop(L) - 1;
	if (argc == 1)
	{
#if COCOS2D_DEBUG >= 1
		if (!tolua_isstring(L, 2, 0, &tolua_err))
			goto tolua_lerror;
#endif
		std::string key = tolua_tostring(L, 2, "");
		localStorageRemoveItem(key);
		return 0;
	}
	return 0;

#if COCOS2D_DEBUG >= 1
tolua_lerror:
    tolua_error(L,"#ferror in function 'lua_cocos2dx_localstorage_removeItem'.",&tolua_err);
    return 0;
#endif    
}

int lua_cocos2dx_localstorage_clear(lua_State* L)
{
	int argc = 0;
#if COCOS2D_DEBUG >= 1
	tolua_Error tolua_err;
	if (!tolua_isusertable(L, 1, "cc.LocalStorage", 0, &tolua_err)) goto tolua_lerror;
#endif
	
	argc = lua_gettop(L) - 1;
	if (argc == 0)
	{
		localStorageClear();
		return 0;
	}
	return 0;

#if COCOS2D_DEBUG >= 1
tolua_lerror:
	tolua_error(L, "#ferror in function 'lua_cocos2dx_localstorage_removeItem'.", &tolua_err);
	return 0;
#endif    
}

int lua_cocos2dx_localstorage_Init(lua_State* L) {
	int argc = 0;
#if COCOS2D_DEBUG >= 1
	tolua_Error tolua_err;
	if (!tolua_isusertable(L, 1, "cc.LocalStorage", 0, &tolua_err)) goto tolua_lerror;
#endif

	argc = lua_gettop(L) - 1;
	if (argc == 1) {
#if COCOS2D_DEBUG >= 1
		if (!tolua_isstring(L, 2, 0, &tolua_err))
			goto tolua_lerror;
#endif
		std::string fullpath = tolua_tostring(L, 2, "");
		localStorageInit(fullpath);
		return 0;
	}
	return 0;

#if COCOS2D_DEBUG >= 1
	tolua_lerror:
	tolua_error(L, "#ferror in function 'lua_cocos2dx_localstorage_removeItem'.", &tolua_err);
	return 0;
#endif    
}

int lua_register_cocos2dx_localstorage(lua_State* L)
{
    tolua_usertype(L,"cc.LocalStorage");
    tolua_cclass(L,"LocalStorage","cc.LocalStorage","",nullptr);

    tolua_beginmodule(L,"LocalStorage");
		tolua_function(L, "init", lua_cocos2dx_localstorage_Init);
		tolua_function(L, "setItem", lua_cocos2dx_localstorage_setItem);
        tolua_function(L, "getItem", lua_cocos2dx_localstorage_getItem);
        tolua_function(L, "removeItem", lua_cocos2dx_localstorage_removeItem);
		tolua_function(L, "clear", lua_cocos2dx_localstorage_clear);
    tolua_endmodule(L);
    std::string typeName = "localStorage";
    g_luaType[typeName] = "cc.LocalStorage";
    g_typeCast["LocalStorage"] = "cc.LocalStorage";
    return 1;
}

int register_all_cocos2dx_localstorage(lua_State* L)
{
    tolua_open(L);
    
    tolua_module(L,"cc",0);
    tolua_beginmodule(L,"cc");

    lua_register_cocos2dx_localstorage(L);

    tolua_endmodule(L);
    return 1;
}

int register_localstorage_module(lua_State* L)
{
    lua_getglobal(L, "_G");
    if (lua_istable(L,-1))//stack:...,_G,
    {
        register_all_cocos2dx_localstorage(L);
    }
    lua_pop(L, 1);
    return 1;
}

还要为Android工程添加这俩文件,步骤如下:

找到文件 /frameworks/cocos2d-x/cocos/scripting/lua-bindings/CMakeLists.txt 添加上述的头文件和源文件:

set(
	...
    manual/localstorage/lua_cocos2dx_localstorage_manual.h
    )

set(
	...
    manual/localstorage/lua_cocos2dx_localstorage_manual.cpp
    )

com.enhance.gameservice

IGameTuningService.java 将这个文件下载下来配置到对应目录下即可,备份一下:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /Users/junaid/Documents/IphoneX/cocos2dx/Cocos2d-x-3.6/cocos2d-x/Countly/CountlyX/cocos2d/cocos/platform/android/java/src/com/enhance/gameservice/IGameTuningService.aidl
 */
package com.enhance.gameservice;
public interface IGameTuningService extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.enhance.gameservice.IGameTuningService {
        private static final java.lang.String DESCRIPTOR = "com.enhance.gameservice.IGameTuningService";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.enhance.gameservice.IGameTuningService interface,
         * generating a proxy if needed.
         */
        public static com.enhance.gameservice.IGameTuningService asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.enhance.gameservice.IGameTuningService))) {
                return ((com.enhance.gameservice.IGameTuningService) iin);
            }
            return new com.enhance.gameservice.IGameTuningService.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_setPreferredResolution: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    int _result = this.setPreferredResolution(_arg0);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
                case TRANSACTION_setFramePerSecond: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    int _result = this.setFramePerSecond(_arg0);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
                case TRANSACTION_boostUp: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    int _result = this.boostUp(_arg0);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
                case TRANSACTION_getAbstractTemperature: {
                    data.enforceInterface(DESCRIPTOR);
                    int _result = this.getAbstractTemperature();
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
                case TRANSACTION_setGamePowerSaving: {
                    data.enforceInterface(DESCRIPTOR);
                    boolean _arg0;
                    _arg0 = (0 != data.readInt());
                    int _result = this.setGamePowerSaving(_arg0);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.enhance.gameservice.IGameTuningService {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public int setPreferredResolution(int resolution) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(resolution);
                    mRemote.transact(Stub.TRANSACTION_setPreferredResolution, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public int setFramePerSecond(int fps) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(fps);
                    mRemote.transact(Stub.TRANSACTION_setFramePerSecond, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public int boostUp(int seconds) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(seconds);
                    mRemote.transact(Stub.TRANSACTION_boostUp, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public int getAbstractTemperature() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getAbstractTemperature, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public int setGamePowerSaving(boolean enable) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(((enable) ? (1) : (0)));
                    mRemote.transact(Stub.TRANSACTION_setGamePowerSaving, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_setPreferredResolution = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_setFramePerSecond = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        static final int TRANSACTION_boostUp = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
        static final int TRANSACTION_getAbstractTemperature = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
        static final int TRANSACTION_setGamePowerSaving = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);
    }

    public int setPreferredResolution(int resolution) throws android.os.RemoteException;

    public int setFramePerSecond(int fps) throws android.os.RemoteException;

    public int boostUp(int seconds) throws android.os.RemoteException;

    public int getAbstractTemperature() throws android.os.RemoteException;

    public int setGamePowerSaving(boolean enable) throws android.os.RemoteException;
}

常见错误

Invalid revision: 3.18.1-g262b901-dirty

解决:这个是因为使用了较高版本CMake导致,手动修改使用较低版本即可,步骤如下:

  1. 打开AndroidStudio菜单的Tools - SDK Manager,选择 SDK Tools , 勾选「Show Package Details」,向下滑动一直找到CMake,把CMake高版本的都去掉,只保留一个3.6版本的。
  2. 这个时候可能还不行,还需要删除安卓SDK目录(例如:D:\Android\Sdk)下的临时文件夹(.temp),清除缓存后才能生效。
gradle  Task :Demo:lint FAILED

解决:这个是因为编译的时候把lint警告也当做错误,从而终止了编译。解决办法就是不把警告当错误即可,在app/build.gradle 文件中添加配置:

android {
    //...
    lintOptions {
        abortOnError false
    }
}

参考: Gradle build: Execution failed for task :app:lint

错误:cvc-complex-type.2.4.a: 发现了以元素 'base-extension' 开头的无效内容。应以 '{layoutlib}' 之一开头。
解决:升级Gradle。
Unable to start the daemon process.

The project uses Gradle 4.6 which is incompatible with Java 11 or newer.

Possible solution:
 - Upgrade Gradle wrapper to 7.2 version and re-import the project


// 解决办法:settings搜索gradle,修改gradle sdk版本为1.8
No version of NDK matched the requested version 20.0.5594570. Versions available locally: 23.1.7779620, 25.1.8937393, 25.2.9519653, 26.1.10909125

// 解决办法:local.properties添加:
ndk.dir=D\:\\NDK\\android-ndk-r19c


// 新版本在gradle里配置:
android {
    ndkVersion = "19.2.5345600" //"major.minor.build"
}
ld: error: lib/libluacocos2d.a(CCLuaEngine.cpp.o): unable to find library from dependent library specifier: lua51.lib

// 解决办法: 
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
#pragma comment(lib,"lua51.lib")
#endif

Cocos 4.0新建lua项目安卓打包报错,CCLuaEngine,lua51.lib

API 'variant.getMergeAssets()' is obsolete and has been replaced with 'variant.getMergeAssetsProvider()'.

// 解决办法:修改build.gradle,将:

variant.mergeAssets.doLast {
}
    
// 修改为:
    
variant.mergeAssetsProvider.get().doLast {
}
This app only has 32-bit [armeabi-v7a] native libraries. Beginning August 1, 2019 Google Play store requires that all apps that include native libraries must provide 64-bit versions. For more information, visit https://g.co/64-bit-requirement
Affected Modules: Bricks

// 解决办法:修改gradle.properties
// PROP_APP_ABI=armeabi-v7a
PROP_APP_ABI=arm64-v8a

进阶技巧

剥离libcocos2dx模块

移除jni工程,因为AndroidStudio加载cpp太卡了,修改调试c++代码还是用VisualStudio比较丝滑。直接注释下面的代码即可:

// 临时注释:工程不再包含cpp项目,否则太卡了
//    externalNativeBuild {
//        if (project.hasProperty('PROP_REBUILD_NDK') && PROP_REBUILD_NDK=='true') {
//            if (PROP_BUILD_TYPE == 'ndk-build') {
//                ndkBuild {
//                    path "jni/Android.mk"
//                }
//            } else if (PROP_BUILD_TYPE == 'cmake') {
//                cmake {
//                    path "../../../../CMakeLists.txt"
//                }
//            }
//        }
//    }

为了灵活期间,可增加一个配置开关来控制(build.gradle):

// 通过修改 gradle.properties 中的 PROP_BUILD_NDK 开关(true/false),来决定是否导入cocos的jni工程
externalNativeBuild {
    if (project.hasProperty('PROP_BUILD_NDK') && PROP_BUILD_NDK=='true') {
        if (PROP_BUILD_TYPE == 'ndk-build') {
            ndkBuild {
                path "jni/Android.mk"
            }
        } else if (PROP_BUILD_TYPE == 'cmake') {
            cmake {
                path "../../../../CMakeLists.txt"
            }
        }
    }
}

gradle.properties 文件中增加一个 PROP_BUILD_NDK 默认设置为false即可:

PROP_REBUILD_NDK=true

# uncomment it and fill in sign information for release mode
RELEASE_STORE_FILE=../sign.keystore
RELEASE_STORE_PASSWORD=xxxxx
RELEASE_KEY_ALIAS=xxx
RELEASE_KEY_PASSWORD=xxxxx

##

单独编译SO

单独编译so库,这样后面在AndroidStudio里可以直接使用库文件,可以大大降低对电脑性能的依赖。

以3.17.2为例说明,该版本编译lib库文件所需的cmake版本是3.6,ndk是16,但是稍微高点也没关系,如下就是使用的NDK19.2.5345600(NDK21.4.7075529也可以)以及CMake3.10.2.4988404进行编译的。

最好通过手动指定目录的方式来去编译,这样不影响电脑的整体环境设置,也就不会影响其他项目的编译,也就是本着谁的项目谁自己管理自己所需的工具。

BuilSO.bat

@REM set abi=armeabi-v7a
set abi=arm64-v8a
set ANDROID_SDK_HOME=D:/Android/Sdk
set ANDROID_NDK_HOME=D:/Android/Sdk/ndk/19.2.5345600
set CMAKE_VERSION=3.10.2.4988404
set CMAKE=%ANDROID_SDK_HOME%/cmake/%CMAKE_VERSION%/bin/cmake
set NINJA=%ANDROID_SDK_HOME%/cmake/%CMAKE_VERSION%/bin/ninja

if not exist %abi% md %abi%
cd %abi%

%CMAKE% ^
  -DANDROID_ABI=%abi% ^
  -DANDROID_NDK=%ANDROID_NDK_HOME% ^
@REM  -DCMAKE_BUILD_TYPE=Debug ^
  -DCMAKE_BUILD_TYPE=Release^
  -DCMAKE_TOOLCHAIN_FILE=%ANDROID_NDK_HOME%/build/cmake/android.toolchain.cmake ^
  -DANDROID_NATIVE_API_LEVEL=16 ^
  -DANDROID_STL=c++_static ^
  -DANDROID_TOOLCHAIN=clang -DCMAKE_GENERATOR="Ninja" ^
  -DCMAKE_MAKE_PROGRAM=%NINJA% ^
  ..

%NINJA%

cd ..

把该批处理文件放置在和CMakeLists.txt 同级目录下,运行后会创建一个临时目录,目录下会有生成的so文件,复制出来使用即可。为了优化so的编译体积,可以在CMakeLists.txt 中增加如下设置:

# 优化编译的so体积
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Os -Wall -s")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Os -Wall -s")

##

日常开发

  • 文件目录结构组织如下:
WinAppRun.bat 	 # 运行Win版可执行程序
libs			# 存放lib文件的
	arm64-v8a
		libcocos2dlua.so
frameworks
res				# 游戏资源目录
src				# 游戏脚本目录
runtime
simulator
.cocos-project.json
.project
CMakeLists.txt
config.json
README.md
UserDefault.xml
  • 维持cocos2dx的命令行创建的项目工程目录不变,需要做的主要是修改目录ressrc下的文件。

  • 使用CocosStudio设计的UI素材发布后直接复制到res目录下即可。

  • src下就是编辑脚本代码。

  • 在Windows上使用VisualStudio编译的可执行程序运行,方便直接查看运行效果,效率会高很多,一旦将要发布的时候再进行编译打包。:warning: 注意:有些游戏是竖屏玩法,这个时候需要修改项目根目录下的配置文件:config.json, 将 isLandscape 修改为 false (默认是true)。

  • 运行测试:在项目的根目录下(res所在目录)创建一个WinAppRun.bat,日常测试就运行它就可以了,非常方便。

    call .\simulator\win32\AppName.exe
    
  • 如果有少量的代码需要编写Java代码时,可以用AndroidStudio打开项目进行Java代码的编写。如果是C++代码,仍然建议使用VisualStudio进行编写,效率会高很多。

打包发布

:warning:需要注意的是,发布阶段需要对源码进行编译加密,这个时候会用到cocos.bat,会用到Python,需要先切换下Python的版本为2.7:

pyenv local  2.7.18

Windows

VisualStudio直接编译,也可以使用VisualStudio的命令行编译。

  • 可以稍微修改下代码,让_defaultResRootPaths_resourcePath在release模式下分别为当前运行目录、Resources
  • 复制res和src到Resources目录下即可。

Android

1、命令行(推荐)

直接进入项目目录下,使用

如果需要使用不同的gradle版本,修改frameworks\runtime-src\proj.android\gradle\wrapper\gradle-wrapper.properties即可,例如:

distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.11.1-bin.zip

打开命令行窗口,先设置下Python版本为2.7:

pyenv local  2.7.18

然后继续执行命令:

.\gradlew assembleRelease

如果需要设定Java版本时,直接修改gradlew.bat的内容,手动设定下JAVA_HOME

# 增加下面这句
set JAVA_HOME=D:\Java\jdk1.8.0_191

# 或
set JAVA_HOME=D:\Java\jdk-17.0.13+11

2、AndroidStudio

略。

三方SDK

SDK 功能作用简介 官方链接
Google Play Services 提供了广泛的 Google API 功能,包括广告服务、地图、身份验证等。 Google Play Services SDK
Facebook 提供了 Facebook 社交平台集成,包括登录、分享、分析等功能。 Facebook SDK for Android
Facebook Audience Network 将 Audience Network SDK 添加到您的 Android 应用  
AppLovin 提供了移动广告解决方案,包括广告展示、收益最大化等功能。others-AppLovin广告接入_applovin 拉取applovin官方广告 AppLovin SDK
Unity Ads 提供了 Unity 游戏开发平台的广告服务,用于在游戏中展示广告并获取收益。 Unity Ads SDK
Firebase 提供了一系列的移动应用开发工具,包括分析、远程配置、消息推送等功能,全面支持应用开发和增长。将 Firebase 添加到您的 Android 项目 Firebase for Android Firebase SDK
appsflyer    
  使用入门:在 Android 项目中使用 AdMob Firebase with Google AdMob  

参考资料

文档信息

Search

    Table of Contents

    目录