【cocos2dx】【iOS工程】如何保存用户在游戏内的绘画数据,并将数据以图像形式展示在预览界面

【cocos2dx】【iOS工程】如何保存用户在应用内的操作数据,并将数据以图像形式展示在预览界面

设备/引擎:Mac(11.6)/Mac Mini

开发工具:Xcode(15.0.1)

开发需求:如何保存用户在应用内的操作数据,并将数据以图像形式展示在预览界面

又到了总结的时候了,之前做过一个涂色类的项目,其中有个技术难点就是怎么保存用户每次的绘画数据,并在预览界面展示用户之前的绘画内容。这几天闲下来就整理整理。

思路:
将用户的绘画数据存储到动态数组中——>每次结束游戏时,遍历动态数组中的数据并将数据存储为一个二进制文件——>用户重新开始游戏时,从保存的二进制文件中加载图像,再用该图像初始化一个CCTexture2D对象,再用该纹理对象创建一个新的精灵,最后将精灵显示在场景中。

简单说就是两步,首先保存好数据,最后将数据提出再展示出来
《获取用户的涂画数据》
根据项目的要求,用户只能在场景内的指定区域来涂色,比如画板上、动物的各部位色块上,为了实现只在指定的区域进行涂色,我们使用了自定义的裁剪节点ColoringClippingNode(CCClippingNode类型)。具体如下:
1.创建背景画布

CCSprite* stencilCanvas = CCSprite::create("DinoColor/canvas.png");
stencilCanvas->setAnchorPoint(ccp(0.0, 0.0));
stencilCanvas->setPosition(CCPointZero);

用来展示用户将要涂色的图像或场景,这个也是基础的背景画布。
2.创建裁剪节点

ColoringClippingNode* clip = ColoringClippingNode::create(stencilCanvas);
clip->setContentSize(CCSizeMake(stencilCanvas->getContentSize().width, stencilCanvas->getContentSize().height));
clip->setAlphaThreshold(0.0f);
clip->setAnchorPoint(ccp(0.5, 0.5));
clip->setPosition(ccp(stencilCanvas->getContentSize().width/2, stencilCanvas->getContentSize().height/2));

以 stencilCanvas 为裁剪模板,设置裁剪节点的大小,设置裁剪的透明度阈值,设置裁剪节点的锚点和位置。
3.用户实际的涂色操作
whiteCanvas为自定义的CCSprite类型的ColorSprite类的实例化对象

whiteCanvas = ColorSprite::CreateColor("DinoColor/canvas.png", ccp(stencilCanvas->getContentSize().width/2, stencilCanvas->getContentSize().height/2), this, m_DrawArray->count());
whiteCanvas->curSprName = "ColoringType_"+std::to_string(ColorManager::shared()->curColorTheme+1)+"_"+std::to_string(ColorManager::shared()->colorAniIndex)+"canvas";
whiteCanvas->showLastSceneImage();
whiteCanvas->initBrushNode();
whiteCanvas->setTag(20);

上述代码依次为:创建画布->设置当前涂色画布的名称(方便后续保存提取对应的数据)->根据保存的涂色数据显示上次的涂色数据->初始化涂色使用的画笔节点->设置一个tag值,方便后续获取。
4.将节点添加到场景中

this->addChild(clip);
m_ClipDrawArray->addObject(clip);
clip->addChild(whiteCanvas);

将裁剪节点添加到当前场景中,将裁剪节点添加到管理裁剪节点的动态数组中,将用户实际的涂色画布作为裁剪节点的子节点,确保涂色操作被裁剪到stencilCanvas指定的区域内。
以上通过使用背景画布、涂色画布和裁剪节点,就可以实现一个用户在指定区域内进行涂色操作的功能。

《保存数据》
我们查了一些iOS工程保存数据内容的方法,最后还是决定用二进制形式(.bin格式)来保存用户的绘画数据。先看保存部分的代码

1.创建渲染对象

CCSize sprSize = _colorSpr->getContentSize();
CCRenderTexture* saverenderTexture = CCRenderTexture::create(sprSize.width, sprSize.height, kCCTexture2DPixelFormat_RGBA8888);

