备忘录模式(Memento Pattern)是一种行为型设计模式,允许保存对象的某个状态,以便在未来可以恢复到这个状态。备忘录模式的关键是将对象的状态封装到备忘录中,从而实现状态的存储和回滚,而不破坏对象的封装性。

在Unity中,备忘录模式非常适用于以下场景:

  • 游戏存档和加载:保存和恢复玩家的游戏进度。
  • 撤销和重做功能:保存玩家的操作状态以支持撤销。
  • 临时状态保存:如玩家的角色状态、物品状态等。

1. 备忘录模式的基本概念

核心结构

备忘录模式包含以下几个部分:

  1. 发起人(Originator):对象的状态需要被保存或恢复。
  2. 备忘录(Memento):存储发起人的内部状态。
  3. 管理者(Caretaker):负责保存和恢复备忘录。

优点

  1. 封装性好:备忘录存储的是发起人的内部状态,但对外界不可见。
  2. 简化撤销和恢复功能:通过备忘录可以轻松实现状态的回滚。
  3. 扩展性强:可以保存和恢复复杂的数据结构。

缺点

  1. 内存开销大:如果状态数据较多,存储多个备忘录可能会占用大量内存。
  2. 实现复杂度高:需要设计备忘录的存储和管理逻辑。
  3. 数据一致性问题:需要确保备忘录和发起人状态的一致性。

2. Unity 实现备忘录模式

以下通过一个示例,展示如何在Unity中用备忘录模式实现玩家位置和状态的保存与恢复。


3. 实现步骤

(1) 定义备忘录类

备忘录类负责存储发起人的状态。可以是玩家的位置、生命值、分数等。

// PlayerMemento.cs
using UnityEngine;

public class PlayerMemento
{
    public Vector3 Position { get; private set; }
    public int Health { get; private set; }

    public PlayerMemento(Vector3 position, int health)
    {
        Position = position;
        Health = health;
    }
}

(2) 定义发起人类

发起人类保存当前状态,并创建备忘录或从备忘录中恢复状态。

// Player.cs
using UnityEngine;

public class Player : MonoBehaviour
{
    public Vector3 Position { get; set; }
    public int Health { get; set; }

    // 创建备忘录
    public PlayerMemento SaveState()
    {
        Debug.Log("Saving state...");
        return new PlayerMemento(Position, Health);
    }

    // 从备忘录恢复状态
    public void RestoreState(PlayerMemento memento)
    {
        Position = memento.Position;
        Health = memento.Health;
        Debug.Log($"Restoring state... Position: {Position}, Health: {Health}");
    }

    private void Update()
    {
        // 模拟玩家移动
        if (Input.GetKey(KeyCode.W))
        {
            Position += Vector3.forward * Time.deltaTime;
        }

        // 模拟玩家受伤
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Health -= 10;
            Debug.Log($"Player took damage. Current health: {Health}");
        }
    }
}

(3) 定义管理者类

管理者类负责保存和管理多个备忘录(如果需要支持多个存档)。

// Caretaker.cs
using System.Collections.Generic;

public class Caretaker
{
    private Stack<PlayerMemento> _mementos = new Stack<PlayerMemento>();

    // 保存备忘录
    public void SaveMemento(PlayerMemento memento)
    {
        _mementos.Push(memento);
        Debug.Log("State saved.");
    }

    // 恢复备忘录
    public PlayerMemento RestoreMemento()
    {
        if (_mementos.Count > 0)
        {
            Debug.Log("State restored.");
            return _mementos.Pop();
        }

        Debug.LogWarning("No state to restore!");
        return null;
    }
}

(4) 测试备忘录模式

创建一个测试脚本,模拟保存和恢复玩家状态。

// GameManager.cs
using UnityEngine;

public class GameManager : MonoBehaviour
{
    private Player _player;
    private Caretaker _caretaker;

    private void Start()
    {
        _player = new GameObject("Player").AddComponent<Player>();
        _caretaker = new Caretaker();

        // 初始化玩家状态
        _player.Position = Vector3.zero;
        _player.Health = 100;

        Debug.Log($"Initial state: Position={_player.Position}, Health={_player.Health}");
    }

