-
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.
-
You aren't checking the return value from loadFromFile, so it might just fail to load your image.
-
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.
-
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.
-
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.
-
But why are you using a CircleShape rather than a simple Sprite?
-
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.
-
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.
-
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?
-
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.
-
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.
-
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?
-
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.
-
Ok, so setTextureRect is unnecessary? This would have saved a lot of headache for me if this had been mentioned in the docs.
-
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 ;)
-
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.
-
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 ;)
-
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.
-
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.
-
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:
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 :)
-
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:
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?
-
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.
-
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.
-
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?
-
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