Angles et distances avec love2d

Créons un cercle qui suit le curseur de la souris. Commençons par créer un cercle :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function love.load()
  --Create an object called circle
  circle = {}

  --Give it the properties x, y, radius and speed
  circle.x = 100
  circle.y = 100
  circle.radius = 25
  circle.speed = 200
end

function love.draw()
  --Draw the circle
  love.graphics.circle("line", circle.x, circle.y, circle.radius)
end

Pour déplacer le cercle jusqu’à la souris, nous devons connaître l’angle. Nous pouvons le connaître avec la fonction math.atan2. Le premier argument est la position y que nous souhaitons accéder moins la position y de notre objet. Le second argument est le même mais pour x. C’est l’une des rares occasions où y est avec x.

Fondamentalement, ce que fait atan2 est qu’il faut un vecteur vertical et horizontal (distance + direction) et avec cette information il retourne un angle :

arc tangente
arc tangente

Pour obtenir la vitesse, nous avons besoin de soustraire la position du cercle à la position de la cible :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function love.update(dt)
  --love.mouse.getPosition returns the x and y position of the cursor.
  mouse_x, mouse_y = love.mouse.getPosition()

  angle = math.atan2(mouse_y - circle.y, mouse_x - circle.x)
end

function love.draw()
  love.graphics.circle("line", circle.x, circle.y, circle.radius)

  --Print the angle
  love.graphics.print("angle: " .. angle, 10, 10)

  --Here are some lines to visualize the velocities
  love.graphics.line(circle.x, circle.y, mouse_x, circle.y)
  love.graphics.line(circle.x, circle.y, circle.x, mouse_y)

  --The angle
  love.graphics.line(circle.x, circle.y, mouse_x, mouse_y)
end
Animation d’un angle
Animation d’un angle

Si atan2 vous déroute, ne vous inquiétez pas, tout ce que vous devez savoir est que math.atan2(cible_y – objet_y, cible_x – objet_x) vous donne un angle. Dans notre cas l’objet est le cercle et la cible le curseur de la souris.

Vous êtes sur le point d’y arriver. Ne vous laissez pas effrayer. Ce n’est pas difficile et si vous ne comprenez pas ça, ce n’est pas grave pour un débutant.

Quand vous lancez le jeu vous remarquerez que l’angle ne dépassera pas 3,14 (Pi, π). C’est parce que atan2 ne retourne pas un angle en degré mais plutôt en radian.

Cette image animée (gif) explique les radians :

Explication radian
Explication radian

Si vous êtes encore perdu, je vous recommande de lire cet article sur wikipedia

Quelques points clef :

  • math.atan2 retourne un angle en radian
  • l’angle retourné est compris entre -3,14 et 3,14.
  • 360 degrés vaut 2×π radian. Donc 90 degré équivaut à $\dfrac{π}{2}$ radian.
  • Le nombre π, écrit aussi Pi, est le rapport de la circonférence d’un cercle à son diamètre ce qui signifie que si nous devions prendre le diamètre d’un cercle et le diviser par la circonférence du cercle (périmètre), nous obtiendrions π.
Explication pi
Explication pi

En Lua nous pouvons obtenir π en utilisant math.pi.

Si vous ne comprenez pas tout correctement maintenant, ne vous découragez pas, surtout si c’est la première fois

Sinus et cosinus

Maintenant nous avons besoin de faire avancer notre cercle vers la souris. Pour ça nous allons utiliser math.cos et math.sin

Les 2 fonctions retournent un nombre entre -1 et 1, basé sur l’angle que nous passons en paramètre.

Voilà un gif pour vous aider à visualiser le sinus et le cosinus :

Animation sinus et cosinus
Animation sinus et cosinus

Et une image pour montrer exactement ce qu’il se passe sur l’image animée :

Explications sinus et cosinus
Explications sinus et cosinus

Sinus et cosinus sont des nombres compris entre -1 et 1 qui sont basés sur l’angle

Si l’angle pointe sur la gauche alors le cosinus sera -1 et le sinus sera 0 :

Explications sinus et cosinus première partie
Explications sinus et cosinus première partie

Si l’angle est en bas alors le cos vaut 0 et le sinus 1 :

Explications sinus et cosinus deuxième partie
Explications sinus et cosinus deuxième partie

