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

引言

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

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

小伙伴私信我,说:

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

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

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

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

1.需求分析

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

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

我们接下来具体分析一下

2.具体分析

1.全盘定时保存

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

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

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

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

2.手动标记

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

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

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

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

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

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

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

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

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

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

接下来直接看下实例

4.观察者模式

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

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

  • 角色名
  • 等级
typescript 复制代码
// PlayerData.ts
export class PlayerData {
    name: string;
    level: number;

    constructor(data: any) {
        this.name = data.name;
        this.level = data.level;
    }
}

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

typescript 复制代码
// Observer.ts
export interface Observer<T> {
    update(data: T): void;
}

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

  • save负责存储数据,这里可以根据具体需求存本地或者服务器。
  • load负责数据加载。
typescript 复制代码
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 可以用于创建一个代理对象,该对象可以拦截对原始对象的访问、属性查找、赋值等操作。这为开发者提供了一种在对象级别上自定义行为的方式。

代码如下:

typescript 复制代码
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对玩家数据进行包装,实现自动保存。

typescript 复制代码
import { ObservableProxy } from "./ObservableProxy";
import { PlayerData } from "./PlayerData";

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

测试代码

typescript 复制代码
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年游戏主程一起学习设计模式

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

知识付费专栏

相关推荐
也无晴也无风雨1 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang2 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
阮少年、5 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
郝晨妤6 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
AvatarGiser6 小时前
《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明
前端·vue.js·elementui
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js
别忘了微笑_cuicui6 小时前
elementUI中2个日期组件实现开始时间、结束时间(禁用日期面板、控制开始时间不能超过结束时间的时分秒)实现方案
前端·javascript·elementui