So I’m using Cinder to make a 2D arcade game with a top-down view (something like mario or zelda).
I have a huge background image and what I want to do is to set visible only a part of the screen which is around my character, and which moves with it.
For example if I have a 50x50 tile background and 3x3 character, then I only want the 9x9 tile area around the character to be visible.
I haven’t coded much so please help me.
Hi,
there’s a lot of ground to cover. Here goes:
Let’s first assume you have a class called Tile
that takes care of loading the image for the tile, as well as keeping track what its rectangle in 2D world coordinates is. It’s better to construct a 3D AxisAlignedBox
for it by giving the tile a little thickness in the z-direction, because then we can more easily do frustum culling later on (see below). Store the box as a member of the Tile
class. Finally, it also has a bool mIsVisible
to keep track if it is visible. Add a void setVisible( bool visible = true )
and bool isVisible() const
method as well.
Let’s also assume you have a class called Tiles
which creates the 50x50 tile instances in its setup()
method and stores them in a std::vector<Tile>
. It also has an update()
method in which we will check each tile for visibility (see below). It then sorts the tiles: all visible tiles are moved to the front of the std::vector
. Finally, it has a draw()
method, which simply renders all visible tiles.
About the visibility check in Tiles::update()
: you should simply pass in the current CameraPersp
instance of the top-down camera and perform frustum culling. This can be done by obtaining the camera frustum, then calling frustum.intersects( tile.getBox() )
:
void Tiles::setCamera( const ci::CameraPersp &cam )
{
mCamera = cam;
}
void Tiles::update()
{
// Assumes mCamera has been properly set.
const auto frustum = Frustum( mCamera );
// Perform visibility check.
// Assumes we have a `const AxisAlignedBox& Tile::getBox() const` method.
for( auto &tile : mTiles )
tile.setVisible( frustum.intersects( tile.getBox() ) );
// Sort tiles so that all visible tiles are first.
std::stable_sort( mTiles.begin(), mTiles.end(),
[]( const Tile& a, const Tile &b ) { return a.isVisible() && !b.isVisible(); }
);
}
void Tiles::draw()
{
for( auto &tile: mTiles ) {
if( !tile.isVisible() )
break;
tile.draw();
}
}
If you don’t have a CameraPersp
and are only using gl::translate()
to move around in the world, the visibility check can be replaced with some simple math that checks the edges of the tile against the edges of the visible screen rectangle. If you also use gl::scale()
to zoom, the math can become a lot harder, which is why I chose to go for the frustum culling option.
I will leave the implementation of the remaining methods and code as an exercise
~Paul
P.S.: if you want to see the effect of the culling, adjust the field of view of the culling camera like this:
void Tiles::setCamera( const ci::CameraPersp &cam )
{
mCamera = cam;
mCamera.setFov( 0.5f * mCamera.getFov() ); // <--
}