(十三)C++自制植物大战僵尸游戏多用户存档实现(二)

植物大战僵尸游戏开发教程专栏地址icon-default.png?t=N7T8http://t.csdnimg.cn/8UFMs


UserData.h

在头文件中定义了枚举类型openUserDataReturnType,用于表示打开用户数据文件的返回状态。FileExistError表示文件存在但是打开错误,FileExistCorrect表示文件在且正确,FileNotExist表示文件不存在。

enum class openUserDataReturnType
{
    FileExistError = 1,
    FileExistCorrect,
    FileNotExist
};

 UserData是一个单例类,通过getInstance()方法获取其唯一的实例。

class UserData :public Director
{
public:
    static UserData* getInstance();
    void flushUserData();
    void flushLevelData();
    void flushSurvivalData();

    void caveUserData(char* key, double value);
    void caveUserData(char* key, bool value);
    void caveUserData(char* key, char* value);
    void caveUserData(char* key, int value);

    void caveLevelData(char* key);
    void openLevelData(char* key);
    bool isHaveLevelData(char* key);

    void caveSurvivalData(char* key);
    void openSurvivalData(char* key);
    bool isHaveSurvivalData(char* key);

    void openLevelPlantsData(char* key);
    void openLevelZombiesData(char* key);
    void openLevelSelectCardData(char* key);
    void openLevelSunData(char* key);
    void openLevelCoinData(char* key);
    void openLevelCarData(char* key);
    void openLevelBulletData(char* key);
    void openLevelOtherData(char* key);
    void openSurvivalOtherData(char* key);

    void removeLevelData(char* key);

    int openIntUserData(char* key);
    double openDoubleUserData(char* key);
    bool openBoolUserData(char* key);
    const char* openStringUserData(char* key);

    void createNewUserDataDocument();
    void createNewLevelDataDocument();
    void setAnewReadData(const bool newRead);

private:
    UserData();
    ~UserData();
    string getUserDataFileName();
    string getLevelDataFileName();
    string getSurvivalDataFileName();
    openUserDataReturnType openUserData();
    openUserDataReturnType openLevelData();
    openUserDataReturnType openSurvivalData();

    bool isHaveMember(char* key);
    bool readLevelData();

    void caveLevelPlantsData(char* key);
    void caveLevelZombiesData(char* key);
    void caveLevelSelectPlantsData(char* key);
    void caveLevelSunData(char* key);
    void caveLevelCoinData(char* key);
    void caveLevelCarData(char* key);
    void caveLevelBulletData(char* key);
    void caveLevelOtherData(char* key);
    void caveSurvivalOtherData(char* key);

    void replaceScene();
#ifndef DLLTEST
    string encryption(string& str);
    string decryption(string& str);
#endif // !DLLTEST

private:
    Document* _userDataDocument;
    Document* _levelDataDocument;
    FileUtils* _fileUtils;
    Global* _global;
    string _userData;
    string _levelData;
    bool _isAnewReadData;
    vector<GSScene*>_gsScene;
    static UserData* _instance;
};
  • 公有函数:
    • flushUserData()flushLevelData()flushSurvivalData():用于将用户数据、关卡数据和生存模式数据刷新到文件中。
    • caveUserData():用于向用户数据中存储不同类型的值,如doubleboolcharint
    • caveLevelData():根据键值key保存关卡数据。
    • openLevelData():打开关卡数据中指定键的值。
    • isHaveLevelData():检查关卡数据中是否存在指定键的值。
    • caveSurvivalData():根据键值key保存生存模式数据。
    • openSurvivalData():打开生存模式数据中指定键的值。
    • isHaveSurvivalData():检查生存模式数据中是否存在指定键的值。
    • openLevelPlantsData()openLevelZombiesData()等:打开关卡数据中不同类型的值,如植物数据、僵尸数据、选择卡片数据等。
    • removeLevelData():从关卡数据中移除指定键的值。
    • openIntUserData()openDoubleUserData()openBoolUserData()openStringUserData():打开用户数据中不同类型的值。
    • createNewUserDataDocument()createNewLevelDataDocument():创建新的用户数据和关卡数据文档
    • setAnewReadData():设置是否重新读取数据的标志。
  • 私有函数:
    • string getUserDataFileName():返回用户数据文件的名称。该函数会返回一个字符串,指定用于存储用户数据的文件名。
    • string getLevelDataFileName():返回关卡数据文件的名称。该函数会返回一个字符串,指定用于存储关卡数据的文件名。
    • string getSurvivalDataFileName():返回生存模式数据文件的名称。该函数会返回一个字符串,指定用于存储生存模式数据的文件名。
    • openUserDataReturnType openUserData():打开用户数据。该函数会返回枚举值,用于指示用户数据的打开状态或结果。
    • openUserDataReturnType openLevelData():打开关卡数据。类似于openUserData(),该函数会返回一个枚举值,用于指示关卡数据的打开状态或结果。
    • openUserDataReturnType openSurvivalData():打开生存模式数据。类似于openUserData(),该函数会返回一个枚举值值,用于指示生存模式数据的打开状态或结果。
    • bool isHaveMember(char* key):检查指定键是否存在于数据中。该函数接受一个char*类型的键作为参数,并返回一个布尔值,指示该键是否存在于数据中。
    • bool readLevelData():读取关卡数据。该函数可能会返回一个布尔值。
    • void caveLevelPlantsData(char* key):向关卡数据中存储植物数据。该函数接受一个char*类型的键作为参数,并将植物数据存储在关卡数据中。
    • void caveLevelZombiesData(char* key):向关卡数据中存储僵尸数据。类似于caveLevelPlantsData(char* key),该函数将僵尸数据存储在关卡数据中。
    • void caveLevelSelectPlantsData(char* key):向关卡数据中存储选择的植物数据。类似于前面的函数,该函数将选择的植物数据存储在关卡数据中。
    • void caveLevelSunData(char* key):向关卡数据中存储阳光数据。类似于前面的函数,该函数将阳光数据存储在关卡数据中。
    • void caveLevelCoinData(char* key):向关卡数据中存储金币数据。类似于前面的函数,该函数将金币数据存储在关卡数据中。
    • void caveLevelCarData(char* key):向关卡数据中存储车辆数据。类似于前面的函数,该函数将车辆数据存储在关卡数据中。
    • void caveLevelBulletData(char* key):向关卡数据中存储子弹数据。类似于前面的函数,该函数将子弹数据存储在关卡数据中。
    • void caveLevelOtherData(char* key):向关卡数据中存储其他类型的数据。类似于前面的函数,该函数将其他类型的数据存储在关卡数据中。
    • void caveSurvivalOtherData(char* key):向生存模式数据中存储其他类型的数据。类似于前面的函数,该函数将其他类型的数据存储在生存模式数据中。
    • void replaceScene():替换场景。
  • 私有变量:
    • _userDataDocument_levelDataDocument:指向用户数据和关卡数据的Document对象。
    • _fileUtils:指向FileUtils对象,用于文件操作。
    • _global:指向Global对象。
    • _userData_levelData:存储用户数据和关卡数据的字符串。
    • _isAnewReadData:是否重新读取数据的标志。
    • _gsScene:存储GSScene对象的向量。

