SFML community forums

Help => Graphics => Topic started by: Composer3 on February 04, 2020, 02:20:04 am

Title: Inherit from sf::Shape (sf::CircleShape)
Post by: Composer3 on February 04, 2020, 02:20:04 am
I am making an asteroids-like game, with the spaceship as a basic triangle shape, and the asteroids (obviously) are circular. I have already made the spaceship class by inheriting from sf::Sprite, but I want to inherit from sf::CircleShape for the asteroids so that I can use triangle-circle collision. I could also have a circle shape as a member of the class, but then I would have to move the circle shape every time I moved the asteroid, and I think this would be simpler. Here is my code so far:

Asteroid.h:
#include <SFML\Graphics.hpp>

class Asteroid : public sf::CircleShape {
  public:
    Asteroid(int radius);
  private:
    sf::Texture _texture;
};
 

Asteroid.cpp:
#include "Asteroid.h"

Asteroid::Asteroid(int radius) : sf::CircleShape(radius) {
    _texture.loadFromFile("images\\asteroid.png");
    setTexture(&_texture);
    setTextureRect(sf::IntRect(0, 0, 20, 20));
    setOrigin(getRadius() / 2, getRadius() / 2);
}

 

main.cpp:
#include <SFML/Graphics.hpp>
#include <iostream>
#include "Spaceship.h"
#include "Asteroid.h"

using namespace std;

const int WIDTH = 720;
const int HEIGHT = 680;

sf::RenderWindow window;

int main() {

    window.create(sf::VideoMode(WIDTH, HEIGHT, 32), "Asteroids!", sf::Style::Titlebar | sf::Style::Close);

    Spaceship spaceship;
    Asteroid asteroid(10);

    sf::Event event;
    while (window.isOpen()) {
        while (window.pollEvent(event)) {
            switch (event.type) {

            case sf::Event::Closed: {
                window.close();
                break;
            }

            default:
                break;
            }
        }

        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up) || sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
            if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
                spaceship.speedY = -5;
            else
                spaceship.speedY = 5;
        else
            spaceship.speedY = 0;

        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left) || sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
            if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
                spaceship.speedX = -5;
            else
                spaceship.speedX = 5;
        else
            spaceship.speedX = 0;

        spaceship.move(spaceship.speedX, spaceship.speedY);

        window.clear();
        window.draw(asteroid);
        window.draw(spaceship);

        window.display();

        sf::sleep(sf::milliseconds(10));
    }

    return 0;
}
 

The Asteroid header and implementation files are quite simple, as I ran into the problem fairly early on. I can't get the Asteroid to draw on the screen. "window.draw(asteroid);" gives no errors, but the asteroid image does not display on the screen.

Any help would be appreciated.
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: eXpl0it3r on February 04, 2020, 08:59:17 am
You aren't checking the return value from loadFromFile, so it might just fail to load your image.
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Composer3 on February 04, 2020, 06:30:16 pm
Thank you for your reply.

I have changed my code:

#include "Asteroid.h"
#include <iostream>

Asteroid::Asteroid(int radius) : sf::CircleShape(radius) {
    if ((_texture.loadFromFile("images\\asteroid.png"))) std::cout << "loaded image successfully" << std::endl;
    setTexture(&_texture);
    setTextureRect(sf::IntRect(0, 0, 20, 20));
    setOrigin(getRadius() / 2, getRadius() / 2);
}
 

output:
loaded image successfully

So this is not the problem. What else am I missing?

Thanks again.
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Mortal on February 05, 2020, 05:28:57 am
your code looks fine, there is no problem with it, but, making the asteroid's radius 10 pixels is quite small. give it higher value like 200 or 300 pixels, it's okay, also set the asteroid's position at center of screen for debugging, to make sure,the asteroid is visible and it's lay within viewport of window. also, make sure of the texture rect is mapped to a valid spot in that texture, means it does't picked transparent or color similar to window's background color or perhaps, the texture itself is corrupted or fully transparent. if that the case then change it with a valid texture or better, use "sf::Image" to create an image with opaque or solid color like "sf::Color::Red". and feed it to the texture update method "sf::Texture::update()". check the official documentation for texture update method.
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Composer3 on February 06, 2020, 06:56:20 pm
Thank you for your reply.

Here is my changed code:

main.cpp:
#include <SFML/Graphics.hpp>
#include <iostream>
#include "Spaceship.h"
#include "Asteroid.h"

using namespace std;

const int WIDTH = 720;
const int HEIGHT = 680;

sf::RenderWindow window;

