【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 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:除了画板之外,游戏内还有各动物的各部位也可以涂画,所以也需要创建他们的精灵对象,方法与上面创建画板的基本一致,不再赘述。
内容有点多,希望能给大家带来帮助!!!有什么问题需要讨论的可以评论私信欢迎讨论~
相关文章:
【cocos2dx】【iOS工程】如何保存用户在游戏内的绘画数据,并将数据以图像形式展示在预览界面
【cocos2dx】【iOS工程】如何保存用户在应用内的操作数据,并将数据以图像形式展示在预览界面 设备/引擎:Mac(11.6)/Mac Mini 开发工具:Xcode(15.0.1) 开发需求:如何保存用户在应用…...
拥抱应用创新,拒绝无谓的模型竞争
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...
【源码+文档+调试讲解】旅游资源网站
摘 要 本论文主要论述了如何使用JAVA语言开发一个旅游资源网站 ,本系统将严格按照软件开发流程进行各个阶段的工作,采用B/S架构,面向对象编程思想进行项目开发。在引言中,作者将论述旅游资源网站的当前背景以及系统开发的目的&…...
Monaco 多行提示的实现方式
AI 代码助手最近太火爆,国内有模型厂商都有代码助手,代码助手是个比较典型的 AI 应用,主要看前端,后端的模型都差不多,国内外都有专门的代码模型。现在都是集中在 VSCode 和 Idea的插件,本文通过 Monaco 实…...
SpringMVC的架构有什么优势?——表单和数据校验(四)
#SpringMVC的架构有什么优势?——表单和数据校验(四) 前言 关键字: 机器学习 人工智能 AI chatGPT 学习 实现 使用 搭建 深度 python 事件 远程 docker mysql安全 技术 部署 技术 自动化 代码 文章目录 - - - - - 表单数据…...
Linux实战记录
踩坑实录: day2: 最坑:安装UB居然不知道创建文件夹。 1.虚拟机上不了网:多重置几次 网卡 2.Winscp链接主机: 用户名 就是 linux terminal中的 第一个用户名!...
时间、查找、打包、行过滤与指令的运行——linux指令学习(二)
前言:本节内容标题虽然为指令,但是并不只是讲指令, 更多的是和指令相关的一些原理性的东西。 如果友友只想要查一查某个指令的用法, 很抱歉, 本节不是那种带有字典性质的文章。但是如果友友是想要来学习的,…...
android CameraX构建相机拍照
Android CameraX 是一个 Jetpack 支持库,旨在简化相机应用的开发工作。它提供了一致且易用的API接口,适用于大多数Android设备,并可向后兼容至Android 5.0(API级别21)。 CameraX解决了在多种设备上实现相机功能时所遇…...
【普中】基于51单片机的矩阵电子密码锁LCD1602液晶显示 proteus仿真+程序+设计报告+讲解视频
【普中】基于51单片机的矩阵电子密码锁LCD1602液晶显示设计 1.主要功能:讲解视频:2.仿真3. 程序代码4. 设计报告5. 设计资料内容清单&&下载链接资料下载链接: 【普中】基于51单片机的矩阵电子密码锁LCD1602液晶显示设计 ( proteus仿真…...
工厂水电燃气表流量计等能耗计量仪表非侵入式拍照抄表的方案
在企业园区、工厂等企事业单位,传统的手动抄表方式已逐渐不能满足现代化、信息化管理的需求。为了提高抄表工作的效率,减少人工操作的误差,同时保障数据的安全性和实时性,我们提出了拍照采集抄表方案。本方案旨在通过拍照的方式&a…...
LLM大模型应用中的安全对齐的简单理解
LLM大模型应用中的安全对齐的简单理解 随着人工智能技术的不断发展,大规模语言模型(如GPT-4)的应用越来越广泛。为了保证这些大模型在实际应用中的性能和安全性,安全对齐(Safe Alignment)成为一个重要的概…...
clickhouse-jdbc-bridge rce
clickhouse-jdbc-bridge 是什么 JDBC bridge for ClickHouse. It acts as a stateless proxy passing queries from ClickHouse to external datasources. With this extension, you can run distributed query on ClickHouse across multiple datasources in real time, whic…...
java中Comparator函数的用法实例?
在Java中,Comparator接口用于比较两个对象的顺序,常用于集合的排序。自Java 8开始,Comparator接口得到了增强,提供了许多默认方法,使得排序逻辑更加灵活和强大。下面将通过几个实例来展示Comparator的用法。 示例1&am…...
mysql实战入门-基础篇
目录 1、MySQL概述 1.1、数据库相关概念 1.2、MySQL数据库 1.2.1、版本 1.2.2、下载 1.2.3、安装 输入MySQL中root用户的密码,一定记得记住该密码 1.2.4、启动停止 1.2.5、客户端连接 1.2.6、数据模型 2、SQL 2.1、SQL通用语法 2.2、SQL分类 2.3、DDL 2.3.1、数据…...
阶段三:项目开发---民航功能模块实现:任务24:航空实时监控
任务描述 内 容:地图展示、飞机飞行轨迹、扇区控制。航空实时监控,是飞机每秒发送坐标,经过终端转换实时发送给塔台,为了飞机位置的精准度,传输位置的密度很大,在地图位置显示不明显。本次为了案例展示效…...
手机容器化 安装docker
旧手机-基于Termux容器化 1、安装app 在手机上安装Termux或ZeroTermux(Termux扩展) 1.1 切换源 注:可以将termux进行换源,最好采用国内源,例如:清华源等 更新包列表和升级包(可选࿰…...
科普文:深入理解Mybatis
概叙 (1) JDBC JDBC(Java Data Base Connection,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成.JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。 优点…...
称重传感器有哪些种类
有关称重传感器的知识,称重传感器是众多传感器产品中的一种,也是很常用的传感器之一,那么称重传感器有哪些种类,称重传感器的分类方式是什么样的,一起来了解下。 称重传感器的分类 主要有六种称重传感器类型…...
程序员鱼皮的保姆级写简历指南第四弹,优秀简历参考
大家好,我是程序员鱼皮。做知识分享这些年来,我看过太多简历、也帮忙修改过很多的简历,发现很多同学是完全不会写简历的、会犯很多常见的问题,不能把自己的优势充分展示出来,导致措施了很多面试机会,实在是…...
UML建模案例分析-时序图和类图的对应关系
概念 简单地说,类图定义了系统中的对象,时序图定义了对象之间的交互。 例子 一个电子商务系统,会员可通过电子商务系统购买零件。具体功能需求如下: 会员请求结账时,系统验证会员的账户是否处于登录状态࿱…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...
【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...
Qemu arm操作系统开发环境
使用qemu虚拟arm硬件比较合适。 步骤如下: 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载,下载地址:https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...
