1

Salut tout le monde smile

Voilà ca fait quelque temps que je bosse sur un jeu de plateau (une adaptation du jeu Sonic, en fait smile), et je cherche a optimiser certains aspects du prog, notamment la gestion des collisions entre le perso et le decor et/ou les objets presents à l'écran. Gerer des sols planes et des murs tout droits ca pose généralement peu de problèmes, par contre dès qu'on s'amuse avec des sols en pente, des courbes ou même des loopings, ca devient un peu plus casse-tête couic

Ma map est divsisée en tiles, et j'utilise 3 octets par tile: texture, obstacle & objet. En pratique, un obstacle présent sur un tile est un "mur" de 1 ou 2 pixels de large sur le coté ou en travers du tile a travers lequel le perso peut toujours passer dans un sens (et pas dans l'autre). J'ai donc par exemple une valeur (entre 0 et 255) pour signaler que le perso peut traverser le coté haut du tile de bas en haut (et donc qu'il est bloqué s'il veut traverser de haut en bas), une autre pour caractériser le coté gauche, une autre pour les 2 cotés en meme temps etc...
Le perso est alors matérialisé par un rectangle dans la zone de l'écran, et pour chaque déplacement de 1 pixel dans une certaine direction je dois tester si le coté correspondant du rectangle est en contact avec un obstacle dans la map (cad qu'il faut tester chaque tile couvert en partie par le coté du rectangle).

Tout ca represente pas mal d'opérations a effectuer pour chaque déplacement d'un seul pixel dans une direction, mais pour le moment les résultats sont assez bons, surtout avec les sols planes et les murs droits évidemment... le systeme fonctionne aussi avec des sols en pente mais c deja moins pratique :/ Par contre j'aimerais bien pouvoir intégrer des courbes etc, et là je pense que ce systeme ne pourra plus convenir du tout...

D'autant plus qu'ici, je ne teste généralement qu'une seule direction à la fois, donc dans le cas des pentes par exemple c'est plus délicat puisqu'il faut que le perso puisse suivre la pente sans passer à travers ni voler par dessus... Alors pour les courbes c'est déjà une autre histoire :/

J'ai pensé à d'autres solutions que de découper les obstacles en tile (genre faire des "blocs" 'pente', 'sol', 'creux', 'quart de rond'...) mais je vois pas trop ce qui pourrait etre le plus efficace...

Comme je pense que pas mal de programmeurs ici se sont dejà amusés à faire un jeu de plateau dans ce genre-là, ca serait bien d'avoir des idées ou des conseils sur des méthodes envisageables pour ce genre de fantaisies... smile

Je pensais au problemes des objets aussi: dans sonic, le perso doit pouvoir marcher sur le dessus d'un bonus comme il le ferait sur un sol normal par exemple, il faut donc que les intéractions entre le perso, la map et les objets soient assez souples pour permettre ca aussi...

Voilà si vous avez déjà réfléchi à cette question pour un jeu de plateau ou quoi, ca m'intéresserait d'avoir votre avis sur la façon d'implémenter tout ça smile
avatar
Un ptit gars qui programme en C/asm sur sa casio et qui vient voir de temps en temps comment ça se passe par ici :)
Un peu de verdure dans ce monde obscur

2

Pour ça on dit pas jeu de plateau, mais jeu de platte-forme ^^
Personnellement, je te conseillerai de stocker avec tes tile un angle (et oui, c tout bête) qui représenterait la pente, ce qui te permettrait ensuite d'appliquer la rotation adéquate à ton sprite (enfin, vu qu'il n'y a pas de rotation hard, ça serait plutôt choisir le sprite parmi les sprites prérotationnées disponibles), et aussi de faire les calculs de changement de vitesse en fonction de la pente (c'est assez complexe dans sonic il me semble), ainsi que le changement de position.
Pour les collisions avec les pentes et les objets, tente une détection de collision par pixels entre la ligne non vide la plus basse du sprite du perso et la ligne du décor qui se trouve au même endroit (Il faudra faire ça vers le haut quand le perso se déplacera vers le haut bien sûr happy). C'est certainement ce qu'il y a de moins compliqué et ça marchera dans tous les cas smile
Je crois que j'ai dit l'essentiel là
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

3

j'édite le titre parceque effectivement, plateau et plate-forme c'est pas exactement pareil wink
avatar
Que cache le pays des Dieux ? - Forum Ghibli - Forum Littéraire

La fin d'un monde souillé est venue. L'oiseau blanc plane dans le ciel annonçant le début d'une longue ère de purification. Détachons-nous à jamais de notre vie dans ce monde de souffrance. Ô toi l'oiseau blanc, l'être vêtu de bleu, guide nous vers ce monde de pureté. - Sutra originel dork.

4

Je ne sais pas comment tu gères tes pentes, mais je peux te dire que Sonic, c'est plutôt une mauvaise idée pour débuter. :]
Pour tes plate-formes, tu peux utiliser des tableaux, définissant l'offset en Y correspondant à l'offset X de ta plate-forme (tableau(x)=offset_y) de la collision sur le pixel en cours. Par exemple pour une plate-forme inclinée à 26.5651° vers le bas:
{0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7};
En fait, en traçant cette fonction, tu verras apparaître ta pente. Ainsi, avec un position_x modulo 16 (car dans mon exemple chaque tile fait 16 pixels) tu peux déterminer sur lequel pixel de la tile à tester les pieds de ton personnage se trouvent. Et si la position des pieds à cet endroit (modulo 16, forcément) est supérieure à celle qui est définie dans le tableau, alors Sonic se trouve sur la plate-forme. Maintenant tu me diras probablement qu'il se trouve sur plusieurs tiles en même temps; oui mais chaque chose en son temps. Tu devras bien sûr faire plusieurs tests et ce à intervalles de 16 pixels. Par exemple, si ton personnage fait 39 pixels, tu testeras à:
x+0, x+16, x+32, x+39
Comme ça tu es sûr de ne manquer aucune tile.
Note: Pour Sonic, je te conseille vivement de ne le faire bouger que d'un pixel par passe, sinon ce sera très dur d'implémenter un moteur de collisions qui soit stable. Prenons le cas où tu veux le faire avancer de 8 pixels, alors tu le feras avancer 8 fois d'un pixel, et ne pas oublier de lancer à chaque fois le moteur de collisions entre deux sick.
Ensuite, lorsque tu as trouvé la plate-forme avec laquelle il y a une collision (eh oui une à la fois!), tu pourras y faire "coller" Sonic; tu connais la valeur de l'offset y (grâce au tableau[position_x%16]), comme les tiles sont toujours à des positions multiples de 16, tu peux connaître la position "idéale" où devraient se trouver ses pieds, qui est offset_y+position_tile*16. Maintenant que c'est fait, Sonic prend l'angle qui est associé à cette plate-forme.
N'oublie pas que Sonic est très dépendant de tout ça. On pourrait imaginer un Mario sans même gérer des angles (c'était le cas jusqu'à Yoshi's island) mais pour Sonic, il faut vraiment que tu gères ça, sachant par exemple que son centre de gravité est perpendiculaire à son angle. Sinon tu ne pourrais pas le faire monter contre les murs (ou qu'à condition de bidouilles extrêmes).
Vu d'un autre angle, c'est très simple, imagine que Sonic monte sur un mur de 90°, alors il n'est plus vraiment attiré vers le bas, mais plutôt vers la droite (l'endroit où se trouve ses pieds). La force de gravité reste présente, mais elle est trop petite (de l'ordre de 2) comparée à la vitesse de Sonic s'il va vite (de l'ordre de 8 à 16) et il pourra donc monter sur ces murs du moment qu'il va assez vite. La force de gravité ne contribue donc qu'à ralentir sa vitesse v / 90° (dans ce cas-là c'est un vecteur).
Mais bon, toutes ces lois physiques, c'est bien joli, mais ce n'est pas applicable dans un véritable de moteur de Sonic, et ce particulièrement si tu prévois de l'implémenter sur TI (demande trop de puissance). Il faut donc éliminer et simplifier le plus possible sans pour autant tomber dans la bidouille.
Ah et une dernière chose à propos des tableaux de collisions, dans mon moteur, je ne gère en fait les collisions qu'en haut et en bas de Sonic. A gauche et à droite, seuls les murs (type dur) sont gérés, c'est pourquoi les tables de collisions ne représentent que y=f(x).
avatar
Highway Runners, mon jeu de racing à la Outrun qu'il est sorti le 14 décembre 2016 ! N'hésitez pas à me soutenir :)

https://itunes.apple.com/us/app/highway-runners/id964932741

5