int main() {

    window.create(sf::VideoMode(WIDTH, HEIGHT, 32), "Asteroids!", sf::Style::Titlebar | sf::Style::Close);

    Spaceship spaceship;
    Asteroid asteroid(200);

    sf::Event event;
    while (window.isOpen()) {
        while (window.pollEvent(event)) {
            switch (event.type) {

            case sf::Event::Closed: {
                window.close();
                break;
            }

            default:
                break;
            }
        }

        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up) || sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
            if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
                spaceship.speedY = -5;
            else
                spaceship.speedY = 5;
        else
            spaceship.speedY = 0;

        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left) || sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
            if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
                spaceship.speedX = -5;
            else
                spaceship.speedX = 5;
        else
            spaceship.speedX = 0;

        spaceship.move(spaceship.speedX, spaceship.speedY);

        window.clear();
        window.draw(asteroid);
        window.draw(spaceship);

        window.display();

        sf::sleep(sf::milliseconds(10));
    }

    return 0;
}
 

Asteroid.cpp:
#include "Asteroid.h"
#include <iostream>

Asteroid::Asteroid(int radius) : sf::CircleShape(radius) {
    // setFillColor(sf::Color::White);
    if ((_texture.loadFromFile("C:\\Users\\Tad\\OneDrive\\Documents\\C++\\SFML_Asteroids\\images\\asteroid.png"))) std::cout << "loaded image successfully" << std::endl;
    setTexture(&_texture);
    setTextureRect(sf::IntRect(0, 0, getRadius() * 2, getRadius() * 2));
    setOrigin(getRadius() / 2, getRadius() / 2);
    setPosition(720 / 2, 680 / 2);
}
 

I found out one of the problems was that the image I was using for the asteroid was not the same across as it was high. So I made my own asteroid image. Now the asteroid is displaying, but part of the top left side is cut off. I will attach a screenshot.

Thanks again for any help.
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Laurent on February 06, 2020, 09:31:30 pm
But why are you using a CircleShape rather than a simple Sprite?
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Composer3 on February 06, 2020, 10:39:10 pm
Because the asteroid is a circle, I thought it would be easier to implement circle-based collisions if I used the circleshape, which has methods like getRaidus(). And I would still like to know what's causing the weird rendering.
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Laurent on February 07, 2020, 09:42:07 am
First, what you draw and what you use to compute collisions don't have to be the same thing; and it's usually not. Because you want detailed stuff on screen, and simpler geometry to speed up computations of collisions.

The circle is the simplest shape in collision detections: it's defined by a center point and a radius. And it has no specific direction, everything is the same around its 360 degrees. However it's more complex in rendering, because your GPU works with triangles; as you can see in your last capture, SFML can only draw a rough approximation of the circle (notice the segmented shape), you would have to subdivide it much more (ie. create more triangles) to see a smooth circle. The sprite, on the other hand, is very simple as it's only made of two triangles -- in your case the circular shape is simply obtained with transparency on the texture itself.

So, store the center and radius of your shape for collision detection, and use a sprite to draw it. And avoid public inheritance: your asteroid is not a circle shape, it just uses one to draw itself on screen.

To answer your question, your last capture shows a mismatch between the texture and the circle shape: the former is smaller than the latter and for some reason it's not stretched to cover the whole circle. That's most likely because you're incorrectly calling setTextureRect -- you shouldn't.
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Composer3 on February 07, 2020, 04:30:03 pm
Thank you. I will use a sprite instead.

And avoid public inheritance: your asteroid is not a circle shape, it just uses one to draw itself on screen.

So are you saying I sould always avoid public inheritance in SFML? Or would it be ok to inherit from a sprite?
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Laurent on February 08, 2020, 10:35:39 am
It's almost always a bad idea to inherit drawable entities, unless you really want to extend them. Because you don't want your Asteroid class to expose low-level functions such as setTexture etc., these details are configured internally by your class and should not be accessible outside.
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Composer3 on February 11, 2020, 03:03:48 am
Thank you for the information.

That's most likely because you're incorrectly calling setTextureRect -- you shouldn't.

How else am I supposed to set the rect for a textured sf::Shape? I understand that I shouldn't use one in this case, and I won't, but I may need to in the future.

It's almost always a bad idea to inherit drawable entities, unless you really want to extend them. Because you don't want your Asteroid class to expose low-level functions such as setTexture etc., these details are configured internally by your class and should not be accessible outside.

I think I may do it anyway, if only just this once. I am doing this project purely as a way to learn C++ and SFML better - kind of my own learning by doing project. I will try to avoid public inheritance with SFML in the future.
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: G. on February 11, 2020, 06:33:36 am
As he said, your texture rect is probably bigger than your texture.
If you want to apply a 300 * 300 px texture to a 400 * 400 px circle or sprite you want a textureRect of 300 * 300, right?
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Laurent on February 11, 2020, 07:54:24 am
The texture rect is the area of the texture that you want to show. If it's the full image, then use the texture size -- or do nothing, as it's the default behavior ;)

