前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【转】Qt之JSON保存与读取

【转】Qt之JSON保存与读取

作者头像
饶文津
发布2020-06-02 14:16:17
1.7K0
发布2020-06-02 14:16:17
举报
文章被收录于专栏:饶文津的专栏饶文津的专栏

简述

许多游戏提供保存功能,使得玩家在游戏中的进度可以被保存,并在以后再玩的时候进行加载。保存游戏的过程通常涉及将每个游戏对象的成员变量序列化为文件。要实现这个功能,可以采取许多格式,其中之一就是 JSON - 使用 QJsonDocument。如果不希望保存的文件可读,或者不需要保持文件大小,还能够以二进制格式序列化文档,这就厉害了O(∩_∩)O。

下面,将演示如何以 JSON 和二进制格式来保存和加载一个简单的游戏。

Character 类

Character 类表示游戏中的非玩家角色(NPC),并存储玩家的姓名、级别和类类型。

提供了 read() 和 write() 函数来序列化成员变量。

class Character
  {
  public:
      enum ClassType {
          Warrior, Mage, Archer
      };

      Character();
      Character(const QString &name, int level, ClassType classType);

      QString name() const;
      void setName(const QString &name);

      int level() const;
      void setLevel(int level);

      ClassType classType() const;
      void setClassType(ClassType classType);

      void read(const QJsonObject &json);
      void write(QJsonObject &json) const;
  private:
      QString mName;
      int mLevel;
      ClassType mClassType;
  };

我们感兴趣的是读和写函数的实现:

void Character::read(const QJsonObject &json)
  {
      mName = json["name"].toString();
      mLevel = json["level"].toDouble();
      mClassType = ClassType(qRound(json["classType"].toDouble()));
  }

在 read() 函数中,由 QJsonObject 参数分配 Character 的成员变量,可以使用 QJsonObject::operator 或者 QJsonObject::value() 来访问 JSON 对象中的值,它们均是 const 函数。如果指定的 key 无效,则返回 QJsonValue::Undefined。

注意:在尝试读取值之前,应该使用 QJsonObject::contains() 检测 key 是否有效,这里假设是有效的,所以没有检测。

void Character::write(QJsonObject &json) const
  {
      json["name"] = mName;
      json["level"] = mLevel;
      json["classType"] = mClassType;
  }

在 write() 函数中,处理与 read() 相反。将 Character 的值分配给 QJsonObject 对象。与访问值一样,也有两种方式来设置 QJsonObject 的值:QJsonObject::operator 和 QJsonObject::insert(),它们都会覆盖指定 key 对应的值。

Level 类

接下来是 Level 类,表示游戏中的级别。

class Level
  {
  public:
      Level();

      const QList<Character> &npcs() const;
      void setNpcs(const QList<Character> &npcs);

      void read(const QJsonObject &json);
      void write(QJsonObject &json) const;
  private:
      QList<Character> mNpcs;
  };

游戏中有很多级别,每个级别都有几个 NPC,所以需要一个 QList 保存 Character 对象。其次,还提供了熟悉的 read() 和 write() 函数。

void Level::read(const QJsonObject &json)
  {
      mNpcs.clear();
      QJsonArray npcArray = json["npcs"].toArray();
      for (int npcIndex = 0; npcIndex < npcArray.size(); ++npcIndex) {
          QJsonObject npcObject = npcArray[npcIndex].toObject();
          Character npc;
          npc.read(npcObject);
          mNpcs.append(npc);
      }
  }

容器可以使用 QJsonArray 写入和读取 JSON。示例中,用关键字“npcs”及其相关联的值构造了一个 QJsonArray。然后,对于数组中的每个 QJsonValue 元素,调用 toObject() 来获取 Character 的 JSON 对象。最后,Character 对象可以读取其 JSON 并附加到 NPC 列表中。

