Cocos2d 数据存储:本地文件(UserDefault/JSON/SQLite)详解
【摘要】 引言在游戏开发中,数据持久化是核心功能之一。Cocos2d-x提供了多种本地存储方案,包括轻量级的UserDefault、通用的JSON格式以及关系型数据库SQLite。每种方案都有其适用场景和优势。本文将深入探讨这三种存储方式的实现细节,帮助开发者根据需求选择最合适的存储方案。技术背景存储方案对比方案数据类型存储结构适用场景性能特点UserDefault键值对XML文件简单配置存储轻量级,...
引言
技术背景
存储方案对比
|
|
|
|
|
|
|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Cocos2d存储架构
-
平台抽象层:封装不同平台的文件操作API -
引擎核心层:提供统一的存储接口 -
应用层:开发者实现的业务逻辑
应用使用场景
-
游戏设置存储:音量、画质等配置(UserDefault) -
玩家存档:角色属性、进度等(JSON/SQLite) -
排行榜系统:玩家分数记录(SQLite) -
游戏资源缓存:纹理、音效路径(JSON) -
关卡设计数据:地图布局、敌人配置(JSON) -
交易记录:游戏内购买历史(SQLite) -
日志系统:错误报告和用户行为(JSON)
不同场景下详细代码实现
场景1:UserDefault基础使用
// UserDefaultDemo.h
#include "cocos2d.h"
class UserDefaultDemo {
public:
static void savePlayerSettings();
static void loadPlayerSettings();
static void demonstrateUsage();
};
// UserDefaultDemo.cpp
#include "UserDefaultDemo.h"
#include "ui/CocosGUI.h"
USING_NS_CC;
void UserDefaultDemo::savePlayerSettings() {
// 获取UserDefault实例
auto userDefault = UserDefault::getInstance();
// 保存基本类型数据
userDefault->setBoolForKey("sound_enabled", true);
userDefault->setFloatForKey("music_volume", 0.8f);
userDefault->setIntegerForKey("difficulty_level", 2);
userDefault->setStringForKey("player_name", "CocosWarrior");
// 提交更改到磁盘
userDefault->flush();
log("玩家设置已保存");
}
void UserDefaultDemo::loadPlayerSettings() {
auto userDefault = UserDefault::getInstance();
// 读取数据并提供默认值
bool soundEnabled = userDefault->getBoolForKey("sound_enabled", false);
float musicVolume = userDefault->getFloatForKey("music_volume", 0.5f);
int difficulty = userDefault->getIntegerForKey("difficulty_level", 1);
std::string playerName = userDefault->getStringForKey("player_name", "Guest");
log("读取设置: 声音=%d, 音量=%.2f, 难度=%d, 名字=%s",
soundEnabled, musicVolume, difficulty, playerName.c_str());
}
void UserDefaultDemo::demonstrateUsage() {
// 检查键是否存在
if(UserDefault::getInstance()->isBoolForKey("tutorial_completed")) {
log("教程已完成");
}
// 删除键值
UserDefault::getInstance()->deleteValueForKey("temp_data");
// 获取所有键名
auto keys = UserDefault::getInstance()->getKeys();
for(const auto& key : keys) {
log("键: %s", key.c_str());
}
}
场景2:JSON数据存储
// JsonStorageDemo.h
#include "cocos2d.h"
#include "json/document.h"
class JsonStorageDemo {
public:
static void saveGameData();
static void loadGameData();
static void parseJsonString();
};
// JsonStorageDemo.cpp
#include "JsonStorageDemo.h"
#include "network/HttpClient.h"
#include "platform/FileUtils.h"
USING_NS_CC;
using namespace rapidjson;
void JsonStorageDemo::saveGameData() {
// 创建JSON文档
Document doc;
doc.SetObject();
auto& allocator = doc.GetAllocator();
// 添加玩家数据
Value playerObj(kObjectType);
playerObj.AddMember("name", "ArcherQueen", allocator);
playerObj.AddMember("level", 25, allocator);
playerObj.AddMember("health", 850, allocator);
// 添加装备数组
Value equipment(kArrayType);
Value sword("Excalibur", allocator);
Value armor("Dragon Scale", allocator);
equipment.PushBack(sword, allocator);
equipment.PushBack(armor, allocator);
// 组装主对象
doc.AddMember("player", playerObj, allocator);
doc.AddMember("equipment", equipment, allocator);
doc.AddMember("coins", 1500, allocator);
// 序列化为字符串
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
doc.Accept(writer);
std::string jsonStr = buffer.GetString();
// 保存到文件
auto fileUtils = FileUtils::getInstance();
std::string path = fileUtils->getWritablePath() + "savegame.json";
fileUtils->writeStringToFile(jsonStr, path);
log("游戏数据已保存至: %s", path.c_str());
}
void JsonStorageDemo::loadGameData() {
auto fileUtils = FileUtils::getInstance();
std::string path = fileUtils->getWritablePath() + "savegame.json";
if(!fileUtils->isFileExist(path)) {
log("存档文件不存在");
return;
}
// 读取文件内容
std::string jsonStr = fileUtils->getStringFromFile(path);
// 解析JSON
Document doc;
doc.Parse(jsonStr.c_str());
if(doc.HasParseError()) {
log("JSON解析错误: %d", doc.GetParseError());
return;
}
// 提取数据
std::string name = doc["player"]["name"].GetString();
int level = doc["player"]["level"].GetInt();
int health = doc["player"]["health"].GetInt();
int coins = doc["coins"].GetInt();
log("加载存档: 玩家=%s, 等级=%d, 生命=%d, 金币=%d",
name.c_str(), level, health, coins);
// 处理装备数组
const Value& equipment = doc["equipment"];
for(SizeType i = 0; i < equipment.Size(); i++) {
log("装备[%d]: %s", i, equipment[i].GetString());
}
}
void JsonStorageDemo::parseJsonString() {
std::string jsonStr = R"({
"enemies": [
{"type": "goblin", "hp": 30, "damage": 5},
{"type": "orc", "hp": 80, "damage": 12}
],
"boss": {"name": "Dragon", "hp": 500}
})";
Document doc;
doc.Parse(jsonStr.c_str());
// 提取敌人数据
const Value& enemies = doc["enemies"];
for(SizeType i = 0; i < enemies.Size(); i++) {
const Value& enemy = enemies[i];
log("敌人%d: 类型=%s, HP=%d, 伤害=%d",
i+1,
enemy["type"].GetString(),
enemy["hp"].GetInt(),
enemy["damage"].GetInt());
}
// 提取Boss数据
const Value& boss = doc["boss"];
log("Boss: %s, HP=%d", boss["name"].GetString(), boss["hp"].GetInt());
}
场景3:SQLite数据库操作
// SqliteDemo.h
#include "cocos2d.h"
#include "sqlite3.h"
class SqliteDemo {
public:
static void createDatabase();
static void insertPlayerData();
static void queryLeaderboard();
static void transactionExample();
};
// SqliteDemo.cpp
#include "SqliteDemo.h"
#include "platform/FileUtils.h"
USING_NS_CC;
void SqliteDemo::createDatabase() {
auto fileUtils = FileUtils::getInstance();
std::string path = fileUtils->getWritablePath() + "game.db";
sqlite3* db;
if(sqlite3_open(path.c_str(), &db) != SQLITE_OK) {
log("无法打开数据库: %s", sqlite3_errmsg(db));
return;
}
// 创建玩家表
const char* createPlayerTable =
"CREATE TABLE IF NOT EXISTS players ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"name TEXT NOT NULL,"
"level INTEGER DEFAULT 1,"
"score INTEGER DEFAULT 0,"
"created_at DATETIME DEFAULT CURRENT_TIMESTAMP"
");";
char* errMsg = nullptr;
if(sqlite3_exec(db, createPlayerTable, nullptr, nullptr, &errMsg) != SQLITE_OK) {
log("创建表失败: %s", errMsg);
sqlite3_free(errMsg);
}
// 创建排行榜表
const char* createLeaderboardTable =
"CREATE TABLE IF NOT EXISTS leaderboard ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"player_id INTEGER,"
"score INTEGER,"
"timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,"
"FOREIGN KEY(player_id) REFERENCES players(id)"
");";
if(sqlite3_exec(db, createLeaderboardTable, nullptr, nullptr, &errMsg) != SQLITE_OK) {
log("创建排行榜表失败: %s", errMsg);
sqlite3_free(errMsg);
}
sqlite3_close(db);
log("数据库已创建: %s", path.c_str());
}
void SqliteDemo::insertPlayerData() {
auto fileUtils = FileUtils::getInstance();
std::string path = fileUtils->getWritablePath() + "game.db";
sqlite3* db;
if(sqlite3_open(path.c_str(), &db) != SQLITE_OK) {
log("无法打开数据库");
return;
}
// 插入玩家数据
const char* insertPlayer =
"INSERT INTO players (name, level, score) VALUES (?, ?, ?)";
sqlite3_stmt* stmt;
if(sqlite3_prepare_v2(db, insertPlayer, -1, &stmt, nullptr) == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, "KnightHero", -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 2, 15);
sqlite3_bind_int(stmt, 3, 3200);
if(sqlite3_step(stmt) != SQLITE_DONE) {
log("插入玩家失败: %s", sqlite3_errmsg(db));
}
sqlite3_finalize(stmt);
}
// 获取最后插入的ID
sqlite3_int64 lastId = sqlite3_last_insert_rowid(db);
log("插入玩家成功, ID: %lld", lastId);
// 插入排行榜记录
const char* insertScore =
"INSERT INTO leaderboard (player_id, score) VALUES (?, ?)";
if(sqlite3_prepare_v2(db, insertScore, -1, &stmt, nullptr) == SQLITE_OK) {
sqlite3_bind_int64(stmt, 1, lastId);
sqlite3_bind_int(stmt, 2, 3200);
if(sqlite3_step(stmt) != SQLITE_DONE) {
log("插入分数失败: %s", sqlite3_errmsg(db));
}
sqlite3_finalize(stmt);
}
sqlite3_close(db);
}
void SqliteDemo::queryLeaderboard() {
auto fileUtils = FileUtils::getInstance();
std::string path = fileUtils->getWritablePath() + "game.db";
sqlite3* db;
if(sqlite3_open(path.c_str(), &db) != SQLITE_OK) {
log("无法打开数据库");
return;
}
const char* query =
"SELECT p.name, l.score, l.timestamp "
"FROM leaderboard l "
"JOIN players p ON l.player_id = p.id "
"ORDER BY l.score DESC LIMIT 10";
sqlite3_stmt* stmt;
if(sqlite3_prepare_v2(db, query, -1, &stmt, nullptr) == SQLITE_OK) {
log("===== 排行榜 =====");
while(sqlite3_step(stmt) == SQLITE_ROW) {
const char* name = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
int score = sqlite3_column_int(stmt, 1);
const char* time = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
log("%s: %d分 (%s)", name, score, time);
}
sqlite3_finalize(stmt);
} else {
log("查询失败: %s", sqlite3_errmsg(db));
}
sqlite3_close(db);
}
void SqliteDemo::transactionExample() {
auto fileUtils = FileUtils::getInstance();
std::string path = fileUtils->getWritablePath() + "game.db";
sqlite3* db;
if(sqlite3_open(path.c_str(), &db) != SQLITE_OK) {
log("无法打开数据库");
return;
}
// 开始事务
sqlite3_exec(db, "BEGIN TRANSACTION", nullptr, nullptr, nullptr);
bool success = true;
sqlite3_stmt* stmt;
// 批量插入数据
const char* insert = "INSERT INTO players (name, level) VALUES (?, ?)";
if(sqlite3_prepare_v2(db, insert, -1, &stmt, nullptr) == SQLITE_OK) {
for(int i = 0; i < 100; i++) {
std::string name = "Player" + std::to_string(i);
sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);
sqlite3_bind_int(stmt, 2, 1 + (i % 50));
if(sqlite3_step(stmt) != SQLITE_DONE) {
log("插入失败: %s", sqlite3_errmsg(db));
success = false;
break;
}
sqlite3_reset(stmt);
}
sqlite3_finalize(stmt);
}
// 提交或回滚事务
if(success) {
sqlite3_exec(db, "COMMIT", nullptr, nullptr, nullptr);
log("事务提交成功, 插入100条记录");
} else {
sqlite3_exec(db, "ROLLBACK", nullptr, nullptr, nullptr);
log("事务回滚");
}
sqlite3_close(db);
}
原理解释
UserDefault原理
-
XML存储:数据以XML格式存储在 Cocos2dxPrefs.ini文件中 -
键值对管理:使用std::unordered_map缓存数据 -
懒写入机制:调用flush()时才写入磁盘 -
类型安全:提供模板方法确保类型正确
JSON原理
-
序列化/反序列化:将C++对象转换为JSON字符串 -
DOM解析:使用rapidjson构建内存对象树 -
流式处理:支持SAX风格的增量解析 -
数据绑定:自动映射JSON字段到结构体
SQLite原理
-
嵌入式数据库:单文件数据库引擎 -
ACID事务:支持原子性、一致性、隔离性、持久性 -
零配置:无需单独服务器进程 -
SQL标准:支持大部分SQL92语法
核心特性
UserDefault
-
简单易用的键值存储 -
自动类型转换 -
数据持久化到本地文件 -
支持基本数据类型
JSON
-
轻量级数据交换格式 -
人类可读的结构化数据 -
支持嵌套对象和数组 -
跨平台兼容性
SQLite
-
完整的SQL支持 -
事务处理能力 -
索引优化查询 -
多平台支持
原理流程图及解释
graph TD
A[应用数据存储需求] --> B{数据复杂度}
B -->|简单键值| C[UserDefault]
B -->|结构化数据| D{数据量}
D -->|小数据| E[JSON]
D -->|大数据| F[SQLite]
C --> G[XML文件存储]
E --> H[文本文件存储]
F --> I[二进制数据库文件]
G --> J[平台文件API]
H --> J
I --> J
J --> K[持久化存储]
-
根据数据复杂度选择存储方案 -
简单键值数据使用UserDefault -
结构化数据根据数据量选择: -
小数据量使用JSON -
大数据量使用SQLite
-
-
所有方案最终通过平台文件API持久化
环境准备
开发环境要求
-
操作系统:Windows 10/macOS/Linux -
开发工具:Visual Studio 2019/Xcode/Android Studio -
Cocos2d-x版本:v3.17或更高 -
编程语言:C++11或更高
安装步骤
-
下载Cocos2d-x引擎 git clone https://github.com/cocos2d/cocos2d-x.git cd cocos2d-x python download-deps.py -
创建新项目 cocos new DataStorageDemo -p com.example.datastorage -l cpp -d ~/projects -
添加SQLite支持 # 复制sqlite3头文件和库 cp -r external/sqlite3/include/* proj.android/app/jni/ cp -r external/sqlite3/prebuilt/android/* proj.android/app/jni/ -
配置项目 # 在CMakeLists.txt中添加 include_directories(${COCOS2DX_ROOT_PATH}/external/json) include_directories(${COCOS2DX_ROOT_PATH}/external/sqlite3/include)
实际详细应用代码示例实现
// GameSaveSystem.h
#ifndef __GAME_SAVE_SYSTEM_H__
#define __GAME_SAVE_SYSTEM_H__
#include "cocos2d.h"
#include "json/document.h"
#include "sqlite3.h"
class GameSaveSystem {
public:
static GameSaveSystem* getInstance();
bool initialize();
void saveGame(const std::string& slotName);
bool loadGame(const std::string& slotName);
void deleteSave(const std::string& slotName);
std::vector<std::string> getSaveSlots();
// 玩家数据访问
void setPlayerName(const std::string& name);
std::string getPlayerName() const;
void setPlayerLevel(int level);
int getPlayerLevel() const;
void setPlayerHealth(int health);
int getPlayerHealth() const;
// 游戏状态访问
void setCoins(int amount);
int getCoins() const;
void setCurrentLevel(int level);
int getCurrentLevel() const;
private:
GameSaveSystem();
~GameSaveSystem();
void saveToUserDefault();
void loadFromUserDefault();
void saveToJson(const std::string& filePath);
bool loadFromJson(const std::string& filePath);
void saveToSqlite(int slotId);
bool loadFromSqlite(int slotId);
// 数据成员
std::string _playerName;
int _playerLevel;
int _playerHealth;
int _coins;
int _currentLevel;
sqlite3* _db;
std::string _currentSlot;
};
#endif // __GAME_SAVE_SYSTEM_H__
// GameSaveSystem.cpp
#include "GameSaveSystem.h"
#include "platform/FileUtils.h"
USING_NS_CC;
using namespace rapidjson;
GameSaveSystem* GameSaveSystem::_instance = nullptr;
GameSaveSystem* GameSaveSystem::getInstance() {
if(!_instance) {
_instance = new GameSaveSystem();
_instance->initialize();
}
return _instance;
}
GameSaveSystem::GameSaveSystem()
: _playerLevel(1), _playerHealth(100), _coins(0),
_currentLevel(1), _db(nullptr) {}
GameSaveSystem::~GameSaveSystem() {
if(_db) {
sqlite3_close(_db);
}
}
bool GameSaveSystem::initialize() {
// 初始化UserDefault数据
auto ud = UserDefault::getInstance();
_playerName = ud->getStringForKey("player_name", "Hero");
_playerLevel = ud->getIntegerForKey("player_level", 1);
_playerHealth = ud->getIntegerForKey("player_health", 100);
_coins = ud->getIntegerForKey("coins", 0);
_currentLevel = ud->getIntegerForKey("current_level", 1);
// 初始化数据库
auto fileUtils = FileUtils::getInstance();
std::string dbPath = fileUtils->getWritablePath() + "saves.db";
if(sqlite3_open(dbPath.c_str(), &_db) != SQLITE_OK) {
log("数据库打开失败: %s", sqlite3_errmsg(_db));
return false;
}
// 创建存档表
const char* createTable =
"CREATE TABLE IF NOT EXISTS saves ("
"slot_id INTEGER PRIMARY KEY AUTOINCREMENT,"
"slot_name TEXT UNIQUE NOT NULL,"
"player_name TEXT,"
"player_level INTEGER,"
"player_health INTEGER,"
"coins INTEGER,"
"current_level INTEGER,"
"created_at DATETIME DEFAULT CURRENT_TIMESTAMP"
");";
char* errMsg = nullptr;
if(sqlite3_exec(_db, createTable, nullptr, nullptr, &errMsg) != SQLITE_OK) {
log("创建表失败: %s", errMsg);
sqlite3_free(errMsg);
return false;
}
return true;
}
void GameSaveSystem::saveGame(const std::string& slotName) {
_currentSlot = slotName;
// 根据数据量选择存储方案
if(slotName.empty()) {
// 快速存档使用UserDefault
saveToUserDefault();
} else {
// 命名存档使用SQLite
int slotId = getSlotIdByName(slotName);
if(slotId == -1) {
// 新建存档
saveToSqlite(0); // 0表示新建
} else {
saveToSqlite(slotId);
}
}
}
bool GameSaveSystem::loadGame(const std::string& slotName) {
_currentSlot = slotName;
if(slotName.empty()) {
loadFromUserDefault();
return true;
} else {
int slotId = getSlotIdByName(slotName);
if(slotId == -1) {
return false;
}
return loadFromSqlite(slotId);
}
}
void GameSaveSystem::saveToUserDefault() {
auto ud = UserDefault::getInstance();
ud->setStringForKey("player_name", _playerName);
ud->setIntegerForKey("player_level", _playerLevel);
ud->setIntegerForKey("player_health", _playerHealth);
ud->setIntegerForKey("coins", _coins);
ud->setIntegerForKey("current_level", _currentLevel);
ud->flush();
log("快速存档完成");
}
void GameSaveSystem::loadFromUserDefault() {
auto ud = UserDefault::getInstance();
_playerName = ud->getStringForKey("player_name", "Hero");
_playerLevel = ud->getIntegerForKey("player_level", 1);
_playerHealth = ud->getIntegerForKey("player_health", 100);
_coins = ud->getIntegerForKey("coins", 0);
_currentLevel = ud->getIntegerForKey("current_level", 1);
log("加载快速存档");
}
void GameSaveSystem::saveToSqlite(int slotId) {
const char* sql;
sqlite3_stmt* stmt;
if(slotId == 0) {
// 新建存档
sql = "INSERT INTO saves (slot_name, player_name, player_level, player_health, coins, current_level) "
"VALUES (?, ?, ?, ?, ?, ?)";
if(sqlite3_prepare_v2(_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, _currentSlot.c_str(), -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, _playerName.c_str(), -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 3, _playerLevel);
sqlite3_bind_int(stmt, 4, _playerHealth);
sqlite3_bind_int(stmt, 5, _coins);
sqlite3_bind_int(stmt, 6, _currentLevel);
if(sqlite3_step(stmt) != SQLITE_DONE) {
log("存档插入失败: %s", sqlite3_errmsg(_db));
}
sqlite3_finalize(stmt);
}
} else {
// 更新存档
sql = "UPDATE saves SET player_name=?, player_level=?, player_health=?, coins=?, current_level=? "
"WHERE slot_id=?";
if(sqlite3_prepare_v2(_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, _playerName.c_str(), -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 2, _playerLevel);
sqlite3_bind_int(stmt, 3, _playerHealth);
sqlite3_bind_int(stmt, 4, _coins);
sqlite3_bind_int(stmt, 5, _currentLevel);
sqlite3_bind_int(stmt, 6, slotId);
if(sqlite3_step(stmt) != SQLITE_DONE) {
log("存档更新失败: %s", sqlite3_errmsg(_db));
}
sqlite3_finalize(stmt);
}
}
log("存档已保存: %s", _currentSlot.c_str());
}
bool GameSaveSystem::loadFromSqlite(int slotId) {
const char* sql = "SELECT player_name, player_level, player_health, coins, current_level "
"FROM saves WHERE slot_id=?";
sqlite3_stmt* stmt;
if(sqlite3_prepare_v2(_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
sqlite3_bind_int(stmt, 1, slotId);
if(sqlite3_step(stmt) == SQLITE_ROW) {
_playerName = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
_playerLevel = sqlite3_column_int(stmt, 1);
_playerHealth = sqlite3_column_int(stmt, 2);
_coins = sqlite3_column_int(stmt, 3);
_currentLevel = sqlite3_column_int(stmt, 4);
sqlite3_finalize(stmt);
log("存档加载成功: %s", _currentSlot.c_str());
return true;
}
sqlite3_finalize(stmt);
}
log("存档加载失败: %s", _currentSlot.c_str());
return false;
}
int GameSaveSystem::getSlotIdByName(const std::string& slotName) {
const char* sql = "SELECT slot_id FROM saves WHERE slot_name=?";
sqlite3_stmt* stmt;
if(sqlite3_prepare_v2(_db, sql, -1, &stmt, nullptr) == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, slotName.c_str(), -1, SQLITE_STATIC);
if(sqlite3_step(stmt) == SQLITE_ROW) {
int id = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
return id;
}
sqlite3_finalize(stmt);
}
return -1;
}
// 其他成员函数实现...
运行结果
-
快速存档使用UserDefault,数据保存在 Cocos2dxPrefs.ini -
命名存档使用SQLite,数据保存在 saves.db -
复杂数据结构使用JSON,保存在 savegame.json
玩家设置已保存
读取设置: 声音=1, 音量=0.80, 难度=2, 名字=CocosWarrior
存档已保存: Slot1
加载存档成功: Slot1
排行榜:
KnightHero: 3200分 (2023-08-25 14:30:22)
ArcherQueen: 2800分 (2023-08-24 10:15:47)
测试步骤以及详细代码
测试步骤
-
创建Cocos2d-x项目 -
添加上述代码文件 -
配置SQLite支持 -
实现测试菜单界面 -
运行并测试各种存储功能 -
验证数据持久化
完整测试代码
// TestScene.cpp
#include "TestScene.h"
#include "GameSaveSystem.h"
USING_NS_CC;
Scene* TestScene::createScene() {
auto scene = Scene::create();
auto layer = TestScene::create();
scene->addChild(layer);
return scene;
}
bool TestScene::init() {
if(!Layer::init()) return false;
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
// 创建测试按钮
auto createButton = [this, &origin, &visibleSize](const std::string& title,
const ccMenuCallback& callback,
float yPos) {
auto label = Label::createWithTTF(title, "fonts/Marker Felt.ttf", 24);
auto item = MenuItemLabel::create(label, callback);
auto menu = Menu::create(item, nullptr);
menu->setPosition(Vec2(visibleSize.width/2 + origin.x,
visibleSize.height/2 + origin.y - yPos));
this->addChild(menu);
return item;
};
// 添加测试按钮
createButton("测试UserDefault", CC_CALLBACK_1(TestScene::testUserDefault, this), 0);
createButton("测试JSON存储", CC_CALLBACK_1(TestScene::testJsonStorage, this), 50);
createButton("测试SQLite", CC_CALLBACK_1(TestScene::testSqlite, this), 100);
createButton("测试存档系统", CC_CALLBACK_1(TestScene::testSaveSystem, this), 150);
return true;
}
void TestScene::testUserDefault(Ref* sender) {
UserDefaultDemo::demonstrateUsage();
UserDefaultDemo::savePlayerSettings();
UserDefaultDemo::loadPlayerSettings();
}
void TestScene::testJsonStorage(Ref* sender) {
JsonStorageDemo::saveGameData();
JsonStorageDemo::loadGameData();
JsonStorageDemo::parseJsonString();
}
void TestScene::testSqlite(Ref* sender) {
SqliteDemo::createDatabase();
SqliteDemo::insertPlayerData();
SqliteDemo::queryLeaderboard();
SqliteDemo::transactionExample();
}
void TestScene::testSaveSystem(Ref* sender) {
auto saveSystem = GameSaveSystem::getInstance();
// 设置测试数据
saveSystem->setPlayerName("TestPlayer");
saveSystem->setPlayerLevel(10);
saveSystem->setPlayerHealth(75);
saveSystem->setCoins(500);
saveSystem->setCurrentLevel(3);
// 测试快速存档
saveSystem->saveGame("");
saveSystem->loadGame("");
// 测试命名存档
saveSystem->saveGame("Slot1");
saveSystem->loadGame("Slot1");
// 测试覆盖存档
saveSystem->setPlayerLevel(11);
saveSystem->saveGame("Slot1");
saveSystem->loadGame("Slot1");
}
部署场景
-
移动设备部署: -
Android:数据存储在 /data/data/<package>/files/ -
iOS:数据存储在应用沙盒Documents目录
-
-
桌面平台部署: -
Windows: %APPDATA%/<appname>/ -
macOS: ~/Library/Application Support/<appname>/ -
Linux: ~/.local/share/<appname>/
-
-
Web部署: -
使用IndexedDB模拟本地存储 -
通过emscripten的LOCALSTORAGE API
-
疑难解答
常见问题1:UserDefault数据丢失
-
未调用flush()提交更改 -
应用安装在外部存储(某些Android版本会清除) -
多进程同时访问冲突
// 确保调用flush
UserDefault::getInstance()->flush();
// 使用绝对路径
auto fileUtils = FileUtils::getInstance();
std::string path = fileUtils->getWritablePath() + "userdefault.xml";
UserDefault::getInstance()->setFilePath(path);
常见问题2:JSON解析性能低下
-
一次性加载整个文件到内存 -
频繁的内存分配 -
缺乏流式处理
// 使用SAX解析器
class JsonHandler : public rapidjson::BaseReaderHandler<> {
public:
bool StartObject() { /* ... */ return true; }
bool Key(const char* str, SizeType length, bool copy) { /* ... */ return true; }
bool Int(int i) { /* ... */ return true; }
// 其他回调方法...
};
void parseLargeJson(const std::string& filePath) {
std::ifstream ifs(filePath);
rapidjson::IStreamWrapper isw(ifs);
JsonHandler handler;
rapidjson::Reader reader;
reader.Parse(isw, handler);
}
常见问题3:SQLite数据库锁定
-
多线程同时访问 -
长事务未提交 -
另一个进程占用数据库
// 使用互斥锁
std::mutex dbMutex;
void safeDbOperation(const std::function<void(sqlite3*)>& op) {
std::lock_guard<std::mutex> lock(dbMutex);
op(_db);
}
// 设置忙处理程序
sqlite3_busy_timeout(_db, 3000); // 3秒超时
// 使用WAL模式
sqlite3_exec(_db, "PRAGMA journal_mode=WAL", nullptr, nullptr, nullptr);
未来展望
-
云同步集成:自动同步本地数据到云端 -
加密存储:AES-256加密敏感数据 -
版本迁移:自动处理数据结构变更 -
数据分析:内置数据统计和分析功能 -
跨平台同步:PC/移动设备数据互通
技术趋势与挑战
趋势
-
二进制JSON:MessagePack/Cap'n Proto替代文本JSON -
NoSQL集成:MongoDB Realm等移动端NoSQL数据库 -
自动模式迁移:类似Core Data的版本管理 -
数据压缩:Zstandard/Snappy压缩存储 -
AI辅助优化:机器学习预测数据访问模式
挑战
-
数据一致性:多设备同步时的冲突解决 -
安全性:防止数据篡改和逆向工程 -
存储限制:移动设备有限的存储空间 -
性能瓶颈:大型数据集的高效查询 -
隐私合规:GDPR/CCPA等法规要求
总结
-
UserDefault:适合简单键值对配置存储 -
JSON:适合结构化数据和配置文件 -
SQLite:适合大量结构化数据和复杂查询
-
根据数据特性选择最合适的存储方案 -
实现高效可靠的数据持久化 -
处理常见的存储问题和性能瓶颈 -
设计可扩展的存储架构
-
简单设置使用UserDefault -
中等复杂度数据使用JSON -
大型结构化数据使用SQLite -
复杂应用组合使用多种方案
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)