Brunni a écrit :
[...]
Vu d'un autre angle, c'est très simple, imagine que Sonic monte sur un mur de 90°, alors il n'est plus vraiment attiré vers le bas, mais plutôt vers la droite (l'endroit où se trouve ses pieds). La force de gravité reste présente, mais elle est trop petite (de l'ordre de 2) comparée à la vitesse de Sonic s'il va vite (de l'ordre de 8 à 16) et il pourra donc monter sur ces murs du moment qu'il va assez vite. La force de gravité ne contribue donc qu'à ralentir sa vitesse v / 90° (dans ce cas-là c'est un vecteur). [...]
Argh, horreur, vade retro !!!
Comment peux-tu comparer une force et une vitesse ???
Rappel de base : force/masse = accélération = dérivée de la vitesse = dérivée seconde de la position.
Et Sonic est toujours attiré vers le bas (accélération g de la pesanteur), il subit toujours une force de gravité Fg = m*g à laquelle il faut ajouter la force de contact Fc = Fn + Ft (composantes normales et tangentielles à la pente) due au terrain, et la force de propulsion Fp horizontale que Sonic exerce avec ses petites jambes musclées (quoique même en chute libre, Sonic arrive à exercer Fp roll ...) et qui est dans la direction (horizontale) donnée par le paddle.
Sachant que (si mes souvenirs sont bons), même sur une faible pente, Sonic se met forcément à marcher de plus en plus vite vers le bas si on ne touche pas aux commandes, on peut donc en conclure que le contact est modélisable pas un glissement sans frottement, ce qui se traduit par Ft = 0 (aucun frottement) et Fn telle que Fg + Fc + Fp soit tangente au terrain (sans quoi il s'envolerait ou s'enfoncerait sous terre).

Si vous y tenez, je peux vous faire un topo complet une fois que j'aurai terminé le problème de vince, ou sinon, utilisez 友達のGoogle (votre ami Google), c'est au programme de Physique de Première S et Terminale S (pfiou, ça ne me rajeunit pas tout ça ...).

@++
avatar
Je ne suis pas développeur Java : je suis artiste Java.
Ce que l’on conçoit bien s’énonce clairement, / Et le code pour l’écrire arrive aisément.
Hâtez-vous lentement ; toujours, avec méthode, / Vingt fois dans l’IDE travaillez votre code.
La perfection est atteinte, non pas lorsqu’il n’y a plus rien à ajouter, mais lorsqu’il n’y a plus rien à retirer.
You don't use science to show that you're right, you use science to become right.

6

GoldenCrystal
: Pour ça on dit pas jeu de plateau, mais jeu de platte-forme ^^

Lol oui c'est pas la première fois que je fais l'erreur en plus fesses fouet
Brunni
: Je ne sais pas comment tu gères tes pentes, mais je peux te dire que Sonic, c'est plutôt une mauvaise idée pour débuter. :]
Ca je suis bien d'accord, mais je n'ai pas dit que je débutais tongue
Brunni :
Pour tes plate-formes, tu peux utiliser des tableaux, définissant l'offset en Y correspondant à l'offset X de ta plate-forme (tableau(x)=offset_y) de la collision sur le pixel en cours. Par exemple pour une plate-forme inclinée à 26.5651° vers le bas:
{0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7};
En fait, en traçant cette fonction, tu verras apparaître ta pente. Ainsi, avec un position_x modulo 16 (car dans mon exemple chaque tile fait 16 pixels) tu peux déterminer sur lequel pixel de la tile à tester les pieds de ton personnage se trouvent. Et si la position des pieds à cet endroit (modulo 16, forcément) est supérieure à celle qui est définie dans le tableau, alors Sonic se trouve sur la plate-forme. Maintenant tu me diras probablement qu'il se trouve sur plusieurs tiles en même temps; oui mais chaque chose en son temps. Tu devras bien sûr faire plusieurs tests et ce à intervalles de 16 pixels. Par exemple, si ton personnage fait 39 pixels, tu testeras à:
x+0, x+16, x+32, x+39
Comme ça tu es sûr de ne manquer aucune tile.
C'est exactement ce que j'ai fait... oui si ce n'est que je n'utilise pas de table de collisions mais plutot un bout de code asm pour determiner ou se trouve le pixel par rapport a la pente sur le tile (facile pour les pentes mais moins flexible evidemment)
Brunni
: Pour Sonic, je te conseille vivement de ne le faire bouger que d'un pixel par passe, sinon ce sera très dur d'implémenter un moteur de collisions qui soit stable. Prenons le cas où tu veux le faire avancer de 8 pixels, alors tu le feras avancer 8 fois d'un pixel, et ne pas oublier de lancer à chaque fois le moteur de collisions entre deux
On est bien d'accord... C'est malheureusement tres lourd, c'est en partie ce qui me pousse a envisager d'autres solutions sad

Ca ressemble effectivement au principe que j'ai adopté dès le départ, mais ca ne me semblait qu'à moitié satisfaisant, et si je voulais améliorer ca devenait vraiment du bidouillage roll

Le plus difficile je trouve c surtout de faire "coller" Sonic a cette pente en ajustant sa position lorsqu'on a rencontré un obstacle... Et puis par exemple si on fait un saut, on doit tester quelques points du bas du sprite pour vérifier qu'on ne rencontre pas le sol, et si jamais le point le plus a gauche rencontre un sol en pente incliné vers la droite par exemple, il faut veiller a ce que Sonic descende encore suffisamment pour ne pas qu'il s'arrete en l'air avec seulement son point inférieur gauche en contact avec le sol... Le choix de ces points est délicat, il depend de l'obstacle lui-meme en fait :/

Actuellement, je fais comme ça (on sent venir le bidouillage sick):


Pour un perso de 16*24 et des tiles 8*8 (on va faire simple):
# Determination du deplacement à effectuer (sur base du clavier, du temps écoulé etc) : disons 3 pix vers la gauche et 1 vers le bas
# boucle tant qu'il reste un deplacement a faire horiz ou vertic:
> si on doit se deplacer horizontalement:
>> s'il y a un mur sur le bon coté du sprite (ici le gauche), alors arret du perso et plus de dep horiz a faire (il reste un dep de 1 pix vers le bas)
>> sinon si le perso n'était pas en train de sauter, on teste certains points dans un coin inférieur du sprite pour voir si on va aborder une pente ou pas (ici les points (x,y+22),(x,y+23) et (x,y+24). Si un des points est sur une pente, et selon ce point, on sait que le perso devra faire un deplacement de vertical de 1 pix vers le haut ou le bas pour suivre la montee
>> on effectue le deplacement de 1 pix

> si on n'a pas encore rencontré de pente dans le déplacement, et que le perso se dirige vers le bas, on regarde s'il y a un sol plat ou en pente sous le sprite et donc si le perso a les pieds sur le sol

> si on doit se deplacer verticalement
>> si un sol autre qu'une pente avait été repéré précédemment, ou si le perso veut monter et qu'il y a un obstacle au dessus de lui, on arrete le mouvement vertical
>> sinon on effectue le deplacement de 1 pix
# fin de la boucle

# selon les pentes qu'on a rencontrées, on donne au perso une vitesse verticale correspondant a l'élan qu'il aurait pris (faut pas oublier ca non plus...)



C'est vraiment à la limite du bidouillage... En gros ca marche bien, il y a qq imperfections a gauche a droite mais pour les corriger ca encombre vraiment le code... De plus si on veut gerer les collisions avec les objets etc ca se corse encore pas mal sad
Je me demandais donc si qqn a une idée pour tourner ca d'une autre maniere qui le rendrait le principe plus clair en evitant les bidouillages, ou bien si au contraire c la bonne méthode a suivre et si je ferais mieux alors d'implémenter tout ca proprement...


Il y a aussi effectivement le probleme de la gravité qui agit sur le perso et qui peut le ralentir ou l'accélerer... Actuellement je pensais plutot jouer sur les accélérations en fonction de la pente sur laquelle se trouve le perso, mais ca reste difficile pour des cas extremes comme lorsque Sonic grimpe sur un sol à 90° par exemple . Encore un truc à voir roll

Sinon bah si vous avez des idées plus efficaces mais plus lourdes dites-le aussi, le jeu tourne entre 30 et 50 fps tout compris actuellement, donc j'ai encore de la marge wink
avatar
Un ptit gars qui programme en C/asm sur sa casio et qui vient voir de temps en temps comment ça se passe par ici :)
Un peu de verdure dans ce monde obscur

7

Merci Ethaniel pour ce petit cours de mécanique grin
Mais en pratique on fait souvent v = v° + g*t , si on prend t=1 (dangereux car dépend du framerate) alors on peut comparer les ordres de grandeurs de g et v wink
Et puis implémenter des forces de frottement c bien joli, mais je suis pas sur que c'est la solution la plus efficace wink
avatar
Un ptit gars qui programme en C/asm sur sa casio et qui vient voir de temps en temps comment ça se passe par ici :)
Un peu de verdure dans ce monde obscur

8