UserData.cpp 

构造函数

UserData::UserData() :
  _global(Global::getInstance())
, _fileUtils(FileUtils::getInstance())
, _userDataDocument(nullptr)
, _levelDataDocument(nullptr)
, _isAnewReadData(false)
{
}

构造函数UserData::UserData()初始化了UserData类的成员变量。具体来说:

  • _global(Global::getInstance()):将Global类的唯一实例赋值给_global成员变量。Global类可能是游戏中的全局管理类,通过调用getInstance()方法获取其唯一实例。

  • _fileUtils(FileUtils::getInstance()):将FileUtils类的唯一实例赋值给_fileUtils成员变量。FileUtils类可能是用于文件操作的工具类,通过调用getInstance()方法获取其唯一实例。

  • _userDataDocument(nullptr)_levelDataDocument(nullptr):将用户数据和关卡数据的文档指针初始化为nullptr,表示当前没有打开的数据文档。

  • _isAnewReadData(false):将重新读取数据的标志初始化为false,表示默认情况下不重新读取数据。


析构函数 

UserData::~UserData()
{
	if (_userDataDocument) delete _userDataDocument, _userDataDocument = nullptr;
	if (_levelDataDocument) delete _levelDataDocument, _levelDataDocument = nullptr;
	_isAnewReadData = false;
}

析构函数UserData::~UserData()用于清理UserData类的资源。具体来说:

  • if (_userDataDocument) delete _userDataDocument, _userDataDocument = nullptr;:如果_userDataDocument不为nullptr,则删除它指向的对象,并将其设置为nullptr,以释放用户数据文档的内存资源。

  • if (_levelDataDocument) delete _levelDataDocument, _levelDataDocument = nullptr;:如果_levelDataDocument不为nullptr,则删除它指向的对象,并将其设置为nullptr,以释放关卡数据文档的内存资源。

  • _isAnewReadData = false;:将重新读取数据的标志设置为false。


getInstance()函数

UserData* UserData::getInstance()
{
	if (_instance == nullptr)
	{
		_instance = new (std::nothrow)UserData;
	}
	return _instance;
}

该函数实现了单例模式,用于获取UserData类的唯一实例。具体来说:

  • if (_instance == nullptr):检查静态成员变量_instance是否为nullptr,即是否已经创建了实例。

  • _instance = new (std::nothrow) UserData;:如果_instancenullptr,则创建一个新的UserData对象,并将其赋值给_instance。这里使用了std::nothrow,表示在内存分配失败时不会抛出异常,而是返回nullptr

  • return _instance;:返回_instance,即UserData类的唯一实例。

通过调用UserData::getInstance(),可以获取到UserData类的单例实例,确保在整个程序中只有一个UserData对象存在。


flushUserData()函数 

void UserData::flushUserData()
{
	StringBuffer buffer;
	rapidjson::Writer<StringBuffer> Writer(buffer);
	_userDataDocument->Accept(Writer);

	string str = string(buffer.GetString());

#ifdef DEBUG
	_fileUtils->writeStringToFile(str, getUserDataFileName());
#else
#   ifndef DLLTEST
	_fileUtils->writeStringToFile(encryption(str), getUserDataFileName());
#   else
	char* buf = new char[str.length() * 3];
	encryption(str.c_str(), buf);
	_fileUtils->writeStringToFile(buf, getUserDataFileName());
	CC_SAFE_DELETE(buf);
#   endif
#endif
	_isAnewReadData = false; // if flush must anew read data
}

该函数用于将用户数据刷新到文件中。具体来说:

  • _userDataDocument中的数据序列化为字符串形式,并保存在str中。

  • 根据编译选项进行不同的处理:

    • #ifdef DEBUG:如果定义了DEBUG宏,则直接将字符串数据写入文件,不进行加密处理。
    • #ifndef DLLTEST:如果未定义DLLTEST宏,则使用加密函数encryption()对字符串进行加密,并将加密后的数据写入文件。
    • #else:如果定义了DLLTEST宏,则先将字符串转换为char*类型的缓冲区buf,然后使用加密函数encryption()对数据进行加密,最后将加密后的数据写入文件。注意,在写入文件后,需要使用CC_SAFE_DELETE删除缓冲区的内存。
  • _isAnewReadData = false;:将重新读取数据的标志设置为false,表示在刷新数据后不需要重新读取数据。

该函数的作用是将_userDataDocument中的数据保存到文件中,根据编译选项和宏定义进行不同的处理(如加密),以满足特定的需求和安全性要求。


flushLevelData()函数 

