Bienvenue, Invité. Merci de vous connecter ou de vous inscrire.
Avez-vous perdu votre e-mail d'activation ?

Auteur Sujet: Polymorphisme Drawable&Transformable  (Lu 3711 fois)

0 Membres et 1 Invité sur ce sujet

ctxnop

  • Newbie
  • *
  • Messages: 28
    • Voir le profil
Polymorphisme Drawable&Transformable
« le: Mai 31, 2013, 04:27:54 pm »
Bonjour,
Je commence à peine a jouer avec la SFML que je connais donc très mal.

Je cherche à faire un système d'arbre pour gérer une scène. Chaque noeud est un Drawable&Transformable, autrement dit soit une Shape, soit un Sprite.

Cependant, il n'y a pas d'ancêtre unique commun aux shape et aux sprites. Du coup je ne vois pas comment faire pour faire quelque chose du genre :
sf::DrawableAndTransformable* item = new [RectangleShape|CircleShape|ConvexShape|Sprite]();

Je ne vois pas pourquoi cet ancêtre n'existe pas. Généralement quand je fais de l'héritage multiple je met toujours une interface entre les les parents et les enfants afin de pouvoir faire du polymorphisme tous les enfants ayant les mêmes parents en commun.
Est-ce volontaire ? Pourquoi ? Et du coup, une suggestion sur comment je pourrais faire autrement ?

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32498
    • Voir le profil
    • SFML's website
    • E-mail
Re : Polymorphisme Drawable&Transformable
« Réponse #1 le: Mai 31, 2013, 04:47:26 pm »
Tu as une mauvaise approche. Il ne faut pas chercher à bâtir tout ton système avec les classes SFML, SFML n'est pas un moteur ou un framework. Il faut que tu conçoives tes propres classes de base, adaptées au système que tu veux mettre en place, puis ensuite tu remplis les implémentations correspondantes avec des sf::Sprite, sf::Shape ou autre. En gros, ce ne sont pas des sprites ou des shapes que tu mets dans ton scenegraph, mais plutôt des noeuds capables de dessiner des sprites et des shapes.

Typiquement tu auras ça (pseudo-code):

class Node : public sf::Drawable, public sf::Transformable
{
    void draw(target, states)
    {
        states.transform *= getTransform();

        drawSelf(target, states);

        foreach (child node)
            target.draw(child, states);
    }

    virtual void drawSelf(target, states) = 0;
};

class SpriteNode : public Node
{
    ...

    virtual void drawSelf(target, states)
    {
        target.draw(m_sprite, states);
    }

    sf::Sprite m_sprite;
}
« Modifié: Juin 02, 2013, 08:36:03 am par Laurent »
Laurent Gomila - SFML developer

ctxnop

  • Newbie
  • *
  • Messages: 28
    • Voir le profil
Re : Polymorphisme Drawable&Transformable
« Réponse #2 le: Mai 31, 2013, 05:05:21 pm »
J'avais effectivement considéré cette méthode.
Simplement je trouve lourd de devoir tout wrapper. J'ai franchement un mauvais sentiment quand je dois encapsuler un objet pour exposer 90-100% de son interface.
Enfin bon, y'a pas 30 types à encapsuler non plus donc c'est pas un drame.

J'ai vu, peu après avoir posté, que quelqu'un avait suggéré sensiblement la même chose. Ma question fait donc un peu doublon, désolé.

Enfin bon, je vais faire comme ça, merci d'avoir répondu.

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32498
    • Voir le profil
    • SFML's website
    • E-mail
Re : Polymorphisme Drawable&Transformable
« Réponse #3 le: Mai 31, 2013, 07:16:58 pm »
Citer
Simplement je trouve lourd de devoir tout wrapper. J'ai franchement un mauvais sentiment quand je dois encapsuler un objet pour exposer 90-100% de son interface.
Plutôt que de dupliquer 90% de son interface, pourquoi ne pas faire simplement un set/getSprite ? Comme je l'ai dit, ton noeud est juste un conteneur/adaptateur de sprite, ce n'est pas un sprite.
Laurent Gomila - SFML developer

ctxnop

  • Newbie
  • *
  • Messages: 28
    • Voir le profil
Re : Polymorphisme Drawable&Transformable
« Réponse #4 le: Juin 01, 2013, 11:27:26 pm »
Désolé pour le temps de réponse ...
Si je fais un getter/setter je ne peux à nouveau plus faire de polymorphisme.
Mon but c'est de faire un code qui parcourt le scenegraph et qui pourra call "draw" sur chaque noeud, mais également des setPosition, move, etc...
L'objet manipulé doit donc posséder l'interface Drawable ET l'interface Transformable.

