古法编程:cocos2dx开发微信小游戏,聊聊踩过的坑

0 阅读3分钟

一、环境准备

1. Cocos2d-x

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开发的微信小游戏效果

image.png

1.jpg