void UserData::flushLevelData()
{
	StringBuffer buffer;
	rapidjson::Writer<StringBuffer> Writer(buffer);
	_levelDataDocument->Accept(Writer);

	string str = string(buffer.GetString());

#ifdef DEBUG
	_fileUtils->writeStringToFile(str, getLevelDataFileName());
#else
#   ifndef DLLTEST
	_fileUtils->writeStringToFile(encryption(str), getLevelDataFileName());
#   else
	char* buf = new char[str.length() * 3];
	encryption(str.c_str(), buf);
	_fileUtils->writeStringToFile(buf, getLevelDataFileName());
	CC_SAFE_DELETE(buf);
#   endif
#endif
}

该函数用于将关卡数据刷新到文件中。具体来说:

  • _levelDataDocument中的数据序列化为字符串形式,并保存在str中。

  • 根据编译选项进行不同的处理:

    • #ifdef DEBUG:如果定义了DEBUG宏,则直接将字符串数据写入文件,不进行加密处理。
    • #ifndef DLLTEST:如果未定义DLLTEST宏,则使用加密函数encryption()对字符串进行加密,并将加密后的数据写入文件。
    • #else:如果定义了DLLTEST宏,则先将字符串转换为char*类型的缓冲区buf,然后使用加密函数encryption()对数据进行加密,最后将加密后的数据写入文件。注意,在写入文件后,需要使用CC_SAFE_DELETE删除缓冲区的内存。

该函数的作用是将_levelDataDocument中的数据保存到文件中,根据编译选项和宏定义进行不同的处理(如加密),以满足特定的需求和安全性要求。


openUserData()函数 

openUserDataReturnType UserData::openUserData()
{
	// 如果有这个存档
	if (_fileUtils->isFileExist(getUserDataFileName()))
	{
		if (_userData.empty()|| !_isAnewReadData)
		{
			_isAnewReadData = true;
#ifdef DEBUG
			_userData = _fileUtils->getStringFromFile(getUserDataFileName());
			_userDataDocument->Parse<rapidjson::kParseDefaultFlags>(_userData.c_str());
#else
#   ifndef DLLTEST
			_userData = _fileUtils->getStringFromFile(getUserDataFileName());
			_userDataDocument->Parse<rapidjson::kParseDefaultFlags>(decryption(_userData).c_str());
#   else
			_userData = _fileUtils->getStringFromFile(getUserDataFileName());
			char* buf = new char[_userData.length()];
			if (decryption(_userData.c_str(), buf)) {
				_userDataDocument->Parse<rapidjson::kParseDefaultFlags>(buf);
			}
			else {
				CC_SAFE_DELETE(buf);
				return openUserDataReturnType::FileExistError;
		}
			CC_SAFE_DELETE(buf);
#   endif
#endif
			if (_userDataDocument->HasParseError()) {
				return openUserDataReturnType::FileExistError;
			}
		}
		return openUserDataReturnType::FileExistCorrect;
	}
	else
	{
		if (!_userDataDocument->IsObject())
		{
			_userDataDocument->SetObject();
			rapidjson::Value _object(rapidjson::kObjectType);
			_userDataDocument->AddMember("UserData", _object, _userDataDocument->GetAllocator());
		}
		return openUserDataReturnType::FileNotExist;
	}
}

该函数用于打开用户数据文件并读取数据。具体来说:

  • 首先判断用户数据文件是否存在,通过调用_fileUtils->isFileExist(getUserDataFileName())来检查。

  • 如果文件存在,执行以下操作:

    • 检查 _userData 是否为空或 _isAnewReadData 是否为 false
    • 如果满足条件,进行数据的读取和解析:
      • #ifdef DEBUG:如果定义了 DEBUG 宏,则直接从文件中读取字符串数据到 _userData,然后使用 _userDataDocument 对象进行解析。
      • #ifndef DLLTEST:如果未定义 DLLTEST 宏,则先从文件中读取经过加密的字符串数据到 _userData,然后使用解密函数 decryption() 对数据进行解密,并使用 _userDataDocument 对象进行解析。
      • #else:如果定义了 DLLTEST 宏,则先从文件中读取字符串数据到 _userData,然后创建一个缓冲区 buf,调用解密函数 decryption() 对数据进行解密,将解密后的数据通过 _userDataDocument 进行解析。如果解密失败,则释放缓冲区内存,返回 openUserDataReturnType::FileExistError
    • 检查解析过程中是否出错,通过调用 _userDataDocument->HasParseError() 来判断。如果出错,返回 openUserDataReturnType::FileExistError
  • 如果文件不存在,执行以下操作:

    • 检查 _userDataDocument 是否为对象类型,如果不是,则将其设置为对象类型,并添加一个名为 "UserData" 的成员对象。
    • 返回 openUserDataReturnType::FileNotExist

该函数的作用是打开用户数据文件,根据文件的存在与否执行相应的读取和解析操作,并返回相应的状态枚举值,用于判断操作是否成功以及文件的存在状态。


openLevelData()函数 

openUserDataReturnType UserData::openLevelData()
{
	// 如果有这个存档
	if (_fileUtils->isFileExist(getLevelDataFileName()))
	{
#ifdef DEBUG
		_levelData = _fileUtils->getStringFromFile(getLevelDataFileName());
		_levelDataDocument->Parse<rapidjson::kParseDefaultFlags>(_levelData.c_str());
#else
#   ifndef DLLTEST
		_levelData = _fileUtils->getStringFromFile(getLevelDataFileName());
		_levelDataDocument->Parse<rapidjson::kParseDefaultFlags>(decryption(_levelData).c_str());
#   else
		_levelData = _fileUtils->getStringFromFile(getLevelDataFileName());
		char* buf = new char[_levelData.length()];
		if (decryption(_levelData.c_str(), buf)) {
			_levelDataDocument->Parse<rapidjson::kParseDefaultFlags>(buf);
		}
		else {
			CC_SAFE_DELETE(buf);
			return openUserDataReturnType::FileExistError;
		}
		CC_SAFE_DELETE(buf);
#   endif
#endif
		if (_levelDataDocument->HasParseError()) {
			return openUserDataReturnType::FileExistError;
		}
		return openUserDataReturnType::FileExistCorrect;
	}
	else{
		return openUserDataReturnType::FileNotExist;
	}
}

