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

news2025/1/18 9:05:21

植物大战僵尸游戏开发教程专栏地址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解码、字符位置的循环左移和字符替换操作。

其他函数

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

活动理论的散点图

import pandas as pd import matplotlib.pyplot as plt# 假设您已经有一个名为 data.xlsx 的 Excel 文件 # 您可以使用以下代码读取数据# 读取 Excel 文件 try:data pd.read_excel(data.xlsx) except Exception as e:print(f"Error: {e}")# 假设您的数据包含以下列:…

2024 OceanBase开发者大会:专场论坛亮点抢先看

4 月 20 日&#xff0c;2024 OceanBase 开发者大会将在上海闵行区漕宝路 3199 号宝龙艾美酒店召开。 此次盛会专为开发者们量身打造&#xff0c;OceanBase诚挚邀请了来自不同行业的最佳实践用户、业界享有盛誉的技术专家&#xff0c;以及OceanBase数据库领域的精英们齐聚一堂。…

线程池学习(通俗易懂)

线程池 线程池是什么ThreadPoolExecutor模拟实现线程池结语 线程池是什么 假设我们要频繁的创建线程和销毁线程,但是创建线程和销毁线程是有成本的. 所以我们可以提前创建一批线程,后面需要使用的时候,直接拿就可以了,这就是线程池. 当线程不再使用的时候,就归还到池子里.为什…

软考131-上午题-【软件工程】-软件可靠性、可用性、可维护性

可靠性、可用性和可维护性是软件的质量属性&#xff0c;软件工程中&#xff0c;用 0-1 之间的数来度量。 0.66 66% 1、 可靠性 可靠性是指一个系统对于给定的时间间隔内、在给定条件下无失效运作的概率。 可以用 MTTF/ (1MTTF) 来度量&#xff0c;其中 MTTF 为平均无故障时间…

算法练习第18天|111.二叉树的最小深度

111.二叉树的最小深度 111. 二叉树的最小深度 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/minimum-depth-of-binary-tree/description/ 题目描述&#xff1a; 给定一个二叉树&#xff0c;找出其最小深度。 最小深度是从根节点到最近叶子节点的最…

vue3 vueUse 连接蓝牙

目录 vueuse安装&#xff1a; useBluetooth: 调用蓝牙API 扫描周期设备 选择设备配对 连接成功 vue3的网页项目连接电脑或者手机上的蓝牙设备&#xff0c;使用vueUse库&#xff0c;可以快速检查连接蓝牙设备。 vueUse库使用参考&#xff1a; VueUse工具库 常用api-CSDN…

【代码】Python3|Requests 库怎么继承 Selenium 的 Headers (2024,Chrome)

本文使用的版本&#xff1a; Chrome 124Python 12Selenium 4.19.0 版本过旧可能会出现问题&#xff0c;但只要别差异太大&#xff0c;就可以看本文&#xff0c;因为本文对新老版本都有讲解。 文章目录 1 难点解析和具体思路2 注意事项2.1 PDF 资源获取时注意事项2.2 Capabiliti…

接口防盗刷的方法有哪些?

在工作中&#xff0c;曾经遇到过一个手机号一天发送上百次验证码得情况&#xff0c;这种明显是出问题了&#xff0c;那怎么解决和防范呢&#xff1f; 这是一个非常有意思的问题&#xff0c;防范措施挺多的。今天这篇文章专门跟大家一起聊聊&#xff0c;希望对你会有所帮助。 1…

固定测斜仪:工程观测的精密利器

在工程观测测量领域&#xff0c;固定测斜仪扮演着至关重要的角色。固定测斜仪&#xff0c;凭借其耐冲击型倾斜传感器、出色的可靠性、快速稳定的特点&#xff0c;以及简洁的安装和智能识别功能&#xff0c;已成为行业内重要工具。其输出信号为RS485数字量&#xff0c;可直接显示…

进行接口测试时,连接数据库,对数据源进行备份、还原、验证操作

进行接口测试时&#xff0c;我们需要连接到数据库中&#xff0c;对数据源进行备份、还原、验证等操作。 一、Python连接数据库常见模块 MysqlDBpython2时代最火的驱动库。基于C开发&#xff0c;对windows平台不友好。现在已经进入python3时代&#xff0c;基本不再使用MysqlCl…

