Hi, I've been making a tile-based 2D Game to pass time and bumped into one problem which is very obscure to me. The code has already maybe thousand lines so I won't be able to post it all here (I could give you the whole VS2013 solution though).
The behavior:
Before I've fixed that diagonal movement doesn't simply sum the horizontal and vertical movement, but divides these movements by square root of two, it worked just fine (the player was always having integral coordinates.
Now, when the coordinates are real numbers, as I move the player on the scene, sometimes I find a sensitive spot. If I manage to stay on one of these spots and not walk past it, all the tiles are not rendered properly - their texture rectangles appear to be shifted although I checked that they're fine. When I didn't focus the view on the sprite, I hadn't had this effect. From this, I deduced that this must be some problem with the sf::RenderTarget::draw that draws the tiles(sprites, I've also tried vertex array, but same thing).
(Look in the attachments)Finally, here are some relevant parts to help your imagination:
TileSet class holds the texture of tiles and the vector of
Tile class, which has sprite pointing to that texture:
// TileSet
class TileSet
{
DECLARE_IMPL // my macro for declaring the struct for pimpl idiom
public:
class Tile;
using TileVec = std::vector<Tile>;
// Tile
class Tile
{
DECLARE_IMPL
public:
Tile(sf::Sprite const& sprite = sf::Sprite());
Tile(Tile const&);
~Tile();
Tile &operator=(Tile const&);
// Get sprite
sf::Sprite const& sprite() const;
};
TileSet(std::string const& filepath, uint tileSize);
TileSet(TileSet const&);
~TileSet();
TileSet &operator=(TileSet const&);
// Load
// Return true on success
bool load(std::string const& filepath, uint tileSize);
// Get tiles
TileVec const& tiles() const;
// Get tileSize
uint tileSize() const;
// Get texture
sf::Texture const& texture() const;
};
// Impl
struct TileSet::Impl
{
TileVec tiles_;
uint tileSize_;
sf::Texture texture_;
};
The
TileSet::load function looks like this:
// Load
// Return true on success
bool TileSet::load(std::string const& filepath, uint tileSize)
{
if(!impl_->texture_.loadFromFile(filepath))
return false;
uint tilecount_x = impl_->texture_.getSize().x / tileSize;
uint tilecount_y = impl_->texture_.getSize().y / tileSize;
impl_->tiles_.resize(tilecount_x * tilecount_y);
for(uint y = 0; y < tilecount_y; ++y)
for(uint x = 0; x < tilecount_x; ++x)
impl_->tiles_[tilecount_x * y + x] = Tile(sf::Sprite(impl_->texture_,
sf::IntRect(x * tileSize, y * tileSize, tileSize, tileSize)));
impl_->tileSize_ = tileSize;
return true;
}
TileMap class does the rendering:
// TileMap
class TileMap : public sf::Drawable, public sf::NonCopyable
{
DECLARE_IMPL
public:
using TileVec = std::vector<std::vector<Tile>>;
TileMap();
TileMap(std::shared_ptr<TileSet> const& tileSet);
~TileMap();
// Draw
virtual void draw(sf::RenderTarget &target, sf::RenderStates states) const override;
// Get tiles
TileVec &tiles();
TileVec const& tiles() const;
// Return width
uint width() const;
// Return height
uint height() const;
// Get tileSet
Auxl::ThreadSafeReturn<std::shared_ptr<TileSet>> tileSet() const;
// Set tiles
void setTiles(TileVec const& tiles);
// Set tileSet
void setTileSet(std::shared_ptr<TileSet> const& tileSet);
};
// Impl
struct TileMap::Impl
{
Impl(std::shared_ptr<TileSet> const& tileSet = nullptr);
TileVec tiles_;
std::shared_ptr<TileSet> tileSet_;
};
The
TileMap::draw function:
// Draw
void TileMap::draw(sf::RenderTarget &target, sf::RenderStates states) const
{
if(impl_->tileSet_->tiles().empty())
return;
sf::Vector2f topLeft = target.getView().getCenter() - target.getView().getSize() / 2.0f;
sf::Vector2f bottomRight = topLeft + target.getView().getSize();
int start_x = topLeft.x / impl_->tileSet_->tileSize();
int start_y = topLeft.y / impl_->tileSet_->tileSize();
float end_x = bottomRight.x / impl_->tileSet_->tileSize();
float end_y = bottomRight.y / impl_->tileSet_->tileSize();
if(start_x < 0)
start_x = 0;
if(start_y < 0)
start_y = 0;
if(end_y > static_cast<int>(impl_->tiles_.size()))
end_y = impl_->tiles_.size();
for(uint y = start_y; y < end_y; ++y)
{
float current_end_x = end_x;
if(current_end_x > impl_->tiles_[y].size())
current_end_x = impl_->tiles_[y].size();
for(uint x = start_x; x < current_end_x; ++x)
{
int tile_id = impl_->tiles_[y][x].id();
if(tile_id != Tile::ID_EMPTY)
{
sf::Sprite sprite = impl_->tileSet_->tiles()[tile_id].sprite();
sprite.setPosition(
x * impl_->tileSet_->tileSize(),
y * impl_->tileSet_->tileSize());
target.draw(sprite, states);
}
}
}
}
Now, this is where I'm drawing the player (white square) moving the view:
// Render
virtual void render(
Object const& object,
sf::RenderTarget &target,
sf::RenderStates states) const override
{
sf::View view = target.getView();
view.setCenter(bodyComponent_->x() + bodyComponent_->width() / 2.0f,
bodyComponent_->y() + bodyComponent_->height() / 2.0f);
target.setView(view);
sf::RectangleShape rect(
sf::Vector2f(bodyComponent_->width(), bodyComponent_->height()));
rect.move(bodyComponent_->x(), bodyComponent_->y());
target.draw(rect, states);
}
Below, you can see an example of the effect, along with the tile set, where you can see that the tile's texture isn't right in its place but contains a bit of adjacent tile's texture.