解释同理,见openUserData()函数。


caveUserData()函数

void UserData::caveUserData(char* key, double value)
{
	switch (openUserData())
	{
	case openUserDataReturnType::FileExistCorrect:
		if (isHaveMember(key))
			(*_userDataDocument)["UserData"][key].SetDouble(value);
		else
			(*_userDataDocument)["UserData"].AddMember(rapidjson::StringRef(key), value, _userDataDocument->GetAllocator());
		break;
	case openUserDataReturnType::FileNotExist:
		(*_userDataDocument)["UserData"].AddMember(rapidjson::StringRef(key), value, _userDataDocument->GetAllocator());
		break;
	case openUserDataReturnType::FileExistError:
		remove(getUserDataFileName().c_str());
		break;
	}
	flushUserData();
}

该函数用于在用户数据中添加或更新指定键(key)对应的双精度浮点数值(value)。具体来说:

  • 调用 openUserData() 函数打开用户数据文件,并根据返回的状态进行不同的操作。

  • 使用 switch 语句根据不同的返回状态进行处理:

    • openUserDataReturnType::FileExistCorrect:如果文件存在且打开成功,执行以下操作:
      • 调用 isHaveMember(key) 函数检查指定键是否存在于用户数据中。
      • 如果键存在,使用 SetDouble() 函数更新对应的双精度浮点数值。
      • 如果键不存在,使用 AddMember() 函数将指定键和值添加到用户数据中。
    • openUserDataReturnType::FileNotExist:如果文件不存在,执行以下操作:
      • 使用 AddMember() 函数将指定键和值添加到用户数据中。
    • openUserDataReturnType::FileExistError:如果文件存在但打开出错,执行以下操作:
      • 调用 remove(getUserDataFileName().c_str()) 函数删除用户数据文件。
  • 调用 flushUserData() 函数将更新后的用户数据刷新到文件中。

该函数的作用是在用户数据中添加或更新指定键对应的双精度浮点数值,并将更新后的用户数据保存到文件中。


openIntUserData()函数

int UserData::openIntUserData(char* key)
{
	switch (openUserData())
	{
	case openUserDataReturnType::FileExistCorrect:
		if (isHaveMember(key))
			return (*_userDataDocument)["UserData"][key].GetInt();
		break;
	case openUserDataReturnType::FileExistError:
		remove(getUserDataFileName().c_str());
		break;
	default: break;
	}
	return 0;
}

该函数用于从用户数据中获取指定键(key)对应的整数值,并返回该整数值。具体来说:

  • 调用 openUserData() 函数打开用户数据文件,并根据返回的状态进行不同的操作。

  • 使用 switch 语句根据不同的返回状态进行处理:

    • openUserDataReturnType::FileExistCorrect:如果文件存在且打开成功,执行以下操作:
      • 调用 isHaveMember(key) 函数检查指定键是否存在于用户数据中。
      • 如果键存在,使用 GetInt() 函数获取对应的整数值,并返回该值。
    • openUserDataReturnType::FileExistError:如果文件存在但打开出错,执行以下操作:
      • 调用 remove(getUserDataFileName().c_str()) 函数删除用户数据文件。
  • 如果未满足上述条件,则返回默认值 0。

该函数的作用是从用户数据中获取指定键对应的整数值,并返回该值。如果用户数据文件存在且打开成功,并且指定键存在于用户数据中,则返回对应的整数值;否则返回默认值 0。如果文件存在但打开出错,则删除用户数据文件。


createNewUserDataDocument()函数 

void UserData::createNewUserDataDocument()
{
	if (_userDataDocument)
	{
		delete _userDataDocument;
		_userDataDocument = nullptr;
	}
	_userDataDocument = new Document();
	_userData.clear();
	_isAnewReadData = false;
}

该函数用于创建一个新的用户数据文档对象。具体来说:

  • 首先检查 _userDataDocument 是否已存在,如果存在,则执行以下操作:

    • 使用 delete 关键字释放 _userDataDocument 的内存。
    • 将 _userDataDocument 指针设置为 nullptr,以确保不再指向已释放的内存。
  • 创建一个新的 Document 对象,并将其赋值给 _userDataDocument

  • 清空 _userData 字符串。

  • 将 _isAnewReadData 设置为 false,表示没有重新读取数据。

该函数的作用是创建一个新的用户数据文档对象,并进行必要的清理和重置操作,以便重新使用和处理用户数据。


caveLevelData()函数 

void UserData::caveLevelData(char* key)
{
	switch (openLevelData())
	{
	case openUserDataReturnType::FileExistCorrect:
		if ((*_levelDataDocument).HasMember(key))
			(*_levelDataDocument).RemoveMember(key);
		break;
	case openUserDataReturnType::FileExistError:
		remove(getLevelDataFileName().c_str());
		return;
		break;
	}

	if (!_levelDataDocument->IsObject())_levelDataDocument->SetObject();

	rapidjson::Value object(rapidjson::kObjectType);
	_levelDataDocument->AddMember(rapidjson::StringRef(key), object, _levelDataDocument->GetAllocator());

	caveLevelPlantsData(key);
	caveLevelZombiesData(key);
	caveLevelSelectPlantsData(key);
	caveLevelOtherData(key);
	caveLevelSunData(key);
	caveLevelCoinData(key);
	caveLevelCarData(key);
	caveLevelBulletData(key);

	flushLevelData();
}

