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

感谢粉丝提供的素材

引言

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

大家好,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.ts
        const 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年游戏主程一起学习设计模式

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

知识付费专栏

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Amos各版本安装指南

Amos下载链接 https://pan.baidu.com/s/1uyblN8Q-knNKkqQVlNnXTw?pwd0531 1.鼠标右击【Amos28】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;选择【解压到 Amos28】。 2.打开解压后的文件夹&#xff0c;鼠标右击【Amos28】选择【以管理员身份运行…

BUUCTF--ciscn_2019_s_91

先看保护&#xff1a; 发现保护全没开&#xff0c;紧接着看看程序执行流程&#xff1a; 经过测试发现了段错误&#xff0c;这肯定就是栈溢出了。因此我们在IDA中看看具体逻辑&#xff1a; 程序主要就这个功能&#xff0c;我们还在程序中发现一个函数&#xff1a; 一段汇编&…

DataLoader与Dataset

一、人民币二分类 二、DataLoader 与 Dataset DataLoader torch.utils.data.DataLoader 功能&#xff1a;构建可迭代的数据装载器 &#xff08;只标注了较为重要的&#xff09; • dataset: Dataset类&#xff0c;决定数据从哪读取及如何读取 • batchsize : 批大小 • num_…

广告公司项目管理工具推荐:高效管理订单与项目的利器

由于广告公司每年都需要处理数千个制作订单&#xff0c;包括促销广告预告片和海报等&#xff0c;迫切需要一款能够高效管理和生产广告流程的软件。 在实施采购项目管理工具之前&#xff0c;广告公司的流程相当繁复&#xff0c;员工们需要使用约10到15个不同的Excel表格&#xf…

vue-element-admin请求接口时报错:431 Request Header Fields Too Large

vue-element-admin请求接口时报错&#xff1a;431 Request Header Fields Too Large 1、使用vue-element-admin框架开发运行后报错431&#xff0c;在网上也找了一些解决方案&#xff0c;有说是生成的Token太大导致的报错&#xff0c;也说有可能是NGINX后台设置的提交大小限制。…

QT的信号与槽

QT的信号与槽 文章目录 QT的信号与槽前言一、QT 打印"hello QT"的dome二、信号和槽机制&#xff1f;二、信号与槽的用法1、QT5的方式1. 无参的信号与槽的dome2.带参的信号与槽dome 2、QT4的方式3、C11的语法 Lambda表达式1、函数对象参数2、操作符重载函数参数3、可修…

为什么JAVA_HOME修改后Java版本不变

今天的实验需要对java project进行降版本后重构。于是去Oracle官网下载了jdk1.7。然后将系统环境变量JAVA_HOME改成了安装后的jdk1.7路径。即 C:\Program Files\Java\jdk1.7.0_80 系统变量Path中直接引用了%JAVA_HOME%\bin。 但是当我查看版本&#xff0c;却出现了javac改了…

stable diffusion 基础教程-提示词之光的用法

基图 prompt: masterpiece,best quality,1girl,solo,looking at viewer,brown hair,hair between eyes,bangs,very long hair,red eyes,blush,bare shoulders,(white sundress),full body,leaning forward,medium breasts,unbuttoned clothes,Negative prompt: EasyNegativ…

GraalVM Native学习及使用

概述 在开发Spring Boot 应用或者其他JAVA程序的过程中&#xff0c;启动慢、内存占用大是比较头疼的问题&#xff0c;往往需要更多的资源去部署&#xff0c;成本大幅提高。为了优化上述问题&#xff0c;常常使用优化程序、使用更小消耗的JVM、使用容器等措施。 现在有一个叫做…

工作流入门这篇就够了!

总概 定义&#xff1a;工作流是在计算机支持下业务流程的自动或半自动化&#xff0c;其通过对流程进行描述以及按一定规则执行以完成相应工作。 应用&#xff1a;随着计算机技术的发展以及工业生产、办公自动化等领域的需求不断提升&#xff0c;面向事务审批、材料提交、业务…

在Cadence中单独添加或删除器件与修改网络的方法

首先需要在设置中使能 ,添加或修改逻辑选项。 添加或删除器件&#xff0c;点击logic-part&#xff0c;选择需要添加或删除的器件&#xff0c;这里的器件必须是PCB中已经有的器件&#xff0c;Refdes中输入添加或删除的器件标号&#xff0c;点击Add添加。 添加完成后就会显示在R1…

学习笔记240102 --- 表单无法输入,是否data中没有提前声明导致的

前端框架 &#xff1a;vue2.x 第三方ui组件&#xff1a;ElementUI 操作系统&#xff1a;windows 浏览器&#xff1a;chrome 谷歌 问题描述 表单使用中&#xff0c;没有在data中提前声明参数&#xff0c;当数据回显时&#xff0c;表单无法输入 <el-form :model"queryPa…

【力扣100】39.组合总和

添加链接描述 class Solution:def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:def backtrack(path,target,res,index):if target0:res.append(path[:])returnif target<0:return for i in range(index,len(candidates)):if target&g…

3个.NET开源简单易用的任务调度框架

前言 今天分享3个.NET开源、简单、易用的任务调度框架&#xff0c;帮助大家在做定时任务调度框架技术选型的时候有一个参考。 Quartz.Net Quartz.NET是一个功能齐全的开源作业调度系统&#xff0c;可用于从最小的应用程序到大规模企业系统。 Quartz.NetUI Quartz.NetUI是一…

算法导论复习——CHP25 多源最短路

问题描述 给定一个带权重的有向图G(V,E)&#xff0c;其权重函数为ω:E→R。 在图中&#xff0c;对所有的结点对 u,v∈V&#xff0c;找出从结点u到结点v的最短路径。 该问题的解以表格&#xff08;二维数组&#xff09;的形式给出&#xff1a;第u行第v列给出从结点u到结…

计算机毕业设计 基于SpringBoot的工作量统计系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

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

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

多任务并行处理相关面试题

我自己面试时被问过两次多任务并行相关的问题&#xff1a; 假设现在有10个任务&#xff0c;要求同时处理&#xff0c;并且必须所有任务全部完成才返回结果 这个面试题的难点是&#xff1a; 既然要同时处理&#xff0c;那么肯定要用多线程。怎么设计多线程同时处理任务呢&…

leetcode递归算法题总结

递归本质是找重复的子问题 本章目录 1.汉诺塔2.合并两个有序链表3.反转链表4.两两交换链表中的节点5.Pow(x,n) 1.汉诺塔 汉诺塔 //面试写法 class Solution { public:void hanota(vector<int>& a, vector<int>& b, vector<int>& c) {dfs(a,b…

基于Spring Cloud + Spring Boot的企业电子招标采购系统源码

随着企业的快速发展&#xff0c;招采管理逐渐成为企业运营中的重要环节。为了满足公司对内部招采管理提升的要求&#xff0c;建立一个公平、公开、公正的采购环境至关重要。在这个背景下&#xff0c;我们开发了一款电子招标采购软件&#xff0c;以最大限度地控制采购成本&#…