Alors, comment pouvons-nous utiliser ces valeurs pour faire avancer notre cercle vers la souris ? Hé bien en multipliant votre vitesse par eux. Par exemple, si la souris est à sur une diagonale, disons en haut à droite, le sinus vaudrait $\approx −0,7$ et le $cos \approx 0,7$.

Maintenant, si nous faisons ceci :

1
2
circle.x = circle.x + circle.speed * dt
circle.y = circle.y + circle.speed * dt

Notre cercle se déplacerait directement en bas à droite. Mais en multipliant par le sin et le cos comme ceci :

1
2
circle.x = circle.x + circle.speed * cos * dt
circle.y = circle.y + circle.speed * sin * dt

Alors notre cercle se déplace horizontalement avec $circle.speed×0.7$

Et devrait bouger verticalement avec $circle.speed\times −0.7$
Ce qui signifie qu’il devrait se déplacer directement vers notre souris. Essayons ça !

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function love.update(dt)
  --love.mouse.getPosition returns the x and y position of the cursor.
  mouse_x, mouse_y = love.mouse.getPosition()

  angle = math.atan2(mouse_y - circle.y, mouse_x - circle.x)

  cos = math.cos(angle)
  sin = math.sin(angle)

  --Make the circle move towards the mouse
  circle.x = circle.x + circle.speed * cos * dt
  circle.y = circle.y + circle.speed * sin * dt
end

function love.draw()
  love.graphics.circle("line", circle.x, circle.y, circle.radius)

  --The angle
  love.graphics.line(circle.x, circle.y, mouse_x, mouse_y)
  love.graphics.line(circle.x, circle.y, mouse_x, circle.y)
  love.graphics.line(circle.x, circle.y, circle.x, mouse_y)

end
Suivi de la souris
Suivi de la souris

Les distances

Maintenant disons que nous voulons seulement déplacer notre cercle quand il est loin de notre souris. Pour faire ça, nous avons besoin de calculer la distance entre eux. Nous utilisons donc le théorème de Pythagore.

Avec lui, nous pouvons calculer la ligne la plus longue (hypoténuse) dans un triangle rectangle.

Pythagore
Pythagore

Fondamentalement, ce que vous faites est d’utiliser la longueur des cotés les plus courts en les mettant au carré. Ensuite vous en faites la somme pour finalement en prendre la racine carré et finalement avoir la longueur du coté le plus long que l’on appelle aussi hypoténuse.

Lorsque vous avez 2 points, dans notre cas le cercle et la souris, il existe également un triangle invisible. Vérifions le :

1
2
3
4
5
6
function love.draw()
  love.graphics.circle("line", circle.x, circle.y, circle.radius)
  love.graphics.line(circle.x, circle.y, mouse_x, mouse_y)
  love.graphics.line(circle.x, circle.y, mouse_x, circle.y)
  love.graphics.line(mouse_x, mouse_y, mouse_x, circle.y)
end
Triangle de Pythagore
Triangle de Pythagore

Si nous utilisons le théorème de Pythagore sur notre triangle nous pouvons trouver son hypoténuse et trouver la distance entre les 2 points.

Créons une nouvelle fonction pour ça. Premièrement nous avons besoin des cotés horizontaux et verticaux :

1
2
3
4
function getDistance(x1, y1, x2, y2)
  local horizontal_distance = x1 - x2
  local vertical_distance = y1 - y2
end

Ensuite nous avons besoin de mettre au carré les nombres. Nous pouvons les multiplier par eux-même ou utiliser ^2.

1
2
3
4
5
6
7
8
function getDistance(x1, y1, x2, y2)
  local horizontal_distance = x1 - x2
  local vertical_distance = y1 - y2

  --Both of these work
  local a = horizontal_distance * horizontal_distance
  local b = vertical_distance ^2
end

Nous avons besoin de faire la somme de ces nombres et d’en prendre la racine carré. Si vous faites $5\times 5$ ou $5^2$, vous obtenez 25. La racine carrée de 25 est 5. Nous pouvons obtenir la racine carrée avec math.sqrt :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function getDistance(x1, y1, x2, y2)
  local horizontal_distance = x1 - x2
  local vertical_distance = y1 - y2
  --Both of these work
  local a = horizontal_distance * horizontal_distance
  local b = vertical_distance ^2

  local c = a + b
  local distance = math.sqrt(c)
  return distance