    private void Update()
    {
        // 保存状态
        if (Input.GetKeyDown(KeyCode.S))
        {
            _caretaker.SaveMemento(_player.SaveState());
        }

        // 恢复状态
        if (Input.GetKeyDown(KeyCode.R))
        {
            var memento = _caretaker.RestoreMemento();
            if (memento != null)
            {
                _player.RestoreState(memento);
            }
        }

        // 显示当前状态
        if (Input.GetKeyDown(KeyCode.P))
        {
            Debug.Log($"Current state: Position={_player.Position}, Health={_player.Health}");
        }
    }
}

4. 运行结果

运行游戏后,按下以下键可以测试功能:

  • W:移动玩家位置。
  • Space:减少玩家生命值。
  • S:保存当前状态。
  • R:恢复上一次保存的状态。
  • P:查看当前状态。

控制台输出示例:

Initial state: Position=(0.0, 0.0, 0.0), Health=100
Player took damage. Current health: 90
Saving state...
Player took damage. Current health: 80
State restored.
Restoring state... Position=(0.0, 0.0, 0.0), Health=90

5. 扩展与优化

(1) 添加多个存档

目前的实现只保存了一个存档。如果需要支持多个存档,可以使用一个List<PlayerMemento>Dictionary<string, PlayerMemento>来存储多个备忘录。

private Dictionary<string, PlayerMemento> _mementos = new Dictionary<string, PlayerMemento>();

public void SaveMemento(string key, PlayerMemento memento)
{
    _mementos[key] = memento;
    Debug.Log($"State saved with key: {key}");
}

public PlayerMemento RestoreMemento(string key)
{
    if (_mementos.ContainsKey(key))
    {
        Debug.Log($"State restored with key: {key}");
        return _mementos[key];
    }

    Debug.LogWarning($"No state found with key: {key}");
    return null;
}

(2) 结合序列化存储到文件

可以将备忘录的数据序列化为JSON或XML文件,保存到磁盘,从而实现游戏的存档功能。

using System.IO;
using Newtonsoft.Json;

public void SaveToFile(PlayerMemento memento, string filePath)
{
    var json = JsonConvert.SerializeObject(memento);
    File.WriteAllText(filePath, json);
    Debug.Log("State saved to file.");
}

public PlayerMemento LoadFromFile(string filePath)
{
    if (File.Exists(filePath))
    {
        var json = File.ReadAllText(filePath);
        return JsonConvert.DeserializeObject<PlayerMemento>(json);
    }

    Debug.LogWarning("Save file not found.");
    return null;
}

(3) Undo/Redo 功能

通过维护两个栈(一个用于撤销,一个用于重做),可以轻松实现撤销/重做功能。


6. 优化与注意事项

(1) 内存管理

如果备忘录需要存储大量数据(如复杂的游戏状态),需要注意内存管理,定期清理过期的备忘录。

(2) 数据一致性

确保备忘录存储的数据完全反映发起人的状态,避免数据不一致的问题。

(3) 备忘录的安全性

如果数据比较敏感,可以对备忘录的访问进行限制,例如设置为只读或通过加密存储。


7. 总结

备忘录模式的优缺点
优点缺点
封装了对象的状态,便于保存和恢复。如果状态较多,可能会占用大量内存。
简化了撤销和恢复功能的实现。实现复杂度较高,尤其是管理多个备忘录时。
不破坏封装性,对外界隐藏发起人的内部状态。如果涉及文件存储,序列化和反序列化可能会增加额外的复杂性。

适用场景
  1. 游戏存档和加载:保存和恢复玩家的游戏进度。
  2. Undo/Redo 功能:例如地图编辑器、道具操作等。
  3. 临时状态保存:如关卡切换时保存玩家状态以便恢复。

通过备忘录模式,Unity中的状态管理变得更加模块化和灵活,尤其在游戏开发中的存档/加载功能中非常实用。

Logo

分享前沿Unity技术干货和开发经验,精彩的Unity活动和社区相关信息

更多推荐