Comment peux-tu comparer une force et une vitesse ???
Heu je ne les compare pas, mais il y a quand-même un rapport (F=m*g).
Oui je sais c'est bizarre. Mais j'essaie de lui expliquer simplement (même si ce n'est pas vraiment correct, c'est comme ça qu'il va devoir faire dans son jeu, et non comme tu le dis, pour les raisons de puissance de calcul sus-citées).
Sinon j'ai l'impression de m'être mal exprimé pour les collisions en haut. Je retente donc une explication plus simple. Ca pourra servir aux débutants qui passent dans ce topic. wink
Imagine que tu as ton personnage, sa position est x=34, y=20. Ses dimensions sont l=32, h=40. Chaque tile fait 16x16 pixels.
Il va falloir tester les collisions dans tous les sens. Mais d'abord il faut commencer par le bas. Dans ce cas-là, on va tester sur toute la largeur du sprite, c'est-à-dire aux positions x, x+16 et x+32 (il y aura trois passes, une boucle for) soit 34, 50 et 64. Pour la position y, on va prendre celle de ses pieds, qui se trouvent en-dessous de lui. Donc y+h, soit ici 60.
Voilà les coordonnées de notre premier point de collision: 34, 60. Pour trouver l'équivalent dans ton tableau, ce n'est pas compliqué: x/16, y/16 ([u]/![/u] divisions entières). Imaginons que dans la case correspondante (2,3) se trouve la valeur 1, indiquant un mur.
On sait maintenant qu'il y a une collision (en bas) et il va falloir "monter" les pieds du personnage de manière à ce que celui-ci se trouve dessus la plate-forme. On va donc tout simplement arrondir ces coordonnées. On connait aussi la position de la tile! C'est 2*16,3*16 soit 32,48. Donc pour donner l'impression que les pieds de Sonic se trouvent dessus la tile, ils devront se trouver à la position y=48. Il suffit donc de soustraire la hauteur (puisque c'est ses pieds qui iront là) et on a la nouvelle position en y du personnage: 8! X reste inchangé dans cette partie du code.
Maintenant pour les plate-formes inclinées, ce n'est pas beaucoup plus compliqué en fait. Revenons au point de départ; on connaît notre point de collision: 34,60. Imaginons maintenant qu'on a à cet endroit une plate-forme inclinée (45°) définie comme suit:
{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
La position en X de la tile est 2*16=32. Donc le point de collision a un offset (décalage) en X valant 2. On trouve dans notre tableau la valeur correspondante: 2. Grâce à cette table, on voit que la position idéale des pieds ne serait pas 48 (3*16) comme dans le cas précédent, mais 48+2=50. Il suffit maintenant de calculer de manière à ce que les pieds se trouvent à la position 50. Ce n'est pas plus compliqué que ça. On trouve donc les coordonnées 50-40=10, et c'est bien deux pixels plus bas que la plate-forme de type dur (fixe)... smile
[Edit] Mega-cross post. J'ai l'air con avec mon explication à deux balles. Enfin comme je l'ai dit ça pourra servir aux débutants qui passeraient dans ce topic smile
avatar
Highway Runners, mon jeu de racing à la Outrun qu'il est sorti le 14 décembre 2016 ! N'hésitez pas à me soutenir :)

https://itunes.apple.com/us/app/highway-runners/id964932741

9

J'ai parlé de glissement sans frottement, donc ... sans force de frottement !

