Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Storing sf::Shape, sf::Sprite, or sf::Text in the one container class  (Read 1993 times)

0 Members and 1 Guest are viewing this topic.

jcol2

  • Newbie
  • *
  • Posts: 2
    • View Profile
Hi there,

I'd like to have a base GameObject class with a data member that can point to sf::Shape, sf::Sprite, or sf::Text. According to SFML docs, these classes all implement both the sf::Drawable and sf::Transformable interfaces. Is there a way to create a data member that can hold instances of sf::Shape, sf::Sprite, or sf::Text and keep the functionality of both interfaces?

E.g.
Drawformable* drawformable = new sf::RectangleShape{...};
drawformable->getPosition();  // accessing sf::Transformable interface
drawformable->draw(...);  // accessing sf::Drawable interface

drawformable = new sf::Sprite{...};  // can be re-assigned sf::Sprite, or sf::Text

Workarounds I've come up wtih:
1. Using templates
template <typename T>
GameObject {
 public:
    GameObject::GameObject(T* drawformable) {
        drawformable_ = drawformable;
    }

    T& GameObject::GetDrawformable() {
        return *drawformable_;
    }

 private:
    T* drawformable_;
};

...

GameObject<sf::Sprite> sprite_obj {new sf::Sprite{...}};
sf::Transformable& transformable = sprite_obj.GetDrawformable();
sf::Drawable& drawable = sprite_obj.GetDrawformable();
 
This solution is good. However, I'd like to avoid the use of templates because I will have to refactor my entire game to use templates.

2. Using two different members
class GameObject {
 public:
    GameObject(sf::Drawable* drawable, sf::Transformable* transformable) {
        drawable_  = drawable;
        transformable_ = transformable;
    }

    GameObject(void* drawformable) {
        drawable_  = static_cast<sf::Drawable*>(drawformable);
        transformable_ = static_cast<sf::Transformable*>(transformable);
        assert(drawable_ && transformable_);
    }

    sf::Drawable& GetDrawable() {
        return *drawable_;
    }

    sf::Transformable& GetTransformable() {
        return *transformable_;
    }

private:
    sf::Drawable* drawable_;
    sf::Transformable* transformable_;
};

...

sf::RectangleShape* rect = new sf::RectangleShape{...};
GameObject obj {rect, rect};
sf::Drawable& drawable = obj.GetDrawable();
sf::Transformable& transformable = obj.GetTransformable();
 
This solution is not perfect, as I have to maintain two pointers to the same object (and the second constructor has the added imperfection of casting).

Is there anyway to do this like I've demonstrated in the initial example? Or are there any other workarounds without using templates, multiple members, or casting?


Cheers,

Jeremy
« Last Edit: June 09, 2018, 03:52:21 am by jcol2 »

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11034
    • View Profile
    • development blog
    • Email
And why do you need to manage different types in the same container?

What do you do if you want to change the text of a sf::Text? Or change the texture rect of a sf::Sprite?

To me this design of everything must fit into one container is flawed, as you only ever want to be that generic on the interface for a specific purpose, for example when you pass all your objects for rendering, then you could have vector of pointers to drawables and then you also don't need access to the transformable interface.

Instead you probably want classes that specifically deal with a certain type of drawable/transformable, then you can also have interfaces to deal with changing the text of sf::Text or the texture rect for a sf::Sprite (e.g. for animation), etc
« Last Edit: June 08, 2018, 08:24:14 am by eXpl0it3r »
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Your second solution could use a templated function instead of void*, so that you don't need those ugly static_casts and you get compiler errors if the passed argument does not inherit from sf::Drawable or sf::Transformable.

Another solution would be to store a single pointer (either sf::Drawable* or sf::Transformable*) and to cross-cast (with dynamic_cast) to the other base class when needed; this is quite ugly and I wouldn't recommend it.

Storing two pointers is not very clean either, but it's simple and it works without hacks.

Unfortunately, I'm afraid there's no cleaner solution to this problem.
Laurent Gomila - SFML developer

jcol2

  • Newbie
  • *
  • Posts: 2
    • View Profile
Hi everyone,

Thanks for your advice.

Your second solution could use a templated function instead of void*, so that you don't need those ugly static_casts and you get compiler errors if the passed argument does not inherit from sf::Drawable or sf::Transformable.

That's a good idea! Maybe something like this is the best solution:
class GameObject {
 public:
    template <typename T>
    GameObject::GameObject(T* drawformable) {
        drawable_ = drawformable;
        transformable_ = drawformable;
    }

    template <typename T>
    T* GameObject::GetDrawformable() {
        return dynamic_cast<T*>(drawable_);
    }

 private:
    sf::Drawable* drawable_;
    sf::Transformable* transformable_;
};
 


And why do you need to manage different types in the same container?
This is a good question. I've been developing my game using a component architecture, where a collection of GameObjects own a bunch of components. Initially, the GameObject owned a sf::RectangleShape instance, as this provided all the functionality I wanted. Each component has an Update(GameObject* o, ...) function, where it can operate on the parent GameObject (and thus the sf::RectangleShape if it chooses so).

However, I'm interested in adding text and sprites, so I would like the GameObject to be able to store sf::Text or sf::Sprite too. I'd like to keep the GameObject / Component architecture, so I've been investigating ways to add sf::Text and / or sf::Sprite capabilities to the GameObject class without major changes.

To me this design of everything must fit into one container is flawed, as you only ever want to be that generic on the interface for a specific purpose, for example when you pass all your objects for rendering, then you could have vector of pointers to drawables and then you also don't need access to the transformable interface.

Instead you probably want classes that specifically deal with a certain type of drawable/transformable, then you can also have interfaces to deal with changing the text of sf::Text or the texture rect for a sf::Sprite (e.g. for animation), etc
I agree it is not ideal. However, if I had containers for specific sf::Drawable instances, such as RectGameObject : GameObject and SpriteGameObject : GameObject, or GameObject<sf::RectangleShape> and GameObject<sf::Sprite>, I would still have to handle the different GameObject types in the Component class! To me, this only moves the problem.

What do you do if you want to change the text of a sf::Text? Or change the texture rect of a sf::Sprite?
This is a great point. I may need to access those interfaces at some point. And if I'm storing pointers to a base class but I want the unique (non-inherited) interface of a derived class, I need to cast somewhere. Either I cast from sf::Drawable to the derived type in the GameObject, or I cast from a base GameObject to a specialised GameObject (that contains the derived type) in the Component.

I think using a template parameter to cast the underlying type (shown in the code above) might be the way to go.


Cheers,

Jeremy
« Last Edit: June 09, 2018, 07:02:26 am by jcol2 »