_colorSpr就是传进来的用户绘画内容对象,为什么要将该精灵渲染到CCRenderTexture中,简单说就是为了将用户绘画内容绘制到一个纹理上,以便后续将其保存为图像数据。这个过程类似于在一个虚拟的画布上绘制 _colorSpr,而不是直接在屏幕上显示。具体原因如下:
1)离屏渲染:CCRenderTexture允许在内存中创建一个虚拟的渲染目标,而不是直接显示在屏幕上。通过离屏渲染,可以在不影响屏幕显示的情况下,捕捉和处理精灵的图像内容,更隐蔽更安全更方便。
2)捕捉精灵状态:在游戏中,当我们需要保存当前精灵的状态,就像现在要保存用户的绘画、涂色数据等操作时,将精灵渲染到 CCRenderTexture 中,可以将当前获取的数据内容保存为一个完整的图像数据,方便后续使用和存储。
3)保存为图像文件:一旦将 _colorSpr 的渲染结果存储在 CCRenderTexture 中,接下来就可以将其保存为图像文件(.bin 文件)。这种方式可以将精灵的图像数据永久化存储到文件系统中,以便将来读取、恢复或分享给其他用户。

2.开始渲染并绘制内容

saverenderTexture->begin();	//开始将渲染目标
_colorSpr->visit();			//调用_colorSpr的visit()方法,用于渲染精灵对象到saverenderTexture上
saverenderTexture->end();	//结束渲染

这部分比较简单不再赘述。

3.保存为图像文件

std::string localPath = CCFileUtils::sharedFileUtils()->getWritablePath() + _fileName + ".bin";
CCImage* saveImage = saverenderTexture->newCCImage();
saveImage->saveToFile(localPath.c_str());
saveImage->release();

localPath 是保存文件的本地路径,使用可写入路径加上_fileName(前面自定义的文件名称)加上.bin 扩展名;
saverenderTexture->newCCImage(); 将 saverenderTexture 转换为CCImage对象;
saveImage->saveToFile(localPath.c_str()); 将CCImage对象保存为二进制文件;
saveImage->release(); 释放CCImage对象,避免内存泄漏。
整段内容总结为:将 _colorSpr的渲染内容捕捉并保存为二进制文件
1)为什么要保存为.bin格式
.bin 格式通常是为了将数据以二进制形式存储到文件中,他也不是指定格式,你可以用它来存储图像、音频、视频、数据结构、存档或配置文件、数据库文件、自定义的一些数据格式等等。
2)以此方式存储数据的好处
二进制存储:.bin 文件以二进制形式存储数据,相比文本文件,可以更有效地存储和读取数据。对于像素数据、图像数据等大量的二进制信息,使用二进制格式可以更节省存储空间和提高读写效率。
数据完整性:二进制文件保存数据时,可以直接以字节流形式写入数据,不需要转换为可打印字符(如文本文件)。这样就可以确保数据在存储和读取过程中的完整性,特别是对于图像、音频等复杂数据结构。
适合图像数据:在游戏开发中,如保存精灵的图像状态或游戏中的地图数据或者是绘画内容数据等等,二进制格式通常更为适合。这些数据通常是复杂的结构化数据,直接以二进制形式存储可以减少数据解析和转换的复杂性。

《获取保存的数据》
获取数据简单说就是从指定的**.bin**文件中加载图像数据,并返回一个CCImage对象,然后再在游戏中进一步处理CCImage对象并显示出来。
1.从指定的.bin文件中加载图像数据
1)构建文件路径:

std::string fullPath = CCFileUtils::sharedFileUtils()->getWritablePath() + _fileName + ".bin";

不再赘述
2)打开文件

FILE* file = fopen(fullPath.c_str(), "rb");
if (!file) {
    // Handle error
    return nullptr;
}

使用fopen函数以二进制只读模式 (“rb”) 打开文件。如果文件打开失败 (file为nullptr),则返回 nullptr,表示加载失败。
3)获取文件大小

fseek(file, 0, SEEK_END);
long fileSize = ftell(file);
fseek(file, 0, SEEK_SET);

使用fseek和ftell函数来获取文件大小。首先将文件指针移动到文件末尾 (SEEK_END),然后使用 ftell 获取当前文件指针的位置,即文件大小。 一旦获取了文件大小,通常需要将文件指针重新定位到文件的开头,以便进一步读取文件内容或者其他操作,也就是最后一行将文件指针移回文件开头 (SEEK_SET)。
注:获取文件大小是为了在读取文件内容之前,知道文件有多大,以便分配足够大小的内存缓冲区来存储文件内容
4)分配内存并读取文件内容