Ensuite, j'ajouterais que Fg est une force conservative, et que Fc est normale à la trajectoire, donc exerce un travail nul.
Donc si Fp = 0 (aucune action sur le paddle), l'énergie mécanique Em = Ec + Ep = 1/2*mv² + mgh = m/2 (v² + 2gh) se conserve, donc la quantité vr2 = 2Em/m = (v² + 2gh) est constante (je ne sais pas si cette quantité a un nom officiel, donc je l'appelle vr2, 'carré de la vitesse de référence à l'altitude nulle'), et vr2 change éventuellement si Fp est non nulle.
Donc quand on lâche les commandes, il suffit de calculer vr2 à cet instant, et on laisse Sonic suivre le terrain en mesurant à chaque incrément de temps dt son altitude h afin de calculer sa vitesse v = sqrt (vr2 - 2gh), donc l'incrément de position ds = v*dt, et donc sa nouvelle position s2 = s1 + ds.
Par contre, si le terrain disparaît au cours de la trajectoire (tramplin, etc.), il ne faut plus suivre le terrain mais passer en mode 'chute libre parabolique' pendant lequel on a toujours la constance de vr2.
La difficulté, c'est de déterminer précisement et facilement quand est-ce que l'on passe d'un mode à l'autre ...
Le critère du passage en chute libre, c'est lorsque l'on voit qu'il faudrait une force Fn dirigée vers le terrain (Fn < 0) pour garder la somme vectorielle Fg + Fc + Fp tangente au terrain, et dans ce cas on pose Fn=0 et Sonic décolle.
Mais ceci oblige à calculer à tout moment les forces qui varient, donc on perd tout l'intérêt de l'utilisation de vr2 (en fait, on l'utilise uniquement si le mobile reste mécaniquement sur un terrain capable de fournir Fn < 0, comme par exemple un chariot de grand-huit sur ses rails).

Pour la force de propulsion Fp, outre la composante horizontale (croix directionnelle) qui, projetée sur la tangente du terrain, exerce un travail et donc modifie vr2, j'avais oublié le bouton de saut qui donne une composante normale au terrain (donc le travail est nul, donc vr2 reste constant) plus grande que Fn (donc Sonic décolle et décrit une parabole).

Enfin, concernant l'efficacité de l'utilisation des forces, c'est la solution la plus efficace en terme de réalisme du moteur physique, c'est même la seule qui soit physiquement exacte dans le cadre de la mécanique newtonienne (et facilement implémentable ... si tu as quelques années à perdre, tu peux toujours implémenter les équations de la relativité générale pour être physiquement exact dans le cadre de cette mécanique grin) ...
Quant à l'efficacité algorithmique (temps de calcul), je ne trouve pas que ce soit bien lourd, il suffit de faire des produits avec les sinus et cosinus de l'angle de la pente du terrain pour se ramener dans le référentiel (O;x;y) de l'écran, et de faire ensuite des sommes selon les composantes x et y ...
Tu peux même faire ça en une seule fois avec une matrice de rotation (l'angle utilisé étant celui de la pente du terrain).
Ainsi, une méthode à la fois subtile et barbare (ça c'est tout moi hehe) pour pouvoir utiliser facilement n'importe quelle forme de terrain est de représenter chaque tile par une matrice de la taille du sprite et dont chaque élément coïncidant avec la surface contient l'angle de la pente en radians.
Je suppose que tu utilise déjà ce type de matrice, laquelle ne contient que des 0 ou des 1 (air ou terre) : dans ce cas, il suffit de faire une matrice de complexes, avec l'angle comme partie réelle et la valeur 0 ou 1 comme partie imaginaire.
Avec ça, la gestion de la trajectoire (position + vitesse, évidemment) sur un terrain tordu ou un looping se fait les doigts dans le nez happy !
En plus, comme le centre du tile de Sonic doit être à une distance fixe (sauf quand il se baisse ou qu'il saute) de la surface, et que cette distance se mesure selon la normale au terrain, le calcul de la position de ce centre est alors immédiat, sans risquer de voir Sonic s'enfoncer sous terre.

@++
avatar
Je ne suis pas développeur Java : je suis artiste Java.
Ce que l’on conçoit bien s’énonce clairement, / Et le code pour l’écrire arrive aisément.
Hâtez-vous lentement ; toujours, avec méthode, / Vingt fois dans l’IDE travaillez votre code.
La perfection est atteinte, non pas lorsqu’il n’y a plus rien à ajouter, mais lorsqu’il n’y a plus rien à retirer.
You don't use science to show that you're right, you use science to become right.

10

lol effectivement Brunni c bien pour les débutants ça... on sait jamais ca servira peut-etre un jour a des pti nouveaux. smile
Sinon y'a qd meme un truc qui cloche a la fin de ta longue explication, c'est que justement si le point de collision (34,60) est sur le cote du sprite et pas sous les pieds du persos, le perso va s'arreter de maniere a ce que son coin inferieur gauche soit bien sur la pente, mais pas ses pieds, qui eux risquent de flotter en l'air... Ou alors il faut veiller a ce que les sprites representant le perso sur une pente soient décalés comme il faut (chez moi chaque sprite a une "origine" parfois différente de son coin supérieur gauche).

Ethaniel j'avais pensé à un systeme de forces aussi pendant un moment pour déterminer les mouvements à effectuer en fonction du temps et de la sollicitation du clavier, mais je trouvais ca un peu farfelu... je vais qd meme potasser un peu tes posts, on ne sait jamais tongue
avatar
Un ptit gars qui programme en C/asm sur sa casio et qui vient voir de temps en temps comment ça se passe par ici :)
Un peu de verdure dans ce monde obscur

11

Heu je ne les compare pas, mais il y a quand-même un rapport (F=m*g).
Presque, t'as plus qu'à intégrer par le temps de 0 à l'instant courant et t'es bon.

12

Farfelu ?
C'est pourtant, à mon avis, l'implémentation la plus simple qui soit physiquement réaliste ...

Si tu décides finalement d'adopter cette méthode, je ressortirai mon Sonic de sous sa poussière pour essayer de trouver les liens entre actions au pad et modélisation physique.
D'ailleurs, ça me fait penser que j'ai toujours une partie de Sonic 3 & Knuckles avec Tails qui m'attend depuis plus d'un an !

@++
avatar
Je ne suis pas développeur Java : je suis artiste Java.
Ce que l’on conçoit bien s’énonce clairement, / Et le code pour l’écrire arrive aisément.
Hâtez-vous lentement ; toujours, avec méthode, / Vingt fois dans l’IDE travaillez votre code.
La perfection est atteinte, non pas lorsqu’il n’y a plus rien à ajouter, mais lorsqu’il n’y a plus rien à retirer.
You don't use science to show that you're right, you use science to become right.

13

Je suis intéressé en tout cas smile C'est écrit en quel langage? Tu parlais de nombres complexes, tu utilises réellement un type 'complex' dans tes progs? confus
Sinon je n'ai pas trop compris comment tu t'y prends dans ce cas-là pour reperer sur quel type de pente se trouve le perso?
avatar
Un ptit gars qui programme en C/asm sur sa casio et qui vient voir de temps en temps comment ça se passe par ici :)
Un peu de verdure dans ce monde obscur

14

C'est écrit en quel langage?
Comment ça, quel langage ?
Je n'ai rien écrit du tout, moi !
Si tu parles de la matrice avec dedans l'angle du terrain au niveau de ce pixel, c'était juste une idée qui m'est venue lors de la rédaction du post.
Tu parlais de nombres complexes, tu utilises réellement un type 'complex' dans tes progs? confus
En TI-Basic sur 92+, oui, ça me permet de concaténer deux matrices de réels en une seule (puisque C et R² sont bijectifs).
Mais sinon, dans un langage de programmation normal, c'est vrai que ça ne se fait pas.
Sinon je n'ai pas trop compris comment tu t'y prends dans ce cas-là pour reperer sur quel type de pente se trouve le perso?
Pour le pixel qui se trouve à la 'verticale locale' (c'est-à-dire dans la direction de la normale au terrain) du centre de masse de Sonic, l'élément de matrice correspondant contient la valeur numérique de l'angle de la pente du terrain au niveau de ce pixel précis.
Plus exactement, afin d'avoir une convention de signe (nécessaire pour les tiles ayant la tête en bas comme les sommets de looping), ce sera l'angle entre ey (vecteur unitaire vertical ascendant pour l'écran) et la normale au terrain dirigée du côté terre vers le côté air.
Par exemple, cet angle vaut :
- pour un sol plat -> 0
- pour un plafond ou un sommet de lopping -> pi (ou -pi, c'est pareil)
- pour un mur vertical dont le côté gauche est à l'air libre -> pi/2
Donc selon le pixel 'au-dessus' (entre guillemets, puisque c'est la verticale locale attachée au terain) duquel se trouve Sonic, un simple cosinus lui donne la direction de sa vitesse dans le référentiel absolu (O;x;y) attaché à l'écran.
Après, je n'ai pas poussé très loin la réflexion, puisque comme je l'ai dit, c'est une idée qui m'est venue comme ça en cours de rédaction.
Cependant, je pense que ça pourrait faciliter les calculs.
En effet, dans les méthodes classiques pour lesquelles l'écran est représenté par une matrice (ou plutôt une juxtaposition de matrices-tiles) ne contenant que des 0 et des 1, il faut regarder tous les pixels à proximité de Sonic (cf. méthode décrite par Brunni), alors que là, la lecture d'une seule valeur dans la matrice suffit !
Par contre, évidemment, lorsque tu écris la matrice correspondant au tile, c'est plus dur que de mettre simplement des 0 et des 1, tu est obligé de faire du pré-traitement, mais tout le temps que tu perds lors de la création du programme sera gagné lors de l'exécution.
Même en Assembleur (je dis 'même', mais je ne fais que de l'Assembleur ...), je fais toujours des prétraitements de folie, comme dans un programme d'il y a quelques temps où il m'a fallu 3 mois pour calculer, entrer sans erreur et vérifier de deux manières différentes 896 valeurs pour que le programme n'ait pas à les calculer à chaque tour de boucle (en plus, chaque valeur dépend des précédentes, donc une erreur, et c'est toute la suite qui est fausse ...).

Si tu as encore besoin d'aide en méca (comme développer plus avant ce que j'ai raconté), n'hésite pas à demander !

@++
avatar
Je ne suis pas développeur Java : je suis artiste Java.
Ce que l’on conçoit bien s’énonce clairement, / Et le code pour l’écrire arrive aisément.
Hâtez-vous lentement ; toujours, avec méthode, / Vingt fois dans l’IDE travaillez votre code.
La perfection est atteinte, non pas lorsqu’il n’y a plus rien à ajouter, mais lorsqu’il n’y a plus rien à retirer.
You don't use science to show that you're right, you use science to become right.

15

OK, ca me parait plus clair maintenant... Ce qui me chipote encore un peu c'est la detection d'un obstacle au moment de la retombée d'un saut par exemple, vu qu'on n'a pas de "verticale locale" pour determiner quel est le pixel à examiner (puisque sonic est dans les airs)... On pourrait décider de choisir comme "verticale" la direction du déplacement, et donc le pixel a tester serait celui le plus en avant sur l'axe du déplacement, mais ça me parait bien trop léger comme test vu que le perso aura toutes les chances de passer a travers un obstacle qui ne le bloquait pas sur toute la "largeur" du sprite...

De même, suivre une pente avec ce système ne dispense pas de tester le reste, vu qu'il suffit que je place un mur au milieu d'une pente pour que sonic passe a travers en suivant le tracé du sol... C'est effectivement un systeme efficace pour suivre les fantaisies du sol, mais ca ne dispense pas d'effectuer des tests supplémentaires bien entendu... Et dans ce cas il faudra décider des autres pixels à tester et de la maniere dont on devra interpreter les valeurs obtenues pour signaler la collision!

Le système des matrices est malheureusement assez gourmand en mémoire, surtout si les tiles sont grands puisqu'ils faut creer plus de "modeles" de matrices différentes afin de garder une palette assez complète pour représenter tous les obstacles... Pour des tiles 16*16 une seule matrice prend deja 256 octets, or il faut au moins en creer une vingtaine pour commencer à avoir une gamme d'obstacles acceptable, ce qui occupera au moins 5 ko de mémoire... Heureusement je travaille en 8*8 donc ce systeme devrait s'avérer moins gourmand, mais bon - au fait de combien de mémoire vive les programmes TI disposent-ils pour fonctionner?

En pensant également à ça, vu que j'ai banni les nombres à virgule flottante de mon univers de prog sur caltoche, il va falloir trouver une méthode efficace pour calculer les sinus et cosinus (au mieux je devrais utiliser une table, et paf voilà encore une bonne centaine d'octets dans la nature roll )
avatar
Un ptit gars qui programme en C/asm sur sa casio et qui vient voir de temps en temps comment ça se passe par ici :)
Un peu de verdure dans ce monde obscur

16

Je sais pas ce que vous racontez là (flemme de lire les posts soporifiques d'éthaniel) mais pour tes problèmes de collisions, étant donné le type de jeu je te conseille l'utilisation de bouding rects. Certes ce n'est pas toujours très précis (enfin c'est assez précis tant que tu peux faire tenir le rectangle +/- à l'intérieur de ton sprite), mais c'est le plus approprié, et il y a fort à parier que c'est ce qui était utilisé par les jeux de sonic happy
Le plus compliqué dans ette technique etant d'appliquer la rotation sur le rectangle... Tu ne devrais pas rencontrer trop de difficultés à l'utiliser.
Il te suffit de tester si un des deux points qui indiquent l'avant du personnage est en collision avec un élément (mur, objet ou ennemi). Et tu appliques la même gestion aux ennemis.
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

17

Orwell> Je ne comprends pas trop où tu bloques.
Sinon je n'ai pas trop compris comment tu t'y prends dans ce cas-là pour reperer sur quel type de pente se trouve le perso?

Ben avec l'exemple que je t'ai donné en haut (./8) c'est très simple. Lorsque tu sais sur quelle plate-forme tu détectes ta collision, tu peux également (sachant son type puisque tu as un tableau de collisions associé) trouver son angle et tout ce dont tu as besoin, grâce à une pente auxilière. Cf ce bout de code:
//Nota bene: i est l'offset du point de collision comme je te l'ai expliqué plus haut.
e=Element(x+i,y+H);
[...]
if (e>=TYPE_BAS && e<TYPE_HAUT)       {
     if ((y+H)%16>=incline[e-TYPE_BAS][(x+i)%16])       {
           cBas=1;
         if (pa==PA_BAS)
             y--;
         else if (pa==PA_DROITE)
             x--;
         else if (pa==PA_HAUT)
             y++;
         else if (pa==PA_GAUCHE)
             x++;
         s->angle=anglesPF[e-TYPE_BAS];
         goto detec01;
     }
 }
D'après ce morceau de code, on peut en arriver aux conclusions suivantes:
1) Je code comme un porc
2) Au lieu d'appliquer une force de pesanteur perpendiculaire au sol, je travaille avec ce que j'appelle (niaisement?) des points d'attraction (pa). C-à-d Sonic est attiré par le haut, le bas, la gauche ou la droite selon son angle. Mais ce n'est sûrement pas une bonne idée, d'ailleurs je vais certainement le recoder à l'occasion (mais pour l'instant j'ai déjà assez à faire avec le son et les ennemis tongue).
3) Il n'y a que deux types de plate-forme: collision en haut et collision en bas.
4) Note le s->angle=anglesPF[e-TYPE_BAS] qui référencie un tableau contenant justement l'angle de la plate-forme du type en question (e).
En pensant également à ça, vu que j'ai banni les nombres à virgule flottante de mon univers de prog sur caltoche, il va falloir trouver une méthode efficace pour calculer les sinus et cosinus (au mieux je devrais utiliser une table, et paf voilà encor une bonne centaine d'octets dans la nature )
Les nombres en virgule fixe sont largement suffisants... et les tables tu peux les calculer en temps réel. Mais sur TI tu peux les calculer la première fois (sachant que tu peux calculer chaque 5 degrés par exemple, et seuls 90° du sinus n'ont besoin d'être calculés car tout le reste peut en être déduit, ça n'est plus très lent au final tongue) et créer un fichier contenant tes tables.
Le plus compliqué dans ette technique etant d'appliquer la rotation sur le rectangle... Tu ne devrais pas rencontrer trop de difficultés à l'utiliser.
En effet. Dans Sonic Advance, le centre de rotation est le milieu de la tête (enfin le milieu en x et le sommet en y). C'est pourquoi ils utilisent un sprite de 64x64 car la GBA prend toujours le centre du sprite (ici 32,32) comme centre de rotation. Et dans Sonic, la rotation n'est (heureusement) pas appliquée aux ennemis. smile
Pour les ennemis, un simple moteur à la Mario suffit car ceux-ci ne vont pas prendre des boucles et des grandes pentes (jusqu'à 30° ça ne pose aucun problème).
Ce qui me chipote encore un peu c'est la detection d'un obstacle au moment de la retombée d'un saut par exemple, vu qu'on n'a pas de "verticale locale" pour determiner quel est le pixel à examiner (puisque sonic est dans les airs)...
Dans les airs, l'angle de Sonic est de 0°. En revanche, sur terre j'utilise un vecteur (vitesse / angle) mais dans les airs seulement les deux composantes vitessex et vitessey. Le problème réside dans la détection du décollage. A ce moment, le vecteur est transformé en les deux composantes (alors que le vecteur prend un angle de zéro et que la vitesse reste active; le fait d'appuyer sur les flèches influence encore un peu le saut même en l'air). wink
avatar
Highway Runners, mon jeu de racing à la Outrun qu'il est sorti le 14 décembre 2016 ! N'hésitez pas à me soutenir :)

https://itunes.apple.com/us/app/highway-runners/id964932741

18

Bon ben je crois que je vais repotasser ce que j'ai fait jusqu'ici alors, il me semble que le principe est bon mais ca nécessite un peu de rangement pour faire disparaitre les bidouillages grin
          // vitesse acquise par le perso en montant ou descendant une pente      if(cptPente && gestionPente!=AUCUNE) m_speedV=cptPente*75;      [...] }Pour le moment je détectais les pentes avant d'effectuer un deplacement horizontal, pour pouvoir demander un deplacement vertical en même temps si nécessaire: void Perso::deplacement(char nbrePixX, char nbrePixY) {      [...]      uchar gestionPente=AUCUNE, cptPente=0;      bool obstacle;      char depH=0, depV=0;      for(uchar cpt=0; cpt<nbrePixX || cpt<nbrePixY; cpt++)      {           if(cpt<nbrePixX)     // deplacement horizontal de 1 pixel           {                if( laMap.bloqueHoriz(m_newPos,m_dirH) )                {                     // si obstacle vertical devant le perso                     m_speedH=0; // il s'arrete                     nbrePixX=0;  // on n'essaie plus de déplacer                }                else                {          // si le perso n'a pas encore rencontre de pente et ne saute pas                     if(!m_enSaut)                     {                          int x_point=m_x+8+(m_dirH?1:-1);                          if(laMap.coll_pente(x_point,m_y+23,1))     // pente au niveau du perso                          {                               gestionPente=PLAT;                               nbrePixY=cpt;                          }                          else if(laMap.coll_pente(x_point,m_y+22,1))     // marche de 1 pixel a monter                          {                               gestionPente=MONTEE;                               m_dirV=0;                               cptPente++;                               nbrePixY=cpt+1;                          }                          else if(laMap.coll_pente(x_point,m_y+24,1))     // marche de 1 pixel a descendre                          {                               gestionPente=DESCENTE;                               m_dirV=1;                               cptPente++;                               nbrePixY=cpt+1;                          }                          else gestionPente=AUCUNE;                          // le type et la direction de la pente rencontree                           // sont dans Map::penteType et Map::penteDir                                         }                                                         [...] // depl du perso ou de la map                     depH += (m_dirH? 1 : -1)                }           }                           // regarde si le perso touche le sol           if(gestionPente!=AUCUNE) m_toucheLeSol=true;           else           {                [...] // teste le bas du sprite pour detecter un sol ou une pente           }                           if(cpt<nbrePixY)     // deplacement vertical de 1 pixel           {                if(gestionPente!=AUCUNE) obstacle=false;     // deplacement demande plus haut par une pente                     else if(m_dirV) obstacle=m_toucheLeSol;                else                 {                     char y_point=m_y+6*(m_etat==EnBoule);                     obstacle = laMap.bloqueVertic(m_newPos,0); // teste le haut du sprite                }                                if(obstacle)     // obstacle horizontal dans la direction du perso                {                                                        m_speedV=0;  // le perso s'arrete                     nbrePixY=0;  // on n'essaie plus de déplacer                }                else                 {                     [...] // depl du perso ou de la map                     depV += (m_dirV? 1 : -1);                }           }      }     // fin du for
C'est peu efficace, car le choix des deplacements a affectuer est quasi indépendant des obstacles rencontrés, il va donc faloir que j'harmonise un peu tout ça...

Pour la rotation des rectangles ca ne fait jamais que quelques points à faire tourner, ca devrait effectivement être faisable... faut voir smile
avatar
Un ptit gars qui programme en C/asm sur sa casio et qui vient voir de temps en temps comment ça se passe par ici :)
Un peu de verdure dans ce monde obscur

19

Bah, des bidouillages, tu risque d'être obligé d'en faire. Si tu utilises les formules style celles d'Ethaniel et que tu t'efforces de coller à toutes les lois physiques, ton jeu va de ramer, et même sur PC si ça se trouve... grin
Enfin quelle est la puissance du processeur de ta casio g100? (enfin j'imagine que tu fais ton Sonic là-dessus non?)
avatar
Highway Runners, mon jeu de racing à la Outrun qu'il est sorti le 14 décembre 2016 ! N'hésitez pas à me soutenir :)

https://itunes.apple.com/us/app/highway-runners/id964932741

20

Je ne suis plus sur de la puissance exacte (8 Mhz, qq chose comme ca), mais bon ce n'est pas le point crucial pour déterminer la vitesse d'un prog happy et puis comme je l'ai dit le jeu est suffisamment rapide pour le moment, et je ne crains pas trop les ralentissements des anims et des mouvements vu qu'ils sont indépendants du framerate... (ca risque par contre d'etre moins fluide mais il faudra voir dans quelle mesure)
Sinon tant que j'y pense, j'utilise déjà une classe Rect pour les contacts Perso-Objets, elle pourrait sans doute me servir aussi pour les obstacles de la map smile
avatar
Un ptit gars qui programme en C/asm sur sa casio et qui vient voir de temps en temps comment ça se passe par ici :)
Un peu de verdure dans ce monde obscur

21

et je ne crains pas trop les ralentissements des anims et des mouvements vu qu'ils sont indépendants du framerate... (ca risque par contre d'etre moins fluide mais il faudra voir dans quelle mesure)
Tu utilises donc le "frameskip"? Ca veut dire que lorsque tu détectes un retard, tu sautes l'affichage? Mais n'oublie pas que tu restes obligé de gérer les points suivants qui sont lents:
-Objets "sprites" (déplacement, animations)
-Collisions et déplacements
-Décompression de la map (généralement à chaque frame, la compression étant inévitable pour Sonic) et gestion de ce qui est vital pour que ton moteur graphique puisse s'y retrouver.
Ces parties doivent donc être optimisées à mort, c'est pourquoi de petits bidouillages pour améliorer la vitesse du moteur de collisions valent la peine, parce que sinon tu tombes vite fait à 0 FPS (frameskip infini).
Utiliser une classe Rect... bof je ne sais pas si c'est une bonne idée. En même temps une détection de collision en rectangle ce n'est pas bien compliqué à implémenter et ce sera certainement plus rapide si c'est fait à la main avec une macro (*mais je peux me tromper*).
avatar
Highway Runners, mon jeu de racing à la Outrun qu'il est sorti le 14 décembre 2016 ! N'hésitez pas à me soutenir :)

