单机游戏数据自动保存方案

感谢粉丝提供的素材

引言

单机游戏数据的自动保存方案

大家好,2023年还有最后的3天

小伙伴私信我,说:

总感觉一股脑的全盘定时保存不科学,也写过保存变化的玩家数据,但是改完数据就得手动标记一下字段变化,感觉不够智能,不知道有没好的设计模式之类可以解决,就只管更新数据就行。

笔者认真思考了一下,结合前面的项目里面用到的,给大家分析一下,大家可以根据具体情况看看

本文将介绍一下单机游戏数据的自动保存方案

本文源工程可在文末阅读原文获取,小伙伴们自行前往。

1.需求分析

根据小伙伴的私信,需求如下:

  • 全盘定时保存不科学。
  • 保存变化的玩家数据,需要手动标记,不智能。
  • 有没有办法只管更新数据就行。

我们接下来具体分析一下

2.具体分析

1.全盘定时保存

其实全盘定时保存也不是不好的设计方案,只是他也是需要针对变化的数据进行存盘。

也就是说我们需要对变化的数据进行标记,在执行全盘保存的时候,需要根据标记位来进行存储。

这方案在后端开发其实是很常见的,定时存盘+离线存盘

能够有效地保证数据存储无误、存储效率更高,服务器宕机时损失最少

2.手动标记

手动标记其实是最有效最直接的去控制指定内容是否需要存盘的方法。

但是由于是手动标记,也就是人为操作,难免会出现错漏的情况。

因此可以借助一下设计模式,去优化一下设计,在数据变化时可以自动标记

下面一起来看下自动保存常用设计模式

3.数据自动保存设计模式

下面是查阅相关资料之后整理出来的一些设计模式和方法

  1. 观察者模式: 使用观察者模式来监测游戏中的变化。每个可能修改数据的对象都是观察者,而存档系统是主题。当对象发生变化时,它通知主题,主题再负责触发保存。

  2. Dirty Flag模式: 引入“脏标志”来标记对象是否发生变化。只有在对象发生变化时才进行保存。这种方式可以减少不必要的保存操作。

  3. 快照模式: 定期创建游戏状态的快照,而不是全盘保存。这样可以避免频繁的保存操作,只在需要时加载最近的快照。

  4. 增量保存: 只保存发生变化的部分数据,而不是整个数据集。这可以减少保存和加载的时间,尤其是在数据量较大的情况下。

接下来直接看下实例

4.观察者模式

我们使用观察者模式来完成一个数据自动保存的实例。

首先我们准备一下玩家数据,其中包括:

  • 角色名
  • 等级
// PlayerData.ts
export class PlayerData {name: string;level: number;constructor(data: any) {this.name = data.name;this.level = data.level;}
}

然后,我们定义一个通用的观察者接口:

// Observer.ts
export interface Observer<T> {update(data: T): void;
}

再然后,实现一个具体的观察者,即自动保存数据的观察者,核心内容如下:

  • save负责存储数据,这里可以根据具体需求存本地或者服务器。
  • load负责数据加载。
