Inheriting from Transformable and Drawable in SFML
Asked Answered
C

1

7

I'm trying to inherit from Transformable and Drawable in SFML in order to make my objects... well, transformable and drawable. I'm making a simple breakout game, but perhaps I'm going about this the wrong way. Here's my code:

#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>

class Player : public sf::Transformable, public sf::Drawable {
    public:
        Player(int x, int y);
        ~Player() {};

        sf::RectangleShape p_rect;

        void doMovement(const sf::RenderWindow& window);
        sf::FloatRect getGlobalBounds() const;
    private:
        virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const {
            states.transform *= getTransform();
            target.draw(p_rect, states);
        }

};

class Ball : public sf::Transformable, public sf::Drawable {
    public:
        Ball(int r, int x, int y);
        ~Ball() {};

        sf::CircleShape b_circle;

        void doXMovement();
        void doYMovement();
        bool doXCollisions(const Player& player);
        bool doYCollisions(const Player& player);
        sf::FloatRect getGlobalBounds() const;
    private:
        virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const {
            states.transform *= getTransform();
            target.draw(b_circle, states);
        }

        bool right;
        bool up;
};

Player::Player(int x, int y) {
    p_rect = sf::RectangleShape(sf::Vector2f(x, y));
}

void Player::doMovement(const sf::RenderWindow& window) {
    setPosition(sf::Mouse::getPosition(window).x, 500);
    if (getPosition().x < 0)
        setPosition(0, 500);
    else if (getPosition().x > 720)
        setPosition(720, 500);
}

sf::FloatRect Player::getGlobalBounds() const {
    return getTransform().transformRect(p_rect.getGlobalBounds());
}

Ball::Ball(int r, int x, int y) {
    b_circle = sf::CircleShape(r);
    b_circle.setPosition(x, y);
    right = true;
    up = false;
}

void Ball::doXMovement() {
    if (right)
        move(1, 0);
    else
        move(-1, 0);
}

void Ball::doYMovement() {
    if (up)
        move(0, -1);
    else
        move(0, 1);
}

bool Ball::doXCollisions(const Player& player) {
    bool coll;
    if (getGlobalBounds().intersects(player.getGlobalBounds())) {
        right = !right;
        coll = true;
    } else
        coll = false;

    if (getPosition().x >= 800 - b_circle.getRadius())
        right = false;
    else if (getPosition().x <= 0)
        right = true;
    return coll;
}

bool Ball::doYCollisions(const Player& player) {
    bool coll;
    if (getGlobalBounds().intersects(player.getGlobalBounds())) {
        up = !up;
        coll = true;
    } else
        coll = false;
    if (getPosition().x <= 0)
        up = false;
    return coll;
}

sf::FloatRect Ball::getGlobalBounds() const {
    return getTransform().transformRect(b_circle.getGlobalBounds());
}

int main() {
    sf::RenderWindow window(sf::VideoMode(800, 600), "Breakout");
    window.setMouseCursorVisible(false);
    Player player(80, 10);
    Ball ball(3, 100, 100);
    sf::Clock clock;
    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed)
                window.close();
        }
        player.doMovement(window);
        if (clock.getElapsedTime().asMilliseconds() >= 3) {
            clock.restart();
            if (!ball.doYCollisions(player))
                ball.doXCollisions(player);
            ball.doYMovement();
            ball.doXMovement();
        }
        window.clear(sf::Color::Black);
        window.draw(player);
        window.draw(ball);
        window.display();
    }
    return 0;
}

Now moving and drawing work (nearly) as expected, however collisions are a bit wonky. First my collisions problems:

  1. Do I need to implement the getGlobalBounds function the way I did? Or is there a better way to do it with things included in Transformable and Drawable?
  2. Should I be performing transformations on the shapes directly, or should I pass the transformations to the draw function like I currently am?

Something strange is also happening with the drawing which is probably a quick fix. Right now the getPosition method returns incorrect values for my ball object. The area it returns seems to be shifted down and to the right a bit. Any reason that might be?

Thanks for any help you are able to give!

EDIT: Also any general C++ tips are welcome, I'm still a beginner.

Capparidaceous answered 28/7, 2013 at 16:24 Comment(0)
H
2

If I were you I would define a new class, called TransformableAndDrawable like this:

class TransformableAndDrawable : public sf::Transformable, public sf::Drawable {
    // Your code here
}

In this class you should define all the members which are generally needed by your transformable and drawable classes. Also, in this class you should define all the methods which can be generally implemented in your transformable and drawable classes. Then, your classes should be inherited from TransformableAndDrawable, like this:

class Player : TransformableAndDrawable {
    // Your code here
}

Now, the answer to the first question is: I would implement in the TransformableAndDrawable class the given method if it is a general method, so all the classes inherited from TransformableAndDrawable will have this method.

Instead of giving different names, like p_rect and p_circle, name these members with the same name, like p_shape, so you will have no naming issues. Also, I believe that you can declare your p_shape to be of an ancestor class or interface (I do not know what classes are defined in the library you are working with) and only when needed specify the nature of the shape (whether it is a circle or a rectangle or something else).

As for the second questions: I like the way you have implemented things, but you have made two mistakes:

  • it is not scalable: we want a general solution, a class which can be used for any shape you are working with now and in the future, don't we?
  • it is not general enough: When I want to know the global bounds of a shape, then I am not interested of the nature of the shape, I would prefer your code to handle the nature of the shape without me knowing it

In short, you should do the following:

  1. Create a wrapper class which will be inherited from Transformable and Drawable

  2. In your wrapper class, be agnostic to the nature of the shape, be as general as possible, hopefully there is some class or interface which is ancestor to both RectangleShape and CircleShape.

  3. Inherit all your drawable and transformable classes from your wrapper class, so you will have a shared functionality among your classes

  4. If something in your wrapper class is not good for a class which was inherited from it, overwrite the method in that class.

EDIT:

I have looked into the library you are using in more detail and found out that there is a class called Shape, which is the ancestor to both CircleShape and RectangleShape. So, instead of these classes use Shape and your code will be more general and reusable.

Heurlin answered 28/7, 2013 at 17:17 Comment(4)
Ok cool, I was thinking about making a superclass for these classes, as I really hate writing the same code twice. Hopefully when doing this, I'm able to fix my strange problem with the drawing.Capparidaceous
Is it a good idea to cast the Shape pointer when using the getRadius function? It would work, but I feel like that might be bad practice. I personally don't think so, because it's in a class that has to have a circle type of shape. EDIT: Like this: if (getPosition().x >= 800 - dynamic_cast<sf::CircleShape*>(shape)->getRadius())Capparidaceous
You can convert it, but I believe you should implement a functionality for that in the superclass too and there you can support any class using templates.Heurlin
How would I go about doing that? Should there be a function of some kind?Capparidaceous

© 2022 - 2024 — McMap. All rights reserved.