OPC-UA是这样在食品和饮料中应用的

什么是 OPC-UA OPC Unified Architecture&#xff0c;即 OPC-UA&#xff0c;是一种基于 TCP/IP 的协议&#xff0c;用于自动化工程师实时、高可靠性、高效性地在控制系统级别共享数据。 OPC-UA 的特点 安全通信: OPC-UA 使用先进的加密方法和严格的访问控制&#xff0c;确保数…

基于STM32的交通灯(OLED屏显示倒计时)的Proteus仿真

文章目录 一、前言二、交通灯1.题目要求2.思路3.画图正常情况模拟故障情况 4.软件 三、总结 一、前言 最近下载了Proteus仿真软件&#xff0c;闲来无事也试试画一个简单的仿真图。 有需要软件的朋友可以去我的另外一篇博客下载和安装。 自用Proteus(8.15)仿真下载安装过程&a…

C++进阶(2)-函数

目录 一、函数提高 1.1函数默认参数 1.2函数占位参数 1.3函数重载 1.3.1函数重载概述 1.3.2函数重载注意事项 二、类和对象 2.1封装 2.1.1封装的意义 2.1.2struct和class区别 2.1.3成员属性设置为私有 2.1.4封装案例 2.2对象的初始化和清理 2.2.1构造函数和析构函数 …

数据大爆炸:WordCount程序的多元化执行方式

文章目录 主要内容1.左方工作区右键New,选择Map文件2.再创建mymap,myreducer,mywordcount类&#xff1a;3.打包在linux中运行&#xff0c;注意处理的文件式完全分布式文件3.1打jar包步骤&#xff1a; 4.完成内容 主要内容 尝试使用不同的方式运行wordcount程序。 1&#xff09…

去哪网拿去花不能提现,只能用于透支消费,那么拿去花提现是怎么实现呢?

去哪网拿去花不能提现&#xff0c;只能用于透支消费&#xff0c;那么拿去花提现是怎么实现呢&#xff1f; 申请携程拿去花之后&#xff0c;有一些人就会想着把钱提现出来拿去用。一般来说&#xff0c;他们都是通过线下门店来提现拿去花&#xff0c;拿去花允许用户先消费后付款&…

Unity类银河恶魔城学习记录13-1 p142 Save system源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili FileDataHandler.cs using System; using System.IO; using UnityEngine; p…

什么地推网推拉新副业平台最值得推荐? 赚取互联网第一桶金

随着互联网的发展&#xff0c;新型行业层出不穷。其中地推网推拉新作为互联网行业具有收入高、门槛低、时间自由等优势&#xff0c;一部分人从中嗅到了商机&#xff0c;开始纷纷接触并加入了进来。但还是有一部分人对于地推网推拉新的了解很少&#xff0c;不知道如何才能加入其…

程序设计|C语言教学——C语言基础1:C语言的引入和入门

一、程序的执行 1.定义 解释&#xff1a;借助一个程序&#xff0c;那个程序能够试图理解你的程序&#xff0c;然后按照你的要求执行。下次执行的时候还需要从零开始解释。 编译&#xff1a;借助一个程序&#xff0c;能够像翻译官一样&#xff0c;把你的程序翻译成机器语言&a…

IoC与Spring

目录 IoC控制反转 现实案例 特点 目的 DI依赖注入 小总结 介绍Spring 狭义和广义上的Spring 传统编码方式的不足 需求引入 弊端分析 IoC控制反转 现实案例 1、买水果问老板各种水果的口感而不是自己去挨个尝试一遍。 2、买房子找中介而不是自己去花时间找房东。…

字节对编码 (BPE):提升语言处理的效率和有效性

原文地址&#xff1a;byte-pair-encoding-bpe-bridging-efficiency-and-effectiveness-in-language-processing 2024 年 4 月 12 日 介绍 在快速发展的自然语言处理 (NLP) 领域&#xff0c;对人类语言高效解析和理解的追求带来了重大创新。字节对编码&#xff08;BPE&#x…