在地图上某块区域,有数个怪物组成的 "哨兵小组",即不会移动, 但拥有警戒视野, 如图1.
这5个哨兵是不会移动, 有站立点可确定一个最小外凸包围圈, 如图2;再产生最小矩形包围圈,如图3.
typedef struct Position_t{ INT mapid; INT x; INT y; }Position_t, MapPieceBoundInfo_t; typedef UINT ElementType_t; typedef UINT GUID_t; class GamePlayer { public: BOOL IsMoving(); Position_t GetPosition(); GUID_t GetGUID(); };
class Monster { public: ElementType_t ElementType(); Position_t GetPosition(); BOOL FindInMyDetection( const Position_t& pos); VOID HeartBeat(); private: MonsterGroupCell* m_sharedGroupCell; };
typedef struct MonsterGroupCell { // players enter this cell std::map< GUID_t, GamePlayer*> m_playerList; ElementType_t m_type; MapPieceBoundInfo_t m_LeftTop; MapPieceBoundInfo_t m_RightBottom; public: inline size_t Size( ){ return m_playerList.size(); } VOID TryDetectGameObject( GamePlayer* gPlayer); VOID TickClearInvalidGameObjectRecord( ); };
按照警戒视野(r)扩宽后, 这个怪物群体的"警戒矩形"将变成 如图4.
class MapData{ public: const INT PIECE_SIZE = 20; BOOL init(); // 进行地图切割 VOID WhenMonstersCreated() { // 怪物创建好后, 进行群组分类, 即产生最大包围矩形 // 第一部分 对怪物进行 分类颤声道 MonsterGroupCell if( m_monsterList.size() > 0 ) { std::map<ElementType_t, MonsterGroupCell*> mapMonsterGroupCells; std::vector<Monster*>::const_iterator iter = m_monsterList.begin(); for( ; iter != m_monsterList.end() ; iter ++ ) { ElementType_t monsterType = (*iter)->ElementType(); MonsterGroupCell* groupCell = mapMonsterGroupCells[ monsterType ]; if( groupCell == NULL ) { MonsterGroupCell* groupCell = new MonsterGroupCell; ASSERT( groupCell != NULL); groupCell->m_LeftTop.x = m_col + 1; groupCell->m_LeftTop.y = m_row + 1; groupCell->m_RightBottom.x = 0; groupCell->m_RightBottom.y = 0; mapMonsterGroupCells[ monsterType ] = groupCell; } Position_t pos = (*iter)->GetPosition(); if( pos.x < groupCell->m_LeftTop.x ) { groupCell->m_LeftTop.x = pos.x; } if( pos.y < groupCell->m_LeftTop.y ) { groupCell->m_LeftTop.y = pos.y; } if( pos.x > groupCell->m_RightBottom.x ) { groupCell->m_RightBottom.x = pos.x; } if( pos.y > groupCell->m_RightBottom.y ) { groupCell->m_RightBottom.y = pos.y; } } //第二部分: 非配各个 MonsterGroupCell 到 地图切片上 // ... }// eof function VOID GameObjectPositionCheck(); private: std::list<GamePlayer*> m_playerList; std::vector<Monster*> m_monsterList; MapPiece** m_Pieces; INT m_mapid; INT m_row ; INT m_col ; INT m_rowPieces; INT m_colPieces; };
一张地图上有不同的 怪物小组(此处都作为不可移动的哨兵), 有 很多不同形状,可能会相互重叠:
以玩家显示器大小 进行对地图切割:
哨兵群体 MonsterGroupCell (图中红色虚线矩形) 也会被切割成各种 形态,并且每个 群体至少属于一个地图片元MapPiece:
class MapPiece { public: VOID OnPlayerMoved( GamePlayer* gPlayer); private: // 地图片元 所拥有的 怪物群体 std::vector< MonsterGroupCell*> m_monsterGroupCells; MapPieceBoundInfo_t m_LeftTop; MapPieceBoundInfo_t m_RightBottom; };
BOOL MapData::init() { m_rowPieces = m_row / PIECE_SIZE + 1; m_colPieces = m_col / PIECE_SIZE + 1; m_Pieces = new MapPiece[ m_rowPieces][ m_colPieces ]; for( INT row = 0; row < m_rowPieces; row ++ ) { for( INT col = 0; col < m_colPieces; col ++) { m_Pieces[ row][ col].m_LeftTop.mapid = m_mapid; m_Pieces[ row][ col].m_LeftTop.x = col * PIECE_SIZE; m_Pieces[ row][ col].m_LeftTop.y = row * PIECE_SIZE; m_Pieces[ row][ col].m_RightBottom.mapid = m_mapid; m_Pieces[ row][ col].m_RightBottom.x = col * PIECE_SIZE + PIECE_SIZE; m_Pieces[ row][ col].m_RightBottom.y = row * PIECE_SIZE + PIECE_SIZE; } } } // eof function init
VOID MapData::WhenMonstersCreated() { // 第一部分 对怪物进行 分类颤声道 MonsterGroupCell ... //第二部分: 非配各个 MonsterGroupCell 到 地图切片上 const INT DETECT_RANGE = 10; std::map< ElementType_t, MonsterGroupCell*>::const_iterator cellIter = mapMonsterGroupCells.begin(); for( ; cellIter != mapMonsterGroupCells.end(); cellIter ++ ) { MonsterGroupCell* cell = cellIter->second; cell->m_LeftTop.x = max( cell->m_LeftTop.x - DETECT_RANGE, 0); cell->m_LeftTop.y = max( cell->m_LeftTop.y - DETECT_RANGE, 0); cell->m_RightBottom.x = min( cell->m_RightBottom.x + DETECT_RANGE, m_col); cell->m_RightBottom.y = min( cell->m_RightBottom.y + DETECT_RANGE, m_row); for( INT cX = cell->m_LeftTop.x; cX <= cell->m_RightBottom.x; cX += PIECE_SIZE ) { for( INT cY = cell->m_LeftTop.y; cY <= cell->m_RightBottom.y; cY += PIECE_SIZE ) { m_Pieces[ cX / PIECE_SIZE ][ cY / PIECE_SIZE ].m_monsterGroupCells.push_back( cell ); } } } }// eof function
引擎内至少有 两个线程:
1.更新游戏对象, 如玩家 或 怪物的属性状态, 诸如 breath / heartbeat / frameCome / tick ...
进行 行走 , 地理位置变更.
2.管理类型性质的 管理类, 当玩家位置变更时, 通知其所在的 地图片元 MapPiece, MapPiece 再根据 所分配的 MonsterGroupCell(s) 和 玩家位置 检测是否进入 了 被检测区域, 如果是, 则 通知 相应的 MonsterGroupCell(s) , 这里有一点注意事项, 一个MonsterGroupCell 可能会过大, 范围超过一个 MapPiece 大小, 这种 MonsterGroupCell 会受到相同的多条 玩家移动的事件 通知.另外只需要 以 玩家所在 MapPiece 为中心的九宫格 的 九个MapPiece 事件检测:
VOID MapData::GameObjectPositionCheck() { std::list< GamePlayer*>::const_iterator iter = m_playerList.begin(); // 遍历每个 该地图上的所有玩家 for( ; iter != m_playerList.end(); iter ++ ) { GamePlayer* gPlayer = (*iter); if( gPlayer->IsMoving() ) { // 玩家所属 九宫格的中心 地图片元 Position_t pos = gPlayer->GetPosition(); INT curPieceX = pos.x / PIECE_SIZE ; INT curPieceY = pos.y / PIECE_SIZE; INT checkPieceX; INT checkPieceY; //遍历九宫格 for( INT rowOff = -1; rowOff <= 1; rowOff ++ ) { for( INT colOff = -1; colOff <= 1; colOff ++ ) { checkPieceX = curPieceX + colOff; checkPieceY = curPieceY + rowOff; if( checkPieceX >= 0 && checkPieceX < m_colPieces) { if( checkPieceY >= 0 && checkPieceY < m_rowPieces) { // 通知 地图片元 piece, 玩家 移动了 MapPiece* piece = m_Pieces[ checkPieceY][ checkPieceX]; piece->OnPlayerMoved( gPlayer); } } } } } } }
地图片元 请求 所分配的 怪物群体 MonsterGroupCell(s) 是否 玩家在其 检测范围内:
VOID MapPiece::OnPlayerMoved( GamePlayer* gPlayer) { if( m_monsterGroupCells.size() > 0 ) { std::vector< MonsterGroupCell*>::const_iterator iter = m_monsterGroupCells.begin(); for( ; iter != m_monsterGroupCells.end(); iter ++ ) { (*iter)->TryDetectGameObject( gPlayer); } } }
地图片元 MonsterGroupCell 根据其检测范围 ( 由 左上角坐标 - 右下角坐标 确定), 记录侵入者信息:
VOID MonsterGroupCell::TryDetectGameObject( GamePlayer* gPlayer) { const Position_t& pos = gPlayer->GetPosition(); if( pos.x >= m_LeftTop.x && pos.y >= m_LeftTop.y && pos.x <= m_RightBottom.x && pos.y <= m_RightBottom.y ) { m_playerList[ gPlayer->GetGUID()] = gPlayer; }else{ if( m_playerList[ gPlayer->GetGUID()] != NULL ) { //标记为离开, 但此时 还是有该玩家的信息, 只是对象实体为 null, m_playerList[ gPlayer->GetGUID() ]= NULL; } } }
另外, 玩家离开后, 并不是实时从 m_playerList 删除, 而是在 其他 较为 不频繁的时刻进行删除.
在怪物的逻辑处理线程内,通过 怪物群体 共享的 MonsterGroupCell, 检测被标记的 玩家 是否确实在 各个具体怪物的 境界范围内:
VOID Monster::HeartBeat() { if( m_sharedGroupCell->Size() > 0 ) { std::map< GUID_t, GamePlayer*>::const_iterator iter = m_sharedGroupCell->m_playerList.begin(); for( ; iter != m_sharedGroupCell->m_playerList.end(); iter ++ ) { //检测是否进入 实际 警戒范围内 if( FindInMyDetection( iter->second->GetPosition() )) { // do some operation // 由 ai 决定 } } } }
//全局 地图信息管理线程 extern std::vector< MapData*> g_MapDataList; static VOID MapDataManagerThreadCallback( VOID* pMapData) { std::vector< MapData*>::const_iterator iter = g_MapDataList.begin(); for( ; iter != g_MapDataList.end(); iter ++ ) { (*iter)->GameObjectPositionCheck(); } }