end

Pour prouver que cela fonctionne, dessinons un cercle avec un rayon de la distance :

1
2
3
4
5
6
7
8
9
function love.draw()
  love.graphics.circle("line", circle.x, circle.y, circle.radius)
  love.graphics.line(circle.x, circle.y, mouse_x, mouse_y)
  love.graphics.line(circle.x, circle.y, mouse_x, circle.y)
  love.graphics.line(mouse_x, mouse_y, mouse_x, circle.y)

  local distance = getDistance(circle.x, circle.y, mouse_x, mouse_y)
  love.graphics.circle("line", circle.x, circle.y, distance)
end
Animation suivi de la souris avec Pythagore
Animation suivi de la souris avec Pythagore

Cela fonctionne !
Maintenant amusez-vous. Je veux seulement que le cercle bouge quand la distance est supérieure à 200 pixels et que plus il est près et plus il bouge doucement :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function love.update(dt)
  mouse_x, mouse_y = love.mouse.getPosition()
  angle = math.atan2(mouse_y - circle.y, mouse_x - circle.x)
  cos = math.cos(angle)
  sin = math.sin(angle)

  local distance = getDistance(circle.x, circle.y, mouse_x, mouse_y)

  if distance < 400 then
    circle.x = circle.x + circle.speed * cos * (distance/100) * dt
    circle.y = circle.y + circle.speed * sin * (distance/100) * dt
  end
end
Animation suivi souris en fonction de la distance
Animation suivi souris en fonction de la distance

Images

Utilisons une image et changeons le look de la souris :

Flèche a utiliser pour l’exemple
Flèche a utiliser pour l’exemple

L’argument optionnel pour la rotation est par défaut à 0.

Avec un angle à 0, le cos vaut 1 et le sin 0 ce qui veut dire que l’objet se déplacerait sur la droite.

Donc quand vous utilisez une image, vous devriez la voir sur la droite par défaut :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function love.load()
  arrow = {}
  arrow.x = 200
  arrow.y = 200
  arrow.speed = 300
  arrow.angle = 0
  arrow.image = love.graphics.newImage("arrow_right.png")
end

function love.update(dt)
  mouse_x, mouse_y = love.mouse.getPosition()
  arrow.angle = math.atan2(mouse_y - arrow.y, mouse_x - arrow.x)
  cos = math.cos(arrow.angle)
  sin = math.sin(arrow.angle)

  arrow.x = arrow.x + arrow.speed * cos * dt
  arrow.y = arrow.y + arrow.speed * sin * dt
end

function love.draw()
  love.graphics.draw(arrow.image, arrow.x, arrow.y, arrow.angle)
  love.graphics.circle("fill", mouse_x, mouse_y, 5)
end

Quand vous lancez le jeu vous devriez noter que la flèche est un peu éteinte

Problème avec la flèche
Problème avec la flèche

C’est parce que l’image tourne autour de sa partie supérieure gauche au lieu de son centre. Pour fixer ça, nous avons besoin de mettre l’origine au centre de l’image :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function love.load()
  arrow = {}
  arrow.x = 200
  arrow.y = 200
  arrow.speed = 300
  arrow.angle = 0
  arrow.image = love.graphics.newImage("arrow_right.png")
  arrow.origin_x = arrow.image:getWidth() / 2
  arrow.origin_y = arrow.image:getHeight() / 2
end

function love.draw()
  love.graphics.draw(arrow.image,
    arrow.x, arrow.y, arrow.angle, 1, 1,
    arrow.origin_x, arrow.origin_y)
  love.graphics.circle("fill", mouse_x, mouse_y, 5)
end

Et maintenant, elle indique correctement notre souris.

Animation de la flèche qui suit la souris
Animation de la flèche qui suit la souris

Résumé

Nous pouvons faire bouger un objet avec un angle en obtenant le cosinus et le sinus de cet angle. Ensuite nous le déplaçons avec le x et une vitesse multiplié par le cosinus. Idem pour y mais avec le sinus. Nous pouvons calculer la distance entre 2 points en utilisant le théorème de Pythagore. Quand vous utilisez une image vous devriez avoir le centre à droite par défaut. N’oubliez pas de mettre l’origine au centre.