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

Java版Flink使用指南——从RabbitMQ中队列中接入消息流
大纲 创建RabbitMQ队列新建工程新增依赖编码设置数据源配置读取、处理数据完整代码 打包、上传和运行任务测试 工程代码 在《Java版Flink使用指南——安装Flink和使用IntelliJ制作任务包》一文中,我们完成了第一个小型Demo的编写。例子中的数据是代码预先指定的。而…...

Python酷库之旅-第三方库Pandas(013)
目录 一、用法精讲 31、pandas.read_feather函数 31-1、语法 31-2、参数 31-3、功能 31-4、返回值 31-5、说明 31-6、用法 31-6-1、数据准备 31-6-2、代码示例 31-6-3、结果输出 32、pandas.DataFrame.to_feather函数 32-1、语法 32-2、参数 32-3、功能 32-4、…...

Linux 高级 Shell 脚本编程:掌握 Shell 脚本精髓,提升工作效率
【Linux】 高级 Shell 脚本编程:掌握 Shell 脚本精髓,提升工作效率 Shell 脚本编程是 Linux 系统管理员和开发人员的必备技能。通过学习高级 Shell 脚本编程,你可以编写更高效、更灵活和更易于维护的脚本。本文将介绍 Shell 脚本编程中的函数…...

【ARMv8/v9 GIC 系列 1.5 -- Enabling the distribution of interrupts】
请阅读【ARM GICv3/v4 实战学习 】 文章目录 Enabling the distribution of interruptsGIC Distributor 中断组分发控制CPU Interface 中断组分发控制Physical LPIs 的启用Summary Enabling the distribution of interrupts 在ARM GICv3和GICv4体系结构中,中断分发…...

《mysql篇》--索引事务
索引 索引的介绍 索引是帮助MySQL高效获取数据的数据结构,是一种特殊的文件,包含着对数据表里所有记录的引用指针,因为索引本身也比较大,所以索引一般是存储在磁盘上的,索引的种类有很多,不过如果没有特殊…...

科研绘图系列:R语言STAMP图(STAMP Plot)
介绍 STAMP图(STAMP plot)并非一个广泛认知的、具有特定名称的图表类型,而是可能指在STAMP(Statistical Analysis of Metagenomic Profiles:“STAMP: statistical analysis of taxonomic and functional profiles”)软件使用过程中生成的各种统计和可视化图表的总称。ST…...

运维团队如何应对动环监控与IT监控分离的挑战
IT与机房动环监控的一体化是当下及未来的必然趋势,这一模式显著节省了运维过程中的时间与成本。一体化平台不仅消除了频繁切换系统的繁琐,更在一个统一界面上实现了多元化的管理运维功能,极大地提升了工作效率。 在机房升级或新建项目中&…...

深入解析大数据核心概念:数据平台、数据中台、数据湖与数据仓库的异同与应用
大数据领域内的诸多概念常常让人困惑,其中数据平台、数据中台、数据湖和数据仓库是最为关键的几个。 1. 数据平台 定义: 数据平台是一个综合性的技术框架,旨在支持整个数据生命周期的管理和使用。它包含数据采集、存储、处理、分析和可视化…...

开发指南040-业务操作日志
平台所有业务操作都存储在核心库,以便统一分析处理。各业务微服务通过feign调用核心日志服务。底层提供了API: <dependency><groupId>org.qlm</groupId><artifactId>qlm-api</artifactId><version>1.0-SNAPSHOT<…...

如何构建数据驱动的企业?爬虫管理平台是关键桥梁吗?
一、数据驱动时代:为何选择爬虫管理平台? 在信息爆炸的今天,数据驱动已成为企业发展的核心战略之一。爬虫管理平台,作为数据采集的第一站,它的重要性不言而喻。这类平台通过自动化手段,从互联网的各个角落…...