Essentially I have a custom WorldSettings class that has a ‘PlayableArea’ struct. This is just two FVector2D’s that define the min and max boundaries in X and Y for the playable area of the world. I then have a manager that creates ‘cells’ at spaced-out intervals within this area (the cells are just TArrays, stored inside another TArray that stores them in a Row-major 1D format). Every frame the manager iterates all the registered GameObjects and moves / places them in the correct cell.
I used to use a Quadtree - but it was actually much less performant and caused big problems when two objects occupied the same space (cells would keep dividing at a crazy rate). Here’s a good bit of reading on the two methods (Quadtree and 2D Grid)
http://zufallsgenerator.github.io/2014/01/26/visually-comparing-algorithms/