该函数用于在关卡数据中创建一个新的键(key)。具体来说:

  • 调用 openLevelData() 函数打开关卡数据文件,并根据返回的状态进行不同的操作。

  • 使用 switch 语句根据不同的返回状态进行处理:

    • openUserDataReturnType::FileExistCorrect:如果文件存在且打开成功,执行以下操作:
      • 检查关卡数据文档中是否存在指定键(key)。
      • 如果存在,使用 RemoveMember() 函数从关卡数据中移除该键。
    • openUserDataReturnType::FileExistError:如果文件存在但打开出错,执行以下操作:
      • 调用 remove(getLevelDataFileName().c_str()) 函数删除关卡数据文件。
      • 返回函数,即终止函数的执行。
  • 检查 _levelDataDocument 是否为对象类型,如果不是,则使用 SetObject() 函数将其设置为对象类型。

  • 创建一个新的 Value 对象,并将其添加到关卡数据文档中,键为指定键(key),值为空对象。

  • 调用其他函数(如 caveLevelPlantsData()caveLevelZombiesData() 等)来处理关卡数据的其他方面。

  • 调用 flushLevelData() 函数将更新后的关卡数据保存到文件中。

该函数的作用是在关卡数据中创建一个新的键,并进行相关的数据处理和保存操作。


caveLevelPlantsData()函数 

void UserData::caveLevelPlantsData(char* key)
{
	unsigned int plantsNumber = 0;
	rapidjson::Value _object(rapidjson::kObjectType);
	rapidjson::Document::AllocatorType& allocator = _levelDataDocument->GetAllocator();
	(*_levelDataDocument)[key].AddMember("Plants", _object, allocator);
	
	for (auto plant : PlantsGroup)
	{
		rapidjson::Value object(rapidjson::kObjectType);
		
		auto visible = plant.second->getPlantAnimation()->isVisible();
		if (visible)
		{
			object.AddMember("PlantsTag", plant.second->getPlantTag(), allocator);
			object.AddMember("PlantsHealthPoint", plant.second->getPlantHealthPoint(), allocator);
			object.AddMember("PlantsPositionX", plant.second->getPlantAnimation()->getPositionX(), allocator);
			object.AddMember("PlantsPositionY", plant.second->getPlantAnimation()->getPositionY(), allocator);
			object.AddMember("PlantsRow", plant.second->getPlantRow(), allocator);
			object.AddMember("PlantsColumn", plant.second->getPlantColumn(), allocator);
			object.AddMember("PlantsLocalZOrder", plant.second->getPlantAnimation()->getLocalZOrder(), allocator);
			object.AddMember("PlantsType", static_cast<int>(plant.second->getPlantType()), allocator);
			object.AddMember("PlantVisible", visible, allocator);

			switch (plant.second->getPlantType())
			{
			case PlantsType::SunFlower:
				object.AddMember("SunShowTime.X", dynamic_cast<SunFlower*>(plant.second)->getSunShowTime().x, allocator);
				object.AddMember("SunShowTime.Y", dynamic_cast<SunFlower*>(plant.second)->getSunShowTime().y, allocator);
				break;
			case PlantsType::PotatoMine:
				object.AddMember("BreakGround", dynamic_cast<PotatoMine*>(plant.second)->getBreakGround(), allocator);
				break;
			default:
				break;
			}

			auto number = to_string(++plantsNumber);
			char* str = new char[number.size() + 1];
			strcpy(str, number.c_str());
			str[number.size()] = '\0';
			(*_levelDataDocument)[key]["Plants"].AddMember(rapidjson::StringRef(str), object, _levelDataDocument->GetAllocator());
		}
	}

	(*_levelDataDocument)[key]["Plants"].AddMember("PlantsNumber", plantsNumber, allocator);
}

该函数用于处理关卡数据中的植物数据。具体来说:

  • 创建一个无类型的 _object 对象,用于作为关卡数据文档中 "Plants" 键的值。

  • 获取 _levelDataDocument 的分配器(allocator)。

  • 将 _object 添加到关卡数据文档中的指定键(key)下的 "Plants" 键。

  • 遍历 PlantsGroup,这是一个存储植物对象的容器。

  • 对于每个植物对象(plant)进行以下操作:

    • 检查植物动画是否可见,如果可见则继续处理。
    • 创建一个无类型的 object 对象,用于存储植物的数据。
    • 将植物的各种属性(如标签、生命值、位置、行列、层级、类型等)添加到 object 中。
    • 根据植物类型的不同,添加特定类型的属性到 object 中。
    • 生成一个表示植物数量的字符串,将其转换为字符数组,并将其作为键添加到关卡数据文档中的 "Plants" 键下,并将 object 作为值。
  • 最后,将 "PlantsNumber" 键和植物数量值添加到关卡数据文档中的 "Plants" 键下。

该函数的作用是处理关卡数据中的植物相关信息,包括植物的位置、属性和数量,并将其保存到关卡数据文档中。

 


openLevelPlantsData()函数

