Sujet n°14092
Posté par Zohran le 27 Aoû - 11:09 (2014)
Titre : Question Script : Gestion de collision entre 2 sprites[OK !]
Bonjour, voilà, je me suis demandé plusieurs fois, en script, comment fait-on pour gérer la collision entre 2 sprites, en tenant compte de la transparence? (comme pour les jeux de plateforme par exemple).

Posté par Mack le 27 Aoû - 11:19 (2014)
Bah généralement, on tiens pas compte de la transparence, on créer des masque de collisions rectangulaire, parce que c'est beaucoup moins chiant.

Posté par Nuri Yuri le 27 Aoû - 11:34 (2014)
Exact, on se préoccupe pas de la transparence => trop de calculs pour une effet minime.
Pour la collision entre deux sprites, faut que tu fasse comme mon script de gestion de la colision de la souris avec un sprite quelconque sauf que tu compares les 4 cotés du sprite des deux sprites pour savoir si il y a collision :

sp1 : haut1, bas1, gauche1, droite1 (calculé selon les propriétés du sprite)
sp2 : haut2, bas2, gauche2, droite2 (calculé selon les propriétés du sprite)

Collision = ((haut1 > bas2 and haut1 <= haut2) or (bas1 >= bas2 and bas1 < haut2)) and ((droite1 > gauche2 and droite1 <= droite2) or (gauche1 >= gauche2 and gauche1 < droite2))
C'est la version simple. Pour la version qui prend en compte les angles des sprites faudra faire des maths (déterminants, produits scalaires, intersection, distance d'une droite à une droite etc...).

Posté par Zohran le 27 Aoû - 15:40 (2014)
Merci pour vos réponses, serait-il possible de prendre un exemple, parce que je ne saisis pas bien ce que vous voulez dire, merci Imbécile heureux

Posté par Mack le 27 Aoû - 21:25 (2014)


En gros tu as deux rectangles, un bleu, et un vert, chacun étant la hitbox de ton sprite.
Dans le premier cas, les deux rectangles de ne se touchent pas, dans le second si.

En programmation, pour savoir si ça se touche ou pas, c'est assez simple :
Chacun des rectangles à 4 angles, le haut gauche ( Violet ), le haut droite ( Orange ), le bas gauche ( bleu ), et le bas droite ( Rouge ).

Dans chacun des rectangles, on connait la position du point Rouge, ainsi que la largeur et la hauteur du rectangle.

En gros on a pour chaque rectangle :
X/Y qui correspond au coordonnées du point Violet en haut à gauche.
W qui correspond à la largeur du rectangle.
H qui correspond à la hauteur.

X1/Y1/W1/H1 correspondent au rectangle Vert, et X2/Y2/W2/H2 correspondent au rectangle Bleu.

Si (X1 >= X2 + W2) OU (X1 + W1 <= X2) OU (Y1 >= Y1 + H1) OU (Y1+ H1<= Y2)
Alors les rectangles ne se touchent pas.
Sinon, les rectangles ce touchent.

C'est une collision AABB, le Site du Zéro à un tuto plutôt bien fait je trouve :
http://fr.openclassrooms.com/informatique/cours/theorie-des-collisions/form…
Je te conseil d'y jeter un oeil, je pense pas avoir était très clair xD.

Posté par Nuri Yuri le 27 Aoû - 22:43 (2014)
En pensant ne se touche pas ça fait tout de suite une condition moins lourde x)

Posté par Mack le 28 Aoû - 09:32 (2014)
Oui ^^.