Note that it can also be bigger than the texture, if you want a repeating effect.
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Composer3 on February 11, 2020, 11:36:44 pm
Ok, so setTextureRect is unnecessary? This would have saved a lot of headache for me if this had been mentioned in the docs.
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Laurent on February 12, 2020, 08:01:29 am
I think the API doc is clear about what this function does, and about the default behavior in case you don't call it. And of course you don't have to call it if you don't need to, like with any other function -- it's usually the other way round: if a function call is mandatory, it is clearly mentioned and highlighted in the documentation ;)
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Composer3 on February 12, 2020, 07:24:48 pm
Just checked the docs again, and you're right, it does say "The texture rect is useful when you don't want to display the whole texture, but rather a part of it. By default, the texture rect covers the entire texture."

I first found out about setTextureRect in the shapes tutorial. Pasted from the section about texturing shapes:
"Shapes can also be textured, just like sprites. To specify a part of the texture to be mapped to the shape, you must use the setTextureRect function. It takes the texture rectangle to map to the bounding rectangle of the shape. This method doesn't offer maximum flexibility, but it is much easier to use than individually setting the texture coordinates of each point of the shape."

This is what had me confused: nowhere in the tutorial did it say that the default is to cover the entire section. Nor does it use a code example that does not use setTextureRect. I suppose if I had read it again I may have noticed that the language does sort of imply that setTextureRect is only necessary if you want to display a portion, but only if I had reason to believe so already. From what it said, I thought that setTextureRect was a mandatory function used to position the texture on the sf::Shape. I am sure that I am not the only one who was confused by this from the tutorial.
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Laurent on February 12, 2020, 08:42:37 pm
Tutorials are only meant to give an overview. The documentation is the only accurate and reliable source of information when it comes to use a function.

Whatever the tutorial says, the fact is that you called a function without fully understanding how to use it; if you did, you would have passed the right rectangle value and your code would have worked anyway ;)
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Composer3 on February 12, 2020, 08:56:28 pm
Even the documentation does not explain what each parameter does in the function. It is missing a vital piece of information - it says what it does, and why you might want to do it, but it does not tell you how to do it. How am I to understand how to correctly use a function if even the docs don't explain it? There are no examples, and the tutorial, which is the only place I've seen an actual call to the function, doesn't explain what the parameters are doing.
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Composer3 on February 12, 2020, 09:03:16 pm
Btw, I didn't fully understand what it was supposed to do until G.'s post. I thought it was the other way around: if I wanted to apply a 400*400 texture to a 300*300 sprite, then I would need a texture rect of 300*300. In other words, what the docs didn't explain was whether the intRect was in local or global coordinates and if local, then of the texture or of the sprite.
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Laurent on February 12, 2020, 09:20:39 pm
Quote
I thought it was the other way around: if I wanted to apply a 400*400 texture to a 300*300 sprite, then I would need a texture rect of 300*300
There's no way you can understand this from that:

Quote from: the API documentation
Set the sub-rectangle of the texture that the shape will display.

The texture rect is useful when you don't want to display the whole texture, but rather a part of it. By default, the texture rect covers the entire texture.

But anyway, I don't really like to argue like this. You spotted a lack in the tutorials, and it's true that we could easily improve it to avoid such a confusion. If you want to submit a PR for this (only the english part of course, we take care of the french translation), you're more than welcome :)
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Composer3 on February 12, 2020, 09:29:19 pm
Quote
I thought it was the other way around: if I wanted to apply a 400*400 texture to a 300*300 sprite, then I would need a texture rect of 300*300
There's no way you can understand this from that:

Quote from: the API documentation
Set the sub-rectangle of the texture that the shape will display.

The texture rect is useful when you don't want to display the whole texture, but rather a part of it. By default, the texture rect covers the entire texture.

I didn't understand that from the docs, I understood that from the tutorial. The docs didn't help any, because I was just kinda skimming over them, but they were not the source of the confusion.

But anyway, I don't really like to argue like this. You spotted a lack in the tutorials, and it's true that we could easily improve it to avoid such a confusion. If you want to submit a PR for this (only the english part of course, we take care of the french translation), you're more than welcome :)

How do I do this?
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Composer3 on February 12, 2020, 09:42:45 pm
Also, I believe beyond a shadow of a doubt that it was not my fault that I was confused by the tutorial. However, I do admit that it may have been my fault that reading the docs did not correct my confusion, or at least point me in the right direction.
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Laurent on February 13, 2020, 07:59:42 am
Quote
How do I do this?
There's a github repository (https://github.com/SFML/SFML-Website) for the website. You can clone/fork it and submit PRs, like you would do with the SFML repo.

If you don't want to do that, you can just open a new issue and describe your proposed improvement.
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Composer3 on February 13, 2020, 07:35:57 pm
Thanks.

Sorry, I know almost nothing about GitHub. I have an account but the only thing I use it for is creating/replying to issues :). How exactly do I create a PR? If it's too complicated to explain on this issue, could you point me to a tutorial on a different site?
Title: Re: Inherit from sf::Shape (sf::CircleShape)
Post by: Laurent on February 14, 2020, 07:51:29 am
I'm sure you can find many tutorials by searching "create github pull request", the first link should even be the official github tutorial :)

I can't give you more details, as I never make PRs myself ;D