https://itunes.apple.com/us/app/highway-runners/id964932741

22

GoldenCrystal a écrit : [...] (flemme de lire les posts soporifiques d'éthaniel) [...]
1/ Pas d'accent STP.
2/ Majuscule STP.
3/ Bouh, snif ...
J'ai beau avoir changé de forum l'année dernière, changé de sujets abordés, changé de pseudo, il faut toujours qu'il y en ait qui disent 'Argh, non, encore un super méga-pavé ingigeste d'Ethaniel ... Courage, fuyons !' ...
Mais en fait je m'en fiche royalement, puisque les personnes à qui mes pavés sont destinés les lisent, elles, et sans trop se plaindre.
Bon, il faut encore que je m'entraîne pour atteindre le niveau de Maître Myrdinn, dont les posts sont unanimement reconnus pour nécessiter un tube (voire deux) d'aspirine ou une immunité au KnockBack pour pouvoir être lus, même par ceux qui sont hyper intéressés hehe.

Bon, allez, c'est parti pour un nouveau pavé grin !!!

Orwell a écrit :
OK, ca me parait plus clair maintenant... Ce qui me chipote encore un peu c'est la detection d'un obstacle au moment de la retombée d'un saut par exemple, vu qu'on n'a pas de "verticale locale" pour determiner quel est le pixel à examiner (puisque sonic est dans les airs)... On pourrait décider de choisir comme "verticale" la direction du déplacement, et donc le pixel a tester serait celui le plus en avant sur l'axe du déplacement, mais ça me parait bien trop léger comme test vu que le perso aura toutes les chances de passer a travers un obstacle qui ne le bloquait pas sur toute la "largeur" du sprite...
De même, suivre une pente avec ce système ne dispense pas de tester le reste, vu qu'il suffit que je place un mur au milieu d'une pente pour que sonic passe a travers en suivant le tracé du sol... C'est effectivement un systeme efficace pour suivre les fantaisies du sol, mais ca ne dispense pas d'effectuer des tests supplémentaires bien entendu... Et dans ce cas il faudra décider des autres pixels à tester et de la maniere dont on devra interpreter les valeurs obtenues pour signaler la collision!
Héhé hehe, enfin, j'attendais cette très judicieuse remarque (preuve que mon post a été lu et compris).
En effet, le système que j'ai décrit (avec codage de l'angle, et donc de la normale locale, au niveau des pixels de la surface) ne fonctionne que pour un mobile ponctuel se déplaçant sur la surface (sauf lors des bonds) ... ce que Sonic n'est pas.
Dans les deux paragraphes que tu as écrits, la cause du problème est la même : la largeur de Sonic, qui impose de tester d'autres pixels, alors que ma méthode a justement été conçue pour qu'un seul test soit nécessaire.
En fait, c'est tout simplement que j'ai un peu simplifié la méthode, et ce que j'ai décrit ne s'applique donc plus qu'à un mobile ponctuel.
Les modifications a apporter pour gérer un mobile étendu sont minimes (et même nulles au niveau de la gestion des forces) ... mais apportent des difficultés au niveau informatique (en clair : besoin d'encore plus d'espace mémoire ...).