void Level::write(QJsonObject &json) const
  {
      QJsonArray npcArray;
      foreach (const Character npc, mNpcs) {
          QJsonObject npcObject;
          npc.write(npcObject);
          npcArray.append(npcObject);
      }
      json["npcs"] = npcArray;
  }

write() 函数类似于 read(),除了功能相反以外。

Game 类

建立了 Character 和 Level 类后,继续看 Game 类:

class Game
  {
  public:
      Game();

      // 游戏保存的格式 Json、Binary(二进制)
      enum SaveFormat {
          Json, Binary
      };

      const Character &player() const;
      const QList<Level> &levels() const;

      void newGame();
      bool loadGame(SaveFormat saveFormat);
      bool saveGame(SaveFormat saveFormat) const;

      void read(const QJsonObject &json);
      void write(QJsonObject &json) const;
  private:
      Character mPlayer;
      QList<Level> mLevels;
  };

首先,定义了 SaveFormat 枚举。这将允许指定游戏应该保存的格式:Json 或 Binary。

接下来,我们为玩家和级别提供了访问器。然后暴漏了三个函数:newGame()、saveGame() 和 loadGame()。

read() 和 write() 函数由 saveGame() 和 loadGame() 使用。

void Game::newGame() {
      mPlayer = Character();
      mPlayer.setName(QStringLiteral("Hero"));
      mPlayer.setClassType(Character::Archer);
      mPlayer.setLevel(15);

      mLevels.clear();

      Level village;
      QList<Character> villageNpcs;
      villageNpcs.append(Character(QStringLiteral("Barry the Blacksmith"), 10, Character::Warrior));
      villageNpcs.append(Character(QStringLiteral("Terry the Trader"), 10, Character::Warrior));
      village.setNpcs(villageNpcs);
      mLevels.append(village);

      Level dungeon;
      QList<Character> dungeonNpcs;
      dungeonNpcs.append(Character(QStringLiteral("Eric the Evil"), 20, Character::Mage));
      dungeonNpcs.append(Character(QStringLiteral("Eric's Sidekick #1"), 5, Character::Warrior));
      dungeonNpcs.append(Character(QStringLiteral("Eric's Sidekick #2"), 5, Character::Warrior));
      dungeon.setNpcs(dungeonNpcs);
      mLevels.append(dungeon);
  }

要设置一个新游戏,需要创建玩家并填充级别和对应的 NPC。

void Game::read(const QJsonObject &json)
  {
      mPlayer.read(json["player"].toObject());

      mLevels.clear();
      QJsonArray levelArray = json["levels"].toArray();
      for (int levelIndex = 0; levelIndex < levelArray.size(); ++levelIndex) {
          QJsonObject levelObject = levelArray[levelIndex].toObject();
          Level level;
          level.read(levelObject);
          mLevels.append(level);
      }
  }

read() 函数中首先要做的是告诉玩家读取自身的数据。然后清除级别列表,以便多次调用 loadGame() 不会导致旧级别的存在。

然后通过从 QJsonArray 读取每个 Level 来填充级别列表。

void Game::write(QJsonObject &json) const
  {
      QJsonObject playerObject;
      mPlayer.write(playerObject);
      json["player"] = playerObject;

      QJsonArray levelArray;
      foreach (const Level level, mLevels) {
          QJsonObject levelObject;
          level.write(levelObject);
          levelArray.append(levelObject);
      }
      json["levels"] = levelArray;
  }

将游戏写入 JSON 类似于如何写 Level。

bool Game::loadGame(Game::SaveFormat saveFormat)
  {
      QFile loadFile(saveFormat == Json
          ? QStringLiteral("save.json")
          : QStringLiteral("save.dat"));

      if (!loadFile.open(QIODevice::ReadOnly)) {
          qWarning("Couldn't open save file.");
          return false;
      }

      QByteArray saveData = loadFile.readAll();

      QJsonDocument loadDoc(saveFormat == Json
          ? QJsonDocument::fromJson(saveData)
          : QJsonDocument::fromBinaryData(saveData));

      read(loadDoc.object());

      return true;
  }