Mais je suis en train de me demander, il y a pas une fonction dans la classe Rectangle qui fait ça ?
( Ça fait trop longtemps que j'ai pas toucher à RM xD )

Posté par Nuri Yuri le 28 Aoû - 10:41 (2014)
Non, la classe Rect c'est du data rien de plus.

Posté par Zohran le 28 Aoû - 11:16 (2014)
D'accord, ça j'ai bien compris mais ce que je ne comprends pas, c'est que si mon image n'a aucune forme droite(rectangle, triangle...etc), comment je fais???

J'avais déjà vu un script de jeu plateforme style mario, et ça a déjà été réalisé, donc ça ne doit pas être infaisable... Il n'y a aucune fonction existante pour cela?

Posté par Mack le 28 Aoû - 11:25 (2014)
Nope, pas à ma connaissance.
Mais de toute façon, si tu le fais au pixel perfect, t'as 99 chance sur 100 que ça te foute la merde.
Après, si tu y tiens, il faut que tu programmes un truc qui te scan ton image, et ajoute un petit rectangle à un polygone pour chaque pixel qui n'est pas du vide.
( Donc ça va être la galère à programmer, et niveau performance ça risque de pas être ça xD )

Posté par Zohran le 28 Aoû - 15:41 (2014)
Et le script déplacement par pixel, il fonctionne comment lui?

Posté par Nuri Yuri le 28 Aoû - 16:38 (2014)
Nan mais fais la différence entre la 2D et la 3D.
En 3D tu as des fonctions pour savoir si des polygones se traversent, en 2D t'as un bitmap et un bitmap te permet pas de définir exactement si c'est bon ou pas.
Les scripts de déplacements par pixels si ils sont bien fait devraient te laisser te déplacer dans toute la case. Pour éviter de marcher sur un mur, il faut que tu interdise au joueur de se déplacer à moins de 10 pixels d'un mur (ça devrait être suffisant) et normalement tout devrait bien aller.

Pour des scripts de ce genre, tu met une hitbox en pixel sur les PNJ pour les tiles, un rayon pour tous les autres PNJ entre eux (10 "pixels" pareil).

T'as deux manière de faire, tout en entier et dans ce cas tu as des sous espaces vectoriels. Avec des coordonnées flotantes et dans ce cas tu as des distance assez simples à calculer.

Posté par Mack le 28 Aoû - 16:41 (2014)
Bah, perso, même quand je programme des déplacements au pixels ( genre pour un jeu de plateforme ), bah j'utilise des rectangle pour presque toute les hitbox.
( Enfin, ça m'arrive d'utiliser des triangles, mais en générale, juste des rectangles c'est largement suffisant. )

Posté par Zohran le 28 Aoû - 18:21 (2014)
Sinon faut créer un tableau contenant l'ensemble des coordonnées X et Y définissant l'image pour chaque bitmap.

Et si je passe par autre chose que rmvx ace, par un langage différent, comme python par exemple ou du C, est-ce plus simple ou non?

Posté par Mack le 28 Aoû - 18:22 (2014)
Bah sans doute oui, mais bon, franchement tu vas t'emmerder pour rien.

C'est pour faire quoi au juste ?

Posté par Zohran le 28 Aoû - 18:24 (2014)
Dans tous les cas c'est pénible alors si je comprends bien x)

Je me posais la question c'est tout. Il me prend souvent en ce moment de vouloir concevoir un projet entièrement neuf de A à Z, et je voulais avoir la réponse sur la collision des sprites depuis un moment, voilà, c'est tout x)

Posté par Mack le 28 Aoû - 18:26 (2014)
Bah comme je te dis, on simplifie quasiment tout le temps la hitbox d'un sprite à une forme ultra simplifié, très très souvent un rectangle ^^.
Et on le fait autant pour les performances, que parce que ça marche mieux ^^.


Je sais pas si tu as déjà touché à Game Maker, mais dedans ils ont une options pour gérer les collisions à la transparance du sprite, et si tu le fais pour des sprites ayant des animations, ça va te foutre un gros bordel puisque selon la frame affiché la hitbox sera pas la même, et du coup d'une frame sur l'autre une partie de la hitbox pourrait se trouver dans un mur ^^.

Posté par Zohran le 28 Aoû - 22:17 (2014)
Merci pour vos réponses, j'ai trouvé une solution adéquate.
J'aurais une dernière demande. Sous rmvx ace, la class Sprite ne figure pas dans le manuel (j'aurais aimé voir comment elle est faite), avez-vous des sources la dessus?

Posté par Nuri Yuri le 28 Aoû - 22:32 (2014)
http://puu.sh/bbRxi/bacd949935.png
Je l'ai dans l'aide perso...

Posté par Zohran le 29 Aoû - 19:19 (2014)
Merci Yuri mais ce n'est pas ça que je cherche, je voudrais la class dans son intégrale, y compris la définition de INITIALIZE par exemple.

