I'm trying to use spatial hashing to speed up collision detection in my game. I believe spatial hashing is better suited for my game than quadtrees, because I have lots of fast-moving objects in my game (bullets, enemies, etc.). Unfortunately, adding and removing enemies from my Spatial Hash Handler class causes the framerate to drop to the single digits. This happens even with relatively few (~10) objects on screen, and even if I'm not actually performing collision detection.
My code is below. It is a modified version of the code from this article.
First, in my CollisionHandler class, I create the SpatialHashHandler, and pass in the screen's dimensions (160x90 pixels), as well as a cell size of 10.
SpatialHashHandler s = SpatialHashHandler(160, 90, 10);
The SpatialHashHandler's constructor looks like this:
SpatialHashHandler::SpatialHashHandler(float w, float h, float s){ width = w; height = h; cellSize = s; cols = width / cellSize; rows = height / cellSize; buckets.reserve(cols*rows); for (int i = 0; i < cols*rows; i++){ vector<GameObject*> bucket; buckets.push_back(bucket); } } Where buckets is a vector of vectors of GameObject pointers, and width, height, and cellSize are floats, and cols and rows are ints. Because my screen size is 160x90 pixels, and cellSize is 10, cols*rows is equal to 144.
Then, every frame, I start by clearing the SpatialHashHandler of all the enemies, and re-inserting them.
void CollisionHandler::insertEnemies(const vector<Enemy*>& enemies){ s.clearBuckets(); for (int i = 0; i < enemies.size(); i++){ s.insertObject(enemies[i]); } } The remainder of the SpatialHashHandler code is as follows:
void SpatialHashHandler::clearBuckets(){ for (int i = 0; i < cols*rows; i++){ buckets[i].clear(); } } void SpatialHashHandler::insertObject(GameObject* object){ vector<int> cellIDs = getID(object); for (int i = 0; i < cellIDs.size(); i++){ buckets[cellIDs[i]].push_back(object); } } vector<int> SpatialHashHandler::getID(GameObject* object){ vector<int> bucketsObjectIsIn; bucketsObjectIsIn.reserve(2); // depending on the size of the object, it may be in multiple cells. very few objects will be in more than 2 cells at once. int x = object->Position()->X(); int y = object->Position()->Y(); int h = 1+(object->Width() / cellSize); // rounding it upward by converting it to an int, then adding 1 or 2 is actually faster than using ceil() int v = 2+(object->Height() / cellSize); // large objects may be in multiple cells at once // so we start at the object's top left corner, and loop through all the cells until we get to the bottom right corner for (int i = 0; i < h; i++){ for (int j = 0; j < v; j++){ Vector2D pos = {x+i*cellSize, y+j*cellSize}; int cell = getCell(pos); if (cell >= cols*rows || cell < 0){ // if the objects are off the screen, it's possible that they may be assigned to a cell that doesn't exist continue; } // if we get here, it means the object is on the screen if (std::find(bucketsObjectIsIn.begin(), bucketsObjectIsIn.end(), cell) == bucketsObjectIsIn.end()){ // if we get here, it means the cell is not a duplicate bucketsObjectIsIn.push_back(cell); } } } return bucketsObjectIsIn; } int SpatialHashHandler::getCell(Vector2D pos){ int x = pos.X() / cellSize; if (pos.X() >= width){ // if the x value is the same as the width, we'll be inserted into buckets that we aren't actually in, so we have to subtract 1 x--; } int y = pos.Y() / cellSize; if (pos.Y() >= height){ y--; } return x + y * cols; } void SpatialHashHandler::getCollideObjects(vector<GameObject*>& returnObjects, GameObject* object){ vector<int> bucketIDs = getID(object); for (int i = 0; i < bucketIDs.size(); i++){ returnObjects.insert(std::end(returnObjects), std::begin(buckets[bucketIDs[i]]), std::end(buckets[bucketIDs[i]])); } } Finally, in my CollisionHandler class, I loop through all of the player's bullets, create a vector called collideEnemies for each one, and pass both the vector and each bullet into the getCollideObjects function, which returns a vector to all the enemies that each bullet may be colliding with. I loop through that vector to perform actual collision detection.
void CollisionHandler::playerBulletEnemyCollisions(){ for (int i = 0; i < BulletHandler::getTheInstance()->getPlayerBullets().size(); i++ ){ Bullet* b = BulletHandler::getTheInstance()->getPlayerBullets()[i]; int bx = b->Position()->X(); int by = b->Position()->Y(); int bw = b->Width(); int bh = b->Height(); vector<GameObject*> collideEnemies; s.getCollideObjects(collideEnemies, b); for (int j = 0; j < collideEnemies.size(); j++){ int ew = collideEnemies[j]->Width(); int eh = collideEnemies[j]->Height(); int ex = collideEnemies[j]->Position()->X(); int ey = collideEnemies[j]->Position()->Y(); // AABB() is just an ordinary bounding box collision detection method if (AABB(ex, ey, ew, eh, bx, by, bw, bh)){ b->setHealth( b->Health() - 1 ); int h = collideEnemies[j]->Health(); collideEnemies[j]->setHealth( h - 1 ); } } } } As I mentioned, this code causes the game's FPS to drop before collision detection even takes place (if I comment out the entire collideEnemies for loop, the FPS still drops). Therefore, I believe it is the actual process of inserting the enemies into the SpatialHashHandler (beginning with the call to the CollisionHandler::insertEnemies() function) that is causing the FPS to drop. My question is, is there anything I am doing wrong, or any way for this code to be made more efficient, so that I can use spatial hashing without the game's FPS dropping?
bucketsObjectIsIndoes not allocate a lot during the run of the function, or use asetinstead. There could be speed lost because the returned vector is copied (maybe this is already optimized by the compiler). Could try to inline the getCell function... Increase the size of the cells. Change the way the GameObjects are stored and iterated over (make sure you don't trash your cache). Revert to a non-space partitioned approach if it gives you better results ;) \$\endgroup\$