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

news2024/9/23 19:26:27

感谢粉丝提供的素材

引言

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

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

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

知识付费专栏

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux程序、进程以及计划任务

目录 一、程序和进程 1、什么是程序&#xff1f; 2、什么是进程&#xff1f; 3、线程是什么&#xff1f; 4、如何查看是多线程还是单线程 5、进程结束的两种情况&#xff1a; 6、进程的状态 二、查看进程信息的相关命令 1、ps&#xff1a;查看静态的进程统计信息 2、…

基于ssm+vue服装商城购物系统

摘要 在基于SSM框架和Vue.js的服装商城购物系统中&#xff0c;整合了多种先进的技术&#xff0c;为电子商务领域的发展提供了有力支持。该系统不仅仅是技术层面的整合&#xff0c;更是对于业务流程和用户体验的深入考虑。以下是对该系统扩展的一些关键方面的讨论&#xff0c;以…

【笔记】书生·浦语大模型实战营——第一课

群公告 1月3日*更新 第一次课程视频链接&#xff1a;https://www.bilibili.com/video/BV1Rc411b7ns/&#xff0c;第一次课程只需要记笔记&#xff0c;没有作业。第一次课程(1月3日)和第二次课程(1月5日)到本周末(1月7日)截止&#xff0c;笔记记录在 知乎/CSDN/Github 或者任何你…

深入了解小红书笔记详情API:为内容创新提供动力

一、小红书笔记详情API简介 小红书笔记详情API是一种允许开发者访问小红书平台上的笔记详细数据的接口。通过这个API&#xff0c;我们可以获取笔记的标题、内容、标签、点赞数、评论数等详细信息。这些数据对于内容创作者和品牌来说至关重要&#xff0c;可以帮助他们了解用户喜…

Spring-IOC综述

文章迁移自语雀。 怎么查看spring的文档 ioc综述 说到spring的ioc,其实就是控制反转,为啥需要控制反转呢,其实是为了功能的增强,如果不用spring, 我们直接使用工厂方法,静态工厂方法, 都是是可以获取到对象的,但是如果需求变了,我们在类的生成时,添加了很多信息,使用工厂就不…

Flutter 中使用 ICON

Flutter Icon URL &#xff1a; https://fonts.google.com/icons&#xff1a; 在Flutter中使用 Icon 步骤如下&#xff1a; 导入图标库 在Dart 文件中导入 material.dart 包&#xff0c;该包包含了 Flutter 的图标库。 import package:flutter/material.dart;使用图标组件 …

C#高级:Lambda表达式分组处理2(WITH ROLLUP关键字)

目录 一、问题引入 二、with rollup查询 三、去掉多余数据 四、拓展 一、问题引入 查询SQL后结果如下&#xff0c;字段分别是用户、项目、批次、工作时间&#xff1a; SELECT UserID,ProjectID,ProBatchesID,WorkHour FROM MAINTABLE GROUP BY HourFiller ,ProjectID ,…

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

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

Node.js+Express 获取前端get请求参数值

前端请求&#xff1a; http://localhost:3002/api/user/login?username002&password002 后端响应 router.get(/api/user/login, (req, res) > {let username req.query.username;let password req.query.password;const sqlStr SELECT * FROM sys_user where use…

基于 unittest 的 Web UI / HTTP 自动化测试框架

GitHub 上发现的一个自动化测试框架&#xff0c;封装的很厉害&#xff0c;对小白很友好&#xff0c;体验了下 demo 很不错&#xff0c;先看看下面封装好的 po 模式&#xff0c;很简洁。 """import seldom from seldom import Seldom from poium import Page, E…

Windows可以ping通ubuntu,但ubuntu无法ping通windows

使用了NAT网卡和桥接网卡&#xff0c;电脑连了WiFi&#xff0c;桥接网卡桥接到WLAN上&#xff0c;Windows可以ping通Ubuntu但反过来不行&#xff01; 1.可能是防火墙的问题&#xff0c;按照如下设置&#xff0c;无果 考虑是不是使用了两个网卡冲突了&#xff0c;取消NAT的链接 …

【损失函数】Cross Entropy Loss 交叉熵损失

1、介绍 主页介绍的几种损失函数都是适用于回归问题损失函数&#xff0c;对于分类问题&#xff0c;最常用的损失函数是交叉熵损失函数 Cross Entropy Loss。它用于测量两个概率分布之间的差异&#xff0c;通常用于评估分类模型的性能。 2、公式 对于二分类问题&#xff0c;交…

基于SSM的网络游戏交易平台设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

市场复盘总结 20240103

仅用于记录当天的市场情况,用于统计交易策略的适用情况,以便程序回测 短线核心:不参与任何级别的调整 昨日回顾: 方法一:指标选股 select * from dbo.ResultAll where 入选类型 like %指标选股% and 入选日期=20240103;方法二:趋势选股法 1、最低价持续3日上涨 2、均价…

useState和setState区别

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

GreenPlum-数据世界的绿洲

GreenPlum的介绍 Greenplum是一个基于开源PostgreSQL数据库系统的高性能、可扩展的大数据处理平台。它是由Pivotal Software&#xff08;现在是VMware的一部分&#xff09;开发并维护的。Greenplum的设计目标是处理大规模的数据集&#xff0c;提供高并发、高吞吐量的查询和分析…

案例087:基于微信小程序的社区养老服务平台设计与实现

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

Node.js本地搭建简单页面小游戏

文章目录 前言1.安装Node.js环境2.创建node.js服务3. 访问node.js 服务4.内网穿透4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5.固定公网地址 前言 Node.js 是能够在服务器端运行 JavaScript 的开放源代码、跨平台运行环境。Node.js 由 OpenJS Foundation&#xff0…

LangChain与昇腾

LangChain这个词今年已经听烂了&#xff0c;今天基于昇腾的角度总结一下&#xff1a; Why LangChain &#xff1f; 场景&#xff1a;构建一个LLM应用 在构建一个新项目时&#xff0c;可能会遇到许多API接口、数据格式和工具。要去研究每一个工具、接口很麻烦。 假设要构建一…

踩坑Vant组件 Dialog的组件调用

今天踩了一个非常蠢的坑&#xff0c;自己给自己蠢死的坑 在使用组件调用时自己没引入Dialog组件导致一直报错 不知道为什么全局引入不好使&#xff0c;后来使用了局部引用 现在没问题了 就这样局部引入一个Dialog.Component就可以了