0
\$\begingroup\$

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?

\$\endgroup\$
5
  • \$\begingroup\$ What does your profiler say? \$\endgroup\$ Commented Jan 11, 2020 at 3:40
  • 1
    \$\begingroup\$ @Vaillancourt My profiler says that the getID() function is by far the most time-consuming function in the entire program, followed by getCell(). i.imgur.com/hwTi0jf.png \$\endgroup\$ Commented Jan 11, 2020 at 3:53
  • \$\begingroup\$ A couple of things I would check: make sure bucketsObjectIsIn does not allocate a lot during the run of the function, or use a set instead. 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\$ Commented Jan 11, 2020 at 4:31
  • \$\begingroup\$ One thing I noticed I think you can optimize is how the functions insertObject and getID interact with each other: basically you're asking the GameObject for the list cells it is in and than iterating thorugh the result. I think would be a better approach to merege them into a single function: instead of populating bucketsObjectIsIn you can directly set buckets[cell].push_back(this). Another way, since you're using rectangular shapes would be to use getID to return a cell iterator with the bounds of that shape. And actually I don't get the std::find part: shouldn't the cells already be unique? \$\endgroup\$ Commented Jan 15, 2020 at 14:04
  • \$\begingroup\$ The only performance problem I see is that as @Vaillancourt says you're allocating bucketsObjectIsIn at least once per object and that may cause an FPS drop in crowd situations, but definetly not with only 10 objects in your scene. \$\endgroup\$ Commented Jan 15, 2020 at 14:07

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.