void UserData::openLevelPlantsData(char* key)
{
	auto plantsNumbers = (*_levelDataDocument)[key]["Plants"]["PlantsNumber"].GetInt();
	for (int i = 1; i <= plantsNumbers; ++i)
	{
		auto type = static_cast<PlantsType>((*_levelDataDocument)[key]["Plants"][to_string(i).c_str()]["PlantsType"].GetInt());
		auto plants = animationLayerInformation->createDifferentPlants(type);
		plants->setPlantPosition(Vec2(
			(*_levelDataDocument)[key]["Plants"][to_string(i).c_str()]["PlantsPositionX"].GetFloat(),
			(*_levelDataDocument)[key]["Plants"][to_string(i).c_str()]["PlantsPositionY"].GetFloat()));
		plants->setPlantLocalZOrder((*_levelDataDocument)[key]["Plants"][to_string(i).c_str()]["PlantsLocalZOrder"].GetInt());
		plants->setPlantRowAndColumn(Vec2(
			(*_levelDataDocument)[key]["Plants"][to_string(i).c_str()]["PlantsRow"].GetInt(),
			(*_levelDataDocument)[key]["Plants"][to_string(i).c_str()]["PlantsColumn"].GetInt()));
		plants->setPlantTag((*_levelDataDocument)[key]["Plants"][to_string(i).c_str()]["PlantsTag"].GetInt());
		
		switch (type)
		{
		case PlantsType::SunFlower:
			dynamic_cast<SunFlower*>(plants)->setSunShowTime(
				Vec2((*_levelDataDocument)[key]["Plants"][to_string(i).c_str()]["SunShowTime.X"].GetFloat(),
					(*_levelDataDocument)[key]["Plants"][to_string(i).c_str()]["SunShowTime.Y"].GetFloat()));
			break;
		case PlantsType::PotatoMine:
			dynamic_cast<PotatoMine*>(plants)->setBreakGround((*_levelDataDocument)[key]["Plants"][to_string(i).c_str()]["BreakGround"].GetFloat());
			break;
		default:
			break;
		}

		plants->createPlantAnimation();
		plants->setPlantHealthPoint((*_levelDataDocument)[key]["Plants"][to_string(i).c_str()]["PlantsHealthPoint"].GetFloat());
		plants->getPlantAnimation()->setVisible((*_levelDataDocument)[key]["Plants"][to_string(i).c_str()]["PlantVisible"].GetBool());
		plants->getPlantAnimation()->getChildByName("SplashOfSoil")->setOpacity(0);

		PlantsGroup.insert(pair<int, Plants*>((*_levelDataDocument)[key]["Plants"][to_string(i).c_str()]["PlantsTag"].GetInt(), plants));
		
		controlLayerInformation->_gameMapInformation->plantsMap[plants->getPlantColumn()][plants->getPlantRow()] = static_cast<unsigned int>(type);/* 地图记录种植的植物 */
	}
}

该函数用于读取关卡数据中的植物信息,并根据这些信息创建和设置植物对象。具体来说:

  • 获取存储在关卡数据文档中的 "PlantsNumber" 键的整数值,表示植物的数量。

  • 使用循环遍历每个植物的索引,从 1 到植物数量。

  • 根据索引,获取植物的类型,并将其转换为 PlantsType 枚举类型。

  • 使用 animationLayerInformation 对象的 createDifferentPlants() 方法创建具有指定类型的植物对象,并将其存储在 plants 变量中。

  • 从关卡数据文档中获取植物的位置、层级、行列、标签等属性,并将这些属性设置到植物对象中。

  • 根据植物类型的不同,进一步处理特定类型的属性,如太阳花的太阳出现时间或土豆雷的破土状态。

  • 创建植物的动画,并设置植物的生命值、可见性以及相关动画的属性。

  • 将植物对象添加到 PlantsGroup 容器中,使用植物标签作为键。

  • 在 controlLayerInformation->_gameMapInformation->plantsMap 中记录植物的位置这段代码看起来是一个游戏开发中的函数,用于读取关卡中的植物数据,并创建相应的植物对象。

  • 创建植物的动画,并设置植物的生命值、可见性以及相关动画的属性。

  • 将植物对象添加到 PlantsGroup 容器中,使用植物标签作为键。

  • 在地图数据中记录植物的位置。

这段代码的目的是根据关卡数据创建植物对象,并将其放置在游戏场景中的适当位置上。具体的实现细节可能根据上下文和代码的其他部分有所变化。


removeLevelData()函数 

void UserData::removeLevelData(char* key)
{
	switch (openLevelData())
	{
	case openUserDataReturnType::FileExistCorrect:
		if ((*_levelDataDocument).HasMember(key))
			(*_levelDataDocument).RemoveMember(key);
		break;
	default:
		return;
		break;
	}
	flushLevelData();
}

该函数用于从关卡数据中移除指定键值的数据。具体来说:

  • 调用openLevelData()函数,根据返回值判断关卡数据文件是否存在并正确打开。

  • 使用switch语句根据openLevelData()的返回值执行不同的操作。

  • 如果返回值是openUserDataReturnType::FileExistCorrect,表示关卡数据文件存在且正确打开。

  • 检查关卡数据文档是否包含指定的键key,如果存在则执行下一步操作。

  • 使用RemoveMember(key)函数从关卡数据文档中移除指定的键值对。

  • 如果openLevelData()的返回值不是openUserDataReturnType::FileExistCorrect,或者关卡数据文档不包含指定的键,则函数直接返回。

  • 在移除键值对后,调用flushLevelData()函数来刷新关卡数据,将更改写入到磁盘。

该函数的作用是删除关卡数据中特定键值的条目,并将更改保存到磁盘。请注意,函数中使用的openLevelData()flushLevelData()函数的实现没有给出,因此具体的逻辑和实现细节可能在这些函数中。


encryption函数 

string UserData::encryption(string& str)
{
	char* encryptString, * encryptString1;
	base64Encode((unsigned char*)str.c_str(), (unsigned int)str.length(), &encryptString);

	string sss(encryptString);
	reverse(sss.begin(), sss.end());
	for (auto& s : sss)
	{
		if (s >= 'a' && s <= 'z')s = ((s - 'a') + 2) % 26 + 'a';
		if (s >= 'A' && s <= 'Z')s = ((s - 'A') + 5) % 26 + 'A';
		if (s >= '0' && s <= '9')s = ((s - '0') + 7) % 10 + '0';
		if (s == '=')s = '+';
	}

	base64Encode((unsigned char*)sss.c_str(), (unsigned int)sss.length(), &encryptString1);

	string s(encryptString1);

	CC_SAFE_FREE(encryptString);
	CC_SAFE_FREE(encryptString1);

	return s;
}