Donc si je fait un SpriteNode qui expose un get/set de type sf::Sprite, mon "visiteur" devra savoir qu'il est sur un SpriteNode et non sur un ShapeNode ou autre, ce qui ne m'arrange pas du tout.
Alors que si j'expose toute l'interface, mon visiteur peut parcourir des "AbstractNode" et appeller ces méthodes quelque soit le type de noeud concret.

Eroy

  • Jr. Member
  • **
  • Messages: 60
    • Voir le profil
    • E-mail
Re : Polymorphisme Drawable&Transformable
« Réponse #5 le: Juin 02, 2013, 12:46:05 am »
C'est pas ça que tu cherches ?
type erasure
(fait de tête, j'ai peut-être fait une erreur mais en gros c'est ça)

/* objet générique */
class AbstractNode
{
public:
    AbstractNode();
    virtual ~AbstractNode();

    virtual void mySexyMethod() = 0;
     ...
     // (et toutes celles qu'on veut public)
};

/* objet conteneur */
template<class Object>
class Node : public AbstractNode
{
public:
    Node(Object& o);
    virtual ~Node();

    virtual void mySexyMethod() override;
     ...

private:
    Object& m_object;
};

template<class Object>
Node<Object>::Node(Object& object) :
    m_object(object)
{}

template<class Object>
Node<Object>::mySexyMethod()
{
    m_object.mySexyMethod();
}

/* création */
AbstractNode prout = Node(*new Sprite);
prout.mySexyMethod();
 
« Modifié: Juin 02, 2013, 12:48:01 am par Eroy »

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32498
    • Voir le profil
    • SFML's website
    • E-mail
Re : Polymorphisme Drawable&Transformable
« Réponse #6 le: Juin 02, 2013, 08:35:39 am »
Citer
Mon but c'est de faire un code qui parcourt le scenegraph et qui pourra call "draw" sur chaque noeud, mais également des setPosition, move, etc...
L'objet manipulé doit donc posséder l'interface Drawable ET l'interface Transformable.
Et ce n'est pas ce que fait le code que j'ai posté plus haut ?
Laurent Gomila - SFML developer

ctxnop

  • Newbie
  • *
  • Messages: 28
    • Voir le profil
Re : Polymorphisme Drawable&Transformable
« Réponse #7 le: Juin 02, 2013, 11:51:11 am »
@Eroy C'est à peu près comme ça que j'ai fait oui.
@Laurent Oui, ton code de ta première réponse et celui que j'ai utilisé (template en plus). Et cette méthode m'impose d'implémenter sf::Drawable et sf::Transformable. Et pour éviter d'implémenter ces deux interfaces tu m'a ensuite proposé un simple get/set du sprite, ce à quoi j'ai répondu que ça m'empêche de faire du polymorphisme et donc je préfère la solution 1.
Après rien ne m'empêche de faire un get/set de sf::Sprite sur le SpriteNode, mais pour autant je suis tout de même obligé d'implémenter sf::Drawable et sf::Transformable afin d'avoir une classe de base possédant les 2 interfaces et sur laquelle faire le polymorphisme.

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32498
    • Voir le profil
    • SFML's website
    • E-mail
Re : Polymorphisme Drawable&Transformable
« Réponse #8 le: Juin 02, 2013, 07:15:21 pm »
Citer
Après rien ne m'empêche de faire un get/set de sf::Sprite sur le SpriteNode
C'est ce que j'avais en tête, oui.

