*近くに何がいるか?― 空間分割
速度のネックになっている(そしてこれからもつきまとうであろう)、
近傍探索の処理を高速化するため、空間分割を実装します。
(ちなみに、近傍探索とは”近くのものを探すこと”で、
身近なものでは、ある地点付近のユニットを探す処理、
衝突判定などがそれにあたります。)
空間分割では、空間(ゲームではマップなどがあたる?)を幾つかの領域に分割し、
オブジェクトをその分割された空間にメンバーとして割り当てます。
分割空間単位で処理を行うことで、ある程度の処理の最適化が望めます。
(かなりの距離を離れているオブジェクト同士は、お互いに考慮しないということです。)
空間分割には、BSPや四分木など、いろいろな手法がありますが、
今回は単純で簡単なセル空間分割を使用します。
これは、空間を等しく長方形で分割します。
さて、ゲームのほうでは、ノードとユニット、どちらにも空間分割が使えます。
ですから、再利用が可能なようにテンプレートで作ります。
まんま本の通りになってしまいましたが、一応乗せておきます。
► スポイラーを表示
CODE:
//再利用可能にしておく
template
class Cell
{
public:
Cell(Vec2D topleft, Vec2D bottomright)
:Box( Box2DAABB(topleft, bottomright) )
{}
std::list Members;
Box2DAABB Box;
};
template
class CellSpacePartition
{
//セル
std::vector > m_Cells;
//検索された近傍ユニットはここに入れられる
std::vector m_Neighbors;
//イテレータをこちらでソウサして、
//中身の実体を返すようにしたほうが便利
typename std::vector::iterator ItrNeighbors;
//スペース全体のサイズ
Vec2D m_SpaceSize;
//セルひとつのサイズ
Vec2D m_CellSize;
//セルの個数(x*y)
Vec2D m_CellsNum;
//m_Cellsのインデックスに合うよう、Space座標を変換する
int PosToIndex(Vec2D& Pos);
public:
CellSpacePartition(double width,
double height,
int numX,
int numY,
int MaxEntities);
//近傍ユニットを計算する
void NeighborSearch(Vec2D Pos, double Radius);
//ユニットの追加
void AddEntity(T& ent);
//毎フレーム位置を更新する
void UpdateEntity(T& ent, Vec2D OldPos);
//―イテレータのソウサ
T& begin(){ItrNeighbors = m_Neighbors.begin(); return *ItrNeighbors;}
T& next(){++ItrNeighbors; return *ItrNeighbors;}
bool end(){return (ItrNeighbors == m_Neighbors.end()) || (*ItrNeighbors == 0);}
};
template
CellSpacePartition::CellSpacePartition(double width,
double height,
int numX,
int numY,
int MaxEntities)
:m_SpaceSize(Vec2D(width, height)),
m_CellsNum(Vec2D(numX, numY))
{
m_CellSize = Vec2D( width/numX, height/numY );
for(int y = 0; y (Vec2D(dL, dT),
Vec2D(dR, dB)));
}
}
//近傍ユニットについて、
//いちいちpush_backやclearをする必要がないよう、
//あらかじめ確保しておく
m_Neighbors.resize( MaxEntities, T() );
}
template
void CellSpacePartition::NeighborSearch(Vec2D Pos, double Radius)
{
std::vector::iterator Nitr = m_Neighbors.begin();
Box2DAABB QBox(Pos - Vec2D(Radius, Radius),
Pos + Vec2D(Radius, Radius));
std::vector >::iterator Citr;
for(Citr = m_Cells.begin(); Citr != m_Cells.end(); ++Citr)
{
if(Citr->Box.CollisionDetection(QBox))
{
std::list::iterator it = Citr->Members.begin();
for (; it != Citr->Members.end(); ++it)
{
if(((*it)->Pos() - Pos).LengthSQ()
int CellSpacePartition::PosToIndex(Vec2D& pos)
{
int idx = (int)(m_CellsNum.x * pos.x / m_SpaceSize.x) +
((int)(m_CellsNum.y * pos.y / m_SpaceSize.y) * m_CellsNum.x);
//本来あってはならないが、保障
if(idx (int)m_Cells.size()-1) idx = (int)m_Cells.size()-1;
return idx;
}
template
void CellSpacePartition::AddEntity(T& ent)
{
int idx = PosToIndex(ent->Pos());
m_Cells[idx].Members.push_back(ent);
}
template
void CellSpacePartition::UpdateEntity(T& ent, Vec2D OldPos)
{
int OldIdx = PosToIndex(OldPos);
int NewIdx = PosToIndex(ent->Pos());
if (NewIdx == OldIdx) return;
m_Cells[OldIdx].Members.remove(ent);
m_Cells[NewIdx].Members.push_back(ent);
}
これをWorldクラスにもたせて、
CODE:
class World
{
…略…
CellSpacePartition*
m_SpacePartition;
…略…
};
こうやって使用することができます。
CODE:
int PathFinder::GetClosestNode(Vec2D pos)
{
…略…
//近傍ノードを更新
m_pUnit->getWorld()->GetSpacePartition()
->NeighborSearch(pos, 40);
//全ノードについて探索
for(Node* it = m_pUnit->getWorld()->GetSpacePartition()->begin();
!m_pUnit->getWorld()->GetSpacePartition()->end();
it = m_pUnit->getWorld()->GetSpacePartition()->next())
{
…略…
}
心持ち、若干軽くなった気がします。
実際に効力が大きく現れるのはもうちょっとノードが増えた時でしょう。
ということで、今回はこの辺で終了。