Plutôt que de donner sans explication ce qu'il faut changer, je vais donner quelques cas simples dans lesquels il est évident que ce qui marche pour le mobile ponctuel se déplaçant à la surface (à savoir le point de contact au niveau des pieds entre Sonic et le sol) fait faire n'importe quoi à l'objet étendu qu'est Sonic.
J'espère qu'ainsi, tu auras une petite idée de la réponse avant même que je la donne hehe.

Cas 1 : considérons comme surface du sol une montée en ligne droite (disons à â=30° de pente) qui se termine par un arc de cerle de très faible rayon (disons tout de même r=8 pixels, pour montrer que même sur les sommets bien arrondis, on observe un comportement bizarre), lequel fait le lien avec une descente en ligne droite avec la même pente (au signe près) afin qu'il n'y ait pas de discontinuité dans la direction de la vitesse.
Le point de contact entre Sonic et le sol étant soumis aux lois du mobile ponctuel, il va monter en ralentissant, suivre l'arc de cercle au sommet avec une vitesse faible et quasi-constante (l'arc est suffisamment petit pour que son altitude soit quasi-constante relativement au reste du décor : la différence d'altitude entre le sommet de la trajectoire et le point de liaison entre la ligne droite et l'arc vaut dz=r(1-Cos(â))=1.07 px, pour une largeur de l=2r.Sin(â)=4 px), puis repartir en accélérant, le tout en suivant scrupuleusement les lois de la mécanique newtonienne.
Maintenant, observons le comportement du centre de masse de Sonic, lequel se trouve disons à R=8 pixels (si le sprite de Sonic est en 16x16) 'au-dessus' (en 'verticale locale') du point de contact.
Lors de la ligne droite de montée, la vitesse du centre de Sonic est égale à celle du point de contact, aucun problème à ce niveau.
Par contre, sur le petit bout d'arc de cercle, le point de contact décrit autour du centre de courbure un arc de cercle de rayon r=8 px, tandis que le centre de masse décrit un arc de cercle de rayon r+R=16 px, donc aura une vitesse double de celle du point de contact.
Or la vitesse du point de contact est continue, donc la vitesse du centre de masse sera discontinue et va doubler brutalement en passant de la droite à l'arc, donc l'énergie cinétique sera discontinue et va quadrupler, alors que l'énergie potentielle sera elle continue : la Physique n'est pas respectée ...
La vitesse qui doit être continue n'est pas celle du point de contact, mais celle du centre de masse, et il faut autoriser les discontinuités de la vitesse de point de contact, ce que ma méthode simplifiée ne permet pas.
Le centre de masse doit quant à lui avoir une vitesse continue le long de sa trajectoire, laquelle est constituée de deux lignes droites reliées par un arc de cercle de rayon r+R=16 px.

Cas 2 : considérons un sol plat et un mur vertical en travers de ce chemin, donc la trajectoire du mobile ponctuel est formée de deux droites orthogonales (après, les forces en présence empêchent le point de monter le mur ... sauf s'il y a un arrondi).
Pour simplifier, considérons que Sonic est un cercle de rayon R=8 px.
Sur le sol plat, le centre de masse se déplacera donc au-dessus du sol à une distance de 8 px au-dessus du sol.
Puis, 8 px avant que le point de contact n'atteigne l'intersection entre le sol et le mur, le centre de masse se trouve à 8 px de distance du mur (Sonic est au contact), soit 8 px 'au-dessus' (en 'verticale locale') du mur.
D'ailleurs, on a un nouveau point de contact qui apparaît, donc la trajectoire 'du' point de contact (si on n'en considère qu'un) doit être discontinue, ce que ne permet pas ma méthode simplifiée.
Donc si on se contente du point de contact, en effet, comme tu le disais 'ca ne dispense pas d'effectuer des tests supplémentaires' ... ce que je ne souhaite pas.
Par contre, le centre de masse a bien une trajectoire continue constituée de deux droites orthogonales (mais les forces en présence empêchent Sonic de monter, même s'il y a un arrondi à la jointure du sol et du mur (à condition que cet arrondi ait un rayon inférieur à R=8 px)).

Avec ces deux cas, je pense que tu as compris où je voulais en venir : comme il faut appliquer les lois physiques au centre de masse et non au point de contact (bah oui, c'est tout simplement cette loi de base de la mécanique qui n'était pas respectée), il faut, dans la matrice des tiles, indiquer la valeur de l'angle local non pas au niveau du pixel de la surface, mais 8 pixels 'au-dessus', c'est-à-dire là où le centre de masse passera obligatoirement (hors sauts).
Tout comme l'ensemble des points de la surface constitue une 'surface physique' 'en dessous' de laquelle le point de contact ne peut pas se trouver (et le long de laquelle j'indiquais l'angle local), j'appelerai 'surface virtuelle' l'ensemble des points 'en dessous' de laquelle le centre de masse ne peut pas se trouver (et le long de laquelle j'indique maintenant l'angle local).
La surface virtuelle se trouve donc en tout point à une distance de 8 px de la surface physique.

Ainsi, dans ton exemple du premier paragraphe (la retombée après un saut), il n'y a pas besoin de chercher quel pixel de la surface correspond à la 'verticale locale', ni de regarder tout le long de la largeur du sprite de Sonic s'il n'y a pas une surface physique sur laquelle se poser, puisque dès que le centre de masse atteint la surface virtuelle, poum, c'est bon, Sonic est en contact avec une surface physique (et ce, automatiquement dans la direction de la 'verticale locale'), ce qui permet d'ailleurs de détecter les collisions dans toutes les directions en analysant uniquement le centre de masse, et non tous les points de la périphérie de Sonic (ce qui réduit très largement les calculs nécessaires).
Dans ton exemple du deuxième paragraphe (le mur en travers du chemin), c'est exactement la même chose, pas besoin de tests supplémentaires.

Par contre, comme je le disais, il y a une petite difficulté au niveau informatique ...
Imaginons le tile 16x16 d'un morceau de terrain à 45°.
Comme ce tile représente la surface physique, la méthode simplifiée consistant à indiquer l'angle local au niveau de cette surface physique ne nécessitait qu'une matrice 16x16 donnant une correspondance point à point avec le tile graphique.
Par contre, la surface virtuelle correspondant au bout de surface physique représenté par le tile comporte une partie de ses points en dehors du carré formé par le tile graphique.
Comme la surface virtuelle est à 8 px de la surface physique, un tile 16x16 nécessite une matrice 32x32, c'est-à-dire une matrice 16x16 au centre (la partie graphique) et une bordure de largeur 8 tout autour (pour être sûr d'avoir les points de la surface virtuelle correspondante), certes avec tout plein de 0 (partout sauf sur la surface virtuelle et sans doute quelques pixels d'épaisseur en dessous), mais 32x32 quand même (soit 1 ko).
En plus, les tiles 16x16 seront juxtaposés, donc les matrices 32x32 vont se recouvrir les unes les autres, ce qui oblige à sommer les éléments de deux matrices au niveau des recouvrements.
Ce recouvrement de matrices se sent bien dans l'exemple du sol plat et du mur vertical.
Chaque matrice de tile est donc affectée par les 8 matrices voisines, donc les matrices de deux tiles identiques avec un environnement différent seront finalement différentes dans le décor complet.
Comme on perd, dans le décor complet, la correspondance entre un tile et sa matrice, ceci revient en fait à créer une grosse matrice de la taille de l'écran (le décor complet) constituée de la sommation de toutes les matrices de tiles.
Comme je le disais, c'est vraiment très lourd au niveau de l'espace mémoire, puisque l'on perd la localité des sprites, pour n'avoir plus que la globalité du décor complet ...
C'est cette globalité qui booste les temps de calculs, mais au détriment, comme toujours, de l'espace mémoire ...

Ma méthode (non simplifiée) est donc physiquement exacte, mathématiquement utilisable, mais informatiquement irréaliste ...
Mais n'oubliez pas que je suis physicien, moi, et non informaticien !!!

Le système des matrices est malheureusement assez gourmand en mémoire, surtout si les tiles sont grands puisqu'ils faut creer plus de "modeles" de matrices différentes afin de garder une palette assez complète pour représenter tous les obstacles... Pour des tiles 16*16 une seule matrice prend deja 256 octets, or il faut au moins en creer une vingtaine pour commencer à avoir une gamme d'obstacles acceptable, ce qui occupera au moins 5 ko de mémoire... Heureusement je travaille en 8*8 donc ce systeme devrait s'avérer moins gourmand, mais bon -
Ouais, ben ce n'est pas gagné, je crois bien qu'on peux oublier ma méthode ...

au fait de combien de mémoire vive les programmes TI disposent-ils pour fonctionner?
Aucune idée, je n'ai jamais rien fait d'autre que du TI-Basic en 7 ans sur ma TI ...

En pensant également à ça, vu que j'ai banni les nombres à virgule flottante de mon univers de prog sur caltoche, il va falloir trouver une méthode efficace pour calculer les sinus et cosinus (au mieux je devrais utiliser une table, et paf voilà encore une bonne centaine d'octets dans la nature roll )
Plutôt que de coder l'angle et de calculer ensuite les sinus et cosinus, tu peux gagner du temps de calcul en codant directement les sinus et cosinus ... ce qui nécessite d'avoir deux matrices au lieu d'une (c'est une fois de plus l'éternel combat entre vitesse d'exécution et espace mémoire ...).