当在 loadGame() 中加载保存的游戏时,做的第一件事是根据保存文件的格式打开保存文件,save.json 用于 JSON,save.dat 用于二进制。如果无法打开文件,打印一个警告,返回 false。

由于 QJsonDocument 的 fromJson() 和 fromBinaryData() 函数都使用 QByteArray,因此无论保存格式如何,都可以用其中的一个来转换保存文件的整个内容。

在构造 QJsonDocument 之后,让 Game 对象读取自身数据,然后返回 true 以示成功。

bool Game::saveGame(Game::SaveFormat saveFormat) const
  {
      QFile saveFile(saveFormat == Json
          ? QStringLiteral("save.json")
          : QStringLiteral("save.dat"));

      if (!saveFile.open(QIODevice::WriteOnly)) {
          qWarning("Couldn't open save file.");
          return false;
      }

      QJsonObject gameObject;
      write(gameObject);
      QJsonDocument saveDoc(gameObject);
      saveFile.write(saveFormat == Json
          ? saveDoc.toJson()
          : saveDoc.toBinaryData());

      return true;
  }

毫不奇怪,saveGame() 看起来非常像 loadGame()。基于格式确定文件扩展名,如果文件打开失败,打印警告,返回 false。然后将 Game 对象写入一个 QJsonDocument,并调用 QJsonDocument::toJson() 或 QJsonDocument::toBinaryData() 来保存游戏,具体取决于指定的格式。

使用

现在准备进入 main() 函数:

int main(int argc, char *argv[])
  {
      // 因为只想展示使用 JSON 游戏的序列化,实际上游戏是不可玩的。因此,只需要 QCoreApplication,没有事件循环。
      QCoreApplication app(argc, argv);

      Game game;
      game.newGame();
      // 游戏开始,加载数据...

      // 假设玩家度过了快乐的时光,取得了伟大的成就,改变 Character、Level 和 Game 对象的内部状态。
      if (!game.saveGame(Game::Json))
          return 1;

      if (!game.saveGame(Game::Binary))
          return 1;

      Game fromJsonGame;
      if (!fromJsonGame.loadGame(Game::Json))
          return 1;

      Game fromBinaryGame;
      if (!fromBinaryGame.loadGame(Game::Binary))
          return 1;

      return 0;
  }

当玩家结束游戏后,保存游戏数据。为了演示,序列化为 JSON 和二进制。可以在与可执行文件相同的目录中检查文件的内容,但二进制保存文件将包含一些无用字符(这是正常的)。

为了显示可以再次加载保存的文件,为每种格式调用 loadGame(),失败时返回 1。假设一切顺利,返回 0 表示成功。

如你所见,使用 Qt 的 JSON 类进行序列化非常简单和方便。使用 QJsonDocument 比 QDataStream 的优点在于,不仅可以得到易读的 JSON 文件,如果需要,也可以选择使用二进制格式,而不需重写任何代码。

save.json 文件(JSON )如下所示:

{
    "levels": [
        {
            "npcs": [
                {
                    "classType": 0,
                    "level": 10,
                    "name": "Barry the Blacksmith"
                },
                {
                    "classType": 0,
                    "level": 10,
                    "name": "Terry the Trader"
                }
            ]
        },
        {
            "npcs": [
                {
                    "classType": 1,
                    "level": 20,
                    "name": "Eric the Evil"
                },
                {
                    "classType": 0,
                    "level": 5,
                    "name": "Eric's Sidekick #1"
                },
                {
                    "classType": 0,
                    "level": 5,
                    "name": "Eric's Sidekick #2"
                }
            ]
        }
    ],
    "player": {
        "classType": 2,
        "level": 15,
        "name": "Hero"
    }
}

原文:http://blog.csdn.net/liang19890820/article/details/52797245

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-03-31 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简述
  • Character 类
  • Level 类
  • Game 类
  • 使用
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档