import { sys } from "cc";
import { Observer } from "./Observer";
import { PlayerData } from "./PlayerData";// AutoSaveObserver.ts
export class AutoSaveObserver implements Observer<PlayerData> {update(data: PlayerData): void {// 在这里执行自动保存操作,可以调用存档系统console.log(`Auto-saving data: ${JSON.stringify(data)}`);this.save(data);}save(data: PlayerData) {sys.localStorage.setItem('playerData', JSON.stringify(data));}load() {const data = sys.localStorage.getItem('playerData');if (data) {const parsedData = JSON.parse(data);return new PlayerData(parsedData);}return new PlayerData({ name: "Player", level: 1 });}
}

再再然后,我们创建一个通用的主题类,使用代理模式来处理观察者管理和通知

其中要实现自动标记/存盘的核心是Proxy:

在 TypeScript 中,Proxy 是 ES6 引入的一种特性,它提供了一种拦截、定义自定义行为的机制。Proxy 可以用于创建一个代理对象,该对象可以拦截对原始对象的访问、属性查找、赋值等操作。这为开发者提供了一种在对象级别上自定义行为的方式。

代码如下:

import { Observer } from "./Observer";// ObservableProxy.ts
export class ObservableProxy<T extends object> {private observers: Observer<T>[] = [];private _target: T;constructor(target: T) {this._target = new Proxy(target, {set: (obj, prop, value) => {if (obj[prop] !== value) {obj[prop] = value;this.notifyObservers();}return true;},});}addObserver(observer: Observer<T>): void {this.observers.push(observer);}removeObserver(observer: Observer<T>): void {const index = this.observers.indexOf(observer);if (index !== -1) {this.observers.splice(index, 1);}}private notifyObservers(): void {for (const observer of this.observers) {observer.update(this._target);}}get target(): T {return this._target;}
}

最后我们通过ObservableProxy对玩家数据进行包装,实现自动保存。

import { ObservableProxy } from "./ObservableProxy";
import { PlayerData } from "./PlayerData";// ObservablePlayerData.ts
export class ObservablePlayerData extends ObservableProxy<PlayerData> {constructor(data: any) {super(new PlayerData(data));}
}

测试代码

import { _decorator, Component, Node } from 'cc';
import { ObservablePlayerData } from './ObservablePlayerData';
import { AutoSaveObserver } from './AutoSaveObserver';
const { ccclass, property } = _decorator;@ccclass('Main')
export class Main extends Component {start() {// Main.tsconst autoSaveObserver = new AutoSaveObserver();var playerData = autoSaveObserver.load();const observablePlayerData = new ObservablePlayerData(playerData);console.log(`Now data: ${JSON.stringify(observablePlayerData.target)}`);// 将自动保存观察者添加到观察者列表中observablePlayerData.addObserver(autoSaveObserver);// 修改玩家数据,会触发自动保存observablePlayerData.target.level = 2;observablePlayerData.target.level = 3;}
}

结果演示

首次运行,初始等级1级,经过修改等级后,玩家等级是3级。

打开冰箱

重新登录后,加载到的等级为3级,测试自动存盘成功。

把大象装进去

把冰箱门关上!下课!

结语

本文源工程可通过私信AutoSave获取。

在哪里可以看到如此清晰的思路,快跟上我的节奏!关注我,和我一起了解游戏行业最新动态,学习游戏开发技巧。

我是"亿元程序员",一位有着8年游戏行业经验的主程。在游戏开发中,希望能给到您帮助, 也希望通过您能帮助到大家。

AD:笔者线上的小游戏《贪吃蛇掌机经典》《重力迷宫球》《填色之旅》大家可以自行点击搜索体验。

实不相瞒,想要个在看!请把该文章分享给你觉得有需要的其他小伙伴。谢谢!

推荐专栏:

100个Cocos实例

8年主程手把手打造Cocos独立游戏开发框架

和8年游戏主程一起学习设计模式

从零开始开发贪吃蛇小游戏到上线系列

知识付费专栏

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/318577.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot项目的三种创建方式

手动创建方式&#xff1a; ①&#xff1a;新建maven项目 ②&#xff1a;引入依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.10.RELEASE</version>&l…

学习Go语言Web框架Gee总结--上下文Context(二)

学习Go语言Web框架Gee总结--上下文Context context/go.modcontext/main.gocontext/gee/context.gocontext/gee/router.gocontext/gee/gee.go 学习网站来源&#xff1a;Gee 项目目录结构&#xff1a; context/go.mod module examplego 1.21.5require gee v0.0.0 replace gee…

电子邮件营销主题管理技巧:提升邮件打开率与转化率的实用指南

有效的沟通在于向正确的受众传递正确的信息。市场营销人员大部分时间都在寻找最佳方式。幸运的是&#xff0c;我们无需耗费太多精力才能找到答案。为什么不直接问问你的受众他们期望的是什么&#xff0c;然后根据他们的需求进行沟通呢&#xff1f; 由于社交媒体的出现&#xf…

B端产品经理学习-B端产品系统调研的工具

系统性调研目标的工具 系统性调研的目标 相对于背景调研&#xff0c;系统行调研是对公司可控因素&#xff08;公司内部&#xff09;和直接作用力&#xff08;消费者、竞争者&#xff09;进行的调研。系统性调研需要输出结论&#xff0c;为达成产品或公司的战略目标而制定行动的…

【CVPR2023】使用轻量 ToF 传感器的单目密集SLAM的多模态神经辐射场

目录 导读 本文贡献 本文方法 轻量级ToF传感器的感知原理 多模态隐式场景表示 时间滤波技术 实验 实验结果 消融实验 结论 未来工作 论文标题&#xff1a;Multi-Modal Neural Radiance Field for Monocular Dense SLAM with a Light-Weight ToF Sensor 论文链接&am…

Reids在Win下无法远程访问

1.将redis在windows上启动主要做了以下配置 1.1.在redis.windows.conf中修改一下 原&#xff1a;bind 127.0.0.1 改&#xff1a;# bind 127.0.0.1 bind 0.0.0.0 原&#xff1a;protected-mode yes 改&#xff1a;protected-mode no去掉了127.0.0.1&#xff0c;加入0.0.0.0后&…

useState和setState区别

一、主要是讲一下类组件的状态和函数组件的状态 1.类组件中state只能有一个&#xff0c; 函数组件中state可以有多个 函数组件&#xff1a;可以使用对个状态&#xff0c;便于控制。 // 文章数量的初始值const [articleData, setArticleData] useState({list: [],// 文章列表…

Linux上管理不同版本的 JDK

当在 Linux 上管理不同版本的 JDK 时&#xff0c;使用 yum 和 dnf 可以方便地安装和切换不同的 JDK 版本。本文将介绍如何通过这两个包管理工具安装 JDK 1.8 和 JDK 11&#xff0c;并利用软连接动态关联这些版本。 安装 JDK 1.8 和 JDK 11 使用 yum 安装 JDK 1.8 打开终端并…

求一个整数二进制中1的个数(三种方法详解)

越过寒冬 前言 今天复习了一些操作符的知识&#xff0c;看到了这道题&#xff0c;并且发先有三种解题思路&#xff0c;觉得有趣&#xff0c;据记下来与诸位分享一下。 题目 写一个函数&#xff0c;给定一个整数&#xff0c;求他的二进制位中1的个数 思路1 既然是二进制位那…

MySQL中的事务到底是怎么一回事儿

简单来说&#xff0c;事务就是要保证一组数据库操作&#xff0c;要么全部成功&#xff0c;要么全部失败。在MySQL中&#xff0c;事务支持是在引擎层实现的&#xff0c;但并不是所有的引擎都支持事务&#xff0c;如MyISAM引擎就不支持事务&#xff0c;这也是MyISAM被InnoDB取代的…

【日积月累】Java Lambda 表达式

目录 【日积月累】Java Lambda 表达式 1.前言2.语法3.应用场景3.1简化匿名内部类的编写3.1简化匿名内部类的编写3.2简化集合类中的操作3.3实现函数式接口3.4简化多个方法的调用3.5简化异步编程 4.总结5.参考 文章所属专区 日积月累 1.前言 Lambda表达式是一个匿名函数&#…

QGIS设计导出Geoserver服务使用的SLD样式

1、打开QGis软件 2、打开shp文件所在所在文件夹&#xff0c;双击添加选中图层 3、编辑shp文件样式 &#xff08;1&#xff09;双击“Layers”中需要编辑的图层 &#xff08;2&#xff09;选择样式 &#xff08;3&#xff09;编辑样式后&#xff0c;选择“应用”—》“确定” 4…