This is a problem that I've been troubleshooting for a full week & it's making me lose my mind. And the problem? Let me explain:
Whenever I set my sprite's texture rect, it just gives me a white square instead of the texture file cropped. And if you're asking if my file exists & if I set the correct directory for it, I'm 100% sure it is as it loads whenever I remove the "sf::setTextureRect" code. I've read others' problems here in the SFML Forums as well as in the subreddit and applied to my code, but none of them worked.
Here are the code files:
game.h (where I load everything):
#pragma once
#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
#include <iostream>
#include <cmath>
#include "player.hpp"
class Game {
private:
// Window Specifications
sf::RenderWindow* window;
sf::VideoMode videoMode;
// Window Event Handler
sf::Event event;
// The Player Itself
Player* player;
// Private Functions
void initWindow();
void initPlayer();
public:
// Constructor & Destructor
Game();
virtual ~Game();
// Accessor
const bool isRunning() const;
// Game Functions
void pollEvents();
void update();
void render();
};
game.cpp (where I handle everything):
#include "game.hpp"
//////////////////////////////// GAME WINDOW SPECIFICATIONS ////////////////////////////////
// Private Game Window Function
void Game::initWindow() {
this->videoMode.width = 800;
this->videoMode.height = 600;
this->window = new sf::RenderWindow(this->videoMode, "SFML Game Sample", sf::Style::Titlebar | sf::Style::Close);
this->window->setFramerateLimit(120);
this->window->setKeyRepeatEnabled(false);
this->window->setVerticalSyncEnabled(false);
}
////////////////////////////////////////////////////////////////////////////////////////////
void Game::initPlayer() {
this->player = new Player();
}
// Game Constructor
Game::Game() {
this->initWindow();
this->initPlayer();
}
// Game Destructor
Game::~Game() {
delete this->window;
delete this->player;
}
// Game Accessor
const bool Game::isRunning() const {
return this->window->isOpen();
}
//////////////////// While-Loop Event to Keep the Game Window Running ////////////////////
void Game::pollEvents() {
while (this->window->pollEvent(this->event)) {
switch (this->event.type) {
case sf::Event::Closed:
this->window->close();
break;
case sf::Event::KeyPressed:
if (this->event.key.code == sf::Keyboard::Escape)
this->window->close();
break;
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// Game Functions
void Game::update() {
// Call The Poll Event to Update Game
this->pollEvents();
}
void Game::render() {
// Clears Each Frame
this->window->clear(sf::Color(125, 125, 125, 255));
// Render Player
this->player->render(*this->window);
// Displays Frame in Game Window
this->window->display();
}
player.h (of course the player itself):
#pragma once
#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
#include <iostream>
#include <cmath>
class Player {
private:
friend int main();
// Private Player Variables
sf::Sprite sprite;
sf::Texture texture;
sf::IntRect currentFrame;
// Private Player Functions
void initTexture();
void initSprite();
public:
// Constructor & Destructor
Player();
virtual ~Player();
// Player Functions
void update();
void render(sf::RenderTarget & rt);
};
player.cpp:
#include "player.hpp"
void Player::initTexture() {
// Load The Texture File
if(!this->sprite.setTexture(this->texture)) {
this->texture.loadFromFile("Textures/spring.png");
}
}
void Player::initSprite() {
// Set a Texture to The Sprite
std::cout << "ERROR: Could Not Load Texture File.\n";
// Crops The Spritesheet The Way I Want it
this->currentFrame = sf::IntRect(2, 2, 120, 120); // THIS IS THE
this->sprite.setTextureRect(this->currentFrame); // PROBLEM !!!
// Set Sprite Position
this->sprite.setOrigin(sf::Vector2f(sprite.getLocalBounds().width, sprite.getLocalBounds().height) / 2.f);
this->sprite.setPosition(300.f, 300.f);
// Resize The Sprite
this->sprite.setScale(sf::Vector2f(1.5f, 1.5f));
}
// Constructor
Player::Player() {
this->initTexture();
this->initSprite();
}
// Destructor
Player::~Player() {
}
void Player::update() {
// still empty for now.
}
void Player::render(sf::RenderTarget & rt) {
rt.draw(this->sprite);
}
Although it WORKS whenever I put sf::IntRect inside this->sprite.loadTextureFile() in player.cpp, but that's not what I want because my goal here is to make a spritesheet animation..
I need an answer asap, as well as an explanation so I can troubleshoot all of this by myself in the future. Again, this has already wasted me a full week & I'm getting frustrated the more time flies >:(.
The white square problem (https://www.sfml-dev.org/tutorials/2.5/graphics-sprite.php#the-white-square-problem) is usually caused by some unintentional copy of the texture or accidental memory mismanagement. The usual paradigm in SFML is to keep textures external from the drawable classes which use them, so that copying the drawable won't make a copy of the texture. Often some sort of resource management (https://github.com/SFML/SFML/wiki/Source%3A-Smart-ResourceManager) is used. SFML also provides an interface for creating custom drawables such as a Player class in a consistent way by inheriting sf::Transformable (https://www.sfml-dev.org/tutorials/2.5/graphics-transform.php#transforming-your-own-classes) and sf::Drawable (https://www.sfml-dev.org/tutorials/2.5/graphics-vertex-array.php#creating-an-sfml-like-entity), which would look something like this:
class Player final : public sf::Transformable, public sf::Drawable
{
public:
void setTexture(const sf::Texture& texture)
{
m_sprite.setTexture(texture);
}
void setTextureRect(sf::IntRect rect)
{
m_sprite.setTextureRect(rect);
}
private:
sf::Sprite m_sprite;
void draw(sf::RenderTarget& target, sf::RenderStates states) const override
{
states.transform *= getTransform();
target.draw(m_sprite, states);
}
};
How this works is explained in the documentation (https://www.sfml-dev.org/tutorials/2.5/graphics-transform.php#transforming-your-own-classes). Note that the class does not have a texture member. Rather, you would use it like this:
int main()
{
sf::Texture playerTexture;
playerTexture.loadFromFile("test.png");
Player player;
player.setTexture(playerTexture);
player.setTextureRect({ 2,2,100,100 });
sf::RenderWindow window(sf::VideoMode(800, 600), "My window");
sf::Clock frameClock;
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
{
window.close();
}
}
float elapsed = frameClock.restart().asSeconds();
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
{
player.move(-20.f * elapsed, 0.f);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
{
player.move(20.f * elapsed, 0.f);
}
window.clear(sf::Color::Black);
window.draw(player);
window.display();
}
return 0;
}
With the texture in its own scope (either as in the example or stored in a resource manager) copying drawable classes like Player can be done safely without accidentally copying any textures they use. There is also no need to use new/delete in this example, everything is fine on the stack. And even if there were a reason to use the heap smart pointers (https://en.cppreference.com/w/cpp/memory/unique_ptr) would be the way to go.