@++
avatar
Je ne suis pas développeur Java : je suis artiste Java.
Ce que l’on conçoit bien s’énonce clairement, / Et le code pour l’écrire arrive aisément.
Hâtez-vous lentement ; toujours, avec méthode, / Vingt fois dans l’IDE travaillez votre code.
La perfection est atteinte, non pas lorsqu’il n’y a plus rien à ajouter, mais lorsqu’il n’y a plus rien à retirer.
You don't use science to show that you're right, you use science to become right.

23

> Brunni:
Non je n'utilise pas de frameskip. En fait je possede une sorte d'horloge interne qui s'incrémente 50 fois par seconde, et à partir de laquelle je peux implémenter des Timers qui jouent le rôle de variables pouvant s'incrémenter à chaque udt (unité de temps) et que je controle comme des chronomètres (start, stop, reset). Donc pour décider d'un sprite à afficher au cours d'une anim par exemple, je me base sur le nbre d'udt qui se sont écoulées depuis le debut de l'anim, et non sur le nbre de frames qui ont été affichées. Ainsi, que le prog tourne à 10, 20 ou même 50 fps, le résultat est le même happy
De même pour gérer les accélérations, je fais ça d'une manière physiquement plus adéquate puisque cela donne quelque chose comme speed += acceleration*elapsedTime; au lieu de speed += acceleration qu'on effectue a chaque frame.
Mon perso n'est donc jamais ralenti par la quantité d'infos à traiter (qui varie énormément d'un point à l'autre de la map), le seul changement qu'on perçoit c'est la fluidité dans les déplacements, vu que par exemple si le framerate est soudainement divisé par 2 alors que le perso se déplaçait de 3 pixels à chaque frame, il doit du coup se déplacer de 6 pixels par frame et ça donne moins bien à l'écran.

> Ethaniel:
Un très beau pavé en effet, mais en tant qu'informaticien j'ai une autre conclusion pour cette méthode happy
En fait je ne vois pas trop l'utilité de passer à des matrices 32*32 étant donné que ce qui nous intéresse à présent, ce n'est plus la surface physique mais bien la surface virtuelle: il suffit donc de considérer l'ancienne surface physique comme étant la virtuelle, et de décaler la position de cette surface par rapport aux positions réelles des obstacles dans la map! Un sol plat pourrait être représenté exactement de la même maniere, sauf que les obstacles se trouveront en pratique non plus au niveau du sol, mais 8 pixels au dessus smile Tout ce qu'il y a à faire, c'est "dilater" les obstacles pour symboliser les zones à l'intérieur desquelles le centre de gravité ne peut pas pénétrer, au lieu de représenter les zones inacessibles pour le rectangle du sprite. Ceci peut se faire facilement par l'éditeur de maps au moment de sa création, et demande juste un effort d'implémentation dans la conception de l'éditeur...
Le seul probleme que je vois actuellement, c'est que ceci est parfait pour ramener un disque à un point, or nous avons ici affaire à des rectangles... Mais rien n'empeche d'utiliser cette méthode pour se ramener à des rectangles plus petits pour diminuer le nbre de points à traiter smile


Brunni encore:
Qu'utilises-tu comme méthode de compression pour la map? Les miennes sont également compressées, mais la décompression se fait en une fois au moment du chargement, je n'avais pas trouvé de méthode pour effectuer la décompression "en live"...
avatar
Un ptit gars qui programme en C/asm sur sa casio et qui vient voir de temps en temps comment ça se passe par ici :)
Un peu de verdure dans ce monde obscur

24

Orwell a écrit : Un très beau pavé en effet,
Merci hehe !
mais en tant qu'informaticien j'ai une autre conclusion pour cette méthode happy
Héhé, vive la complémentarité smile !
En fait je ne vois pas trop l'utilité de passer à des matrices 32*32 étant donné que ce qui nous intéresse à présent, ce n'est plus la surface physique mais bien la surface virtuelle: il suffit donc de considérer l'ancienne surface physique comme étant la virtuelle, et de décaler la position de cette surface par rapport aux positions réelles des obstacles dans la map! Un sol plat pourrait être représenté exactement de la même maniere, sauf que les obstacles se trouveront en pratique non plus au niveau du sol, mais 8 pixels au dessus smile Tout ce qu'il y a à faire, c'est "dilater" les obstacles pour symboliser les zones à l'intérieur desquelles le centre de gravité ne peut pas pénétrer, au lieu de représenter les zones inacessibles pour le rectangle du sprite.
Je vois que tu as parfaitement compris ce que je tentais d'expliquer happy.

La 'dilatation' dont tu parles correspond à la bordure de 8 éléments ajoutés tout autour de la matrice 16x16 du tile graphique.
Mais il est vrai que cette 'barrière de protection' (le 'volume' entre la surface physique et la surface virtuelle) n'a pas besoin d'être présente tout autour du tile graphique, seulement dans la zone où elle est nécessaire (au-dessus pour un sol, au-dessous pour un plafond, à gauche ou à droite pour un mur).
Ta solution consiste à décaler la sélection 16x16 pour inclure le morceau utile de ma bordure.
Par contre, s'il y a autre chose que des surfaces horizontales ou verticales, ta méthode nécessite quelques adaptations ...

Je vais tenter de représenter graphiquement ce que j'avais en tête en parlant du tile 16x16 d'un morceau de terrain à 45° (dont je n'ai même pas utilisé les propriétés dans la suite de mon post ...), mais là en 8x8 pour raisons de place (donc une barrière d'épaisseur 4 px).
L'angle de 45° n'apparaît pas bien, mais là, ce n'est pas ma faute hehe,,
 !Tile de départ | Surface virtuelle |   Avec bordure
               |                   |
               |                   | ,,,,,,,,,,,,,,,,
               |           O       | ,,,,,,,,O,,,,,,,
               |          O        | ,,,,,,,O,,,,,,,,
               |         O         | ,,,,,,O,,,,,,,,,
   .......X    |       .O.....X    | ,,,,.O.....X,,,,
   ......XX    |       O.....XX    | ,,,,O.....XX,,,,
   .....XXX    |      O.....XXX    | ,,,O.....XXX,,,,
   ....XXXX    |     O ....XXXX    | ,,O,....XXXX,,,,
   ...XXXXX    |    O  ...XXXXX    | ,O,,...XXXXX,,,,
   ..XXXXXX    |       ..XXXXXX    | ,,,,..XXXXXX,,,,
   .XXXXXXX    |       .XXXXXXX    | ,,,,.XXXXXXX,,,,
   XXXXXXXX    |       XXXXXXXX    | ,,,,XXXXXXXX,,,,
               |                   | ,,,,,,,,,,,,,,,,
               |                   | ,,,,,,,,,,,,,,,,
               |                   | ,,,,,,,,,,,,,,,,
               |                   | ,,,,,,,,,,,,,,
Dans ce cas, le tile graphique ne change pas, et donc, quand on les juxtapose, il y a recouvrement des bordures.

Maintenant, voici graphiquement ta solution (à savoir : il suffit donc de considérer l'ancienne surface physique comme étant la virtuelle, et de décaler la position de cette surface par rapport aux positions réelles des obstacles dans la map! | OXXXXXXX | °.....XX XXXXXXXX | XXXXXXXX | .....XXX XXXXXXXX | XXXXXXXX | ....XXXX
), mais optimisée (tile 8x8, -4 px de bordure => reste le carré 4x4 inférieur droit, dans lequel on met le maximum de matière).Tile de départ | S phys -> S virt | Décalage
               |                  |
   .....XXX    |     .....OXX     | .....°..
   ....XXXX    |     ....OXXX     | ....O...
   ...XXXXX    |     ...OXXXX     | ...O....
   ..XXXXXX    |     ..OXXXXX     | ..O.....
   .XXXXXXX    |     .OXXXXXX     | .O.....X
   XXXXXXXX