char* buffer = new char[fileSize];
size_t bytesRead = fread(buffer, 1, fileSize, file);

根据文件大小fileSize分配一个足够大的缓冲区buffer,用于存储文件内容。使用fread函数从打开的文件中读取数据,将文件内容读取到buffer中。
5)创建 CCImage 对象

CCImage* image = new CCImage();
if (!image->initWithImageData(buffer, static_cast<int>(bytesRead))) {
    // Handle error
    delete[] buffer;
    delete image;
    return nullptr;
}

使用 CCImage 对象的 initWithImageData 方法,将 buffer 中的二进制数据初始化为 CCImage 对象。如果初始化失败,释放buffer和image对象,然后返回nullptr,表示加载图像数据失败。
6)清理资源

delete[] buffer;
fclose(file);

成功加载图像后,释放 buffer 内存,并关闭文件。
7)返回图像数据

return image;

2.将获取到的图像显示在游戏内
1)构建文件名

std::string canvasFileName = "ColoringType_"+std::to_string(ColorManager::shared()->curColorTheme+1)+"_"+std::to_string(i+1)+"canvas";

目的是为了获取到你保存数据时对应的文件名称,以便加载对应的数据图像。
2)加载二进制图像文件

CCImage* canvasImage = ColorManager::shared()->loadImageFromBinaryFile(canvasFileName);

loadImageFromBinaryFile方法内容就是上面所提到的如何提取数据位图像的内容,不再赘述。
3)初始化纹理对象

if (canvasImage != NULL) {
    CCTexture2D* canvasTexture = new CCTexture2D();
    if (canvasTexture && canvasTexture->initWithImage(canvasImage)) {
        // 创建和设置精灵对象
        // ...
    }
}

如果成功加载了 canvasImage,则创建一个 CCTexture2D 对象 canvasTexture,并使用 canvasImage 初始化它。这个步骤是为了将图像数据转换为纹理对象,以便后续在精灵中显示。
4)创建和设置精灵对象

CCSprite* stencilSpr = CCSprite::createWithTexture(CCTextureCache::sharedTextureCache()->addImage("DinoColor/canvas.png"), CCRect(0, 0, 739, 640));
stencilSpr->setAnchorPoint(ccp(0.0, 0.0));
stencilSpr->setPosition(CCPointZero);

CCSprite* canvasSpr = CCSprite::createWithTexture(canvasTexture);
canvasSpr->setPosition(ccp(lastscenePos.x+x_x, canvasSpr->getContentSize().height/2));

canvasSpr是加载了从二进制文件中读取的纹理数据的精灵,设置它的位置,这个精灵将显示用户之前涂色的内容。
5)创建裁剪节点并添加精灵

CCClippingNode* clip = CCClippingNode::create(stencilSpr);
clip->addChild(canvasSpr);

CCClippingNode 是一个用于裁剪其子节点显示区域的节点。用stencilSpr也就是画板作为裁剪模板,将canvasSpr作为子节点添加到裁剪节点中。这样做可以确保canvasSpr只在stencilSpr指定的区域内显示。
PS:除了画板之外,游戏内还有各动物的各部位也可以涂画,所以也需要创建他们的精灵对象,方法与上面创建画板的基本一致,不再赘述。

内容有点多,希望能给大家带来帮助!!!有什么问题需要讨论的可以评论私信欢迎讨论~

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-10 12:30:05       4 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-10 12:30:05       5 阅读
  3. 在Django里面运行非项目文件

    2024-07-10 12:30:05       4 阅读
  4. Python语言-面向对象

    2024-07-10 12:30:05       5 阅读

热门阅读

  1. activiti6学习

    2024-07-10 12:30:05       7 阅读
  2. Android Camera Framework:从基础到高级

    2024-07-10 12:30:05       11 阅读
  3. React Native与React Native Web:跨平台开发的新选择

    2024-07-10 12:30:05       8 阅读
  4. React Native

    2024-07-10 12:30:05       6 阅读
  5. ——探索从懵懂学童到职场人的期待与感悟

    2024-07-10 12:30:05       7 阅读
  6. ArduPilot开源代码之AP_MSP

    2024-07-10 12:30:05       8 阅读