/*! * \brief Gestion des collisions entre deux sf::Sprites en gérant la transparence ( pixel perfect ). * \author * \note Utilise la version pre-1.3. * \warning Les deux Sprites ne doivent pas avoir subis de rotation!! * \param s1 : premier sf::Sprite. * \param s2 : deuxième sf::Sprite. * \param AlphaMax : seuil de transparence : -1 par defaut (PixelPerfect désactivé) * \return true si collision il y a, false en cas contraire. */ inline bool AreColliding(const sf::Sprite& s1, const sf::Sprite& s2, const int AlphaMax = -1) { // Définition deux objets représentant les dimensions de s1 et s2. // Si les Sprites ont été tournés, les calculs ne fonctionneront pas! sf::Vector2f pos = s1.GetPosition() - s1.GetCenter(); const sf::FloatRect r1(pos.x, pos.y, pos.x + s1.GetSize().x, pos.y + s1.GetSize().y); pos = s2.GetPosition() - s2.GetCenter(); const sf::FloatRect r2(pos.x, pos.y, pos.x + s2.GetSize().x, pos.y + s2.GetSize().y); // Espace de collision potentiel. sf::FloatRect zone; // Testons si les Sprites se superposent. if (r1.Intersects(r2, &zone)) { if (AlphaMax >= 0) { int left1 = static_cast<int>(zone.Left - r1.Left); int top1 = static_cast<int>(zone.Top - r1.Top); int left2 = static_cast<int>(zone.Left - r2.Left); int top2 = static_cast<int>(zone.Top - r2.Top); int width = static_cast<int>(zone.GetWidth()); int height = static_cast<int>(zone.GetHeight()); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (s1.GetPixel(x+left1, y+top1).a > AlphaMax && s2.GetPixel(x+left2, y+top2).a > AlphaMax) return true; } } return false; } return true; } return false; }
/** * Partie de Game Develop * * Par Florian "4ian" Rival * */ #define SDL_COLLIDE_MAX(a,b) ((a > b) ? a : b) #define SDL_COLLIDE_MIN(a,b) ((a < b) ? a : b) //////////////////////////////////////////////////////////// /// Vérifie une collision entre deux objets ( pixel perfect ) /// Code adapaté depuis un code pour la SDL //////////////////////////////////////////////////////////// bool CheckCollision(sf::Sprite *SpriteObjet1, sf::Sprite *SpriteObjet2) { if (SpriteObjet1->GetTop() + SpriteObjet1->GetHeight() > SpriteObjet2->GetTop() && SpriteObjet1->GetLeft() + SpriteObjet1->GetWidth() > SpriteObjet2->GetLeft() && SpriteObjet1->GetLeft() < SpriteObjet2->GetLeft() + SpriteObjet2->GetWidth() && SpriteObjet1->GetTop() < SpriteObjet2->GetTop() + SpriteObjet2->GetHeight()) { float ax = SpriteObjet1->GetLeft(); float ay = SpriteObjet1->GetTop(); float bx = SpriteObjet2->GetLeft(); float by = SpriteObjet2->GetTop(); /*a - bottom right co-ordinates*/ float ax1 = ax + SpriteObjet1->GetWidth() - 1; float ay1 = ay + SpriteObjet1->GetHeight() - 1; /*b - bottom right co-ordinates*/ float bx1 = bx + SpriteObjet2->GetWidth() - 1; float by1 = by + SpriteObjet2->GetHeight() - 1; /*check if bounding boxes intersect*/ if((bx1 < ax) || (ax1 < bx)) return false; if((by1 < ay) || (ay1 < by)) return false; float inter_x0 = SDL_COLLIDE_MAX(ax,bx); float inter_x1 = SDL_COLLIDE_MIN(ax1,bx1); float inter_y0 = SDL_COLLIDE_MAX(ay,by); float inter_y1 = SDL_COLLIDE_MIN(ay1,by1); for(int y = inter_y0 ; y <= inter_y1 ; y++) { for(int x = inter_x0 ; x <= inter_x1 ; x++) { if((SpriteObjet1.GetPixel(x-ax , y-ay).a!=0)/*compute offsets for surface*/ && (SpriteObjet2.GetPixel( x-bx , y-by).a!=0))/*before pass to SDL_TransparentPixel*/ return true; } } return false; } return false; }
Note : Normalement, mon code utilise des objets. Ici, j'ai fait directement un test avec des sf::Sprite. 4ian
Une autre solution, simple et peu gourmande, consiste a attribuer un cercle a nos objets, puis a téster la collision entre ces cercles.
Mais il nous faudra avant tout une petite fonction de calcul de distance entre deux points : distance_entre_deux_points .
Puis, tout simplement :
/*! * \brief Gestion des collisions entre deux cercles * \param p1 : Centre du premier cercle * \param r1 : Rayon du premier cercle * \param p2 : Centre du second cercle * \param r2 : Rayon du second cercle * \return true si collision il y a, false en cas contraire. */ inline bool AreColliding(const sf::Vector2f& p1, const float r1, const sf::Vector2f& p2, const float r2) { if (Distance(p1, p2) <= r1+r2) return true; return false; }
/*! * \brief Gestion des collisions entre deux sf::Sprites, qui donne la direction du choc pour le sprite 1. * \author Scaerloc * \note on peut la mixer avec la fonction "pixel-perfect" ci-dessus, pour faire du pixel perfect. * \param sprite1 : premier sf::Sprite, qui est la cible (typiquement, un personnage qui veut savoir s'il peut se déplacer dans telle ou telle direction) * \param sprite2 : deuxième sf::Sprite, qui est l'obstacle. * \return un vector de 5 booléens (toutes directions, haut, bas, gauche et droite), qui valent true si le sprite 2 rentre dans le sprite 1 par la direction définie. */ // énumération des directions enum {DIR_DROITE = 0, DIR_GAUCHE = 1, DIR_HAUTE = 2, DIR_BAS = 3, TOUTES_DIR = 4}; std::vector < bool > collisionSprites(sf::Sprite sprite1, sf::Sprite sprite2) { std::vector < bool > collisionsV(5, false); /*Creation des rectangles représentants les surfaces (noms honteusements pompés à la fonction de pixel perfect ;)*/ sf::FloatRect r1(sprite1.GetLeft(), sprite1.GetTop(), sprite1.GetLeft() + sprite1.GetWidth(), sprite1.GetTop() + sprite1.GetHeight()), r2(sprite2.GetLeft(), sprite2.GetTop(), sprite2.GetLeft() + sprite2.GetWidth(), sprite2.GetTop() + sprite2.GetHeight()), zone; if (r1.Intersects(r2, &zone)) { collisionsV[TOUTES_DIR] = true; if (zone.Left < r1.Left + r1.GetWidth()/2 && zone.GetHeight() > MARGE_COLLISION_BAS) // si cela arrive par la gauche collisionsV[DIR_GAUCHE] = true; // on met le bool "gauche" à true, etc... if (zone.Right > r1.Left + r1.GetWidth()/2 && zone.GetHeight() > MARGE_COLLISION_BAS) collisionsV[DIR_DROITE] = true; if (zone.Top > r1.Top + r1.GetHeight()/2) collisionsV[DIR_BAS] = true; if (zone.Bottom < r1.Top + r1.GetHeight()/2) collisionsV[DIR_HAUTE] = true; } return collisionsV; // on renvoie le vector de booléens }
Suite au mise à jour de la SFML, la fonction membre sf::Sprite.Left n'est plus disponible; voici donc une nouvelle solution pour réaliser un test de collision localisé qui retourne la localisation sous forme angulaire (Le sprite2 est la référence au cercle trigonometrique);
- 0° est a droite du second sprite, 180° a gauche, 90° en haut, 270° en bas, (45°, 135°, 225°, 315°, sont également des valeurs pouvant être retourné)
- -1 signifie qu'il n'y a pas de collision
/*//////////////////////////////////////// Code de localisation des Collision Par Scheb *///////////////////////////////////////// #ifndef PI #define PI (3.14159265358979323846) #endif //Fonction outil pour la rotation d'un point inline sf::Vector2f RotationPoint(const sf::Vector2f& Point, float Angle) { Angle = Angle * (PI/180.0); sf::Vector2f RotatedPoint; RotatedPoint.x = Point.x * cos(Angle) + Point.y * sin(Angle); RotatedPoint.y = -Point.x * sin(Angle) + Point.y * cos(Angle); return RotatedPoint; } int CollisionBoiteSensible( sf::Sprite& Image1, sf::Sprite& Image2) { //Schema de la represensation trigonometrique // 135° 90° 45° // B_____C // 180° | | 0° // | | // A_____D // 225° 270° 315° //Cette fonction retourne une valeur angulaire suivant ce schema sf::Rect<float> boite1; boite1.Left = Image1.GetPosition().x - Image1.GetCenter().x * Image1.GetScale().x; boite1.Top = Image1.GetPosition().y - Image1.GetCenter().y * Image1.GetScale().y; boite1.Right = Image1.GetPosition().x + Image1.GetSize().x - Image1.GetCenter().x * Image1.GetScale().x; boite1.Bottom = Image1.GetPosition().y + Image1.GetSize().y - Image1.GetCenter().y * Image1.GetScale().y; if(Image1.GetRotation() != 0.f) //Si l'angle est different, on doit recalculer les boites { sf::Vector2f pt_A, pt_B, pt_C, pt_D; //Schema des points du carrée // B_____C // | | // | | // A_____D pt_A.x = boite1.Left; pt_A.y = boite1.Bottom; pt_B.x = boite1.Left; pt_B.y = boite1.Top; pt_C.x = boite1.Right; pt_C.y = boite1.Top; pt_D.x = boite1.Right; pt_D.y = boite1.Bottom; //Rotation sf::Vector2f pt_Ar, pt_Br, pt_Cr, pt_Dr; pt_Ar.x = 0; pt_Ar.y = Image1.GetSize().y; pt_Br.x = 0; pt_Br.y = 0; pt_Cr.x = Image1.GetSize().x; pt_Cr.y = 0; pt_Dr.x = Image1.GetSize().x; pt_Dr.y = Image1.GetSize().y; pt_A = RotationPoint(pt_Ar, Image1.GetRotation()); pt_B = RotationPoint(pt_Br, Image1.GetRotation()); pt_C = RotationPoint(pt_Cr, Image1.GetRotation()); pt_D = RotationPoint(pt_Dr, Image1.GetRotation()); sf::Vector2f centre; centre.x = Image1.GetCenter().x * Image1.GetScale().x; centre.y = Image1.GetCenter().y * Image1.GetScale().y; centre = RotationPoint(centre, Image1.GetRotation()); pt_A += Image1.GetPosition() - centre; pt_B += Image1.GetPosition() - centre; pt_C += Image1.GetPosition() - centre; pt_D += Image1.GetPosition() - centre; sf::Vector2f pt_Af, pt_Bf, pt_Cf, pt_Df; //Schema des points du carrée après rotation /* // Bf // / \ // Af Cf // \ / // Df */ //Utilisation de l'operateur ? : condition ? valeur_vrai : valeur_faux; pt_Af = pt_A; pt_Af = ((pt_Af.x < pt_B.x)? pt_Af : pt_B); pt_Af = ((pt_Af.x < pt_C.x)? pt_Af : pt_C); pt_Af = ((pt_Af.x < pt_D.x)? pt_Af : pt_D); pt_Bf = pt_A; pt_Bf = ((pt_Bf.y < pt_B.y)? pt_Bf : pt_B); pt_Bf = ((pt_Bf.y < pt_C.y)? pt_Bf : pt_C); pt_Bf = ((pt_Bf.y < pt_D.y)? pt_Bf : pt_D); pt_Cf = pt_A; pt_Cf = ((pt_Cf.x > pt_B.x)? pt_Cf : pt_B); pt_Cf = ((pt_Cf.x > pt_C.x)? pt_Cf : pt_C); pt_Cf = ((pt_Cf.x > pt_D.x)? pt_Cf : pt_D); pt_Df = pt_A; pt_Df = ((pt_Df.y > pt_B.y)? pt_Df : pt_B); pt_Df = ((pt_Df.y > pt_C.y)? pt_Df : pt_C); pt_Df = ((pt_Df.y > pt_D.y)? pt_Df : pt_D); //Schema des points du carrée après rotation // __Bf__ // | / \ | // Af Cf // | \ / | // __Df__ boite1.Left = pt_Af.x; boite1.Top = pt_Bf.y; boite1.Right = pt_Cf.x; boite1.Bottom = pt_Df.y; } sf::Rect<float> boite2; boite2.Left = Image2.GetPosition().x - Image2.GetCenter().x * Image2.GetScale().x; boite2.Top = Image2.GetPosition().y - Image2.GetCenter().y * Image2.GetScale().y; boite2.Right = Image2.GetPosition().x + Image2.GetSize().x - Image2.GetCenter().x * Image2.GetScale().x; boite2.Bottom = Image2.GetPosition().y + Image2.GetSize().y - Image2.GetCenter().y * Image2.GetScale().y; if(Image2.GetRotation() != 0.f) //Si l'angle est different, on doit recalculer les boites { sf::Vector2f pt_A, pt_B, pt_C, pt_D; //Schema des points du carrée // B_____C // | | // | | // A_____D pt_A.x = boite2.Left; pt_A.y = boite2.Bottom; pt_B.x = boite2.Left; pt_B.y = boite2.Top; pt_C.x = boite2.Right; pt_C.y = boite2.Top; pt_D.x = boite2.Right; pt_D.y = boite2.Bottom; //Rotation sf::Vector2f pt_Ar, pt_Br, pt_Cr, pt_Dr; pt_Ar.x = 0; pt_Ar.y = Image2.GetSize().y; pt_Br.x = 0; pt_Br.y = 0; pt_Cr.x = Image2.GetSize().x; pt_Cr.y = 0; pt_Dr.x = Image2.GetSize().x; pt_Dr.y = Image2.GetSize().y; pt_A = RotationPoint(pt_Ar, Image2.GetRotation()); pt_B = RotationPoint(pt_Br, Image2.GetRotation()); pt_C = RotationPoint(pt_Cr, Image2.GetRotation()); pt_D = RotationPoint(pt_Dr, Image2.GetRotation()); sf::Vector2f centre; centre.x = Image2.GetCenter().x * Image2.GetScale().x; centre.y = Image2.GetCenter().y * Image2.GetScale().y; centre = RotationPoint(centre, Image2.GetRotation()); pt_A += Image2.GetPosition() - centre; pt_B += Image2.GetPosition() - centre; pt_C += Image2.GetPosition() - centre; pt_D += Image2.GetPosition() - centre; sf::Vector2f pt_Af, pt_Bf, pt_Cf, pt_Df; //Schema des points du carrée après rotation /* // Bf // / \ // Af Cf // \ / // Df */ //Utilisation de l'operateur ? : condition ? valeur_vrai : valeur_faux; pt_Af = pt_A; pt_Af = ((pt_Af.x < pt_B.x)? pt_Af : pt_B); pt_Af = ((pt_Af.x < pt_C.x)? pt_Af : pt_C); pt_Af = ((pt_Af.x < pt_D.x)? pt_Af : pt_D); pt_Bf = pt_A; pt_Bf = ((pt_Bf.y < pt_B.y)? pt_Bf : pt_B); pt_Bf = ((pt_Bf.y < pt_C.y)? pt_Bf : pt_C); pt_Bf = ((pt_Bf.y < pt_D.y)? pt_Bf : pt_D); pt_Cf = pt_A; pt_Cf = ((pt_Cf.x > pt_B.x)? pt_Cf : pt_B); pt_Cf = ((pt_Cf.x > pt_C.x)? pt_Cf : pt_C); pt_Cf = ((pt_Cf.x > pt_D.x)? pt_Cf : pt_D); pt_Df = pt_A; pt_Df = ((pt_Df.y > pt_B.y)? pt_Df : pt_B); pt_Df = ((pt_Df.y > pt_C.y)? pt_Df : pt_C); pt_Df = ((pt_Df.y > pt_D.y)? pt_Df : pt_D); //Schema des points du carrée après rotation // __Bf__ // | / \ | // Af Cf // | \ / | // __Df__ boite2.Left = pt_Af.x; boite2.Top = pt_Bf.y; boite2.Right = pt_Cf.x; boite2.Bottom = pt_Df.y; } if(boite1.Intersects(boite2)) //S il y a une intersection { sf::Vector2f Centre; Centre.x = boite1.Left + (boite1.Right - boite1.Left)/2.f; Centre.y = boite1.Top + (boite1.Bottom - boite1.Top)/2.f; //On prend le centre de l'objet de réference //Schema de la technique de visualisation // // | | // 135°| 90° | 45° // ____B_____C____ // | | // 180°| | 0° // ____A_____D____ // | | // 225°| 270°| 315° // //On cherche la zone où se trouve le centre de l'objet de reference if(Centre.x <= boite2.Left) { if(Centre.y <= boite2.Top) { return 135; } else if(Centre.y >= boite2.Bottom) { return 225; } else { return 180; } } else if(Centre.x >= boite2.Right) { if(Centre.y <= boite2.Top) { return 45; } else if(Centre.y >= boite2.Bottom) { return 315; } else { return 0; } } else { if(Centre.y <= boite2.Top) { return 90; } else if(Centre.y >= boite2.Bottom) { return 270; } else { //Cas "parfait" -> toutes directions = 360 (cas normal de 0 à 359) return 360; } } } //Si valeur négative, pas de collision return -1; }