本着高效简洁的目的, 根据 <深入学习 MONGODB > 这本书做指导, 数据库设计遵循下面两条规则
规则 1: 预先分配磁盘空间并填充空白数据
规则 2: 文档要自给自足。数据的计算是由 C++ 客户端程序( 这里的客户端程序不是指游戏客户端, 是指数据库服务器)来完成,若查找的信息需要经过计算且无法从文档中获得, 就会付出高昂的性能代价,优化文档使得这些计算信息能从文档中直接获得
// 分割线 ///
MONGODB 的文档 ( 相当于 MYSQL 的记录 )能非常直观的表达游戏中的逻辑数据结构,并且跟客户端(这里的客户端,是指游戏数据库服务器 )的代码能很好的建立逻辑映射。可以用一个mongodb文档来表示一个游戏角色的个人背包信息。
在 C++ 的服务端代码中, 用下面这个结构体来表达一个背包格子的信息
// 可以表示游戏中的装备(非叠加道具, 比如武器, 盔甲等), 也可以表示叠加道具(比如血瓶,回程卷等)
typedef struct tagItem
{
int nItemID; // 道具唯一编号; 比如某把武器, 或者血瓶, 都有唯一编号的
int nNum; // 数量, 作为可叠加道具, 这个字段才有意义; 作为装备, 这个字段没意义
}ST_ITEM;
而背包信息, 就是一组有限数量的 ST_ITEM 对象集合( 假设背包有 64 个格子 ):
typedef struct tagBagDocument
{
int nCharID; // 角色唯一 ID
int nUsed; // 已使用的格子数, used <= 64
ST_ITEM stBagItemList[ 64 ]; // 如果 stBagItemList[ i ].nItemID 等于 0, 就认为 stBagItemList[ i ] 是空格子
}ST_BAG_DOCUMENT;
在 mongodb 中, 假设数据库 test 中的集合 bag 是存放所有角色的背包信息, 而 bag 中的一个文档, 就是一个角色的背包信息。可以用结构体 ST_BAG_DOCUMENT 来描述文档信息
示范代码:
mongo::DBClientConnection oDB; // 连接 mongodb 的代码省略, 默认 oDB 已经连接到 mongodb 了
// oDB.connect(...)
{
mongo::BSONObjBuilder oBOJ;
oBOJ.append( "characterid", nCharID );
oBOJ.append( "used", 0 ); // 已使用的格子数, 参见页首 规则 2
// 背包有 64 个格子, 参见页首 规则 1
mongo::BSONArrayBuilder arr_o;
for( int i=0; i<64; i++ )
{
mongo::BSONObjBuilder o;
arr_o.append( o.obj() );
}
oBOJ.appendArray("array", arr_o.arr());
oDB.insert( "test.bag", oBOJ.obj() );
oDB.ensureIndex( "test.bag", BSON("characterid"<<1), /*unique*/true);
}
{
memset( ( void* )&stBagDoc, 0, sizeof( stBagDoc ) );
std::auto_ptr< mongo::DBClientCursor > cursor = m_oDB.query( "test.bag", QUERY( "characterid" << nCharID ) );
if( cursor->more() )
{
mongo::BSONObj p = cursor->next();
stBagDoc.nCharID = nCharID;
stBagDoc.nUsed = p.getIntField( "used" );
mongo::BSONObj myarray = p["array"].Obj();
std::vector< mongo::BSONElement > v;
myarray.elems( v );
int i = 0;
std::vector< mongo::BSONElement >::iterator vecIter = v.begin();
for ( ; vecIter!=v.end(); ++vecIter )
{
stBagDoc.stBagItemList[ i ].nItemID = (*vecIter)[ "itemid" ].Int();
stBagDoc.stBagItemList[ i ].nNum = (*vecIter)[ "num" ].Int();
i++;
if ( i >= 64 )
{
break;
}
}
return true;
}
return false;
}
{
char szItemID[ 30 ];
sprintf( szItemID, "array.%d.itemid", nPos );
char szNum[ 30 ];
sprintf( szNum, "array.%d.num", nPos );
mongo::BSONObj obj = BSON("$set"<< BSON( szItemID << 0 << szNum << 0 "$inc" << BSON( "used" << -1 ) ) ); // 已使用的格子数 "used" 要减 1
m_oDB.update( "test.bag", QUERY( "characterid" << nCharID ), obj );
}
{
char szItemID[ 30 ];
sprintf( szItemID, "array.%d.itemid", nPos );
char szNum[ 30 ];
sprintf( szNum, "array.%d.num", nPos );
mongo::BSONObj obj = BSON("$set"<< BSON( "used" << nUsedCell << szItemID << stItem.nItemID ) );
m_oDB.update( "test.bag", QUERY( "characterid" << nCharID ), obj );
}
// nPos 是背包格子的下标值, 从 0 开始计数
void CDBEventFunc::UpdateItemNum(const int nCharID, const int nPos, const int nUsedCell, const ST_ITEM& stItem)
{
int nItemID = stItem.nItemID;
int nNum = stItem.nNum;
char szNum[ 30 ];
sprintf( szNum, "array.%d.num", nPos );
char szItemid[ 30 ];
sprintf( szItemid, "array.%d.itemid", nPos );
// 如果堆叠道具的数量为 0, 那么这个格子应该设置为空格子
int nTmpID = ( 0 == nNum ) ? 0 : nItemID;
// 条件修改
mongo::BSONObj obj = BSON("$set"<< BSON( "used" << nUsedCell << szNum << nNum << szItemid << nTmpID ) );
m_oDB.update( "test.bag", QUERY( "characterid" << nCharID ), obj );
}
举一反三,设计其他功能系统时, 比如好友系统, 工会系统, 也可以参照上述的背包系统设计思路,轻松进行开发
规则 1: 预先分配磁盘空间并填充空白数据
规则 2: 文档要自给自足。数据的计算是由 C++ 客户端程序( 这里的客户端程序不是指游戏客户端, 是指数据库服务器)来完成,若查找的信息需要经过计算且无法从文档中获得, 就会付出高昂的性能代价,优化文档使得这些计算信息能从文档中直接获得
// 分割线 ///
MONGODB 的文档 ( 相当于 MYSQL 的记录 )能非常直观的表达游戏中的逻辑数据结构,并且跟客户端(这里的客户端,是指游戏数据库服务器 )的代码能很好的建立逻辑映射。可以用一个mongodb文档来表示一个游戏角色的个人背包信息。
在 C++ 的服务端代码中, 用下面这个结构体来表达一个背包格子的信息
// 可以表示游戏中的装备(非叠加道具, 比如武器, 盔甲等), 也可以表示叠加道具(比如血瓶,回程卷等)
typedef struct tagItem
{
int nItemID; // 道具唯一编号; 比如某把武器, 或者血瓶, 都有唯一编号的
int nNum; // 数量, 作为可叠加道具, 这个字段才有意义; 作为装备, 这个字段没意义
}ST_ITEM;
而背包信息, 就是一组有限数量的 ST_ITEM 对象集合( 假设背包有 64 个格子 ):
typedef struct tagBagDocument
{
int nCharID; // 角色唯一 ID
int nUsed; // 已使用的格子数, used <= 64
ST_ITEM stBagItemList[ 64 ]; // 如果 stBagItemList[ i ].nItemID 等于 0, 就认为 stBagItemList[ i ] 是空格子
}ST_BAG_DOCUMENT;
在 mongodb 中, 假设数据库 test 中的集合 bag 是存放所有角色的背包信息, 而 bag 中的一个文档, 就是一个角色的背包信息。可以用结构体 ST_BAG_DOCUMENT 来描述文档信息
示范代码:
mongo::DBClientConnection oDB; // 连接 mongodb 的代码省略, 默认 oDB 已经连接到 mongodb 了
// oDB.connect(...)
//创建背包
void CreateBagDoc(const int nCharID){
mongo::BSONObjBuilder oBOJ;
oBOJ.append( "characterid", nCharID );
oBOJ.append( "used", 0 ); // 已使用的格子数, 参见页首 规则 2
// 背包有 64 个格子, 参见页首 规则 1
mongo::BSONArrayBuilder arr_o;
for( int i=0; i<64; i++ )
{
mongo::BSONObjBuilder o;
o.append( "itemid", 0 );
o.append( "num", 0 );
arr_o.append( o.obj() );
}
oBOJ.appendArray("array", arr_o.arr());
oDB.insert( "test.bag", oBOJ.obj() );
oDB.ensureIndex( "test.bag", BSON("characterid"<<1), /*unique*/true);
}
//读取背包信息
bool LoadBagDoc(const int nCharID, ST_BAG_DOCUMENT& stBagDoc){
memset( ( void* )&stBagDoc, 0, sizeof( stBagDoc ) );
std::auto_ptr< mongo::DBClientCursor > cursor = m_oDB.query( "test.bag", QUERY( "characterid" << nCharID ) );
if( cursor->more() )
{
mongo::BSONObj p = cursor->next();
stBagDoc.nCharID = nCharID;
stBagDoc.nUsed = p.getIntField( "used" );
mongo::BSONObj myarray = p["array"].Obj();
std::vector< mongo::BSONElement > v;
myarray.elems( v );
int i = 0;
std::vector< mongo::BSONElement >::iterator vecIter = v.begin();
for ( ; vecIter!=v.end(); ++vecIter )
{
stBagDoc.stBagItemList[ i ].nItemID = (*vecIter)[ "itemid" ].Int();
stBagDoc.stBagItemList[ i ].nNum = (*vecIter)[ "num" ].Int();
i++;
if ( i >= 64 )
{
break;
}
}
return true;
}
return false;
}
// 删除道具, nPos 是背包格子的下标值, 从 0 开始计数
void DeleteItem(const int nCharID, const int nPos){
char szItemID[ 30 ];
sprintf( szItemID, "array.%d.itemid", nPos );
char szNum[ 30 ];
sprintf( szNum, "array.%d.num", nPos );
mongo::BSONObj obj = BSON("$set"<< BSON( szItemID << 0 << szNum << 0 "$inc" << BSON( "used" << -1 ) ) ); // 已使用的格子数 "used" 要减 1
m_oDB.update( "test.bag", QUERY( "characterid" << nCharID ), obj );
}
// 把拾取到的装备放入背包
// nPos 是背包格子的下标值, 从 0 开始计数
// nUsedCell 是更新后已使用的格子数, stItem 是道具信息
void CDBEventFunc::UpdateBagCell(const int nCharID, const int nPos, const int nUsedCell, const ST_ITEM& stItem){
char szItemID[ 30 ];
sprintf( szItemID, "array.%d.itemid", nPos );
char szNum[ 30 ];
sprintf( szNum, "array.%d.num", nPos );
mongo::BSONObj obj = BSON("$set"<< BSON( "used" << nUsedCell << szItemID << stItem.nItemID ) );
m_oDB.update( "test.bag", QUERY( "characterid" << nCharID ), obj );
}
// 更新( 增加或者扣除 )可叠加的道具数量
// nPos 是背包格子的下标值, 从 0 开始计数
// nUsedCell 是更新后已使用的格子数, stItem 是道具信息
void CDBEventFunc::UpdateItemNum(const int nCharID, const int nPos, const int nUsedCell, const ST_ITEM& stItem){
int nItemID = stItem.nItemID;
int nNum = stItem.nNum;
char szNum[ 30 ];
sprintf( szNum, "array.%d.num", nPos );
char szItemid[ 30 ];
sprintf( szItemid, "array.%d.itemid", nPos );
// 如果堆叠道具的数量为 0, 那么这个格子应该设置为空格子
int nTmpID = ( 0 == nNum ) ? 0 : nItemID;
// 条件修改
mongo::BSONObj obj = BSON("$set"<< BSON( "used" << nUsedCell << szNum << nNum << szItemid << nTmpID ) );
m_oDB.update( "test.bag", QUERY( "characterid" << nCharID ), obj );
}
举一反三,设计其他功能系统时, 比如好友系统, 工会系统, 也可以参照上述的背包系统设计思路,轻松进行开发