Posté par Pαlвσlѕку le 29 Aoû - 20:00 (2014)
Il faudrait probablement pour ça le code source de la dll, ce qu'on a pas.

Posté par Nuri Yuri le 30 Aoû - 13:46 (2014)
La classe dans son intégrale ?
Si tu parle du code, déjà c'est du C et pas du Ruby après c'est un code à but lucratif donc tu peux rêver pour le trouver dans le manuel d'aide.
Sinon, la méthode initialize de sprite c'est :
¤Sprite.new(viewport=nil) : Crée un sprite contenu dans un viewport ou la fenêtre générale en fonction de l'argument viewport.

Posté par Zohran le 31 Aoû - 19:10 (2014)
Bonsoir, pour contourner le problème de la gestion de la collision entre deux sprites, je suis en train de créer une sous classe de Sprite, appelé Picture, qui permettrait une collision précise.

Cette classe crée un second sprite invisible en noir et blanc qui définit la zone de collision...bref, je vais pas rentrer dans le détail.

Par contre, je suis coincé pour une chose, j'ai un bug lorsque je veux faire un clone de la superclasse.
Je vous link le script si ça peut vous donner une piste:

Code:
class Picture < Sprite
 
  attr_reader:limits_table
 
  def initialize(file,viewport=nil)
    @file = file
    @limits_table = [[],[],[],[]]
   
    super(viewport)
    self.bitmap = Bitmap.new(file)
   
    update
    make_limits_table
  end
 
  def update
    super
    update_collision_zone
  end
 
  def update_collision_zone
    @collision_zone = super.clone
    @collision_zone.bitmap = Bitmap.new(@file+"_collision_zone")
    @collision_zone.visible = false
  end
 
  def dispose
    super
    @collision_zone.dispose
  end
 
  def pixels_number
    return @collision_zone.width*@collision_zone.height
  end
 
  def make_limits_table
    table = []
    y_table = []
    x = y = -1
   
    for i in 0..pixels_number
      x > @collision_zone.width-1 ? x = 0 : x += 1
      y = 1*i/@collision_zone.width-1
      if @collision_zone.bitmap.get_pixel(x,y) == Color.new(0,0,0)
        table.push([x,y])
      end
    end
   
    width = table.sort.reverse[0][0]-table.sort[0][0]
    height = table.sort.reverse[0][1]-table.sort[0][1]
   
    for i in 0..table.size-1
      y_table.push(table[i].reverse)
    end
   
    #Ensemble des pixels les plus bas de l'image
    for i in 0..width-1
      @limits_table[0].push(y_table.sort.reverse[i])
      @limits_table[0][i] = @limits_table[0][i].reverse
    end
   
    #Ensemble des pixels les plus à gauche de l'image
    for i in 0..height-1
      @limits_table[3].push(table.sort[i])
    end
   
    #Ensemble des pixels les plus à droite de l'image
    for i in 0..height-1
      @limits_table[3].push(table.sort.reverse[i])
    end
   
    #Ensemble des pixels les plus haut de l'image
    for i in 0..width-1
      @limits_table[3].push(y_table.sort[i])
      @limits_table[0][i] = @limits_table[0][i].reverse
    end
   
  end

  def front?(picture,direction)
    result = false
    case direction
    #Bas
    when 0
      self_y_table = []
      y_table = []
      for i in 0..@limits_table[0].size-1
        self_y_table.push(@limits_table[0][i][1]+1)
      end
     
      for i in 0..picture.limits_table[3].size-1
        y_table.push(picture.limits_table[3][i][1])
      end
     
      self_y_table += y_table
      self_y_table.uniq.size != self_y_table.size ? true : false
    #Gauche
    when 1
      self_x_table = []
      x_table = []
      for i in 0..@limits_table[1].size-1
        self_x_table.push(@limits_table[1][i][0]-1)
      end
     
      for i in 0..picture.limits_table[2].size-1
        x_table.push(picture.limits_table[2][i][0])
      end
     
      self_x_table += x_table
      self_x_table.uniq.size != self_x_table.size ? true : false
    #Droite
    when 2
      self_x_table = []
      x_table = []
      for i in 0..@limits_table[2].size-1
        self_x_table.push(@limits_table[2][i][0]+1)
      end
     
      for i in 0..picture.limits_table[1].size-1
        x_table.push(picture.limits_table[1][i][0])
      end
     
      self_x_table += x_table
      self_x_table.uniq.size != self_x_table.size ? true : false
    #Haut
    when 3
      self_y_table = []
      y_table = []
      for i in 0..@limits_table[3].size-1
        self_y_table.push(@limits_table[3][i][1]-1)
      end
     
      for i in 0..picture.limits_table[0].size-1
        y_table.push(picture.limits_table[0][i][1])
      end
     
      self_y_table += y_table
      self_y_table.uniq.size != self_y_table.size ? true : false
    end
  end
 
  def collision?(picture,direction)
   
  end
 