该函数用于对字符串进行加密处理。具体来说:

  • 使用base64Encode()函数将字符串str进行Base64编码,并将结果存储在encryptString中。

  • 创建一个string类型的变量sss,并将encryptString赋值给它。

  • 使用reverse()函数将sss字符串进行反转。

  • 遍历sss字符串中的每个字符,根据字符的范围进行不同的加密操作:

    • 如果字符在小写字母az之间,将字符进行循环右移2位。
    • 如果字符在大写字母AZ之间,将字符进行循环右移5位。
    • 如果字符在数字09之间,将字符进行循环右移7位。
    • 如果字符是等号=,将其替换为加号+
  • 使用base64Encode()函数将经过加密处理后的sss字符串进行Base64编码,并将结果存储在encryptString1中。

  • 创建一个string类型的变量s,并将encryptString1赋值给它。

  • 使用CC_SAFE_FREE()函数释放encryptStringencryptString1的内存。

  • 返回加密后的字符串s

该函数的作用是对输入的字符串进行加密处理,首先使用Base64编码,然后进行字符位置的循环右移和字符替换操作。


decryption()函数 

string UserData::decryption(string& str)
{
	unsigned char* decryptString = nullptr, * decryptString1 = nullptr;
	
	auto ret = base64Decode((unsigned char*)str.c_str(), (unsigned int)str.length(), &decryptString);
	
	if (ret > 0) decryptString[ret] = '\0';
	else return "";
	
	string sss(reinterpret_cast<char*>(decryptString));
	
	for (auto& s : sss)
	{
		if (s >= 'a' && s <= 'z')s = ((s - 'a') + 24) % 26 + 'a';
		if (s >= 'A' && s <= 'Z')s = ((s - 'A') + 21) % 26 + 'A';
		if (s >= '0' && s <= '9')s = ((s - '0') + 3) % 10 + '0';
		if (s == '+')s = '=';
	}
	reverse(sss.begin(), sss.end());

	ret = base64Decode((unsigned char*)sss.c_str(), (unsigned int)sss.length(), &decryptString1);

	if (ret > 0) decryptString1[ret] = '\0';
	else { CC_SAFE_FREE(decryptString); return ""; }

	string s(reinterpret_cast<char*>(decryptString1));

	CC_SAFE_FREE(decryptString);
	CC_SAFE_FREE(decryptString1);

	return s;
}

该函数用于对加密过的字符串进行解密处理。具体来说:

  • 创建两个指针decryptStringdecryptString1,并初始化为nullptr

  • 使用base64Decode()函数将字符串str进行Base64解码,并将结果存储在decryptString中。保存解码后的字节数到ret中。

  • 如果解码成功,将decryptString最后一个字节设置为\0,表示字符串的结束。否则,返回空字符串。

  • 使用reinterpret_castdecryptString转换为string类型,并赋值给sss

  • 遍历sss字符串中的每个字符,根据字符的范围进行不同的解密操作:

    • 如果字符在小写字母az之间,将字符进行循环左移2位。
    • 如果字符在大写字母AZ之间,将字符进行循环左移5位。
    • 如果字符在数字09之间,将字符进行循环左移3位。
    • 如果字符是加号+,将其替换为等号=
  • 使用reverse()函数将sss字符串进行反转。

  • 使用base64Decode()函数将经过解密处理后的sss字符串进行Base64解码,并将结果存储在decryptString1中。保存解码后的字节数到ret中。

  • 如果解码成功,将decryptString1最后一个字节设置为\0,表示字符串的结束。否则,释放decryptString的内存,并返回空字符串。

  • 使用reinterpret_castdecryptString1转换为string类型,并赋值给s

  • 使用CC_SAFE_FREE()函数释放decryptStringdecryptString1的内存。

  • 返回解密后的字符串s

该函数的作用是对输入的加密字符串进行解密处理,包括Base64解码、字符位置的循环左移和字符替换操作。

其他函数

函数众多,列举了部分重要函数,其他函数看自行查看。 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python | Leetcode Python题解之第29题两数相除

题目&#xff1a; 题解&#xff1a; class Solution:def divide(self, dividend: int, divisor: int) -> int:INT_MIN, INT_MAX -2**31, 2**31 - 1# 考虑被除数为最小值的情况if dividend INT_MIN:if divisor 1:return INT_MINif divisor -1:return INT_MAX# 考虑除数为…

Pandas数据分析学习笔记

前言 开刷Pandas数据分析&#xff0c;看起来很好理解&#xff0c;不过没做笔记没敲代码心里总是不安稳&#xff0c;所以复现下课程代码并演示其中遇到的问题&#xff0c;顺便水一水笔记好了 参考资料&#xff1a; 课程视频链接&#xff1a;Pandas数据分析从入门到实战 数据…

Halo自定义页面

在使用Halo后台维护项目&#xff0c;有的页面是固定的&#xff0c;但内容需要一些自定义样式&#xff0c;内容动态编辑生成&#xff0c;这个时候就需要自定义页面; Halo版本 版本&#xff1a;2.121.首先在theme.yaml中添加自定义页面并指定文件名 spec:customTemplates:page:…

面试题集中营—GC日志简析及频繁GC的调优

如何查看GC日志 有两种方式查看GC日志&#xff0c;一种是动态命令行查看 jstat -gc <pid> 300 5 第二种就是在JVM参数中增加打印的参数&#xff0c;如下&#xff1a; -XX:PrintGCDetails -XX:PrintGCTimeStamps 表示打印每次GC的日志以及GC发生的时间 -Xloggc:gc.log …

如何解决PPT中获取加载项是灰色的,无法链接到Power BI的问题?

问题描述&#xff1a; 最近有朋友留言询问:“在尝试之前我发布的如何在PPT中展示Power BI报告的操作步骤的时候&#xff0c;想要在PPT中展示Power BI报告&#xff1f;只需这样做&#xff01; (qq.com) 碰到在PowerPoint中【获取加载项选项】是灰色&#xff0c;无法链加载Powe…

WordPress网站上添加看板娘

