Cocos2d-x全局数据管理(单例模式/GameManager)详解
【摘要】 引言在游戏开发中,全局数据管理是核心需求之一。玩家分数、游戏状态、配置信息等需要在不同场景间共享的数据,需要一个可靠的管理机制。Cocos2d-x作为流行的2D游戏引擎,虽然提供了场景管理机制,但缺乏内置的全局数据管理方案。本文深入探讨如何使用单例模式实现GameManager类,为Cocos2d-x游戏提供高效、安全的全局数据管理解决方案。技术背景单例模式在游戏开发中的重要性数据共享:跨场...
引言
技术背景
单例模式在游戏开发中的重要性
-
数据共享:跨场景访问游戏状态 -
资源优化:避免重复加载全局资源 -
逻辑集中:统一管理游戏核心逻辑 -
状态持久化:维护游戏进度和设置 -
事件中心:作为全局事件分发器
Cocos2d-x架构特点
-
基于场景(Scene)的层级结构 -
节点(Node)树形组织游戏对象 -
导演(Director)控制游戏流程 -
内存管理机制(引用计数)
单例模式实现要点
-
私有构造函数防止外部实例化 -
静态方法提供全局访问点 -
静态成员变量持有唯一实例 -
线程安全考虑(可选) -
防止拷贝和赋值
应用使用场景
-
玩家数据管理:分数、生命值、金币等 -
游戏状态控制:暂停/继续/结束状态 -
关卡进度保存:已完成关卡记录 -
配置信息管理:音效开关、难度设置 -
全局事件系统:跨场景消息传递 -
资源管理:共享纹理、音效等资源 -
成就系统:解锁状态和进度跟踪
不同场景下详细代码实现
场景1:基础单例实现
// GameManager.h
#ifndef __GAME_MANAGER_H__
#define __GAME_MANAGER_H__
#include "cocos2d.h"
class GameManager : public cocos2d::Ref {
public:
// 获取单例实例
static GameManager* getInstance();
// 销毁单例实例
static void destroyInstance();
// 初始化方法
virtual bool init();
// 示例数据成员
CC_SYNTHESIZE_READONLY(int, _score, Score);
CC_SYNTHESIZE_READONLY(int, _lives, Lives);
CC_PROPERTY(bool, _isPaused, IsPaused);
// 分数操作方法
void addScore(int points);
void resetScore();
// 生命值操作方法
void loseLife();
void gainLife();
void resetLives();
// 暂停状态切换
void togglePause();
private:
// 私有构造函数
GameManager();
// 析构函数
virtual ~GameManager();
// 静态实例指针
static GameManager* _instance;
};
#endif // __GAME_MANAGER_H__
// GameManager.cpp
#include "GameManager.h"
// 初始化静态成员
GameManager* GameManager::_instance = nullptr;
GameManager::GameManager()
: _score(0), _lives(3), _isPaused(false) {
CCLOG("GameManager created");
}
GameManager::~GameManager() {
CCLOG("GameManager destroyed");
}
GameManager* GameManager::getInstance() {
if (!_instance) {
_instance = new (std::nothrow) GameManager();
if (_instance && _instance->init()) {
_instance->autorelease();
} else {
CC_SAFE_DELETE(_instance);
_instance = nullptr;
}
}
return _instance;
}
void GameManager::destroyInstance() {
CC_SAFE_RELEASE_NULL(_instance);
}
bool GameManager::init() {
if (!cocos2d::Ref::init()) {
return false;
}
CCLOG("GameManager initialized");
return true;
}
// 分数操作
void GameManager::addScore(int points) {
_score += points;
CCLOG("Score increased by %d. Total score: %d", points, _score);
}
void GameManager::resetScore() {
_score = 0;
CCLOG("Score reset to 0");
}
// 生命值操作
void GameManager::loseLife() {
if (_lives > 0) {
_lives--;
CCLOG("Lost a life. Remaining lives: %d", _lives);
}
}
void GameManager::gainLife() {
_lives++;
CCLOG("Gained a life. Total lives: %d", _lives);
}
void GameManager::resetLives() {
_lives = 3;
CCLOG("Lives reset to 3");
}
// 暂停状态切换
void GameManager::togglePause() {
_isPaused = !_isPaused;
CCLOG("Game paused state toggled. Now: %s", _isPaused ? "PAUSED" : "RUNNING");
}
场景2:带配置管理的扩展单例
// ConfigManager.h
#ifndef __CONFIG_MANAGER_H__
#define __CONFIG_MANAGER_H__
#include "cocos2d.h"
#include <map>
#include <string>
class ConfigManager {
public:
static ConfigManager* getInstance();
static void destroyInstance();
bool init();
void saveConfig();
void loadConfig();
// 配置项访问
void setBoolForKey(const std::string& key, bool value);
bool getBoolForKey(const std::string& key, bool defaultValue = false);
void setIntForKey(const std::string& key, int value);
int getIntForKey(const std::string& key, int defaultValue = 0);
void setFloatForKey(const std::string& key, float value);
float getFloatForKey(const std::string& key, float defaultValue = 0.0f);
void setStringForKey(const std::string& key, const std::string& value);
std::string getStringForKey(const std::string& key, const std::string& defaultValue = "");
private:
ConfigManager();
~ConfigManager();
static ConfigManager* _instance;
std::map<std::string, bool> _boolMap;
std::map<std::string, int> _intMap;
std::map<std::string, float> _floatMap;
std::map<std::string, std::string> _stringMap;
};
#endif // __CONFIG_MANAGER_H__
// ConfigManager.cpp
#include "ConfigManager.h"
#include "platform/CCFileUtils.h"
#include <fstream>
#include <sstream>
ConfigManager* ConfigManager::_instance = nullptr;
ConfigManager::ConfigManager() {}
ConfigManager::~ConfigManager() {}
ConfigManager* ConfigManager::getInstance() {
if (!_instance) {
_instance = new (std::nothrow) ConfigManager();
if (_instance && _instance->init()) {
// 不自动释放,手动管理生命周期
} else {
CC_SAFE_DELETE(_instance);
_instance = nullptr;
}
}
return _instance;
}
void ConfigManager::destroyInstance() {
CC_SAFE_DELETE(_instance);
_instance = nullptr;
}
bool ConfigManager::init() {
loadConfig();
return true;
}
void ConfigManager::loadConfig() {
auto fileUtils = cocos2d::FileUtils::getInstance();
std::string path = fileUtils->getWritablePath() + "game_config.cfg";
if (fileUtils->isFileExist(path)) {
std::ifstream file(path);
if (file.is_open()) {
std::string line;
while (getline(file, line)) {
std::istringstream iss(line);
std::string type, key, valueStr;
if (getline(iss, type, ',') &&
getline(iss, key, ',') &&
getline(iss, valueStr)) {
if (type == "bool") {
setBoolForKey(key, valueStr == "true");
} else if (type == "int") {
setIntForKey(key, std::stoi(valueStr));
} else if (type == "float") {
setFloatForKey(key, std::stof(valueStr));
} else if (type == "string") {
setStringForKey(key, valueStr);
}
}
}
file.close();
}
}
}
void ConfigManager::saveConfig() {
auto fileUtils = cocos2d::FileUtils::getInstance();
std::string path = fileUtils->getWritablePath() + "game_config.cfg";
std::ofstream file(path);
if (file.is_open()) {
for (const auto& pair : _boolMap) {
file << "bool," << pair.first << "," << (pair.second ? "true" : "false") << "\n";
}
for (const auto& pair : _intMap) {
file << "int," << pair.first << "," << pair.second << "\n";
}
for (const auto& pair : _floatMap) {
file << "float," << pair.first << "," << pair.second << "\n";
}
for (const auto& pair : _stringMap) {
file << "string," << pair.first << "," << pair.second << "\n";
}
file.close();
}
}
// 配置项访问方法实现
void ConfigManager::setBoolForKey(const std::string& key, bool value) {
_boolMap[key] = value;
}
bool ConfigManager::getBoolForKey(const std::string& key, bool defaultValue) {
auto it = _boolMap.find(key);
return it != _boolMap.end() ? it->second : defaultValue;
}
void ConfigManager::setIntForKey(const std::string& key, int value) {
_intMap[key] = value;
}
int ConfigManager::getIntForKey(const std::string& key, int defaultValue) {
auto it = _intMap.find(key);
return it != _intMap.end() ? it->second : defaultValue;
}
void ConfigManager::setFloatForKey(const std::string& key, float value) {
_floatMap[key] = value;
}
float ConfigManager::getFloatForKey(const std::string& key, float defaultValue) {
auto it = _floatMap.find(key);
return it != _floatMap.end() ? it->second : defaultValue;
}
void ConfigManager::setStringForKey(const std::string& key, const std::string& value) {
_stringMap[key] = value;
}
std::string ConfigManager::getStringForKey(const std::string& key, const std::string& defaultValue) {
auto it = _stringMap.find(key);
return it != _stringMap.end() ? it->second : defaultValue;
}
原理解释
-
唯一实例:通过私有构造函数和静态实例变量保证 -
全局访问:通过静态getInstance()方法访问 -
生命周期管理:在游戏启动时创建,退出时销毁 -
数据共享:所有游戏组件通过单例访问相同数据 -
扩展性:可继承扩展为特定功能的单例管理器
核心特性
-
线程安全(可选):使用双重检查锁定或静态局部变量 -
延迟初始化:首次使用时创建实例 -
自动释放:集成Cocos2d-x的内存管理 -
数据封装:提供类型安全的访问方法 -
序列化支持:保存/加载游戏状态 -
观察者模式集成:作为事件中心使用 -
跨平台兼容:适配不同平台的文件系统
原理流程图及解释
graph TD
A[游戏启动] --> B[调用GameManager::getInstance]
B --> C{实例是否存在?}
C -- 否 --> D[创建新实例]
D --> E[初始化成员变量]
E --> F[添加到自动释放池]
C -- 是 --> G[返回现有实例]
F --> G
G --> H[游戏各组件访问数据]
H --> I[修改或读取数据]
I --> J[游戏场景切换]
J --> K[再次访问GameManager]
K --> G
L[游戏退出] --> M[调用destroyInstance]
M --> N[释放单例资源]
-
游戏启动时首次调用getInstance() -
检查静态实例指针是否为空 -
若为空则创建新实例并初始化 -
将实例添加到自动释放池(可选) -
返回实例指针供游戏组件使用 -
后续访问直接返回现有实例 -
游戏退出时显式销毁单例释放资源
环境准备
开发环境要求
-
引擎版本:Cocos2d-x 3.x 或 4.x -
开发工具:Visual Studio/Xcode/Android Studio -
编程语言:C++11或更高版本 -
平台支持:iOS/Android/Windows/Mac/Linux
项目配置步骤
-
创建Cocos2d-x项目 cocos new MyGame -p com.yourcompany.mygame -l cpp -d ./projects -
添加GameManager类文件 -
在Classes目录下创建GameManager.h和GameManager.cpp -
添加ConfigManager相关文件(可选)
-
-
配置CMakeLists.txt # 添加源文件 list(APPEND GAME_SOURCE Classes/GameManager.cpp Classes/ConfigManager.cpp # 其他源文件... ) # 添加头文件路径 include_directories( Classes ${COCOS2D-X_ROOT}/cocos # 其他路径... ) -
在AppDelegate中初始化 // AppDelegate.cpp #include "GameManager.h" bool AppDelegate::applicationDidFinishLaunching() { // 初始化导演 auto director = cocos2d::Director::getInstance(); // 初始化游戏管理器 GameManager::getInstance(); // 其他初始化代码... return true; } void AppDelegate::applicationWillTerminate() { // 清理游戏管理器 GameManager::destroyInstance(); }
实际详细应用代码示例实现
主场景使用示例
// HelloWorldScene.cpp
#include "HelloWorldScene.h"
#include "GameManager.h"
#include "ConfigManager.h"
#include "SimpleAudioEngine.h"
USING_NS_CC;
Scene* HelloWorld::createScene() {
return HelloWorld::create();
}
bool HelloWorld::init() {
if (!Scene::init()) {
return false;
}
// 获取游戏管理器实例
auto gameManager = GameManager::getInstance();
// 获取配置管理器实例
auto configManager = ConfigManager::getInstance();
// 创建UI元素
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
// 分数标签
char scoreStr[20];
sprintf(scoreStr, "Score: %d", gameManager->getScore());
auto scoreLabel = Label::createWithTTF(scoreStr, "fonts/Marker Felt.ttf", 24);
scoreLabel->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - 50));
this->addChild(scoreLabel, 1);
// 生命值标签
char livesStr[20];
sprintf(livesStr, "Lives: %d", gameManager->getLives());
auto livesLabel = Label::createWithTTF(livesStr, "fonts/Marker Felt.ttf", 24);
livesLabel->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - 100));
this->addChild(livesLabel, 1);
// 暂停按钮
auto pauseButton = MenuItemImage::create(
"pause_normal.png",
"pause_selected.png",
CC_CALLBACK_1(HelloWorld::pauseCallback, this));
pauseButton->setPosition(Vec2(origin.x + visibleSize.width - 50,
origin.y + 50));
// 加分按钮
auto addScoreButton = MenuItemImage::create(
"plus_normal.png",
"plus_selected.png",
CC_CALLBACK_1(HelloWorld::addScoreCallback, this));
addScoreButton->setPosition(Vec2(origin.x + 100, origin.y + 100));
// 菜单
auto menu = Menu::create(pauseButton, addScoreButton, nullptr);
menu->setPosition(Vec2::ZERO);
this->addChild(menu, 1);
// 显示配置项
bool soundEnabled = configManager->getBoolForKey("sound_enabled", true);
char configStr[50];
sprintf(configStr, "Sound: %s", soundEnabled ? "ON" : "OFF");
auto configLabel = Label::createWithTTF(configStr, "fonts/Marker Felt.ttf", 24);
configLabel->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - 150));
this->addChild(configLabel, 1);
// 更新标签的调度器
this->schedule([=](float dt) {
sprintf(scoreStr, "Score: %d", gameManager->getScore());
scoreLabel->setString(scoreStr);
sprintf(livesStr, "Lives: %d", gameManager->getLives());
livesLabel->setString(livesStr);
soundEnabled = configManager->getBoolForKey("sound_enabled", true);
sprintf(configStr, "Sound: %s", soundEnabled ? "ON" : "OFF");
configLabel->setString(configStr);
}, 0.1f, "update_labels");
return true;
}
void HelloWorld::addScoreCallback(Ref* pSender) {
auto gameManager = GameManager::getInstance();
gameManager->addScore(10);
// 播放音效
auto configManager = ConfigManager::getInstance();
if (configManager->getBoolForKey("sound_enabled")) {
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("coin.wav");
}
}
void HelloWorld::pauseCallback(Ref* pSender) {
auto gameManager = GameManager::getInstance();
gameManager->togglePause();
if (gameManager->getIsPaused()) {
Director::getInstance()->pause();
} else {
Director::getInstance()->resume();
}
}
void HelloWorld::menuCloseCallback(Ref* pSender) {
Director::getInstance()->end();
}
游戏结束场景
// GameOverScene.cpp
#include "GameOverScene.h"
#include "GameManager.h"
#include "HelloWorldScene.h"
USING_NS_CC;
Scene* GameOverScene::createScene() {
return GameOverScene::create();
}
bool GameOverScene::init() {
if (!Scene::init()) {
return false;
}
auto gameManager = GameManager::getInstance();
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
// 游戏结束标签
auto label = Label::createWithTTF("Game Over", "fonts/Marker Felt.ttf", 48);
label->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height/2 + 50));
this->addChild(label, 1);
// 最终分数标签
char scoreStr[50];
sprintf(scoreStr, "Final Score: %d", gameManager->getScore());
auto scoreLabel = Label::createWithTTF(scoreStr, "fonts/Marker Felt.ttf", 36);
scoreLabel->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height/2 - 20));
this->addChild(scoreLabel, 1);
// 重新开始按钮
auto restartButton = MenuItemFont::create("Restart", [=](Ref* sender) {
// 重置游戏状态
gameManager->resetScore();
gameManager->resetLives();
gameManager->setIsPaused(false);
// 切换到游戏场景
Director::getInstance()->replaceScene(HelloWorld::createScene());
});
restartButton->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height/2 - 80));
// 退出按钮
auto exitButton = MenuItemFont::create("Exit", [](Ref* sender) {
Director::getInstance()->end();
});
exitButton->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height/2 - 130));
auto menu = Menu::create(restartButton, exitButton, nullptr);
menu->setPosition(Vec2::ZERO);
this->addChild(menu, 1);
return true;
}
运行结果
-
主场景显示当前分数、生命值和声音设置 -
点击"+"按钮增加10分并播放音效 -
点击暂停按钮切换游戏暂停状态 -
当生命值为0时切换到游戏结束场景 -
游戏结束场景显示最终分数 -
点击重新开始按钮重置游戏状态
GameManager created
GameManager initialized
Score increased by 10. Total score: 10
Game paused state toggled. Now: PAUSED
Lost a life. Remaining lives: 2
Final Score: 150
Score reset to 0
Lives reset to 3
测试步骤以及详细代码
单元测试框架
// GameManagerTest.cpp
#include "gtest/gtest.h"
#include "GameManager.h"
#include "ConfigManager.h"
USING_NS_CC;
class GameManagerTest : public testing::Test {
protected:
void SetUp() override {
// 重置单例状态
GameManager::destroyInstance();
ConfigManager::destroyInstance();
}
void TearDown() override {
// 清理资源
GameManager::destroyInstance();
ConfigManager::destroyInstance();
}
};
TEST_F(GameManagerTest, SingletonInstance) {
GameManager* instance1 = GameManager::getInstance();
GameManager* instance2 = GameManager::getInstance();
ASSERT_NE(instance1, nullptr);
ASSERT_EQ(instance1, instance2);
}
TEST_F(GameManagerTest, ScoreManagement) {
auto manager = GameManager::getInstance();
EXPECT_EQ(manager->getScore(), 0);
manager->addScore(100);
EXPECT_EQ(manager->getScore(), 100);
manager->resetScore();
EXPECT_EQ(manager->getScore(), 0);
}
TEST_F(GameManagerTest, LifeManagement) {
auto manager = GameManager::getInstance();
EXPECT_EQ(manager->getLives(), 3);
manager->loseLife();
EXPECT_EQ(manager->getLives(), 2);
manager->gainLife();
EXPECT_EQ(manager->getLives(), 3);
manager->resetLives();
EXPECT_EQ(manager->getLives(), 3);
}
TEST_F(GameManagerTest, PauseToggle) {
auto manager = GameManager::getInstance();
EXPECT_FALSE(manager->getIsPaused());
manager->togglePause();
EXPECT_TRUE(manager->getIsPaused());
manager->togglePause();
EXPECT_FALSE(manager->getIsPaused());
}
TEST_F(GameManagerTest, ConfigSaveLoad) {
auto config = ConfigManager::getInstance();
config->setBoolForKey("test_bool", true);
config->setIntForKey("test_int", 42);
config->setFloatForKey("test_float", 3.14f);
config->setStringForKey("test_string", "hello");
config->saveConfig();
config->destroyInstance();
// 重新加载
config = ConfigManager::getInstance();
config->loadConfig();
EXPECT_TRUE(config->getBoolForKey("test_bool"));
EXPECT_EQ(config->getIntForKey("test_int"), 42);
EXPECT_FLOAT_EQ(config->getFloatForKey("test_float"), 3.14f);
EXPECT_EQ(config->getStringForKey("test_string"), "hello");
}
手动测试步骤
-
启动游戏进入主场景 -
验证初始分数(0)和生命值(3) -
多次点击加分按钮,验证分数增加 -
点击暂停按钮,验证游戏暂停/恢复 -
触发游戏结束条件(如生命值为0) -
在游戏结束场景验证最终分数 -
点击重新开始按钮,验证状态重置 -
更改声音设置并重启游戏验证持久化
部署场景
-
移动游戏: -
iOS/Android应用商店发布 -
集成广告和分析SDK -
考虑不同屏幕尺寸适配
-
-
PC游戏: -
Windows/Mac独立应用 -
Steam/Epic平台发布 -
键盘鼠标控制优化
-
-
Web游戏: -
使用Emscripten编译为HTML5 -
集成WebGL渲染 -
考虑浏览器兼容性
-
-
跨平台游戏: -
统一代码库多平台构建 -
平台特定功能抽象层 -
自动化构建流水线
-
-
多人游戏: -
单例作为本地玩家状态管理 -
网络同步关键状态 -
冲突解决机制
-
疑难解答
常见问题1:单例在场景切换时被销毁
-
单例被意外释放 -
使用了非静态实例 -
自动释放池管理不当
// 确保单例不被自动释放
GameManager* GameManager::getInstance() {
if (!_instance) {
_instance = new (std::nothrow) GameManager();
if (_instance && _instance->init()) {
// 不加入自动释放池,手动管理
// _instance->autorelease(); // 注释掉这行
} else {
CC_SAFE_DELETE(_instance);
_instance = nullptr;
}
}
return _instance;
}
// 在AppDelegate中正确管理生命周期
void AppDelegate::applicationDidEnterBackground() {
// 游戏进入后台时保存状态
GameManager::getInstance()->saveGameState();
}
void AppDelegate::applicationWillEnterForeground() {
// 游戏回到前台时恢复状态
GameManager::getInstance()->loadGameState();
}
常见问题2:多线程访问冲突
-
多个线程同时修改单例数据 -
缺乏同步机制
// 线程安全单例实现(C++11)
class ThreadSafeGameManager {
public:
static ThreadSafeGameManager& getInstance() {
static ThreadSafeGameManager instance;
return instance;
}
// 删除拷贝构造函数和赋值运算符
ThreadSafeGameManager(const ThreadSafeGameManager&) = delete;
ThreadSafeGameManager& operator=(const ThreadSafeGameManager&) = delete;
// 数据操作方法(加锁)
void addScore(int points) {
std::lock_guard<std::mutex> lock(_mutex);
_score += points;
}
int getScore() const {
std::lock_guard<std::mutex> lock(_mutex);
return _score;
}
private:
ThreadSafeGameManager() = default;
mutable std::mutex _mutex;
int _score = 0;
};
常见问题3:内存泄漏
-
单例未被正确销毁 -
循环引用导致引用计数不为零
// 在适当的地方显式销毁单例
void GameManager::purge() {
destroyInstance();
CCLOG("GameManager purged");
}
// 在AppDelegate中调用
void AppDelegate::applicationWillTerminate() {
// 确保所有单例被销毁
GameManager::purge();
ConfigManager::destroyInstance();
// 其他清理工作...
}
// 避免循环引用
class SomeClass : public cocos2d::Ref {
// 使用弱引用指向单例
CC_SYNTHESIZE_WEAK(GameManager*, _gameManager, GameManager);
};
未来展望
-
脚本集成:Lua/JavaScript绑定单例管理 -
热更新支持:动态更新配置数据 -
云同步:多设备间游戏状态同步 -
数据分析:集成玩家行为追踪 -
AI集成:基于玩家数据的智能调整 -
跨引擎通用:抽象为独立库支持其他引擎
技术趋势与挑战
趋势
-
服务化架构:单例作为微服务节点 -
数据驱动设计:配置驱动游戏逻辑 -
ECS架构集成:单例作为系统管理器 -
响应式编程:基于RxCpp的观察者模式 -
模块化设计:插件式功能扩展
挑战
-
大型项目管理:多团队协作下的单例滥用 -
测试复杂性:全局状态难以模拟 -
内存占用:长期运行的游戏状态膨胀 -
安全风险:敏感数据全局暴露 -
性能瓶颈:集中式访问成为热点
总结
-
核心实现: -
线程安全的单例模板 -
数据封装与访问控制 -
生命周期管理方法 -
序列化与持久化支持
-
-
扩展功能: -
配置管理系统 -
事件通知机制 -
状态保存与恢复 -
跨场景数据共享
-
-
最佳实践: -
何时使用单例模式 -
避免单例滥用的策略 -
内存管理注意事项 -
多线程安全处理
-
-
应用场景: -
玩家数据管理 -
游戏状态控制 -
全局事件系统 -
配置信息管理
-
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)