Citer
mais pour autant je suis tout de même obligé d'implémenter sf::Drawable et sf::Transformable afin d'avoir une classe de base possédant les 2 interfaces et sur laquelle faire le polymorphisme.
"obligé" ? Non, mais c'est de loin la meilleure solution.
"implémenter" ? Y a juste Drawable::draw à redéfinir, et encore ce n'est pas obligatoire si vraiment tu n'en veux pas (c'est juste pour pouvoir faire window.draw(o) au lieu de o.draw(window)...).

En gros là je t'ai présenté la meilleure approche pour faire un scenegraph avec SFML ; c'est d'ailleurs exactement la stratégie qui a été adoptée dans le livre à paraître prochainement sur SFML. Tu as toutes les fonctions de transformation gratos en héritant de sf::Transformable, et tu peux faire window.draw(object) en héritant de sf::Drawable. Et tu peux facilement passer des transformations récursivement aux enfants avec sf::RenderStates et la fonction draw. Donc qu'est-ce qui te gêne exactement avec cette façon de faire ?
« Modifié: Juin 02, 2013, 07:18:46 pm par Laurent »
Laurent Gomila - SFML developer

ctxnop

  • Newbie
  • *
  • Messages: 28
    • Voir le profil
Re : Polymorphisme Drawable&Transformable
« Réponse #9 le: Juin 03, 2013, 11:57:39 am »
En fait je crois qu'on dis la même chose depuis le début, ce n'est qu'une légère différence de vocabulaire qui donne l'impression qu'on est pas d'accord.

class AbstactNode
    : public sf::Drawable, public sf::Transformable
{
};

class SpriteNode
    : public AbstractNode
{
private:
    sf::Sprite mSprite;

public:
    // Un get/set sur le sprite
    sf::Sprite& getSprite() { return mSprite; }
    void setSprite(const sf::Sprite& s) { mSprite = s; }

    // Ré-implémentation de sf::Transformable
    void setPosition(float x, float y) { mSprite.setPosition(x, y); }
    void setPosition(const sf::Vector2f& p) { mSprite.setPosition(p); }
    // etc...
};

class RectangleShape
    : public AbstractNode
{
private:
    sf::RectangleShape mShape;

public:
    // Un get/set sur le sprite
    sf::RectangleShape& getShape() { return mShape; }
    void setShape(const sf::RectangleShape& s) { mShape = s; }

    // Ré-implémentation de sf::Transformable
    void setPosition(float x, float y) { mShape.setPosition(x, y); }
    void setPosition(const sf::Vector2f& p) { mShape.setPosition(p); }
    // etc...
};

AbstractNode* n = new SpriteNode();
n.setPosition(24, 73);
// etc...
 

Si je veux pouvoir faire du polymorphisme afin d'avoir un code capable de manipuler et dessiner les nodes, peut importe le type réel qu'il y a derrière, je suis bel et bien obligé d'implémenter sf::Transformable et sf::Drawable.
Si je me contente du get/set, alors je ne peux pas faire de .setPosition() sur mon node. Je suis obligé de savoir qu'il s'agit d'un SpriteNode pour faire un .getSprite().setPosition().
Pour autant, comme je disais plus haut, écrire un "proxy" me procure toujours un petit sentiment de gène, j'ai l'impression de faire un truc que je ne devrais pas.

C'est pour ça que, dans la mesure où tu as les Sprites et les Shapes qui héritent déjà de Transformable ET de Drawable, je ne comprend pas quelles sont les raisons qui font qu'il n'y a pas un héritage intermédiaire qui donnerai un parent unique à toute ces classes. Ce qui permettrait d'utiliser cette classe comme base pour le polymorphisme, sans pour autant m'en servir comme un framework ou un moteur.
Je ne compte pas dériver Sprite pour en faire un personnage par exemple.

En supposant que :
namespace sf
{
    class Transformable { /* ... */ };
    class Drawable { /* ... */ };
    class DisplayableObject :  public Transformable, public Drawable { /* ... */ };
    class Sprite : public DisplayableObject { /* ... */ }:
    class Shape : public DisplayableObject { /* ... */ };
}
 

J'aurai alors pondu un code de ce genre là :

class Node
{
// Tout ce qu'il faut pour gérer un arbre, un pointeur sur le parent, une liste d'enfant, etc...
};

class AbstractEntity
    : public Node
{
    virtual sf::DisplayableObject& getObject() = 0;
};

class CharacterEntity
{
private:
    sf::Sprite mSprite;
public:
    virtual sf::DisplayableObject& getObject() { return mSprite; }
};

void MoveAndDraw(AbstractEntity* entity, sf::RenderTarget& target)
{
    entity->getObject().setPosition(27, 93);
    target.draw(entity.getObject());
}
 

Laurent

  • Administrator
  • Hero Member
  • *****
  • Messages: 32498
    • Voir le profil
    • SFML's website
    • E-mail
Re : Polymorphisme Drawable&Transformable
« Réponse #10 le: Juin 03, 2013, 12:22:19 pm »
En fait il y a un aspect important que tu n'as pas saisi. Dans mon exemple on ignore complètement l'aspect sf::Transformable du sprite, et on utilise celui du Node. Donc tu n'as pas à réimplémenter quoique ce soit pour le répercuter sur le sprite, lui on le laisse avec ses transformations d'origine et on n'y touche jamais.

Ce sont les setPosition/setRotation/setScale que tu vas faire sur ton Node qui seront prises en compte, dans Node::Draw (avec la ligne states.transform *= getTransform()). Le fait de passer récursivement le sf::RenderStates jusqu'au sprite va t'assurer que celui-ci sera dessiné avec les transformations appliquées au Node et à ses parents dans la hiérarchie.
Laurent Gomila - SFML developer

ctxnop

  • Newbie
  • *
  • Messages: 28
    • Voir le profil
Re : Polymorphisme Drawable&Transformable
« Réponse #11 le: Juin 03, 2013, 12:37:26 pm »
Ah d'accord, effectivement j'étais passé à côté de cette ligne.
Vu comme ça, ça simplifie grandement les choses.
Comme quoi on a bien fait de continuer d'en discuter parce que j'avais vraiment pas fait attention à la présence de cette ligne alors qu'elle est le point central de la solution...
Du coup je vais avoir un gros nettoyage à faire parce que j'ai déjà tout implémenté  :'(