Au chapitre 13, nous avons expliqué comment détecter une collision. Dans ce chapitre, nous allons plus loin. Imaginez que vous ayez un jeu où vous pouvez vous déplacer et où il y a des murs que vous ne pouvez pas traverser. Pour y parvenir, nous devons non seulement détecter la collision, nous devons également nous assurer que le joueur s’arrête de bouger et ne reste pas coincé à l’intérieur du mur. Nous devons repousser le joueur.
Avant de commencer, je tiens à vous dire que la résolution des collisions est très difficile, et c’est quelque chose que même les professionnels ont du mal à gérer. Regardez les speedruns par exemple. Il y a beaucoup de problèmes où vous pouvez passer à travers les murs et autres. La collision que nous allons faire est assez solide mais loin d’être parfaite. Et ne vous sentez pas mal si vous avez du mal à comprendre ce que nous faisons ici.
Pour ce tutoriel, nous avons besoin de deux choses : un joueur qui peut marcher dans 4 directions et quelques murs. Créons une classe de base pour les deux. Une classe d’entité.
Nous avons donc les fichiers suivants :
- main.lua
- payer.lua
- wall.lua
- entity.lua
- classic.lua
Commençons par la classe Entity. Il a un x, y, une largeur et une hauteur. Utilisons une image pour nos murs et notre lecteur, et utilisons la largeur et la hauteur de cette image.
|
|
end Et ajoutons la détection de collision. Savez-vous toujours comment le faire et pourquoi cela fonctionne comme ça? Peut-être relisez le chapitre 13.
|
|
Maintenant que nous avons notre sous-classe, nous pouvons créer notre lecteur et notre mur. Voici les images que nous utiliserons (les bordures colorées nous aident à voir s’il y a collision) :
|
|
|
|
Faisons en sorte que nous puissions déplacer notre joueur en utilisant les touches fléchées.
|
|
Bon maintenant, créons ces objets dans main.lua
|
|
Maintenant, nous pouvons nous promener, et bien sûr, nous pouvons traverser notre mur. Voici donc une idée, que se passe-t-il si nous gardons une trace de notre position précédente, puis chaque fois que nous heurtons un mur, nous revenons simplement à cette position. De cette façon, le joueur ne peut jamais traverser un mur.
Nous devons apporter quelques modifications à notre classe Entity. Nous ajoutons un dernier objet pour nos positions précédentes, et nous ajoutons une fonction qui vérifie s’il y a collision et si c’est le cas, nous ramène à notre position précédente.
Puisqu’il y a beaucoup de petites modifications au code dans ce chapitre, j’ai ajouté —- AJOUTEZ CECI aux endroits où vous devez ajouter / changer quelque chose au code.
|
|
Nous devons appeler la fonction baseclass dans notre classe Player pour que cela fonctionne.
|
|
Et nous avons besoin d’appeler la fonction resolveCollision() dans main.lua
|
|
Essaye le. Vous remarquerez que cela fonctionne … en quelque sorte. Si vous regardez attentivement, vous verrez peut-être qu’il y a parfois un petit écart entre le lecteur et le mur. C’est parce que les joueurs se déplacent si vite qu’ils sautent sur cette partie, touchent le mur et sont renvoyés à leur position précédente.
Au lieu de placer le joueur sur sa position précédente, nous devons déplacer le joueur jusqu’à ce qu’il ne touche plus le mur. Donc, fondamentalement, nous le repoussons. Pas de retour à sa position précédente, mais juste assez pour qu’il ne chevauche plus le mur (pour que les bords se touchent).
Pour cela, nous devons savoir deux choses :
- Jusqu’où faut-il déplacer le joueur pour qu’il ne chevauche plus le mur
- Dans quelle direction devons-nous éloigner le joueur du mur ?
Pour l’instant, supposons que le joueur vient de la gauche, puis plus tard, nous pourrons le faire fonctionner dans toutes les directions.
Nous devons donc calculer combien le joueur chevauche le mur. Nous pouvons le faire en calculant le côté droit du joueur – le côté gauche du mur. Si le côté droit du joueur est en position x 225 et le côté gauche du mur en position x 205, alors le joueur doit être poussé vers la gauche avec 20 pixels.
|
|
Maintenant, il n’y a plus d’espace. Et comme nous ne poussons le joueur que horizontalement, il peut désormais se déplacer verticalement tout en touchant le mur.
Faisons-le fonctionner dans toutes les directions. Pour déterminer de quelle direction vient le joueur, nous pouvons utiliser notre position précédente. Parce qu’à moins que nous venions du coin, notre position précédente était déjà alignée verticalement ou horizontalement avec le mur.
Si nous venions de la gauche, nous sommes déjà alignés verticalement. Nous entrons déjà en collision avec le mur à un niveau vertical. Rappelez-vous les quatre conditions du chapitre 13? Juste avant de toucher le mur de gauche, deux de ces conditions sont remplies.
Le côté inférieur de A est plus bas que le côté supérieur de B.
Le côté supérieur de A est plus haut que le côté inférieur de B.
Dans ce cas, nous devons nous déplacer horizontalement pour entrer en collision avec le mur, ce qui signifie que nous savons que le joueur est venu du côté gauche ou droit.
Nous vérifions donc si sur notre position précédente si nous étions déjà en collision horizontale ou verticale avec le mur et sur cette base, nous pouvons déterminer si nous devons repousser le joueur verticalement ou horizontalement.
|
|
Maintenant que nous savons cela, nous devons vérifier de quel côté ils se touchent. Un moyen simple consiste à vérifier le centre du mur et du lecteur. Dans le cas, il était déjà aligné verticalement: si le centre du joueur est plus à gauche que le centre du mur, nous le poussons vers la gauche. Essayons-le.
|
|
Ça marche! Mais d’accord, c’est beaucoup d’informations à la fois. Récapitulons cela.
- Le joueur et le mur sont déjà à la même hauteur. Avec ces informations, nous pouvons déterminer que le joueur doit avoir bougé horizontalement pour entrer en collision avec le mur.
- Le centre du joueur est plus à gauche que le centre du mur, nous devons donc le pousser vers la gauche.
- Le côté droit du lecteur est à 5 pixels à droite du côté gauche du mur. Ou en termes plus simples: le joueur doit être repoussé de 5 pixels pour ne plus se chevaucher avec le mur.
- Le joueur est repoussé correctement! Je suis à court de mots .. nous l’avons fait!
Donc ça marche. Mais il y a encore une chose que je veux corriger. Parce qu’en ce moment, nous appelons player: resolverCollision(mur) mais si nous devions l’appeler dans l’autre sens autour du mur: resolverCollision (joueur), ce ne serait pas le cas. Bien sûr, dans ce cas, nous savons que le mur est plus fort que le joueur, mais supposons que nous ne le savons pas.
Pour résoudre ce problème, nous pouvons ajouter une variable appelée force, et la propriété avec une force plus faible est repoussée.
|
|
|
|
Maintenant, nous vérifions la valeur dans Entity: resolverCollision (e) et si la valeur de self.strength est supérieure à la valeur de e.strength, alors nous appelons e: resolverCollision (self). Nous inversons donc les rôles.
|
|
Et maintenant, peu importe l’ordre dans lequel nous appelons la fonction.
Poussez une caisse
Le mur repousse le joueur. Mais que faire si nous voulons que le joueur bouge quelque chose? Pour ce faire, nous devons apporter quelques modifications à notre code.
Créons d’abord une classe Box. Nous pouvons utiliser cette image pour cela :
|
|
Ensuite, nous voulons ajouter la boîte, et pendant que nous y sommes, créons un tableau d’objets.
|
|
Nous pouvons maintenant utiliser une boucle ipair pour mettre à jour et dessiner les objets, mais qu’en est-il de la résolution de la collision? Parce que nous voulons que tous les objets vérifient la collision avec tous les autres objets. Comment pouvons-nous y parvenir? Nous pourrions simplement avoir deux boucles for, où nous devons également vérifier s’il ne s’agit pas du même objet. Ainsi :
|
|
Le problème avec ceci est cependant que nous vérifions maintenant la collision deux fois pour chaque objet. Après avoir appelé player: resolverCollision (mur), nous n’avons pas besoin d’appeler wall: resolverCollision (joueur) mais c’est essentiellement ce que nous faisons. Donc, pour éviter cela, nous parcourons la liste des objets dans la première boucle, et dans la deuxième boucle, nous parcourons la liste, en commençant par l’objet à côté de l’objet dans la première boucle.
|
|
(J’ai ajouté un mur et une boîte supplémentaires pour le rendre plus clair)
Ainsi, le premier objet, le joueur, résout la collision avec les quatre objets suivants.
Le deuxième objet, le mur, n’a pas besoin de résoudre la collision avec le joueur, car cela a déjà été fait précédemment par le joueur.
Etc.
Quoi qu’il en soit, nous ne pouvons pas encore pousser la boîte. Nous devons rendre le joueur plus fort que la boîte.
|
|
Et maintenant, nous pouvons pousser la boîte. Génial, alors avons-nous terminé? Nan! Essayez de pousser la boîte contre le mur. Cela ne semble pas correct, n’est-ce pas ?
Pourquoi cela arrive-t-il ? Parce que le joueur pousse la boîte contre le mur, mais le mur repousse la boîte au joueur. Ce que vous voulez, c’est que la boîte et le joueur cessent de bouger lorsqu’ils entrent en collision avec le mur. Pour résoudre ce problème, nous devons passer la force du mur sur la boîte, de sorte que lorsque la boîte est suffisamment solide pour repousser le joueur.
Mais cette force ne devrait être que temporaire, sinon vous ne pourriez pas pousser la boîte dès que vous la touchez, car elle obtient la même force que le joueur. Nous créons donc une propriété distincte pour ce qu’on appelle tempStrength.
|
|
Presque fini! Le problème est maintenant que le joueur pousse la boîte dans le mur et que le mur repousse la boîte dans le joueur, mais après cela, nous ne résolvons plus jamais la collision entre la boîte et le joueur. Parce que nous l’avons déjà fait dans cette boucle. Donc, ce que nous devons faire, c’est continuer à vérifier la collision entre les objets jusqu’à ce que toute collision soit résolue.
Faisons en sorte que la fonction Entity:resolverCollision (e) renvoie vrai ou faux en fonction de la collision. S’il y a collision, nous parcourons à nouveau les objets et vérifions une fois de plus s’il y a collision.
|
|
Maintenant, nous voulons faire en sorte qu’il continue de vérifier la collision tant qu’il y a eu résolution de la collision. Pour cela, nous pouvons utiliser une boucle while. Cela continue de boucler tant que la déclaration est vraie. Attention cependant! Si vous utilisez une boucle while dans le mauvais sens, cela peut créer une boucle sans fin et planter votre jeu. Pour cette raison, ajoutons également une limite au nombre de boucles. Il peut arriver que, dans une occasion étrange, nous continuions à avoir des collisions et nous nous retrouvions coincés dans une boucle inifite. Il vaut mieux être en sécurité. Si après 100 boucles il y a encore collision, nous rompons la boucle while.
|
|
Ça marche! Ça marche enfin! On peut même avoir plusieurs boites et ça marche toujours !
Bibliothèques
Notre collision fonctionne très bien, mais loin d’être parfaite. Pour une gestion plus avancée des collisions, consultez Bump. Pour une collision avec des formes autres que des rectangles, consultez HC.
Résumé
Nous pouvons résoudre la collision en poussant le joueur hors de l’objet avec lequel il entre en collision. Nous pouvons utiliser une propriété de force pour déterminer quel objet doit pousser lequel. Nous pouvons transmettre cette force de sorte que lorsque vous poussez une boîte dans un mur, la boîte repoussée par le mur repousse le joueur. Nous devons continuer à résoudre les collisions avec les objets jusqu’à ce qu’il n’y ait plus de collision. Avec une boucle while, nous pouvons parcourir quelque chose tant que la déclaration utilisée est vraie. Nous devons être prudents avec l’utilisation d’une boucle while, car une boucle sans fin fera planter notre jeu.