【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建模案例分析-时序图和类图的对应关系
概念 简单地说,类图定义了系统中的对象,时序图定义了对象之间的交互。 例子 一个电子商务系统,会员可通过电子商务系统购买零件。具体功能需求如下: 会员请求结账时,系统验证会员的账户是否处于登录状态࿱…...

51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...

【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...
日常一水C
多态 言简意赅:就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过,当子类和父类的函数名相同时,会隐藏父类的同名函数转而调用子类的同名函数,如果要调用父类的同名函数,那么就需要对父类进行引用&#…...
LangChain【6】之输出解析器:结构化LLM响应的关键工具
文章目录 一 LangChain输出解析器概述1.1 什么是输出解析器?1.2 主要功能与工作原理1.3 常用解析器类型 二 主要输出解析器类型2.1 Pydantic/Json输出解析器2.2 结构化输出解析器2.3 列表解析器2.4 日期解析器2.5 Json输出解析器2.6 xml输出解析器 三 高级使用技巧3…...
6.计算机网络核心知识点精要手册
计算机网络核心知识点精要手册 1.协议基础篇 网络协议三要素 语法:数据与控制信息的结构或格式,如同语言中的语法规则语义:控制信息的具体含义和响应方式,规定通信双方"说什么"同步:事件执行的顺序与时序…...
OpenGL-什么是软OpenGL/软渲染/软光栅?
软OpenGL(Software OpenGL)或者软渲染指完全通过CPU模拟实现的OpenGL渲染方式(包括几何处理、光栅化、着色等),不依赖GPU硬件加速。这种模式通常性能较低,但兼容性极强,常用于不支持硬件加速…...