end

Ca bug à la ligne où il y a @collision_zone = super.clone.
Merci de vos réponses par avance !

Posté par Nuri Yuri le 31 Aoû - 20:12 (2014)
super appel la méthode du même nom dans la super classe.
Fais gaffe ton truc est une machine à problèmes.

Posté par Zohran le 31 Aoû - 20:16 (2014)
Donc je ne peux pas faire une copie du Sprite et de ces données? :(

Posté par Nuri Yuri le 31 Aoû - 22:41 (2014)
Je sais pas, si tu fais self.clone et que ça marche pas ça veut dire que c'est pas possible. Les sprites sont des objets du type T_DATA, c'est pas évident de copier ces objets sans définir de méthode pour le faire car ils ont une mémoire propre que Ruby ne regarde pas.

Posté par Zohran le 31 Aoû - 22:49 (2014)
Ouais exacte, c'est ce que je craignais aussi, j'ai le message can't create clone Picture.
Bon bah va falloir faire un clone manuel...

Posté par Nuri Yuri le 1 Sep - 06:47 (2014)
La question est, est-ce que c'est utile de faire un clone d'un sprite si le clone ne sera jamais visible ?

Posté par Zohran le 1 Sep - 10:50 (2014)
Nuri Yuri a écrit:
La question est, est-ce que c'est utile de faire un clone d'un sprite si le clone ne sera jamais visible ?

Il n'est pas visible certe, mais j'en ai besoin pour la méthode get_pixel, et pour cela, faut un sprite.

Ah moins que tu ais une autre idée en tête? Imbécile heureux

Posté par Nuri Yuri le 1 Sep - 11:02 (2014)
La méthode get_pixel, width, height c'est dans Bitmap pas Sprite.
T'as pas sur SMFL ou des trucs tordu du genre, les méthodes se trouve là ou le data est stocké par là où il est pointé.

Posté par Zohran le 1 Sep - 11:23 (2014)
On peut créer un bitmap sans Sprite??? Scooby-doo
Alors là je savais pas o_o

EDIT: C'est vrai que le manuel n'oblige pas d'assigner le bitmap à un sprite, je n'y avais jamais fait attention o_o
Bah du coup, j'apprend un sacré truc la o_o

Il me reste un dernier problème, j'ai beau avoir une image avec des pixels noirs et blanc, quand je crée le tableau qui référence les coordonnées des pixels noirs, il me met les coordonnées de tous les pixels du bitmap...

En gros, le problème, quand j'utilise la méthode front, ça ne me retourne pas toujours le bon résultat, ça rate quelque part :/

Je link la dernière version du code(correction de quelques erreurs)
Code:
class Picture < Sprite
 
  attr_reader:limits_table
  attr_reader:collision_zone_table
 
  def initialize(file,viewport=nil)
    @file = file
    @limits_table = [[],[],[],[]]
    @collision_zone_table = []
   
    super(viewport)
    self.bitmap = Bitmap.new(file)
   
    update
  end
 
  def update
    super
    update_collision_zone
  end
 
  def update_collision_zone
    @collision_zone = Bitmap.new(@file+"_collision_zone")
    make_tables
  end
 
  def dispose
    super
    @collision_zone.dispose
  end
 
  def pixels_number
    return @collision_zone.width*@collision_zone.height
  end
 
  def make_tables
    table = []
    y_table = []
    x = y = -1
   
    for i in 0..pixels_number
      x > @collision_zone.width-1 ? x = 0 : x += 1
      y = 1*i/@collision_zone.width-1
      if @collision_zone.get_pixel(x,y) == Color.new(0,0,0)
        table.push([x,y])
        @collision_zone_table.push([x,y])
      end
    end
   
    width = table.sort.reverse[0][0]-table.sort[0][0]
    height = table.sort.reverse[0][1]-table.sort[0][1]
   
    for i in 0..table.size-1
      y_table.push(table[i].reverse)
    end
   
    #Ensemble des pixels les plus bas de l'image
    for i in 0..width-1
      @limits_table[0].push(y_table.sort.reverse[i])
      @limits_table[0][i] = @limits_table[0][i].reverse
    end
   
    #Ensemble des pixels les plus à gauche de l'image
    for i in 0..height-1
      @limits_table[1].push(table.sort[i])
    end
   
    #Ensemble des pixels les plus à droite de l'image
    for i in 0..height-1
      @limits_table[2].push(table.sort.reverse[i])
    end
   
    #Ensemble des pixels les plus haut de l'image
    for i in 0..width-1
      @limits_table[3].push(y_table.sort[i])
      @limits_table[3][i] = @limits_table[0][i].reverse
    end
   
    #Ajout des X et Y pour obtenir les coordonnées réelles des pixels
    for i in 0..@limits_table[0].size-1
      @limits_table[0][i][0] += self.x
      @limits_table[0][i][1] += self.y
    end
   
    for i in 0..@limits_table[1].size-1
      @limits_table[1][i][0] += self.x
      @limits_table[1][i][1] += self.y
    end
   
    for i in 0..@limits_table[2].size-1
      @limits_table[2][i][0] += self.x
      @limits_table[2][i][1] += self.y
    end
   
    for i in 0..@limits_table[3].size-1
      @limits_table[3][i][0] += self.x
      @limits_table[3][i][1] += self.y
    end
   
    for i in 0..@collision_zone_table.size-1
      @collision_zone_table[i][0] += self.x
      @collision_zone_table[i][1] += self.y
    end
   
  end
 
  def front?(picture,direction)
    unless collision?(picture)
      return false
    else
      case direction
      #Bas
      when 0
        self_y_table = []
        y_table = []
        for i in 0..@limits_table[0].size-1
          self_y_table.push(@limits_table[0][i][1]+1)
        end
     
        for i in 0..picture.limits_table[3].size-1
          y_table.push(picture.limits_table[3][i][1])
        end
     
        self_y_table += y_table
        self_y_table.uniq.size != self_y_table.size ? true : false
      #Gauche
      when 1
        self_x_table = []
        x_table = []
        for i in 0..@limits_table[1].size-1
          self_x_table.push(@limits_table[1][i][0]-1)
        end
     
        for i in 0..picture.limits_table[2].size-1
          x_table.push(picture.limits_table[2][i][0])
        end
     
        self_x_table += x_table
        self_x_table.uniq.size != self_x_table.size ? true : false
      #Droite
      when 2
        self_x_table = []
        x_table = []
        for i in 0..@limits_table[2].size-1
          self_x_table.push(@limits_table[2][i][0]+1)
        end
     
        for i in 0..picture.limits_table[1].size-1
          x_table.push(picture.limits_table[1][i][0])
        end
     
        self_x_table += x_table
        self_x_table.uniq.size != self_x_table.size ? true : false
      #Haut
      when 3
        self_y_table = []
        y_table = []
        for i in 0..@limits_table[3].size-1
          self_y_table.push(@limits_table[3][i][1]-1)
        end
     
        for i in 0..picture.limits_table[0].size-1
          y_table.push(picture.limits_table[0][i][1])
        end
     
        self_y_table += y_table
        self_y_table.uniq.size != self_y_table.size ? true : false
      end
    end
  end
 
  def collision?(picture)
    result = 0
    for i in 0..picture.collision_zone_table.size-1
      if @collision_zone_table.include?(picture.collision_zone_table[i])
        result += 1
      end
    end
    result > 0 ? true : false
  end
 
end

Laissez tomber, j'ai trouvé une méthode bien plus simple pour la méthode Front x)