【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 errorreturn 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 errordelete[] 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:除了畫板之外,游戲內還有各動物的各部位也可以涂畫,所以也需要創建他們的精靈對象,方法與上面創建畫板的基本一致,不再贅述。
內容有點多,希望能給大家帶來幫助!!!有什么問題需要討論的可以評論私信歡迎討論~