一、环境准备
1. Cocos2d-x
-
版本:3.17.2
2. Emscripten SDK (emsdk)
用于将 C++ 编译为 WASM + JS 使用版本:3.1.10
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
# 安装并激活指定版本
./emsdk install 3.1.10
./emsdk activate 3.1.10
3. H5 游戏包转微信小游戏包工具
工具地址:developers.weixin.qq.com/minigame/de… 页面向下滚动即可找到对应转换工具。
4. Cocos2d-x 常见问题官方文档
developers.weixin.qq.com/minigame/de…
二、踩坑记录
2024 年底开始尝试将 Cocos2d-x 游戏转为微信小游戏包,因业余时间有限,历时一年多完成。以下为实际遇到的问题与修改方案。
1. 可写目录权限问题
// 修改前
bool FileUtilsEmscripten::init()
{
_defaultResRootPath = "/";
return FileUtils::init();
}
// 修改后
bool FileUtilsEmscripten::init()
{
_defaultResRootPath = "/";
auto pDir = getWritablePath();
if(0 != access(pDir.c_str(), 0)) {
mkdir(pDir.c_str(), 0777);
}
return FileUtils::init();
}
2. 去掉引擎中的 CCASSERT
iOS 平台触发 CCASSERT 会导致崩溃,直接注释相关断言。
void Node::addChild(Node *child, int localZOrder, int tag)
{
//CCASSERT( child != nullptr, "Argument must be non-nil");
//CCASSERT( child->_parent == nullptr, "child already added. It can't be added again");
addChildHelper(child, localZOrder, tag, "", true);
}
void Node::addChild(Node* child, int localZOrder, const std::string &name)
{
// CCASSERT(child != nullptr, "Argument must be non-nil");
// CCASSERT(child->_parent == nullptr, "child already added. It can't be added again");
addChildHelper(child, localZOrder, INVALID_TAG, name, false);
}
3. Spine 版本兼容问题
项目使用较老 Spine 版本,需将引擎内 Spine 替换为 2.x; 若项目本身使用 3.x 则无需替换。
4. 界面变形问题
由默认固定宽高导致,改为由 JS 传入实际屏幕宽高。
// 修改前
GLViewImpl* GLViewImpl::create(const std::string& viewName, bool resizable)
{
auto ret = new (std::nothrow) GLViewImpl;
if(ret && ret->initWithRect(viewName, Rect(0, 0, 960, 640), 1.0f, resizable)) {
ret->autorelease();
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}
// 修改后,s_width 和 s_height 由 JS 传入
/*
extern "C" {
EMSCRIPTEN_KEEPALIVE
void cocos_set_window_size( int width, int height)
{
s_width = width;
s_height = height;
}
}
*/
GLViewImpl* GLViewImpl::create(const std::string& viewName, bool resizable)
{
auto ret = new (std::nothrow) GLViewImpl;
if(ret && ret->initWithRect(viewName, Rect(0, 0, s_width, s_height), 1.0f, resizable)) {
ret->autorelease();
return ret;
}
CC_SAFE_DELETE(ret);
return nullptr;
}
5. 声音问题
改用 JS 播放音频,C++ 仅将声音路径传递给 JS 层,由 JS 实现播放逻辑。
6. 多触摸问题
JS 层接收触摸事件,透传给 C++ 处理。
JS 层触摸转发
var Module = WXGameKit.gameInstance.Module || (GameGlobal.Module = GameGlobal.Module || {});
const touchHandler = {
onTouchStart: function(event) {
event.touches.forEach(function(touch){
let percentX = touch.clientX / window.innerWidth;
let percentY = 1.0 - touch.clientY / window.innerHeight;
Module._cocos_onTouchStart(touch.identifier, percentX, percentY);
});
},
onTouchMove: function(event) {
event.touches.forEach(function(touch){
let percentX = touch.clientX / window.innerWidth;
let percentY = 1.0 - touch.clientY / window.innerHeight;
Module._cocos_onTouchMove(touch.identifier, percentX, percentY);
});
},
onTouchEnd: function(event) {
event.changedTouches.forEach(function(touch){
let percentX = touch.clientX / window.innerWidth;
let percentY = 1.0 - touch.clientY / window.innerHeight;
Module._cocos_onTouchEnd(touch.identifier, percentX, percentY);
});
},
onTouchCancel: function(event) {
Module._cocos_onTouchCancel(0, 0, 0);
},
};
wx.onTouchStart(touchHandler.onTouchStart.bind(touchHandler));
wx.onTouchMove(touchHandler.onTouchMove.bind(touchHandler));
wx.onTouchEnd(touchHandler.onTouchEnd.bind(touchHandler));
wx.onTouchCancel(touchHandler.onTouchCancel.bind(touchHandler));
C++ 层导出函数
extern "C" {
EMSCRIPTEN_KEEPALIVE
void cocos_onTouchStart(int touchId, float x, float y)
{
}
EMSCRIPTEN_KEEPALIVE
void cocos_onTouchMove(int touchId, float x, float y)
{
}
EMSCRIPTEN_KEEPALIVE
void cocos_onTouchEnd(int touchId, float x, float y)
{
}
EMSCRIPTEN_KEEPALIVE
void cocos_onTouchCancel(int touchId, float x, float y)
{
}
}
7. Tilemap 路径问题
// 修改前
void TMXMapInfo::internalInit(const std::string& tmxFileName, const std::string& resourcePath)
{
if (!tmxFileName.empty())
{
_TMXFileName = FileUtils::getInstance()->fullPathForFilename(tmxFileName);
}
if (!resourcePath.empty())
{
_resources = resourcePath;
}
_objectGroups.reserve(4);
// tmp vars
_currentString = "";
_storingCharacters = false;
_layerAttribs = TMXLayerAttribNone;
_parentElement = TMXPropertyNone;
_currentFirstGID = -1;
}
// 修改后
void TMXMapInfo::internalInit(const std::string& tmxFileName, const std::string& resourcePath)
{
if (!tmxFileName.empty())
{
if (tmxFileName.find_last_of("/") != string::npos)
{
string dir = tmxFileName.substr(0, tmxFileName.find_last_of("/") + 1);
_relativeTMXFileDir = dir;
}
_TMXFileName = FileUtils::getInstance()->fullPathForFilename(tmxFileName);
}
if (!resourcePath.empty())
{
_resources = resourcePath;
}
_objectGroups.reserve(4);
// tmp vars
_currentString = "";
_storingCharacters = false;
_layerAttribs = TMXLayerAttribNone;
_parentElement = TMXPropertyNone;
_currentFirstGID = -1;
}
// 修改前
else if (elementName == "image")
{
TMXTilesetInfo* tileset = tmxMapInfo->getTilesets().back();
// build full path
std::string imagename = attributeDict["source"].asString();
tileset->_originSourceImage = imagename;
if (!_externalTilesetFullPath.empty())
{
string dir = _externalTilesetFullPath.substr(0, _externalTilesetFullPath.find_last_of('/') + 1);
tileset->_sourceImage = dir + imagename;
}
else if (_TMXFileName.find_last_of('/') != string::npos)
{
string dir = _TMXFileName.substr(0, _TMXFileName.find_last_of('/') + 1);
tileset->_sourceImage = dir + imagename;
}
else
{
tileset->_sourceImage = _resources + (!_resources.empty() ? "/" : "") + imagename;
}
}
// 修改后
else if (elementName == "image")
{
TMXTilesetInfo* tileset = tmxMapInfo->getTilesets().back();
// build full path
std::string imagename = attributeDict["source"].asString();
tileset->_originSourceImage = imagename;
if (!_externalTilesetFullPath.empty())
{
string dir = _externalTilesetFullPath.substr(0, _externalTilesetFullPath.find_last_of('/') + 1);
tileset->_sourceImage = dir + imagename;
}
else if (_TMXFileName.find_last_of('/') != string::npos)
{
string dir = _relativeTMXFileDir;
tileset->_sourceImage = dir + imagename;
}
else
{
tileset->_sourceImage = _resources + (!_resources.empty() ? "/" : "") + imagename;
}
}
8. 可写路径持久化问题
默认可写路径重启后丢失,修改自定义可写路径。
// 修改前
string FileUtilsEmscripten::getWritablePath() const
{
return "/cocos2dxWritablePath/";
}
// 修改后
string FileUtilsEmscripten::getWritablePath() const
{
return "/CustomWritablePath/";
}
游戏效果
感兴趣的同学,可以看看2dx开发的微信小游戏效果