续接上篇——基于LNMP部署wordpress-CSDN博客 目录 一.下载并解压 二.设置头文件 修改header.php 修改配置文件footer.php 三.将你设置的主题包上传到/usr/share/nginx/html/wp-content这个目录里 四.扩展——将看板娘修改到左侧 一.下载并解压 [rootaliyun ~]# wget htt…

技术分享 | app测试中常用的Android模拟器

Emulator Emualor 是 Android Studio 自带的模拟器&#xff0c;是官方提供的工具&#xff0c;Android 开发最常使用的就是这一款。 它功能非常齐全&#xff0c;电话本、通话等功能都可正常使用。用户可以使用键盘输入&#xff0c;鼠标点击模拟器按键输入&#xff0c;甚至还可…

安卓投屏延时数据如何测试,测试工具如何写?

背景&#xff1a; 投屏其实在android等使用场景非常非常多&#xff0c;比如现在火爆小米汽车的车机&#xff0c;上面涉及到手机和车机互联画面相关都是属于投屏范围。这种跨设备的投屏场景&#xff0c;流畅的体验是最重要的&#xff0c;这里就会要求投屏中最重要的一个性能指标…

Nerf技术原理

Neural Radiance Fields (NeRF) 是一种3D场景重建技术,用于从一组稀疏的2D图像创建高质量的3D模型。这一技术基于深度学习,通过训练一个神经网络来模拟场景的体积密度和颜色分布,实现在新的视角下渲染出高质量的3D图像。 NeRF的核心原理 NeRF的核心在于使用一个全连接的神经…

开源事件通知库libevent及网络连接管理模块bufferevent详解

目录 1、libevent介绍 1.1、什么是libevent&#xff1f; 1.2、libevent特点 1.3、网络连接管理模块bufferevent 2、bufferevent有什么用&#xff1f; 3、bufferevent的整体设计与实现细节 3.1、整体概况 3.2、evbuffer与bufferevent 3.3、defer callback 4、bufferev…

听说英伟达和诺和诺德要共建医药研发超算,超算安腾默默笑了

继黄仁勋公开发表“生命科学才是未来”的观点后&#xff0c;英伟达押注生物计算赛道又有新动作。日前&#xff0c;诺和诺德基金会宣布将出资和英伟达合作&#xff0c;在丹麦建造一台专注于生成式AI应用的超级计算机&#xff0c;以推动医疗保健、生命科学和绿色转型领域的研究与…

【JavaSE进阶】08-反射机制 09-注解

1 反射机制 a) 反射的基本概念 b) Java中的类反射 c) 安全性和反射 d) 反射的两个缺点 1.1 反射的基本概念 反射的概念是由 Smith 在 1982 年首次提出的&#xff0c;主要是指程序可以访问、检测和修改它本身状 态或行为的一种能力, 并能根据自身行为的状态和结果&#…

六、项目发布 -- 2. 数据库环境准备

之前我们是采用mock方式获取这些接口&#xff0c;也就是这些接口的数据其实是固定的&#xff0c;现在我们将从数据库中来获取这些数据并且在界面上进行展示。 1、数据库环境准备 到MYSQL官网上&#xff0c;主要下载服务端&#xff0c;社区版是免费的&#xff0c;安装好MYSQL …

结构体(C语言)

“点赞&#xff0c;留言&#xff0c;收藏&#xff0c;关注” 就是对阿林最大的支持 1.自定义类型 什么是自定义类型&#xff1f;C语言中有一些自带的数据类型&#xff0c;比如说char&#xff0c;int&#xff0c;float&#xff0c;double&#xff0c;long等数据类型就是C语言的…

目标检测——标注鱼类数据集

一、重要性及意义 鱼类的检测在多个领域都表现出其重要性和意义。以下是几个主要方面的阐述&#xff1a; 首先&#xff0c;从食品安全和营养价值的角度来看&#xff0c;鱼类作为人们日常生活中的重要蛋白质来源&#xff0c;其质量和安全性备受关注。鱼类营养成分检测能够评估…

C++异步回调示例:多线程执行任务,主线程通过回调监测任务状态

1、回调函数 回调函数定义&#xff1a;把函数的指针或者地址作为参数传递给另一个参数&#xff0c;当这个指针被用来调用其所指向的函数时&#xff0c;那么这就是一个回调的过程&#xff0c;这个被回调的函数就是回调函数。回调函数不是由该函数的实现方直接调用&#xff0c;而…

反转二叉树(力扣226)

解题思路&#xff1a;用队列进行前序遍历的同时把节点的左节点和右节点交换 具体代码如下&#xff1a; class Solution { public:TreeNode* invertTree(TreeNode* root) {if (root NULL) return root;swap(root->left, root->right); // 中invertTree(root->left)…

oracle 数据库 迁移 mysql

将 Oracle 数据库迁移到 MySQL 是一项复杂的任务&#xff0c;因为这两种数据库管理系统具有不同的架构、语法和功能。下面是一个基本的迁移步骤&#xff0c;供你参考&#xff1a; 步骤一&#xff1a;评估和准备工作 1.评估数据库结构&#xff1a;仔细分析 Oracle 数据库的结构…

让15万的车也配激光雷达,速腾发布中长距「千元机」MX

‍作者 |老缅 编辑 |德新 4月15日&#xff0c;国内头部激光雷达公司速腾聚创发布了新一代中长距激光雷达MX。 相比较其产品配置&#xff0c;最令人惊喜的是它的价格。 「MX将以低于200美元的价格作为基础&#xff0c;实现第一个项目的量产。」速腾聚创CEO邱纯潮在发布会现场…

Python连接Oracle数据库问题解决及Linux服务器操作知识

背景说明 最近在做一个视频分析的项目&#xff0c;然后需要将视频分析的数据写入到oracle数据库&#xff0c;直接在服务器上测试数据库连接的时候出现了这个bug提示&#xff0c;自己通过不断的研究探讨&#xff0c;最终把这个问题成功进行了解决&#xff0c;在这里进行一下记录…