Les ° correspondent à des points de la surface virtuelle dont le point correspondant sur la surface physique n'apparaît plus sur le tile, et qui doivent donc être effacés.
XXX °.....XXXXXXXXXX .....XXXXXXXXXXX ....XXXXXXXXXXXX <tile 1><tile 2>
Et si on colle à droite un tile de sol plat de 4 pixels de haut, ça donne :<tile 1><tile 2>
.....°..OOOOOOOO
....O...........
...O............
..O.............
.O.....XXXXXX
Problème : il manque l'arrondi ...
tile 1><tile 2> .....OOOOOOOOOOO ....O........... ...O............ ..O............. .O.....XXXXXXXXX O.....XXXXXXXXXX O....XXXXXXXXXXX O...XXXXXXXXXXXX <tile 1><tile 2>Sachant que le tile de droite aura au maximum 4 pixels de terre (sans quoi la surface virtuelle est hors zone), on peut directement remplacer les ° par l'arrondi, ce qui donne :<Par contre, si on veut faire une longue pente à 45° ... le tile à 45° utilisé ne marche pas.
Ou alors, il faut utiliser des tiles qui n'acceptent que certains autres tiles autour d'eux, afin d'assurer la continuité de la surface virtuelle.
Cette continuité est super importante, puisqu'il suffit d'un seul petit trou d'un pixel dans la surface pour que Sonic passe à traves et plonge dans les ténèbres (Murphy veille !).
En fait, il faut vraiment faire les tiles au cas par cas ...
Ceci peut se faire facilement par l'éditeur de maps au moment de sa création, et demande juste un effort d'implémentation dans la conception de l'éditeur...
Le seul probleme que je vois actuellement, c'est que ceci est parfait pour ramener un disque à un point, or nous avons ici affaire à des rectangles... Mais rien n'empeche d'utiliser cette méthode pour se ramener à des rectangles plus petits pour diminuer le nbre de points à traiter smile
Si tu vois comment faire, tant mieux happy !

Au fait, je viens d'y penser : tu parlais de looping.
Et un looping, ça impose au moins deux plans, et le passage de Sonic d'un plan à l'autre.
En effet, quand il commence le looping, il passe devant la fin du looping qui n'est pas un obstacle (mais le début sur lequel il est en est un), et quand il termine le looping, il passe devant le début du looping qui n'est plus un obstacle.
As-tu pensé à gérer ceci ?

@++
avatar
Je ne suis pas développeur Java : je suis artiste Java.
Ce que l’on conçoit bien s’énonce clairement, / Et le code pour l’écrire arrive aisément.
Hâtez-vous lentement ; toujours, avec méthode, / Vingt fois dans l’IDE travaillez votre code.
La perfection est atteinte, non pas lorsqu’il n’y a plus rien à ajouter, mais lorsqu’il n’y a plus rien à retirer.
You don't use science to show that you're right, you use science to become right.

25

Oui je me doute bien qu'une simple translation de l'obstacle ne suffit pas, il faut une méthode plus complexe que ça... smile Mais ça c'est du prétraitement à effectuer au moment de la compilation de la map, on n'est donc pas forcé de chercher une méthode particulièrement efficace puisqu'on dispose de la puissance et de la mémoire du pc happy

Sinon pour les loopings, je pensais à un truc barbare: je place un objet fixe quelque part en avant-plan dont le sprite sera la partie du looping qui masque le perso grin
avatar
Un ptit gars qui programme en C/asm sur sa casio et qui vient voir de temps en temps comment ça se passe par ici :)
Un peu de verdure dans ce monde obscur

26

Justement, je me posais la question à propos de la map : comment est-elle utilisée pendant le jeu ?
Est-ce que c'est une grosse matrice avec un élément par pixel de la map, ou est-ce que c'est une matrice 16² (resp. 8²) fois plus petite donnant le numéro du tile 16x16 (resp. 8x8) à utiliser ?
Je pense que c'est la deuxième solution, mais comme je l'ai déjà montré, la surface physique et la surface virtuelle ont du mal à rester dans le même carré de 16x16 dès que l'on a autre chose que de l'horizontal et du vertical.
Dans ce cas, il faudrait peut-être utiliser plusieurs tiles ayant le même graphisme, à savoir ... de l'air, mais avec une surface virtuelle différente à chaque fois et en adéquation avec un tile (avec une surface physique cette fois-ci) adjacent.
Ce travail d'adéquation sera fait sur PC par l'éditeur de map, certes, mais il faudra tout de même pas mal de tiles différents (qui eux sont stockés sur la calculatrice) ...

Pour le looping, ce n'est pas si simple ...
En effet, quand tu parles de la partie du looping qui masque le perso, il ne faut pas oublier qu'un tour avant, le perso montait sur cette même partie.
Dit autrement, dans un looping, la surface virtuelle se coupe, et au point d'intersection, il y a à la fois l'angle correpondant au début du looping et celui correspondant à la fin, et une partie de la trajectoire (donc le long de la surface virtuelle) se trouve dans la barrière de protection d'une autre partie, ce qui est paradoxal s'il n'y a pas de séparation en deux plans.

Enfin bon, ça ce n'est pas vraiment mon domaine, je préfère faire la modélisation physique grin ...

@++
avatar
Je ne suis pas développeur Java : je suis artiste Java.
Ce que l’on conçoit bien s’énonce clairement, / Et le code pour l’écrire arrive aisément.
Hâtez-vous lentement ; toujours, avec méthode, / Vingt fois dans l’IDE travaillez votre code.
La perfection est atteinte, non pas lorsqu’il n’y a plus rien à ajouter, mais lorsqu’il n’y a plus rien à retirer.
You don't use science to show that you're right, you use science to become right.

27

Chez moi la map est une matrice à 3 dimensions dont chaque élément correspond à un tile et caractérise 1) la texture qui le recouvre, 2) les obstacles présents, et 3) l'éventuel objet qui s'y trouve. Les textures et les obstacles sont donc totalement indépendants, ce qui permettrait même d'adopter un autre systeme que l'actuel découpage des obstacles sur les différents tiles happy

Pour les loopings je vois mieux de quoi tu parles à présent, mais ça c du domaine du bidouillage (une chtite variable estSurLooping qui empeche de se cogner la tete lorsqu'on se trouve dans la boucle, et le tour est joué grin) - Encore faut-il savoir qu'on se trouve sur un looping, mais ça ce n'est pas difficile si on attribue correctement les ID à chaque matrice d'bstacles smile

Sinon en fait je ne pense pas que cette méthode demanderait énormément de matrices différentes (à condition de ne pas inventer de formes de terrain complètement farfelues)... mais si un problème d'espace mémoire se pose réelement, la lecture dans une matrice peut toujours se ramener à un bout de code, et particulièrement lorsque le contenu de la matrice est "simple" (pas besoin d'utiliser une matrice si tout ce qu'elle fait c'est indiquer que la surface virtuelle est verticale et sur le coté gauche du tile par exemple)... On peut travailler au cas par cas sans problème smile
avatar
Un ptit gars qui programme en C/asm sur sa casio et qui vient voir de temps en temps comment ça se passe par ici :)
Un peu de verdure dans ce monde obscur

28

Ah ben moi pour les loopings, j'utilise une astuce qui est assez simple en fait, avec qu'un seul plan mais ça signifie qu'il n'y a qu'une seule définition de tile possible à la fois, c-à-d que celle qui se trouve sur l'autre plan est forcément nulle (la décompression de deux plans + un de collisions demanderait trop de temps CPU; avec deux plans je suis déjà à près de 4% par frame!)
J'ai deux bits qui indiquent "ne doit se trouver que sur le plan 1 ou 2". Et lorsque je lis un élément depuis la map, j'applique un masque (masquant le premier bit ou le deuxième selon sur quel plan Sonic se trouve). Si ce n'est pas le bon bit qui a été masqué, alors la tile aura une valeur beaucoup trop grande et ne sera pas prise en compte par le moteur puisqu'elle ne sera d'aucun type connu.
Donc c'est une alternative aux deux plans, mais ça ne permet que de dire si on veut que la tile ne se trouve que sur un plan ou l'autre mais pas ce qu'il y aurait sur les deux plans en même temps comme dans le vrai jeu.
Les ennemis, eux, masquent les 2 bits. Donc il est possible de faire des plate-formes de collisions qui ne sont reconnues que par ces ennemis (car pour Sonic elles n'apparaissent ni sur le plan 1 ni sur le plan 2), typiquement au bord des précipices pour les faire tourner.
[Edit]
Bon, je viens de terminer la rotation de Sonic love et ce n'est pas très beau parce que comme je n'ai qu'un angle associé à chaque tile, on voit chaque "étape" d'un looping. Mais si tu prévois de pré-roter les sprites par étape de 45° comme sur Mega Drive, alors c'est une autre histoire; et je te conseille si ton moteur n'est pas trop avancé d'utiliser le milieu des pieds de Sonic comme point de collision central plutôt que la tête ou le haut-gauche du personnage (comme on le fait normalement). Ainsi, ton moteur peut fonctionner plus ou moins de la même manière mais tu n'auras pas de problème pour la rotation, et pour les autres trucs plus complexes que tu risques de rencontrer plus tard, tout sera plus simple wink (sinon il faut par exemple prévoir que la taille de sonic change en temps réel et que ton moteur de collision supporte ça sans planter où que ce soit, ce qui n'est pas le cas du mien en tous cas grin)
avatar
Highway Runners, mon jeu de racing à la Outrun qu'il est sorti le 14 décembre 2016 ! N'hésitez pas à me soutenir :)

https://itunes.apple.com/us/app/highway-runners/id964932741

29

Sinon, vous pouvez regarder les sources de SMA de PpHd smile
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

30

Ils l'ont deja fait.