J'ai grandi entre les jeux 8-bit et les jeux 16-bit. Vous allez avoir largement l'occasion de comprendre ce dont je veux parler, mais à moins que vous ne sortiez du bouquin "the sprite decade" de Rodrigo Copetti, un petit brossage de tableau devrait faire du bien.
Si vous prenez à peu près n'importe quel appareil électronique moderne, il aura certainement une zone mémoire spéciale dans laquelle les valeurs indiquent des couleurs de pixel et qui est utilisée par la puce graphique pour construire le contenu à l'écran. Tout ce que le jeu / programme aura à faire, c'est remplir cette mémoire avec l'image qu'il veut voir à l'écran et le tour est joué. C'est vieux comme Space Invaders.
Mais les machines des années 85-95 n'ont pas le luxe de fonctionner comme ça, surtout si elles veulent afficher assez de couleurs: la vitesse d'accès à la mémoire est encore trop réduite. Alors on ruse. On utilise des puces prévues pour l'affichage de texte en remplaçant la ROM "caractères" par des bouts de tuyaux et on les baptise "tiles" (prononcez "taïlz"). On se rajoute une 2eme passe qui peut positionner des images librement n'importe où à l'écran et on les baptise "sprites" (prononcez "spraït" et essayez de ne pas faire de renvoi avec les bulles ;)
J'ai grandi avec pas fort loin de moi un manuel du C64 qui donnait quelques clés pour utiliser ce genre de hardware, mais le temps que je sois prêt à le maîtriser, tout le monde était passé sur PC où la mémoire de type "grosse grille de pixels" règne en maître, et tant pis si ça veut dire qu'on va passer une demi-seconde à tracer l'écran de Congo Bongo avant que le jeu ne démarre.
J'ai grandi en analysant les jeux que j'avais en main sur C64, NES, GameBoy, Master System, SuperNES, MegaDrive en remarquant que "mais oui, il suffit de réécrire les 4 caractères qui forment la pièce d'or et toutes les pièces à l'écran seront animées en même temps! ... En fait non! regarde! Elle est symétrique horizontalement et verticalement! Donc tu réécris juste 8x8x4 bits et comme la SNES dessine les autres parties en miroir, ça suffit pour tout animer!". Et en écrivant des jeux à écran fixe, où on doit enregistrer ce qui est à l'écran sous le curseur de la souris avant de redessiner le curseur et prier pour que ça ne clignote pas trop, avec des "pièces d'or" immobiles parce que même 8 fois plus rapide qu'une SNES, mon PC aurait ramé à tout redessiner.
Puis sont arrivés les jeux "impossibles". Ceux qui vous faisaient un dégradé de 200 nuances du bleu au rouge dans le fond de l'écran alors qu'on a que 16 couleurs. Ceux qui vous mettent des ennemis dans tous les sens alors qu'il n'y a que 8 sprites. Ceux qui vous animent une tour-qui-tourne, un plancher en 3D ... sur le même matériel 100% 2D que celui dont on vient de parler. Et toujours le même réflexe: comment ont-ils combiné les manipulations du hardware pour obtenir l'effet qu'on voit à l'écran ? Comment est-ce que moi, je m'y prendrais pour le réaliser.
Alors voilà: la Nintendo DS est la dernière machine héritière de cette génération. C'est donc sur DS que je veux faire mon jeu. C'est ça où le faire sur une vraie console 16-bit, mais soyons honnête: je n'ai pas (encore?) le niveau en pixel art pour avoir un rendu qui me plaise sur le hardware de l'époque ni le temps pour approcher chaque problème de programmation en devant l'ultra-optimiser pour que ça passe sur les quelques MHz de ces reines des consoles.
Great. You've read through all the posts related to the management of a tile-based level and how the game logic uses it. The basis is also covered in the tutorial branch through the 'cando()' function that filters contents of the map to say whether we're green to do some kind of move or not.
(2005-12-31) [tiled][tagtionary]
Deuxième petit programme pour la DS ... Hier matin, un peu avant le temps de midi, j'entends passer la musique que mon frère avait écrit pour un jeu jamais réalisé: XMASMAN vs Packs ... un concept de tetris ou le père noël doit empiler des cadeaux qui dégringolent...
J'avais fait des super petits sprites (vous savez ce que c'est, hein, maintenant) avec un chouette programme (Delux Paint II, je l'avais déjà sur mon 8086, celui-là!), et je les ai toujours... alors je me dit "voilà le petit jeu que je pourrais facilement avoir réalisé pour noël!". Il marcherait nickel avec 2 écrans, pas trop még-à-l'eau ni rien. Mais bon, ce serait ridicule de devoir redessiner tout avec le SpriteEditor DS qui n'est pas encore au dixième des possibilités de DP2. Donc je rebidouille un petit "sprite grabber", un outil qui affiche une image complète et qui permet d'en récupérer des morceaux. Première esquisse sur le temps de midi. Ca tourne dans l'émulateur. cool.Je reviens le soir @home et ma fée est toujours à Maastricht pour la-journée-ou-les-magasins de-bricolages-sont-ouverts-jusque-21h ... Et je rattaque. A 20h30, j'ai intégré le code de Chishm et DragonMinded, et je fais le premier essai "temps réel" d'écriture de fichiers sur ma petite carte SD. Et ça marche, les gars! Sortez les cotillons, c'est la journée du code qui fonctionne (après une semaine de combat avec les codes à redondance cycliques de ma carte-réseau-qui-tue qui s'est enfin mise à fonctionner le matin)! Donc voilà, y'aura encore un peu de boulot pour qu'on puisse choisir n'importe qu'elle image et sauver dans plusieurs fichiers, mais le départ est là ^_^(2006-08-25) [skyblog][mybrew][coding][libfat][sprite editor][ch1]
Vous courriez ? J'en suis fort aise! Eh bien, Sautez, maintenant...
Je discutais DS et bilous avec Dider, mon beau-frère informaticien, et je me rends compte que des techniques que j'ai apprises "sur le tas" avec mon Bilou's Adventure en QuickBasic peuvent parfois être pas si évidentes que ça pour des personnes qui ne baignent pas dedans depuis 15 ans ^_^ Outre les aspects techniques qui font que les images du jeu s'affichent convenablement, une question-clé était "mais comment programmes-tu les murs, les sauts, etc." Rappelons-nous tout d'abord que sur une console de jeu, l'écran (en tout cas pour les modes 2D) ressemble plus au carrelage d'une salle de bain qu'à n'importe quoi d'autre. Vous avez de jolis pavés (parfois appelés "tiles" ou "blocs" selon les cas) sur lesquels vous avez amoureusement dessiné des bouts de livre, d'encrier, de crayon, etc. Chacun porte un numéro et il suffit d'inscrire son numéro dans une grande grille pour construire l'image voulue. Bion. Maintenant qu'on sait ça, on voudrait pouvoir détecter s'il y a ou non un sol en-dessous de Bilou, histoire de s'avoir s'il doit tomber ou rester sur place. Rien de plus simple: on va calculer les coordonnées d'un "point-test" situé juste en-dessous de Bilou et regarder le numéro du pavé au-dessus duquel il se trouve. S'il s'agit d'un crayon, d'une latte, etc. on ne bouge plus! Sur PC, j'avais traduit ça en une division des couleurs du jeu en deux sous-ensembles: les couleurs "solides" (pour dessiner les livres, etc) et les couleurs "de fond" (pour dessiner le fond. warf.), mais sur une console comme la DS, on a généralement plusieurs plans de décor, et pour commencer, on peut tout à fait décider que tout ce qui n'est pas "solide" set sur un autre plan: lors du calcul du "point-test", on verra soit le pavé #0 (du vide), soit un autre pavé, qui indique qu'on a affaire à du sol.
Bien sûr, ce que je fais avec Bilou et ses points-test peut être généralisé à tout le reste du jeu: les éponges, les crayons, les gommes, les gouttes d'encre et tout le tintouin. Il suffira de programmer les routines de déplacement de chaque élément du jeu pour qu'il fasse ses propres tests et réagisse en conséquence (passage de l'état 'sur le sol' à l'état "dans le vide", par exemple). Avec ça, vous ne devriez pas avoir trop de mal à vous construire un petit perso qui tombe quand il n'y a pas de sol et qui avance quand il est au sol.Maintenant, quid des sauts et compagnie ? Bon, si vous avez déjà suivi un petit cours de physique, vous savez qu'un objet qui bouge a un vecteur-vitesse. En clair, si Bilou avance vers la droite, son vecteur-vitesse vaut p.ex. (5,0), c'est à dire qu'à chaque "tranche de jeu" (ou 'tic'), il sera 5 pixels plus loin sur la droite mais qu'il ne monte pas et qu'il ne descend pas. A l'inverse avec un vecteur-vitesse (0,5) Bilou descend comme s'il était sur un ascenseur. Pour donner l'impression qu'il tombe, il suffit d'augmenter la vitesse verticale à chaque tic. Mieux, même, si je démarre avec (0,-5) et que je garde la politique "augmente la vitesse verticale à chaque tic tant qu'il n'y a pas du sol en-dessous de Bilou", il va faire un gentil saut sur place. Avec un petit calcul niveau secondaire-sup, vous pouvez même calculer la vitesse initiale à donner à Bilou lorsque le joueur appuie sur le bouton de saut pour qu'il puisse (par exemple) faire un saut suffisant pour grimper par-dessus un obstacle de 64 pixels de haut. Facile: il faudra au moins une vitesse initiale de 11. Et c'est là que le bas blesse: on vient de dire que 8 était un maximum :-/ Résultat,
on pourrait diminuer la gravité (se dire que tomber d'un pixel tous les 1/60 secondes, c'est un peu beaucoup: vous avez traversé 1/3 de la hauteur de l'écran en seulement une seconde)
on pourrait ajuster nos routines de tests pour qu'une vitesse de plus de 8 pixels sur 1/60 secondes ne pose pas de problème technique
on pourrait revoir le modèle du saut et "tricher" un peu avec les lois de la physique...
En pratique, si vous regardez sauter Mario, vous vous rendrez compte qu'on a pas vraiment respecté les bonnes vieilles lois de Newton dans les sauts, et pour cause: le joueur doit pouvoir doser son saut, et la manière la plus naturelle de le faire, c'est de relâcher plus ou moins tard le bouton de saut. Dans la réalité, bien sur, ça ne correspond à rien: une fois que vous êtes lancés, vous ne pouvez plus décider que "oups, non, finalement, il faudrait que je saute un peu moins loin si je ne veux pas me tordre la cheville en retombant". On pourrait faire comme dansninji & zarbiet demander au joueur de laisser le bouton enfoncé d'autant plus longtemps qu'il veut sauter loin (mimant alors une "accumulation d'énergie" avant le saut) et ne faire le saut que lorsque le joueur relâche le bouton, mais je vous laisse imaginer sauter de passerelle en passerelle sur ce principe.Non, ce que l'on va faire, c'est considérer que l'énergie dont notre Bilou dispose pour le saut n'est pas consommée complètement en un instant pour lui donner la vitesse (0,-11), mais plutôt que pendant une première phase du saut, Bilou conserve une vitesse verticale plus basse, mais n'est plus soumis à la pesanteur (en gros, il monte à vitesse constante) aussi longtemps que le joueur garde le doigt sur le bouton de saut. Avec un bon dosage de la durée max de cette "phase ascenseur", on pourra atteindre la hauteur souhaitée sans dépasser la vitesse qu'on s'est fixée.
Reste le coup du saut pendant la course. Le premier jeu bilou était assez déroutant dans ce sens. J'avais voulu obtenir un effet "à la Sonic" ou le personnage saute d'autant plus haut et loin qu'il avait pu prendre de la vitesse. L'ennui, c'est que je passais simplement de (v,0) à (v,-v) au moment du saut. Si vous faites le calcul, ça veut dire que au moment où le jouer appuie sur le bouton, c'est comme si l'énergie de Bilou était subitement doublée ... d'où un saut qui n'a rien à voir avec la vitesse de Bilou à ce moment-là. Idéalement, j'aurais plutôt du prendre quelque-chose comme (sqrt(v),sqrt(v)) pour conserver sa quantité d'énergie ... On testera tout ça...
On discutait DS et sprites avec Cyril, et je lui explique que je voudrais faire quelques essais de gravité pour mes jeux DS, par exemple avec mes petites pommes -- "Comme Newton, hein?" qu'il rajoute.
Donc vous avez la un aperçu de la session de 'rumbling' de ce temps de midi, avec "TetattDS" qui ressemble de moins en moins à un jeu de Tetris depuis que j'ai intégré mon code du Sprite Editor DS pour récupérer mes pommes :P J'en ai aussi profité pour regarder un peu (et documenter en UML) la découpe du code de Sten. J'aime encore bien le partage entre les classes FieldGraphics (tout ce qui est dessin un peu bas niveau) et PlayField (grosso-modo, c'est l'algorithme de jeu comme je l'aurais écrit en BASIC si le jeu avait utilisé des caractères sur un fond tout noir -- sauf qu'on fait appel à FieldGraphics)
Un peu de pioche dans mes vieilles images Deluxe Paint, ce matin, et j'ai notamment retrouvé un des arbres (et son décor) que j'avais fait pour bilou. Au départ, un dessin au porte-mine que j'avais scanné (du temps où il ne fallait pas trembler pour faire un bon résultat avec le scanner ISA racheté à Pascal), puis utilisé comme "calque de référence" pour reconstruire intégralement le résultat que vous avez là. Ma technique de remplissage à l'époque consistait à utiliser un dégradé "mélangé" par deluxe paint (avec un point de lumière approprié) puis à passer un coup de "smooth" sur le tout (flou gaussien, quoi). Les champignons, etc. on subi plus ou moins le même sort avant d'être retransformés en simples silhouettes pour les besoins du dessin.
A peu près à la même époque, une bande de joyeux driles nous sortaient SuperFrog sur Amiga, ensuite porté sur PC. Bin, je dois vous dire, j'aime bien les arbres de SuperFrog. leur côté sympa, d'abord, le tronc "ligneux" comme si il y avait plusieurs "tuyaux" indépendant qui avaient fusionné pour faire l'écorce, etc. Et, surtout, ils font partie du level design par ces "branchages" sur lesquels notre héro vert peut courir et sauter. Tiens, et par curiosité, je me suis amusé à décrotiquer un peu le feuillage. Tout d'abord, il faut trouver un repère pour la grille. Le bord du sol nous donne un bon indice, reste à superposer l'image avec elle-même (avec un peu de transparence dans Gimp) et à la décaler vers la droite jusqu'à ce qu'un carré net nous apparaisse au milieu de l'image toute floue. Voilà. Un coup de "Image>Configure Grid" et on est parti. Bon, clairement le "trou" dans le feuillage a été construit sur base de 4 blocs (je les appelle A,B,C,D si vous voulez bien). Capturons ces 4 blocs et faisons-en un calque séparé en mode "différence". En le promenant le long de notre grille, les pixels identiques vont se retrouver tout noirs (x-x=0) ... si jamais on voit un carré complet devenir tout noir, c'est que un de nos blocs (A,B,C ou D) a été utilisé à cet endroit de l'image.C'est bien sur le cas pour le deuxième trou du feuillage, mais à pas mal d'autres endroits, on retrouve nos blocs, à quelques différences près (des feuilles au lieu du trous, ou certaines feuilles retirées pour faire le bord de l'abre).
En répétant le processus, on peut identifier les différents blocs de l'arbre, chercher éventuellement des blocs de base supplémentaires, etc. En fait, ici, quasiment tout le feuillage est construit sur 4 blocs de base qui ont servi à la construction du "trou". Seules les platformes que l'on superpose au feuillage et quelques "raccords" sur le bord du feuillage font exception à cette règle. Chapeau. Oh, et pour ce qui est des couleurs, (#045624,#047604, #049a04) pour les feuillages (#645614, #544604, #343204) pour le tronc (allez, avec un petit pixel de (#8c7644 ou #746634 autour de la bouche) et (#242224) et quelques touches de brun foncé pour les ombres du feuillage (audacieux, mais ça a marché, visiblement).
Suite au commentaire de CJ sur "armageddon machine", j'ai cherché davantage de niveaux de Commander Keen 4 & 5. Je suis tombé sur une véritable mine d'or: les niveaux tels qu'ils seraient vus dans TEDEDIT 5, l'outil d'édition de niveaux de Apogee/ID Software, réalisés par "pleinair123". Le layout même de ces niveaux suggérait que pleinair123 avait accès pas uniquement à des screenshots réassemblés, mais bien à une reproduction de ce que donnerait un éditeur. J'en veux pour preuve les flèches transparentes, qui servent de guide aux plateformes mobiles et surtout, ces curieux blocs gris avec un B que vous voyez à gauche de l'image et qui, selont la légende, servent de "frontière" pour les objets qui rebondissent (étoiles à pointes, passerelles, etc.) Intéressant de constater que, bien que les éléments du jeu soient nettement plus sophistiqués que ce qu'il y avait dans le GameMaker, ce simple "plan" supplémentaire contenant les ennemis et ce genre d'objets de contrôle invisible permet des fonctionnements bien plus riches en se prenant nettement moins la tête.
Déçu, par contre, par l'absence de map équivalentes pour "Secret of the Oracle", j'ai cherché tout ce qui pourrait m'aider à reverse-engineerer les fichiers .CK4. Bin j'étais pas le premier : http://files.keenmodding.org/mobydoc.txt
Tout est là. De quoi se faire son petit éditeur ou viewer pour Commander Keen. Maintenant, que ceux qui pensent que celà veut dire qu'on est à 2 doigts d'un KeenDS s'arrêtent tout de suite:1. Commander Keen est toujours la propriété intellectuelle d'ID software2. Son inventeur serait ravi de refaire des Keens s'il récupérait cette propriété intellectuelle3. Tout utilisation non-privée des infos dans "mobydoc.txt" (comme un homebrew) constituerait une violation des droits d'auteurs de Apogee4. Il resterait quand même le plus costaud: reconstruire le comportement de tous les monstres, la physique du jeu, etc. Bref. Je pense que ça n'intéresserait pas grand monde un keen sur DS qui avance mal, qui saute de travers et où les robots roses ne montent pas aux barres. Donc, je vais rester sur mon Bilou et ses éditeurs de niveau (non, la structure des niveaux de Commander Keen - même si elle est source d'inspiration - n'est pas géniale au point de vouloir utiliser TED pour Bilou :P) Oh, et pour ce qui est du titre, je prends le niveau "security" de Armageddon Machine (le 2eme du jeu), au pif. Mode easy: 6 Sparky, 3 Ampton et 2 SliceStars.Mode normal: 13 Sparky, 5 Ampton et 5 SliceStars
Mode hard: on rajoute 4 Sparky, 1 ampton et une SliceStar qui se balade horizontalement au milieu des écrans bleus ...
Alors imaginez dans les niveaux plus loin, là où il faut commencer à rester sur les plateformes volantes pour ne pas mourru sous un feu crépitant de lasers, vous comprendrez vite que l'expérience n'est pas du tout la même.Idem pour le niveau final. Ce que j'ai dit à propos des ennemis rebondissant dans un espace limité ne tient évidemment que s'il y en a (et il n'y en a aucun en mode easy dans cette partie du jeu). Le Shikadi Master est lui aussi réservé au mode "normal", et seule une rangée de lasers est activée, ce qui laisse au joueur le soin de passer à l'aide du pogo du côté d'où les lasers viennent sans être inquiété... Donc, non, frérot: si t'as pas re-terminé les Commander Keen au moins en mode "normal", t'as jamais essayé plus que la "grande foire au bonus" réservée aux moins de 14 ans. Désolé. edit: oh, et on ne sauvegarde pas au milieu d'un stage, hein ?
A ce stade-ci, j'ai déjà fait un jeu DS, profitant de l'aspect Open Source d'un autre jeu pour en garder le moteur et injecter les premiers graphismes de développés avec mon premier soft DS: un éditeur de sprite du nom de SEDS. Dans cette section, on va voir une série de tentatives ayant pour objectif de pouvoir proposer des niveaux de jeu plus grand qu'un écran. Il n'y a pas encore de projet dec jeu arrêté, d'où l'emploi des maps de Sonic. Des décisions qui mêneront à un moteur de jeu fonctionnel utilisé dans un premier jeu en 2009 mais qui sera ensuite révisé pour les jeux suivants au fur et à mesure que "défauts" de ce système initial se font de plus en plus flagrantes. Je vous les laisse donc principalement par soucis de respect historique, mais on verra plus loin de le chapitre les techniques qui sont plus convaincantes.
(2007-09-07) [ch1]
Un autre petit outil en Perl (basé sur la bibliothèque de manipulation d'images Imlib) qui décortique une image et identifie les tiles uniques (8x8) et le nombre de couleurs utilisées... Il ne fallait pas grand chose pour en faire un convertisseur .gif->.spr un peu plus automatisé. Je m'y suis attelé ^_^. L'outil en question s'appelle "imlib2spr.pl" et est disponible dans le CVS du projet 'runme'.
Mais pourquoi chercher à transformer une image .gif en un tas de tuiles, me demanderez-vous. Okay, certaines images contiennent des zones un peu répétitives et pourront donc prendre moins de place en VRAM... et c'est tout ?
Ooh non! Notre DS est effectivement capable d'afficher directement des images 'bitmap' (c'est ce que je fais quand vous envoyez à runme un fichier .pcx), mais avec une taille maximale de 256x256 ou de 512x512, selon la configuration d'écran choisie. Impeccable tant que vous voulez une image fixe, mais imaginons que je veux faire scroller horizontalement une image de 320 pixels de large, je fais quoi ?
Je passe à 512x512 ? le bitmap fera alors 256K, presque la totalité de la mémoire vidéo! Je tronque à 256x256 et je redessine une colonne chaque fois que je me décale d'un pixel ? Bonjour le code!
Sous GBA, pas tout ces soucis: l'écran faisait 240 pixels de large et le mode bitmap 256x256 aussi, donc on avait tout loisir de redessiner des blocs dans la partie complètement masquée de l'écran, puis de scroller un peu, puis de redessiner, etc.
Eh bien, si l'on prend la peine de convertir notre bitmap en "tiles", on peut continuer ce petit jeu: une fois les tiles en mémoire, une "carte" peut très bien contenir deux fois l'image initiale sans nécessiter d'avantage de mémoire vidéo (allez, quand-même 1 ou 2K de plus, mais c'est quand même mieux que de passer de 64 à 256K, non?)
Bref, c'est Cyril qui sera content. Lui qui me proposait y'a pas 48 heures d'ajouter un mode "construisons notre map" dans SEDS ... On en est plus très loin ;)
à la base, c'était ça:
#!/usr/bin/perl
use Image::Imlib2;$image = Image::Imlib2->load($ARGV[0]);print STDERR "$ARGV[0]: ".$image->width."x".$image->height."\n";while ($ty<$image->height) { $tx=0; while ($tx<$image->width) { foreach $y (0..7) { foreach $x(0..7) { ($r,$g,$b,$a) = $image->query_pixel($tx+$x,$ty+$y); $r=pack("C1",$r); $g=pack("C1",$g); $b=pack("C1",$b); $colors{"$r$g$b"}=$ncolors++ if !exists $colors{"$r$g$b"}; $tile.=$colors{"$r$g$b"}; } } $tiles{$tile}.="($tx,$ty)"; $tx+=8; $tile=""; $ntiles++;}$ty+=8;print STDERR ".";}print scalar(keys(%tiles)) . "unique tiles (/$ntiles) detected\n";print scalar(keys(%colors)) . "unique colors detected\n";exit 0;note: il nous faudra installer le package libimage-imlib2-perl pour faire fonctionner le tout.
rem: pendant ce temps-là, Globoeil nous faisait une release de son "virtual game maker DS" ... à découvrir sur globoeil.fr/vgdms ...
Plus de 640K de mémoire vidéo sur la DS -- c'est presqu'autant que la mémoire vive dont on disposait sous DOS sans les extensions. Mais comme je vous le montrait dans un de mes posts précédents, il faut répartir cette mémoire entre les deux écrans, les fonds, les cartes, les sprites et éventuellement, les palettes et les textures. Bref, pas mal de monde.
Pour un jeu de plateforme comme Bilou et Badman, le plus raisonable serait de partir sur 256K pour les décors, 128 à 256K pour les sprites (tout ça sur l'écran principal) et de garder les 128K restant pour gérer l'écran secondaire (map, inventaire ... peu importe). 256K de décors ... avec mes blocs qui font 16x16 pixel, ça veut dire que j'ai droit à un bon millier de blocs différents conservés simultanément en mémoire. Soit près de 5 écran de DS. C'est déjà honnête ...
Bien sûr, rien n'empèche de changer le contenu de la VRAM au fil du jeu. C'est notamment ce que j'ai l'intention de faire pour les blocs animés (pensez à la pièce d'or de SuperMario ou au bloc-question). On leur attribue quelques tiles sur lesquels on ira recopier systématiquement l'étape d'animation suivante au bon moment (depuis nos 4Mo de mémoire de travail, donc). Ainsi on gagne du temps et de la place. On pourrait aussi essayer d'éliminer certains blocs non-affichés pour les remplacer par d'autre en fonction de la progression du joueur dans le niveau, par exemple si l'on voulait faire un traveling continu sur les écrans d'Another World... mais il y a peu de chances que je me serve de quelque-chose de ce genre.
Dans un niveau ou le scrolling est libre dans toutes les directions, on choisira de préférence une map 512x512, de manière à pouvoir toujours modifier les "bords" de la map pour avancer dans le niveau sans que le joueur ne remarque quoi que ce soit. Il y a aussi des map 512x256 et 256x256 que l'on utilisera dans des jeux ou le scrolling suit un seul axe (les shoot'm'up, par exemple, ou des niveaux spéciaux dans un jeu de plate-forme).
Evidemment, on pourrait se dire que c'est du gâchis de prendre une carte de 512x512 alors que 256+16 aurait suffi à un scrolling fluide (réfléchissez un peu: pourquoi le GBA a-t-il un écran de 240 pixels de large, selon vous?). Mais ce serait compter sans le fait que la "carte" des tiles peut aussi nous servir directement pour savoir si notre personnage (et tous ses adversaires) ont affaire à un mur ou à un trou. Chaque "tile" est encodé sur 16 bit dans la carte (cf cet autre post précédent) et on peut choisir parmis 1024 "tiles" différents, les autres bits permettant de "retourner" les blocs horizontalement et verticalement.
Restent 4 bits inutilisés qui pourraient se révéler bien pratiques pour encoder directement le type de bloc: creux, plein, "montée" à 45°, "descente" à 45°, "montée" à 22.5% ...
Dans Rayman Designer, il y a 24 types de blocs différents, y compris le bloc semi-solide (on peut passer à travers en sautant, mais pas en tombant), les blocs glissants (de nouveau 7 inclinaisons différentes), des blocs qui blessent, qui tuent, qui noient, qui font rebondir, escaladable, etc.
<obsolete why="si les bits "palette" sont actuellement effectivement utilisés, c'est en les combinant par blocs de 4 tiles que l'on étendra le nombre de propriétés disponibles.">
Bien sûr, avec 4 bits de libre, c'est un peu juste pour proposer les 24 types de Rayman, et pourtant, dans Bilou, il nous faudra bien tout ça. Mais dans notre cas, il y a peu de chance que l'on se serve du "retournement" de tiles. Le plus souvent, ça fouterait en l'air tous nos efforts d'éclairage sur les blocs. Résultat, on pourrait s'arranger, par exemple, pour stocker certains blocs "à l'envers" et pour pouvoir se servir du bit "miroir horizontal" pour faire la différence entre les montées et les descentes ... L'alternative, ce serait d'avoir une des combinaisons pour indiquer que le bloc est "spécial", et de se servir à ce moment de son numéro pour choisir le comportement approprié, sachant que ce sera un peu plus compliqué à traiter... L'idéal, ce serait de garder ça pour les interactions que seul le personnage fera (grimper, se blesser, etc.) et d'avoir les effets sensibles égalements pour les monstres directement encodés sur la map...
</obsolete>
Enfin. 'faudra tester tout ça. J'espère que ça ne vous a pas paru trop imbuvable: j'avais besoin de mettre les idées au clair avant de me lancer dans le code de gestion des niveaux ...
(2007-11-15) [tiled][sidescroller][translate me][slopes][level design][cheat sheet][vram][jump-thru]
Ideas for smooth scrolling on the DS...
Bien sûr, la DS est capable de faire un scrolling fluide à travers un grand niveau, mais ce n'est pas nécessairement une mince affaire. Je cogitais sur les méthodes possibles l'autre jour et j'ai pris mes notes dans mon blog.
Map bases always have 32×32 entries. To create bigger BGs (Like 64×64) you need to use several map bases. As for tile bases - If you want to make use of the full tileset (1024 tiles), you will have to use several tile bases - 4 Tile bases for 256col full tileset, and 2 Tile bases for full tileset of 16col mode. Since the Map bases and the Tile bases are located on the same region in VRAM, they can overlap. Since a map base is 2Kbytes and a Tile base is 16Kbytes, each Tile base contains 8 Map bases.
Une surface affichable sur la DS est constituée de 32x32 tiles (l'écran en fait 32x24) et occupe 2KB dans la mémoire vidéo. Pour construire des plans de décor plus larges (p.ex 64x64 tiles), il nous faudra plusieurs 'morceaux' de 32x32 mis côte à côte pour former une surface plus grande. On retrouve le même phénomène avec les tiles (pour utiliser l'entièreté d'un jeu de tiles (1024 tiles), il nous faut 4 sections de 16K en mode 256 couleurs). Notez que c'est au programmeur de choisir s'il va stocker 16K de tiles ou 8 maps dans une région donnée de la mémoire vidéo.
La console nous permet de positionner l'écran librement dans la surface définie (pour chaque plan séparément, hein, ) et -- contrairement aux carte VGA -- par défaut la surface 'boucle' sur elle-même (si la zone affichée déborde par la droite, c'est la gauche de la surface qui sera réutilisée).
We can have hybrid technique ...
Le système de scrolling va forcément mélanger software et hardware. Le côté hardware, c'est le choix de la portion visible de la surface en VRAM (on l'appelle "onscreen surface" -- litéralement 'à l'écran' en anglais). Le côté software consistera à modifier des portions de cette surface (de préférence qui sont actuellement invisible pour le joueur) afin de donner l'illusion d'un décalage continu alors qu'en réalité, nous somme piégés dans un carré de 512x512 pixels. Avant de ce lancer dans le code, il serait bon de se rappeler que rien ne nous oblige à avoir une technique unique pour les scrolling horizontaux et verticaux. Les uns pourrait parfaitement s'opérer à grands coups de DMA sur des tranches entières alors que les autres se feraient "bloc par bloc" gérés entièrement depuis le CPU.
we can't have smooth scrolling in a 64x64 area if we reprogram 32x32 entries (dma copy) at once (<-- cyril contests this, see next post).
we could do the "John Carmack" trick, using hardware scroll in the 64x64 area and then "jump" to a second 64x64 area when we're about to move out, but that would kind of be silly on hardware that allows to "wrap around" the area with our viewport (which the PC hardware couldn't do properly).
doing "double-buffer" of the map will involve a burst of up to 8K memory transfer (through DMA), potentially leading to gameplay lags
we have better things to do with the other maps (e.g. additional planes)
DMA copy for the map might be interesting to save CPU (well, it is actually frozen during the move), but it requires that the offscreen map is a verbatim copy of what's used on screen. manual copy of the map parts instead allow us to use more bits for tiles meta-data and wipe them as the tiles are moved on screen. That would give us 64 types of block, more than we need to encode "slipperiness", identify bonus, etc.
thinking again ...
Autre petite constatation: rien ne nous oblige à utiliser du 512x512 pixels! en fait, ce serait même l'inverse (en cas de scrolling horizontal, ce mode nous oblige à faire plus de mises à jour et ne nous offre pas grand-chose en contrepartie). Au passage, ça nous permettrait de faire un "scrolling à la John Carmack" plus efficacement (enfin, au moins une fois de temps en temps...).
Mais entretemps, je suis nettement plus sceptique sur l'interêt de la technique Carmack (rendue célèbre par la série des Commander Keen) sur le hardware de la DS.
On discutait scrolling avec Cyril hier midi, et il m'a fait remarqué assez justement que je m'inquiétais inutilement pour les scrolling à grande vitesse. Bin oui, tant qu'on ne scrolle que d'un pixel à la fois on peut s'en sortir très bien avec seulement 512 pixels de large dans la mémoire vidéo. Au cours du décalage, on finira bien alors à se retrouver avec un morceau de 256 pixels appartenant à un seul bloc de VRAM à l'écran et on a les mains libres pour changer le contenu du deuxième.
Mais si la vitesse devient trop élevée, on risque de se retrouver à passer d'un 'bloc' à l'autre mais en gardant toujours plus d'un bloc de VRAM à l'écran, ce qui nous empèche de mettre l'image à jour sans prendre le risque d'un affichage inconsistant. Mais c'est là que Cyril est intervenu: rien n'empèche d'arrêter le scrolling plus tôt que prévu juste pour ce 1/60eme de seconde... On a alors de nouveau les mains libres pour copier le bloc suivant en VRAM, et au coup suivant, on scrollera un peu plus. L'un dans l'autre, ça devrait être invisible (edit 26.11) J'ai testé sur le temps de midi: c'est indécelable.
Reste un autre problème: l'encodage des "types" de blocs. Si je faisais une copie élément par élément à la main (jouable si on remet un bloc de 8x8 pavés à jour d'un coup), je pouvais chiper les bits "miroir" pour encoder les types dans la carte "offscreen", et les remettre à zéro avant la copie dans la surface "onscreen".Mais pour remettre tout un bloc à jour (2K), il serait franchement préférable de passer par le DMA.Mais bonne nouvelle, on peut s'en sortir avec un encodage un peu plus subtil. Imaginez: on a besoin d'encoder si chaque tile est solide, traversable, incliné, etc. mais aussi s'il s'agit d'un bonus, de pics, de lave, etc. Trop pour 4 bits ... sauf si on encode que 12 types de tiles et que l'on se sert des numéros 12 à 15 pour des blocs plus "spéciaux", par exemple ceux dont seul le personnage du joueur doit s'occuper.
Alors, quel est le truc ? Eh bien voilà. Notre niveau n'est pas construit à partir de tiles 8x8 mais à partir de blocs 16x16. On peut donc supposer que la lave prendra bien 16x16, et ce n'est pas juste 2 bits (de 12 à 15) que l'on a pour faire la différence entre les picots, la lave ou les bonus, mais bien 4x2 bits (2 dans chaque tile faisant partir du bloc 16x16). Soit 256 types de blocs spéciaux possibles. De quoi voir venir...
edit : au final, seul le scrolling vertical utilisera des transferts DMA.
autre bonne nouvelle: les animations marchent! enfin, quand je dis "marchent", je devrais plutôt dire "rampent", puisque pour l'instant, seul le "petit ver jaune" de la forêt a été animé.Je suis pas mal fier de mon coup, je dois dire. J'ai gardé un code tout à fait générique, qui est paramétrisé par un petit script de commandes. Petite astuce, les déplacements du ver sont intégrés dans la séquence d'animation elle-même, ce qui permet d'avoir un contrôle plus fin du déplacement (pour vraiment donner l'impression que le ver rampe, et pas qu'il glisse sur le sol pendant qu'il se tortille, comme on le voit si souvent dans les jeux amateurs).
En revanche, si je commence à être assez rôdé sur les std::vector<GameObject*>::iterator, je suis encore un peu perplexe sur les destructeurs virtuels. L'environnement d'exécution du "petit script" en question doit être "renettoyé" chaque fois que l'on veut relancer le script, et pour l'instant, ça se traduit encore trop souvent par un écran rouge de la mort. J'aime bien la méditation, mais toutes les 5 minutes, ça devient lassant. (quicklink: l'assembleur ARM par TONC: http://www.coranac.com/tonc/text/asm.htm) D'un autre côté, j'ai un peu relooké mon script qui traduit une image (comme celle ci-dessus) en un tas de tiles et les données pour la map, de sorte qu'on puisse lui indiquer quelle couleur doit être considérée comme "transparente", et il s'arrange tout seul pour que les tiles entièrement de cette couleur soient considérés par la toute nouvelle routine de "testpoints" comme étant "EMPTY", tout le reste étant de type "BLOCK"...
Sans doute juste une curiosité pour vous, mais de plus en plus important pour moi : voici le contenu de la mémoire vidéo de mon SpriteEditor, vu sur un plan "16 couleurs" (1KB par ligne) à gauche et sur un plan "256 couleurs" (2KB par ligne) à droite. Du temps du DOS, j'avais refais et plastifié sur un carton de 16x16 centimètres la table ASCII pour mes programmes en BASIC et en assembleur. J'ai d'ailleurs eu droit à du "oh! great! Can i borrow you your mouse pad ? please ?" en pleine démoparty parce qu'un gars qui passait par là avait bigrement besoin de ces infos pour continuer son intro 4K.
Les vieux de la vieille reconnaîtront pas mal de ce jeu de caractères, d'ailleurs, même si j'ai remplacé les symboles "coeur, pique, trèfle, carreau" par une animation de pacman qui me sert de 'scrobber' et que vous reconnaitrez à partir de la 4eme ligne quelques éléments de l'interface de SEDS (accessibles à l'aide de YOUR_CHARSET(0), et programmés à grand coups de codes hexa ... ça rappelle des souvenirs de CPC, ça, hein ;)
Version 256 couleurs, évidemment, ça ressemble furieusement à ma palette, puisque -- souvenez-vous -- j'avais reprogrammé un caractère par couleur pour pouvoir redessiner facilement ma grille ...
Il y a des curieux qui se demandent ce que peuvent bien être ces 8K de n'importe quoi entre les caractères et les couleurs ?
Allez, je vous aide: il y a quatres plans sur un écran DS et chaque écran peut contenir 32x32 caractères encodés sur 16 bits ...(2008-08-29) [coding][tiled][cheat sheet][vram][sprite editor]
Ouaip. "un petit jeu tout simple où il suffirait de trouver toutes les pommes pour passer au niveau suivant" disais-je donc. Pendant les préparatifs de SeafoxDS, j'avais ajouté la possibilité de demander le chargement d'un fichier .pcx (une belle grosse image bitmap, quoi) comme image de fond. Sympa.
Histoire de tester ça, je bidouille un peu hier soir, mais pas moyen de sortir le décor de Donkey : King of Swing (qui n'est là qu'à titre temporaire, hein) sur mon écran: je me retrouve à la place avec un tas de tiles bleu suivant un motif improbable et quelques pixels aléatoires ça et là.
J'avais beau fixer les lignes "SUB_BG0_CR = BG_BMP8_256x256|BG_BMP_BASE(1)|BG_PRIORITY(3);", identiques dans le mode "télécharger un .pcx et l'afficher" de runMe et dans le moteur de jeu ... rien n'y fait.
J'ai fini par me rendre compte ce matin que changer les attributs d'un fond particulier ne suffit pas à faire de lui un fond bitmap sur la DS. Il faut d'abord s'assurer que le mode vidéo supporte bien ce type de plan, mais aussi qu'il peut s'appliquer au plan choisi. En clair, les seules possibilités sont renseignées dans gbatek:
BG Mode Engine A BG Mode (DISPCNT LSBs) (0-6, 7=Reserved)
Mode BG0 BG1 BG2 BG3 0 Text/3D Text Text Text 1 Text/3D Text Text Affine 2 Text/3D Text Affine Affine 3 Text/3D Text Text Extended 4 Text/3D Text Affine Extended 5 Text/3D Text Extended Extended 6 3D - Large -
Le mode "bitmap 256 couleurs" est un des modes étendus (de même que les plans avec rotations et 1024 tiles, d'ailleurs), et donc disponible uniquement sur les plans 2 et 3 dans le mode 5. Vous suivez ? Ce que nintendo appelle le "mode texte", c'est en réalité le mode "tile" qui effectivement s'apparente au bon vieux mode texte des années 80 à ceci près que vous avez 1024 caractères (et non pas 256), 16 palettes de 256 couleurs et que chaque "caractère" peut combiner comme il veut n'importe lesquelles de ces couleurs (il y a aussi un mode 16x16 couleurs, pour les nostalgiques de la GBA, bien sûr :)
Y'a un hic, évidemment: j'avais pour ainsi dire "hard-codé" mon moteur de jeu pour que la map utilise les plans 2 et 3 (histoire de bénéficier des effets de zoom, par exemple ... ça m'avait paru sympa). Mauvaise idée, comme dirait l'autre, hein :P
Oh, rassurez-vous, ce n'est pas un problème insurmontable. En soi, le "bitmap" tiendrait sans difficulté en mode "texte". C'est juste que ça me rappelle ma lecture de ce matin dans le bus, selon laquelle les druides théorisent que "l'univers, pour sa bonne marche, dépend de l'équilibre de quatre forces : le charme, la conviction, le doute, et l'envie d'emmerder le monde".
(2009-01-22) [coding][tiled][english][cheat sheet][hardware][vram]
[section pour ch1]
Le premier moteur
Avec la mise en ligne du code source de quelques vieux jeux MS-DOS, je vais enfin avoir ce qu'il me faut pour démarrer sérieusement mon premier moteur de jeu. S'il y avait déjà eu une petite démo diffusée à ce stade, elle n'était guère convaincante au niveau du comportement des personnage et fonctionnait fort sur le principe "tout ce qui n'est pas complètement transparent est solide" et son "niveau" devait être entièrement réalisé dans un programme de dessin puis converti.
(2009-02-04) [ch1]
Xargon : may the source be with you ...
Vous ne connaissez pas Xargon ? pensez à Jill of the Jungle avec un chromosome Y et vous ne serez pas loin... Vous ne connaissez pas Jill of the Jungle non plus ? Ah. Bin probablement que votre PC n'avait pas encore de carte SoundBlaster ou d'écran VGA en 1992. C'est vrai que ça coûtait un peu cher, à l'époque. Quoi qu'il en soit, si je vous en parle, ce n'est pas parce que les 3 épisodes de Xargon sont disponibles sur classicdosgames.com. Non, c'est parce que le code source de Allen Pilgrim est également disponible. C'est du code C, mode réel pour le DOS, mais suffisamment propre pour que tous les aspects "hardware" soient extrait de la logique du jeu -- et croyez moi, sur PC, c'est un exploit majeur. Ce n'est pas la bibliothèque d'abstraction du hardware qui m'intéresse, bien sûr. Même si c'était impressionnant à l'époque, le scrolling des jeux de plate-forme Epic ne pouvait concurrencer celui du moteur de John Carmack (commander Keen & autres), le player de fichiers CMF (adlib/midi) était bien connu, mais je n'ai jamais vu d'éditeur correspondant, et si l'on se laissait impressionner par un "yeeaaah" en ramassant un gros bonus, le mixage des effets sonores était en réalité peu convaincant (voire absent).
Non, ce qui m'intéresse c'est la logique du jeu. Et quelque-part, cette logique me crie le maître-mot de Mollusk: "ne vous posez pas de question: codez". C'est vrai quoi. Je me prends la tête avec mes test-points alors qu'en fait, ce n'est qu'une optimisation bancale que je traîne depuis l'age du BASIC, renforcée par l'étude d'un jeu NES. La gestion des collisions perso-monde dans Xargon est bien plus simplement basée sur les possibilités de l'ensemble des tiles recouverts et l'affectation de propriétés sur chacune de ces tiles.
int cando (int n, int newx, int newy, int ourflags) {
int x,y,temp;
int flagor, result;
int startx, endx, starty, splity, endy;
startx=newx/16;
starty=newy/16;
endx=(newx+objs[n].xl+15)/16;
endy=(newy+objs[n].yl+15)/16;
splity=(objs[n].y+kindyl[objs[n].objkind]+15)/16;
flagor=f_notstair;
result=0xffff;
for (y=starty; y<endy; y++) {
if (y>=splity) flagor=0;
for (x=startx; x<endx; x++) {
temp=(info[board(x,y)].flags|flagor)&ourflags;
result&=temp;
};
};return (result);
};
Comme disait Colomb en quittant le Portugal "Il suffisait d'y penser": le tout est de concevoir les propriétés des tiles de manières à ce qu'elles "s'additionnent" bien. Un personnage ne peut se déplacer vers un nouvel endroit que si tous les tiles possèdent les bonnes propriétés ("pas un bloc", ce que Allen appelle f_passthru. Celà demande parfois un peu de jonglage mental. f_water est évident, f_climbable aussi. f_not_stairs un peu moins...
int trymove (int n, int newx, int newy) {
int ourflags;
ourflags=f_playerthru;
if (newy>objs[n].y) ourflags|=f_notstair;
if (cando (n,newx,newy,ourflags)==ourflags) {
moveobj (n,newx,newy);
return (1); // successfully made it to new location.
}
else if (cando (n,objs[n].x,newy,ourflags)==ourflags) {
moveobj (n,objs[n].x,newy);
return (2); // could only move vertically
}
else if (cando (n,newx,objs[n].y,ourflags)==ourflags) {
moveobj (n,newx,objs[n].y);
return (4); // could only move horizontally
};
return (0); // couldn't move at all.
};
J'aime bien aussi la manière dont il a réussi à capturer les comportements de déplacement simples Il faudra que je tente d'en faire autant. Bion. Je vous en dirai sans doute plus. Malgré tout, la routine de gestion du personnage principal fait quand-même 7 pages, et je n'ai pas encore tout lu.(2009-02-05) [coding][open source][tiled][y92][sidescroller][collisions][physics][english][nostalgy]
World Collisions
Je ne suis plus trop emballé par les tests-points tels que je les avait décrit précédemment. En premier, ils ne capturent suffisamment toutes les collisions. A moins de les multiplier, on risque toujours d'avoir une partie du personnage qui rentre dans un coin de mur lors d'un mouvement, et en donnant à chaque état sa propre liste de testpoints, on ne fait qu'augmenter les possibilités de bugs.
Le deuxième souci avec les test-points, c'est qu'ils sont simplement binaires. Alumés ou éteints. Point barre. Pas moyen de préciser qu'un poisson ne peut se déplacer que dans l'eau ou qu'un oiseau ne peut aller que dans l'air, mais que la grenouille peut évoluer dans les deux milieux. Si on y réfléchit bien, ce genre de situation apparaît bien plus souvent qu'on ne pourrait le penser. Le simple ajout d'échelles dans nos niveau oblige de définir que "escalader une échelle" ne peut s'envisager que s'il y a une échelle à escalader. Le Recreational Game Maker sur lequel j'ai développé (entre-autres) la série des badman ne permettait pas ce genre de subtilité: une échelle était tout simplement un bloc sans gravité, de sorte que l'on pouvait y monter en sautant -- à une vitesse franchement irréaliste -- et on aurait vu le personnage ridiculement patauger dans l'air en faisant de petits bonds si on avait appuyé sur la touche "monter à l'échelle" alors qu'il n'y avait aucune échelle.
L'approche "nature du tile", telle qu'elle est utilisée dans Xargon est intéressante à plus d'un titre, et je soupçonne les concepteurs de Rayman (la version 2D) d'avoir suivi une approche similaire. En particulier, elle permet l'introduction de "grillage à monstres", des blocs invisibles et non-bloquant pour le personnage et les projectiles, mais qui sont assimilés à un mur par certaines plates-formes mobiles et par certains monstres. Comme je l'avais déjà précisé lors de l'analyse des maps de Commander Keen 5, je trouve que c'est une technique élégante pour construire les niveaux. Si je compare à "Inside the Machine" (l'autre jeu dont j'ai étudié les sources pendant mes longues soirées d'hiver en Suisse), l'alternative consiste à programmer les monstres de sorte qu'ils ne s'éloignent jamais de plus de n pixels de leur position initiale, en maintenant une variable supplémentaire "distance" qui les force à faire demi-tour. C'est exactement le genre de "pré-câblage de code" que je cherche à éviter. Décider jusqu'où un monstre va, c'est une question de level design, pas de programmation du comportement des monstres.
if cando(dx,dy) then (x,y) := (x+dx,y+dy)
elsif cando(0,dy) then (x,y) := (x,y+dy)
else {
(x,y) := (x,align_on_tile(y+dy));
trigger_testfail_event
}
Enfin, certains testpoints sont clairement plus important que d'autres, et le déplacement indiqué par le contrôleur doit être ajusté en fonction de la situation. J'ai le problème dans le saut de Bilou, actuellement: quelque soit le testpoint qui détecte une collision, le mouvement complet est annulé. Résultat, si Bilou rentre dans un mur lors d'un saut, il va rester "collé" au mur jusqu'à ce que je relâche la touche de direction. Celà n'arriverait pas si je pouvais donner une priorité, ou une description plus complète du rôle des différents testpoints (p.ex. puisque c'est un test-point "mur", il n'annulerait que le déplacement horizontal, etc.)Mais en fait, ce genre de logique est commune à tous les déplacements guidés par la gravité dans un jeu de plate-forme. Ce n'est pas quelque-chose de spécifique au comportement de Bilou, et celà pourrait sans difficultés être introduit dans le contrôleur du sprite. En tout cas, ce n'est clairement pas à introduire dans la classe GameObject elle-même, puisque si on veut faire un r-type, un boulder-dash ou un zelda, on en aura pas besoin. Mais alors, à quoi servent encore nos test-points ? eh bien, à décider de quel sera le prochain état lorsque le contrôleur signale qu'il n'est pas possible de continuer. Ca veut dire aussi que le contrôleur devra évidemment ajuster la position du sprite de manière à respecter les "cando()" mais pour que les testpoints puissent détecter quelque-chose quand-même :P
[done] extract persistent state (found in map and accessible by other classes)
[done] iGobPubstate::cando(x,y,flags)
[done] manipulate tile_flags array (that translate tile type into tile flags) from the script
[done] controllers report "fail" when no "cando()" can be applied
Voilà. Une fois qu'on dispose de tout un jeu de .BBL avec les tileset correspondant, c'est bien plus simple de reconstituer le rôle des 20 bytes de contrôle ... C'est ce que fait de son mieux ce petit outil "bblmeta.pl" que je vous offre également. Chaque ligne rappelle le numéro du bloc (en hexa) et sa position dans l'image générée par bbl2png (le premier, par exemple, r02 c12 correspond au premier canon, tourné vers la gauche sur la 3eme ligne de l'image). Suit ensuite les "flags" tels que lus dans le fichier .BBL puis l'interprétation de ces flags par le script bblmeta.plJ'ai repris la technique des répertoires UNIX: un '-' chaque fois qu'il n'y a rien à signaler, une lettre sinon. Ainsi "B--a-K" serait un bloc solide (B) animé qui interagit avec une clé (probablement une porte). "--g-+-" est un bonus qui rapporte uniquement des points, etc. Pour être exhaustif, je dirais que le programme reconnait:
Bloc, Floor, Wall ou ciel (-)
Pickme : l'objet peut être ramassé pour l'inventaire
g, l ou r : gravité normale, vers la gauche ou la droite. Les autres combinaisons ne sont pas reconnues.
a : bloc animé,
t: réagit au contact
bonus/malus : H=hitpoints, x=kills, + : score
L = 1UP, K=clé/porte.
En plus de ces "flags", certaines valeurs sont indiquées dans une liste entre parenthèse, comme la succession des blocs pour une animation, le nombre de hitpoints ou de clés, ou de points modifiés lors d'un contact , le numéro du monstre tiré (pour les blocs qui tirent des monstres), etc.
Bref, ce n'est certainement pas aussi souple que l'éditeur du game-maker (et d'ailleurs, ce n'est pas un éditeur, juste un outil d'inspection), mais ça capturera la plus grande partie des cas que l'on retrouve dans un jeu de plates-forme comme les Badman. (2009-03-26) [tiled][special block][tools][game maker][coding][pppteam][badman][keys and locks][y96][english][perl][scripts][level data]
Voilà typiquement le genre d'environnement qui était pénible à construire avec l'ancienne version de LEDS et qui devient simplissime avec le curseur de copie et autres nouveautés. Mais c'est aussi le genre d'environnement qui pose problème au moteur de jeu dans sa version ".999" parce que j'utilise les deux plans de tiles comme arrière-plan alors qu'à d'autres endroits dans le jeu (p.ex. quand Bilou est dans un arbre), il se situe en fait _entre_ les deux plans.
Le résultat, c'est que souvent, les pieds de Bilou étaient masqués, comme sur l'image ci-contre, soit parce que j'avais oublié de repasser l'herbe en arrière plan, soit parce qu'il y avait déjà un autre élément de décor par-derrière l'herbe. Quand j'avais lu les spécifications techniques de la console Genesis, j'avais été plutôt étonné de voir qu'il n'y avait que deux plans de tiles. Or, Sonic est parfois devant le décor et parfois derrière (sans compter l'image de fond, bien sûr, qui occupe le 2eme plan). Le truc, c'est que contrairement aux console de Nintendo, la Genesis permettait de définir pour chaque tile si les sprites avait priorité ou pas pour l'affichage.
Je vais donc tenter de reproduire cette approche en software: puisque je dois tester les "flags" de chaque tile lors du déplacement de Bilou, je peux assez aisément en ajouter un qui force Bilou (et les autres sprites) à "passer par-devant le décor" quand ils sont au moins partiellement en contact avec ce tile-là. Ca risque bien de compliquer un rien l'édition de niveaux, mais ça devrait valoir la peine...
identifie le tile présent à une position donnée et donne la hauteur du sol à ce pixel
C'est par manque de temps que ce petit algorithme de gestion des pentes se retrouve sur ce blog plutôt que dans le code. Vous l'aurez devinez sur base de la table ci-dessus, la fonction groundheight va nous être précieuse. Elle renverra 0 si (x,y) tombe dans un tile de type "ciel", une valeur de -1 à -7 selon que le sol se termine dans ce tile-là (-1 pour le pixel le plus bas, -7 pour le pixel le plus haut) et la valeur -8 si le sol se termine en réalité juste au-dessus du tile.
Quick Note : the 'x' and 'y' are copies of the object's hotspot position, and the result of the algorithm is the new_x, new_y position at which the GOB's hotspot should be moved. I still have to figure out where cando(new_x, new_y) should be applied to ensure walking slopes do not allow one to move through walls, etc. I also have to figure out how to integrate this properly with the OO model I have so far : what is under the responsibility of iWorld, iGobController and GameObject.-->
Une des idées simplificatrices de base (par rapport aux essais précédents) est qu'il n'est pas nécessaire de traiter immédiatement l'effet de la pente sur la vitesse horizontale (je ne sais pas vous, mais moi, je vais généralement moins vite en montée qu'en descente). A la place, je peux utiliser l'élévation du sprite à l'instant t-1pour ajuster sa vitesse horizontale à l'instant t... Le résultat, c'est que le calcul de la position actuelle peut être effectué en une seule passe, se basant exclusivement sur la fonction ground_height() qui retourne pour chaque position horizontale dans un tile pentu la hauteur de la pente depuis la base du tile.
En revanche, on est forcé de refaire un test à chaque fois qu'on passe une frontière de tile parce qu'on ne sait pas dire si la pente continue sur sa lancée ou non. Une fois qu'on a déterminé dans quel colonne de tiles (1er listing) le mouvement se termine, il ne reste plus qu'à calculer les nouvelles coordonnées à l'intérieur de ce tile. ancien post-scriptum: je ne vous ai pas oublié, mes lecteurs francophones (près de 50% d'entre-vous). Vous aurez peut-être compris que ce billet fait office de "pseudo-code retapé en vitesse et commenté". Le temps de loisir est distillé au compte-goutte ces temps-ci, entre les dents de *deline et les grands rangements de la maisonnette qui en a bien besoin.(2009-12-02) [coding][tiled][english][slopes][sketch]
Enfin, des pentes qui marchent !
C'est encore un peu mystérieux, mais ça commence à fonctionner! Petite démo téléchargeable (non-jouable, par contre) de mon "cas d'étude pour le débugging". Je vais tagguer le CVS pour qu'on puisse retrouver les sources correspondantes.
Je dis "mystérieux", parce qu'au départ, l'appleman semble "hésiter" à prendre les pentes, chose qui n'a pourtant pas été programmée comme telle. Je soupçonne un effet de bord des vitesses inférieures à 1pixel/frame, sans avoir pu creuser complètement.(2009-12-10) [download][tiled][milestone][english][mybrew][slopes][video][appleman]
SMB3, un des premiers jeux de plate-forme à introduire des pentes, va doucement sur ses 20 ans. On pourrait s'attendre, du coup, à ce que la gestion de terrains pentus soit un sujet largement traité dans les tutoriels, mais c'est loin d'être le cas. Quand j'ai attaqué la question, même le tuto de Tony PA "Tile Based Game Tutorials" ne m'avait pas satisfait. Il ne prenait en compte que des pentes à 45°, mais en plus, certaines combinaisons conduisaient à des bugs ou des "glitches" désagréables. Pour construire mon petit algorithme, je me suis donc appuyé sur un tableau "groundheights[]" pouvant être étendu avec n'importe quelle forme de terrain : marches, dunes, pentes de degrés divers, etc. Notre seule limitation sera le nombre de bits disponibles pour indiquer de quelle pente il s'agit. Et pour trouver l'algorithme correct, j'ai appliqué les fonctions élémentaires que je m'étais données (ground_height, next_tile, etc.) sur chacun des cas qui peuvent être rencontrés et que j'ai maintenant reproduit dans un recoin du niveau 1 de la greenzone. C'est presque fonctionnel, à l'exception du cas "e" (la pointe) où Bilou se "coince" et ne peut être débloqué que par un saut (l'appleman, lui, a déjà trouvé la parade et "saute par-dessus" l'obstacle !) Encore un peu de patience, donc, et je vous livrerai l'algo revu et corrigé. La principale difficulté est qu'il avait été pensé uniquement pour suivre une pente existante, mais que j'ai aussi besoin de savoir si on est toujours bien sur la pente ou si les tests de chute habituels peuvent à nouveau être appliqués.
(2009-12-15) [english][coding][tiled][slopes]
Bon, c'est pas le tout de dessiner des pixels de lianes/vignes sur lesquelles grimper. Maintenant, il va falloir coder ça, aussi. La base est relativement simple : un nouveau type de tile qui permet l'utilisation du comportement "grimper", au même titre que le sol permet de marcher. C'est plutôt le passage d'un autre état (à l'arrêt ou en saut) vers l'aggripage de liane qui va demander une attention particulière. Deux éléments entrent en ligne de compte :
le joueur appuie-t-il sur la flèche vers le haut ?
Y-a-t'il une liane à laquelle s'aggriper ?
La deuxième question revient à un test du type "cando(...)", à ceci près qu'il ne fait pas partie du comportement "normal", mais qu'il risque de devoir être évalué depuis les transitions de la machine d'état.
Autre petite difficulté technique en vue : lorsqu'on est au sol, il n'y a a priori qu'une position où il est effectivement possible de monter à une liane : quand on est pile sous celle-ci. Du point de vue du gameplay, ce serait l'horreur d'imposer ça et il faudra donc programmer une sorte de "magnétisme" qui positionne le joueur qui tente de monter à une liane juste sous celle-ci s'il n'en est pas trop éloigné. La solution s'ébauche, mais ce n'est pas encore assez précis à mon goût ...(2010-02-06) [tiled][sidescroller][greenzone][berrybat][coding][NutsnBolts][english][state machine][vines][sketch][iGobController][bilou][climbing]
Crumbling Floor ... revisited.
Alors quoi ? Si c'est si simple de faire un sol qui croule dans Manic Miner, pourquoi est-ce la prise de tête ici ? Me suis-je trop compliqué la vie avec mon moteur de jeu et mes scripts ?
L'état est entièrement encodé dans le graphisme
Willy ne tourne qu'à 25Hz, Bilou à 60. A chaque frame, le sol de Willy est dégradé d'un pixel de plus si Willy est toujours dessus. Si je veux faire un autre style d'animation ou si je souhaite quelque-chose comme des morceaux de pont qui balancent quand on marche dessus puis tombent dans le vide après un bref délai, la "technique Willy" n'est plus appliquable. Bien sûr, en 1983, Matthew Smith ne s'était pas encombré avec "mais, on pourrait vouloir faire disparaître le sol un peu différemment plus tard dans le jeu" ^^"
J'ai une approche "bottom-up"
Le programme de Carles est contruit en divisant le problème (coder Jet Set Willy) en sous-problèmes (déplacer Willy, gérer les collisions), etc. Dans mon projet, j'ai une machine (la DS) et un objectif un peu difficile à réaliser directement sur cette machine (Bilou). Du coup, j'essaie de me construire une machine virtuelle (moteur de jeu, langage de script, outils d'édition de maps, etc) dans laquelle le problème serait plus facile à résoudre. Cela se prète mieux à la généralisation à un "game maker" par la suite, mais ça veut dire aussi que tant que je n'ai pas trouvé le bon jeu d'abstractions, les solutions sont bancales.
J'évite de sur-utiliser les sprites Je pourrais ajouter quelques "actions externes" à mon langage d'expressions qui altèreraient le contenu de la map, et tout gérer avec des sprites. Je marche sur un pont ? hop, le fond disparaît et est remplacé par un sprite du même morceau de pont qui bascule. Si je ne suis pas resté trop longtemps, le sprite "réécrit" le bloc sur la map avant de disparaître. Ce genre d'approche risque d'introduire trop de sprites sans qu'on puisse contrôler combien il devrait y en avoir. Vous vous souvenez du "spinning block" de Mario World ? Imaginez un peu qu'il ait fallu animer un sprite pour chaque block qui se mette a tourner parce que vous l'avez touché ... bonjour l'horreur!
L'idée est donc de rajouter quelques abstractions de base, telles que "une animation liée à une position sur la carte" (pour les ponts, le spinning block, etc.) et "une portion de la map dont je peux changer le contenu" (pour les passerelles que l'on fait apparaître ou disparaître à coup d'interrupteurs comme dans Keen). Les cogitations sont en bonne voie. Le code suivra ... comme les autres éléments de mon énorme "todo list". Derrière la façade de "vous aimez le nouveau look du site", il y a donc une sorte de "bouillonnement" de choses difficile à décrire qui avance dans le bon sens dans les slots de temps libre qu'il me reste. J'espère parvenir à en dire plus long prochainement.
On a pas toujours la chance d'avoir les sources des moteurs de jeu qu'on a envie d'étudier, mais pas mal de gens depuis l'époque 16-bit on travaillé à des éditeurs de niveau, ce qui nous permet de faire quand même pas mal de déduction sur la façon dont ce jeux-là fonctionnaient. Et alors que le moteur qu'on a esquissé dans la section précéedente a permis de construire "Apple Assault", c'est l'occasion de jeter un coup d'oeil à toutes ces approches qui ont fait tourner de vrais projets.
(2010-11-15) [ch1]
vsnes à la rescousse...
Bion, nous y voilà: à gauche la version SNES, à droite la version GBA. Même lieu (le boss du chaudron), Dixie sélectionnée et Diddy en mode "suiveur" dans les deux cas. Ambiance sombre et dantesque sur SNES, grosse bouillie rougeâtre à droite. Qu'il s'agisse du sol, du décor de fond ou de la lave, on a l'impression que la version GBA n'a eu droit qu'à un seul dégradé ... Je pourrais refaire la même analyse avec plein d'autres screenshots, je pense.
Avant de commencer à dire ce qui aurait été faisable ou non sur GBA, voyons un peu de quoi étaient faits les jeux de RARE sur la SNES. J'ai trouvé un outil assez ultime pour ça: vSNES, un analyseur de cartouche et de savestate ... Comprenez : je démarre la ROM dans l'émulateur zSNES, je presse F2 pour sauver l'état, je quitte, et vSNES va interpréter l'état sauvé (contenu de la VRAM, notamment) et me permettre de visualiser les couleurs utilisées, le tileset (avec les différentes palettes disponibles), le rendu de l'écran dans les différents modes (DKC en mode 7 donne un résultat assez ... inattendu) Première observation: 64K de mémoire vidéo pour la SNES, tileset et sprites partagés, les deux travaillant avec 16 couleurs par pavé de 8x8 pixels (les "tiles", vous vous souvenez). Chaque sprite et chaque pavé sur la map peut choisir la palette de 16 couleurs qu'il utilise, ce qui explique que j'ai des bananes oranges et un donkey correct à gauche, et des bananes correct avec un donkey psychédélique à droite. Deuxième observation, les couleurs sont bien en R5G5B5 sur SNES, tout comme sur GBA. On aurait donc en principe pu reprendre les images de DKC (et de DKC2, vu que je n'ai pas de raison de penser que cette partie-là du moteur de jeu a été modifiée entre les deux épisodes) et les afficher telles quelles sur GBA. On a 3 plans dans le jeu SNES, la seule subtilité est que la priorité des tiles (devant ou derrière les sprites) peut être manipulé tile-par-tile sur SNES (comme sur la SEGA Genesis, donc) alors qu'il faudra obligatoirement passer par un layer supplémentaire sur DS ou GBA. J'espère trouver un outil similaire pour GBA (no$gba sous Wine, peut-être) pour faire la même analyse sur la version portable du jeu. Ma seule hypothèse jusqu'ici est que l'écran de la petite portable (sans rétro-éclairage) aurait conduit à un jeu inutilisable si les graphismes avaient été conservés tels quels. Si je remets la main sur la DS Phat de ma fée, je pourrai peut-être me faire une meilleure idée...(2010-11-16) [tiled][gba][donkey][snes][hardware][vram][translate me]
Life-changing books (1/3)
Sans ce bouquin sur l'étagère de mon père, il y a des chances que ma vie n'aurait pas vraiment été la même. Oh, j'avais bien 2 ou trois autres bouquins de programmation mieux adaptés à mes 8 ans, mais le "guide de l'utilisateur" était purement et simplement une mine d'or. Musique multi-piste sur le SID, fonctionnement de l'écran et des sprites: tout y étais (enfin, me semblait-il), même si j'ai découvert par la suite d'autres techniques telles que la reprogrammation des caractères ... C'est armé de cette brique à reliure à anneaux que je me suis attaqué notamment à Calimero et à Space Mission.
Une bonne partie de mes temps libres (en dehors de la guitare et des LEGOs) aura donc consisté à remplir des grilles de 24x21 carrés ou à construire des niveaux à l'aide d'une copie manuscripte du jeu de caractères PETSCII que je pouvais par la suite convertir en lignes de nombres traîtées par des POKE (écriture directe dans la mémoire de l'ordi depuis le programme BASIC).
Autant dire que mon premier pas dans le monde du PC m'a complètement perturbé. Exit les memory maps ... pas de registre de coordonnées pour les sprites, mais des commandes PUT et GET ... Pas de registre de timbre ou d'attaque, mais une commande PLAY capable de "jouer" à travers le PC speaker le genre de ligne mélodique de votre tout premier GSM... et écran monochrome. Pas franchement le grand amour. Mais voilà: le PC est présent en permanence alors que le C64 n'est monté que pendant les vacances.(2011-02-20) [basic][C64][calimero][who am I][y91][y92][ch1]
2 tiles de haut contre 1 de large. Pour un personnage bipède, c'est l'idéal, si je tire les conclusions qui s'imposent de la lecture du Sonic Physics Guide. Attention, je parle ici de la zone de collision. À aucun moment celà n'implique que l'on doive caser tous ses dessins dans un rectangle de 20x40 comme dans le RSD game-maker, hein ? En revanche, il est indispensable que le personnage puisse "tenir debout" sur un élément constitué d'un seul "tile" sans que ça ne paraisse bizarre au joueur. Idéalement, il devrait aussi pouvoir tomber dans un trou qui ne fait qu'un seul tile de large. Difficile de dire si c'est le cas ou non pour Bilou, par contre. Ok, il est loin d'avoir les proportions de l'homme de Vitruve, et sera plus proche de 16x24 pixels dans la school zone qu'autre chose. Difficile aussi de lui appliquer la règle "un obstacle doit plus ou moins arriver à la ceinture pour avoir l'air un rien costaud"... Mais je garde ça en tête pour la révision de Badman... (ces 2 croquis Biloupométriques datent de 2005, quand je m'étais mis en tête de faire une version 3D de la school zone en utilisant OGRE ... et que je me suis rendu compte que je ne parvenais plus à dessiner un Bilou correct qu'une fois sur 4 :P)(2012-01-20) [3D][tiled][sidescroller][y2k5][collisions][bilou]
J'ai envie d'avoir plus de souplesse dans les pentes que juste "45° dans quel sens?". Si ça n'apporte pas grand-chose au niveau du gameplay en tant que tel (un ennemi-marcheur en haut d'une pente garde un avantage stratégique même pour d'autres formes de pentes), ça permet de construire des niveaux plus "organiques", ce qui n'est déjà pas si mal.
En revanche, j'ai déjà saturé le nombre de "type de blocs" dont je dispose vu ma technique d'encodage. L'idée cette fois serait de combiner le "type" (encodé par élément 8x8) avec la position du tile au sein d'un bloc de 64x16. Si ça reste jouable au niveau du moteur de jeu, ça demande un support spécifique dans l'éditeur de sprite pour "préparer" ces blocs de 64x16 contenant 16 tiles alloués de manière contigüe dans la SpriteRam (alors qu'ils sont normalement alloués par bloc de 4) puis de les disposer conformément à ce qui est prévu pour un des type d'obstacles souhaités.
En comparaison, le moteur de SMW (selon Lunar Magic) offre 3 angles de pente: 'normal' (22.5), 'gradual' (11.) et 'steep' (45). Au niveau du gameplay, les pentes 'steep' étaient les seules à pousser d'office le joueur vers le bas (si ma mémoire est bonne). Bon, je sais, ce n'est sans doute pas ultra-prioritaire pour faire avancer Bilou, mais mon petit J.l.n est né vendredi dernier, ce qui réduit un peu ma liberté d'action. Un peu de bidouille dans les éditeurs devrait donc être plus aisé que d'aller créer du code pour de nouvelles interactions avec Inkjet. (2013-01-21) [wish][allocation][tiled][english][slopes][gameplay]
Un grand pas en avant ...
Pas évident d'avoir des pentes correctement intégrées dans le déroulement du jeu.
Souvenez-vous: dans Apple Assault, si les pentes fonctionnaient presque correctement, il me restait une certaine probabilité que Bilou se "bloque" arrivé au sol, et ce n'est qu'avec la révision du moteur de jeu en Septembre dernier que le bug fut corrigé. Mais on était pas tiré d'affaire pour autant... A force de refaire des tests pour m'assurer que Bilou ne puisse plus se bloquer dans les murs suite à un atterissage en catastrophe (en mode "pas à pas"), je finis par me rendre compte il y a quelques semaines que lorsque Bilou arrive au bord d'un livre (terminé par un tile pentu en bordure), il oscille quelques fois entre "marcher" et "attendre" avant de finalement se décider par sauter. C'est la plupart du temps presqu'imperceptible pour le joueur qui ne notera peut-être qu'un temps de retard, mais je me suis mis avec ce projet d'avoir un moteur de jeu irréprochable. Exit l'à-peu-près et les excuses bidon: si je ne peux pas être le moteur candidat pour Super Mario World 3 sur SNES, alors le côté "documentation de comment on aurait sans doute pu faire les choses avant de passer à la 3D" perd sa raison d'être.
Commençons par un rappel du fonctionnement des pentes dans mon environnement découpé en pavés (les "taïlze/tiles"). Bilou n'y est qu'un rectangle qui doit pouvoir naviguer sans rentrer dans les tiles solides (noirs). Il possède en plus un point de référence (hot spot, en rouge vif sur l'image) qui doit rester en contact avec le sol lorsqu'on suit une pente. Pour avancer d'un pixel vers la gauche, le hot'spot passe d'abord dans un tiles complètement vide (gh=0) où il n'y a pas de sol à suivre. La fonction do_slopes détecte ça et teste du coup le tile situé juste en dessous. Son pixel le plus à droite correspond à une hauteur gh=-8. On se retrouve ré-aligné sur le prochain pixel ... tout va bien.Le problème apparaît seulement lors d'une transition pente/trou, comme j'en ai ajouté sur le bord des livres. Ici, lorsque'on regarde sous le tile vide, il y a ... un tile vide. Pour do_slopes, celà signifie qu'il n'y a plus de pente à suivre. Idéalement, on devrait donc juste se retrouver "le long de la ligne bleue" inférieure, qui empèche Bilou de tomber jusqu'à ce qu'il l'ait complètement quitté. Mais voilà, à ce moment-là, on est pas encore sur le sol! Du coup, lorsqu'on teste s'il est possible de tomber d'un pixel, la réponse est "oui" et le contrôleur envoie un FAIL.
Seulement, voilà, l'échec du contrôleur n'implique pas forcément une chute. C'est à travers les transitions du comportement de Bilou que les choses vont maintenant se jouer. Tomber d'un pixel ne serait pas impeccable, mais celà nous conviendrait. Seulement, pour celà, il faudrait que le point-test placé au sol indique du vide ... et étant décalé sur la droite, il est toujours dans la pente! Du coup, c'est vers l'état "do_slopes. C'est ainsi qu'on parvient finalement à quitter le livre après ce qui semble être un instant d'hésitation.à l'arrêt" (idle) que Bilou passe. Mais puisqu'on a toujours le DPAD incliné vers la gauche, on quitte dès l'image suivante cet état pour tenter à nouveau une marche... qui échoue encore. Heureusement, à chaque échec, on avance d'un quart/demi de pixel qui correspond au "mouvement entammé". Une fois que l'Inspector Widget est devenu assez précis pour faire ce genre d'analyse, il devient assez évident que la solution est de forcer le personnage à s'aligner sur le sol quand on quitte une pente, et de ne considérer qu'on chute que s'il est possible de descendre alors que le personnage est déjà aligné sur ce qui devrait être du sol.
Pour faire bonne mesure (et pour éviter que Bilou ne se retrouve en suspens sur un bord de livre parce qu'il n'a pas sauté assez haut), j'ajuste enfin le saut à travers une plate-forme à sens unique (les branches de Apple Assault): on ne peut plus atterir sur ce type de plate-forme que lorsqu'on vient d'au-dessus de la plate-forme.(2013-05-24) [InspectorWidget][coding][tiled][sidescroller][collisions][english][slopes][sketch]
NSMB Editor !
Cette fois j'ai battu Bowser sans utiliser de Mega Mushroom (interdiction édictée par *deline). Et je réalise tout à coup que "new" Super Mario Bros a ... 7 ans. Bien assez pour que les éditeurs de niveaux aient fleuri, et j'ai même réussi a faire un peu tourner NSMBe un tout petit peu sous Linux. J'avoue que je n'ai pas l'intention de créer mes propres niveaux à NSMB. C'est un exercice qui ne m'a jamais vraiment attiré. Par contre, je suis curieux de voir ce que je peux apprendre sur la structure du jeu.
Comme on pouvait s'y attendre, un grand nombre de plate-formes sont en réalité des "sprites" (soit avec des OAM, soit avec la couche 3D. Difficile à dire), même celles qui ne sont pas forcément mobiles, mais dont le comportement est suffisamment différent du sol. Les portes, les début de niveaux, les entrées/sorties par des tuyaux, sont eux aussi gérés avec des sprites. Les sprites sont organisés en "bancs" que l'on peut utiliser ou non dans le niveau, certains bancs en excluant d'autres (pas de bowser et de nessie ensemble: ils sont tous les deux pour le banc n°5, par exemple). Par contre, on pourra les compléter avec des Hammer Bros ou des chomp chomps, comme on préfère.
Les pièces d'or et les bloc-question font partie de la couche "sol". Idem pour les blocs-à-power-up. Ils sont sans aucun doute fait à partir de bons vieux "tiles". Chose assez inhabituelle, en revanche: on les édite et tant "qu'objets", pas juste en plaçant des pavés sur une grande grille. Chaque objet peut être sélectionné, déplacé, redimensionné individuellement, ce qui s'avère très efficace pour la conception rapide de niveau comparé à ce que LEDS propose. Du coup, on retrouve des éléments assez curieux comme des "pointillés de pièces d'or" qui permettent d'avoir un "damier de pièces d'or en diagonale" avec un seul objet. Tout ça n'a pas l'air d'être juste un choix de fonctionnement de l'éditeur: les niveaux eux-même contiennent ce genre d'objets: si on clique sur un "bloc" du dessus d'une plate-forme, on sélectionne directement l'entièreté du sommet de la plate-forme. Je présume que c'est une technique pour réduire l'espace sur la cartouche: le contenu en mémoire du niveau sera reconstitué en appliquant des commandes du type "remplir (10,10)-(20,20) avec du sol" puis "remplir (10,9)-(20,9) avec de l'herbe", etc. J'y crois d'autant plus fort quand je vois des motifs "effaceurs" qui n'auraient aucun sens dans un système utilisant des textures et des polygones.
each special block has 4 [0-3] digits laid out on the map that identifies the block type
block % statements allow to link collision areas
first digit identifies who can interact with the block
second digit gives the properties that the block should have. So far, x0yz behaves like an empty tile, x1yz like a water tile, x2yz like a wall tile and x3yz like a floor tile. Perferably, dedicated properties would be used, requiring an update of infiniMap::getTileType.
Toujours aussi tendu dans --IRL, au point que j'ai fait les modifications il y a bien une semaine mais que je n'ai toujours pas eu le temps d'en parler. Assez curieusement, j'ai des triangles flashy blancs qui apparaissent dans tous les sens quand je m'approche de l'emplacement d'une éponge sur DS, chose que je ne parviens pas à reproduire dans l'émulateur... et qui ne se produisait pas avec la version intégrée "SchoolTest.nds" ToT
Je pense bien avoir mis le doigt sur ce qui provoque ce curieux bug de bonus-à-moitié-disparus, par contre... et à reprendre le fichier construit proprement, la 3D passe aussi dans runme. Cafouillages de téléchargements, j'imagine. Il faut dire que le "nouvel hébergeur" (le site web de sourceforge) est d'une lenteur affligeante à l'heure des CDNs. Il va falloir que je trouve un autre plan B ou que je parvienne à prolonger mon occupation du serveur qui marchait si bien jusque là.(2014-02-13) [3D][special block][tiled][runme]
Collision avec le monde
Au milieu de ma série de scans sur le thème "Critical Link", je retombe sur un diagramme UML représentant la séquence d'action qui découle d'une collision entre un personnage et un bloc spécial. Comme il va prochainement me falloir gérer des collisions entre crayons fixes et taille-crayon, je blog en stock ...
.
(2014-04-05) [special block][tiled][collisions][uml]
pinky LEDS
Voilà donc le premier pas vers un nouveau mode d'encodage des blocs spéciaux: chaque bloc peut se voir assigner un "symbole" (façon CPC basic) qui apparaîtra dans une jolie (?) couleur rosée lorsque l'éditeur fait le rendu du niveau. Il faut toujours faire l'encodage "à la main" (c'est même un peu plus délicat que précédemment, puisqu'on ne peut plus loucher sur les codes existants :P) mais dès qu'on déplace la vue, l'éditeur nous confirme que les règles du moteur de jeu ont bien été respectée (numéro de bloc spécial connu, un bloc tout entier a été utilisé, etc.) ... et le petit symbole pointu ne laissera plus de doute sur le fait qu'on a encodé un picot et non pas une fin de niveau ou un bonus. Ce n'est qu'un premier pas, mais comme ça, je peux enfin trouver et corriger le problème d'interaction avec les taille-crayons dans le "niveau de *deline". J'espère pouvoir du coup aligner 2 ou 3 autres niveaux-courses d'ici la deadline de la neocompo (s'il y en a une cette année). (2014-07-14) [level editor][coding][tiled][english][special block][wysiwyg][done][user interface]
Les tiles ... du C64 à la NDS
Chose plutôt inhabituelle, après pas loin d'un an de bidouille, mon premier article sur le site de la BilouCorp est finalement en ligne. J'avais envie d'y faire une rétrospective sur ce qui a fait de certaines machines des plate-formes assez merveilleuses pour la programmation de jeux vidéos et de jeter un coup d'oeil à la façon dont leur caractéristiques techniques ont formatté les jeux qui ont tourné dessus. Le premier jet, c'est donc sur les les tiles, ces petits pavés graphiques qui m'ont accueilli sur le C64 dans "Space Mission" et que je reprogramme dans SEDS pour vous faire les jeux de Bilou. Bonne lecture. (2014-11-22) [english][tiled]
Keen: ClipToWalls()
Pour éviter que Keen ne passe à travers les murs ou ne reste bloqué en bas d'une pente, les niveaux attribuent à chaque bloc de 16x16 pixels un ensemble de propriétés. C'est la technique des "tiles" bien connue de la plupart des lecteurs. Ce qui est particulier dans le moteur d'ID software, c'est la possibilité de définir séparément l'animation, la pente de sol, le comportement en tant que bloc spécial (valeur du bonus, couleur de la clé, etc.) et la polarité du mur. Plus fastidieux à définir que le simple remplissage en tant que bloc solide de mon éditeur de niveau, mais heureusement, dans Keen, il y a presque systématiquement correspondance entre graphisme et propriétés, et l'éditeur de niveau peut automatiser tout ce qui n'est pas passages secrets.
La notion de "polarité des murs" est intéressante. Lorsque le personnage se retrouve partiellement dans un mur, c'est cette polarité qui décide si Keen est repoussé vers la gauche ou vers la droite. On aura donc jamais de situation comparable au "zipping" des jeux NES où Megaman se met à traverser le niveau à vitesse super-sonique lorsqu'il rentre dans un mur simplement parce que les programmeurs se sont contenté de "si on est dans un bloc solide, on repousse Megaman sur le tile suivant". Evidemment, ça suppose que les murs font toujours au moins deux tiles de large.
A Zipping technique in Megaman. Propelled at 1 tile/frame or so.
C'est intéressant de voir que ID software a choisi ici une technique radicalement opposée à celle d'Epic Megagames. Pour Jill of the Jungle et Xargon, Epic faisait en sorte que le personnage n'entre jamais dans des tiles qu'il ne peut pas traverser (grâce à la fonction "cando"). Pour Keen, think() peut potentiellement faire rentrer un personnage dans un mur, mais le déplacement via ClipToWalls() le repoussera automatiquement dans la bonne direction, sans risquer de se faire piéger par un demi-tour de dernière minute comme dans Mario Bros.(2015-01-10) [english][keen][coding][tiled][bordures]
Il m'aura fallu me promener dans le code désassemblé de Super Mario Bros 1 pour finir par y croire. Oui, sur cette cartouche de 40KB, les niveaux sont extrêmement compacts. Oui, la technique pour en faire le rendu est à la base de ce que j'avais observé avec l'éditeur pour NSMB: bien que le hardware travaille avec des "tiles", le niveau est décrit par une série de commandes (un byte de coordonnées, un byte indiquant le type de commande et éventuellement une taille). On aura ainsi des "un tuyau de 3 blocs de haut" puis "une rangée de 5 briques" et "un escalier de hauteur 3". Voire même "un trou dans le sol de largeur 4". Pas de "patterns extensibles" derrière ces codes, mais directement du code assembleur!Les buissons, nuages, le sol sont eux décrits sur une autre couche qui est utilisée en premier lieu pour remplir la zone mémoire sur laquelle le niveau sera ensuite dessiné.
L'équipe RD4 aura profité au maximum du fait que le jeu ne fait pas marche arrière: le niveau est encodé écran par écran, et les coordonnées des objets sont données à l'intérieur de l'écran en cours. Un marqueur est ajouté aux objets qui se trouvent sur un nouvel écran par rapport à leur prédécesseur. (I AM ERROR) Il faudra attendre que SMB3 embarque une RAM additionnelle dans sa cartouche pour que le jeu puisse se souvenir des blocs déjà cassés et des pièces collectées, rendant ainsi possible une navigation plus libre dans le niveau (/I AM ERROR). Un monde de différence, donc, avec le stockage brutal d'un tableau de MxN éléments que j'utilise dans LEDS (avec des niveaux de 32 ou 64KB. Plus gros que l'entièreté du jeu SMB1, donc), mais qui me permet en contre-partie une souplesse totale pour que les livres ne soient pas tous exactement les même. Allez, apparemment, une simple compression .zip pourrait réduire les niveaux à une taille d'environ 8KB.(2016-01-10) [tiled][mario][asm][NES][level data]
Nathan Am Error
Quelque-part entre les chroniques de la saga Zelda et "la révolution playstation", je vais bientôt ranger un outsider: "I AM ERROR" par Nathan Altice. Outsider parce que ce livre-ci a été pensé comme un ouvrage de référence universitaire par un expert d'une faculté "arts & culture" qui va étudier en détail en quoi les limites de la NES ont influencé les gens qui ont créé dessus, leurs oeuvres et les gens qui ont découverts leurs oeuvres, un peu comme on pourrait écrire un livre sur la technique des pigments dans les peintures de la renaissance et l'impact que le changement de matériel pour les pinceaux a eu sur l'oeuvre de Léonard de Vinci. Ici, bien sûr, il sera plutôt question du nombre de couleurs et de tiles, des possibilités de scrolling puis des puces et astuces que les développeurs de jeu et de chez Nintendo ont utilisé pour faire des histoires toujours plus grandes, dans des environnements toujours plus vastes .
En découvrant les maps des jeux Capcom sur NES, j'avais déjà supposé que la console imposait le choix du sens de scrolling: horizontal ou vertical. J'étais loin d'imaginer que pour faire un jeu tel que Megaman qui alterne phase horizontale sur phase verticale, il aura fallu bricoler les cartouches et leur rajouter une puce de contrôle, parce que Super Mario Bros, par exemple, a soudé le choix d'un scrolling horizontal. Toutes ces puces -- les "mappers" -- sont bien plus fréquentes que le SuperFX de la superNES, mais pourtant il n'en a presque jamais fait mention. C'est un détail pour le joueur, presqu'un secret de fabrication. Un secret peu avouable pour les développeurs qui avaient au départ misé sur le "famicom disk system" qui remplaçait la ROM graphique de la cartouche par une RAM remplie à partir du contenu d'une diskette. Pourtant, c'est bien en ajoutant une puce de RAM que Super Mario 3 est capable de retenir quels blocs ont été cassés (la NES n'ayant que 2KB!, à peine de quoi retenir le contenu de 8 écrans).
Ce n'est qu'à travers ce genre de ruse -- pagination pour passer de l'overworld de Zelda à un des donjons, copie à la volée de sprites présents dans la ROM-programme vers la nouvelle RAM auquel le processeur graphique (PPU) a accès -- que les développeurs NES vont pouvoir faire mieux et plus riche que Super Mario Bros, qui saturait déjà les 16KB de mémoire que le PPU sait utiliser. Ici aussi, j'avais soupçonné ce genre de technique en voyant tourner SMB3 dans Nesticle, mais j'étais loin de me douter de la diversité des approches, des subtilités ajoutant des nouveaux systèmes d'interruptions sur une ligne précise de l'écran (devenue partie intégrante des designs de Nintendo depuis la SNES) ou l'intégration d'une puce sonore additionnelle que la NES utilisera comme elle aurait utilisé "l'entrée micro" du 2eme contrôleur de la Famicom. En revanche, "comment on fait tenir un niveau sur 100 bytes" restera une curiosité dans ce livre. Dommage pour moi. Heureusement - j'imagine - pour la majorité des étudiants qui commandent le livre sur MIT Press... (2016-04-21) [reading][y98][tiled][NES][hardware]
Le premier a bien servi. Après Apple Assault, il a pu être étendu pour réaliser School Rush, plus coloré. Mais il est temps de retourner à la table à design: il reste certaines fonctions plus avancées qui n'ont toujours pas pu voir le jour. Encore plus de couleur, plus de pentes, une gestion plus réfléchie des blocs spéciaux... C'est le moment de mettre en place ce que l'étude des autres jeux nous a appris.
(2017-04-28) [ch1]
Adding the MapAnim
Allons-y donc pour l'ajout d'un nouveau type d'animations dans mon moteur de jeu: la modification du contenu de la map elle-même dans le temps. Jusqu'ici, c'était limité à "faire disparaître le bloc en (x,y)" ou "retire-lui ses propriétés, mais laisse-le visible" (pour les bonus cachés). ça peut marcher pour certaines choses, mais ça suppose que les éventuels effets liés à la disparition soient entièrement pris en charge par un sprite supplémentaire. Idéal si l'image doit quitter son emplacement (un morceau de pont qui tombe, un décompte des points qui monte, etc), celà dit.
Il y avait aussi les "blockanims", qui permettraient par exemple de faire tourner les pièces de Super Mario World sur elles-même. On procède alors par une mise à jour de la mémoire "tiles" et toutes les instances de l'objet sont mises à jour simultanément sur l'image à l'écran. Le nouveau "MapAnim", lui, correspond plutôt aux besoins pour faire bouger un buisson devant lequel on passe, mettre des petites étoiles là où il y avait un bonus, etc. Comme vous pouvez le constater sur les diagrames, le mécanisme qui permet de mettre à jour le niveau (les données gérées par la classe InfiniMap) n'est pas particulièrement simple. Il met en jeu une description de chaque type de bloc spécial (les BlockInfos) et un objet temporaire capturant "tel bloc à tel endroit" (le BlockArea) qui permet d'interagir avec le personnage (GameObject) qui vient de passer par là. C'est finalement InfiniMap lui-même qui procède à l'effacement sur base du résultat du test de collision. La seule bonne nouvelle, c'est que c'est assez souple pour permettre des (petites) portes, des bonus, des bumpers et même les guides invisibles qui font faire demi-tour aux encriers.
Du point de vue du moteur de jeu, un niveau est avant tout une grille dont chaque case reçoit des propriétés: solide, solide uniquement quand on tombe, pente, spécial, etc.
Les "blocs spéciaux" ne sont pas compris par le moteur de jeu lui-même (d'où leur caractère "spécial") mais décrits dans les scripts qui définissent les règles du jeu.
Ainsi, quand Bilou touche par exemple un Bonus, le moteur de jeu récupère quelques bits d'information pour chaque pavé (8x8) de la grille constituant le bloc (16x16) et reconstruit un numéro de bloc spécial (de 0 à 255) qui permettra de retrouver les instructions à exécuter (faire un son, déclencher une animation, donner des points, etc.)
Quand Bilou touche une pente, en revanche, le moteur de jeu sait qu'il doit aller regarder une petite table indiquant la hauteur de chaque pixel de la pente. A priori, la forme de cette pente pourrait être n'importe quoi. Mon éditeur de niveau connaît les pentes à 45° et à 22.5°
Mais voilà: j'aimerais pouvoir avoir plus de souplesse: des escaliers, des coudes, par exemple. Un petit schéma de rien du tout qui traîne depuis des années (2010?) sur un bloc de brouillon en atteste.
"Le mieux serait d'avoir deux boutons 'pente' [dans l'éditeur de sprites] qui forcent le remplissage d'une ligne de 64x16 pixels avec des pentes suivies (les modèles)"
ajoute encore le calepin.
En effet, la technique que j'envisageais alors consistait à utiliser comme information supplémentaire la position du graphisme au sein d'une ligne de blocs dans l'éditeur de niveau (une "tranche" de 16 pavés de 8x8 successifs). On pourra alors convertir l'information de la grille "pente compliquée vers la droite" en "transition douce de 0° à 22°" parce qu'on est dans le 6eme pavé sur une tranche de 16. Sur les dernières années, ça n'a jamais été mis en service et mes derniers tours d'horizon dans le code de l'éditeur de Sprites me confirme que travailler "par tranche de 16 pavés" serait un vrai casse-tête (le mode et le moment de l'allocation des pavés dans la mémoire vidéo n'étant absolument pas prévu pour ça). Et cet été vient une idée qui jette un autre éclairage sur ce problème: "Les pentes douces, c'est un problème pour l'éditeur de niveaux. Ce n'est pas quelque-chose à régler dans l'éditeur graphique. Et l'éditeur de niveau sait par contre traiter des groupes de chiffres pour montrer qu'un bloc spécial est soit un bonus, soit des pics, etc. En prenant donc un type de pavé qui dit "pente compliquée, regardez les chiffres en-dessous pour en savoir plus", je peux diriger le moteur de jeu vers une autre table de hauteurs qui définit la pente (parmi 16 pentes possibles), mais aussi éventuellement fixer le coefficient de friction (sol dur, glissant, boue-qui-ralentit, etc.)
On the game design axis, I am quite satisfied with the final level of school Rush and how it integrates with the earlier levels. It took A. (from the S-Team) Some 30 attempts to beat it, but she hasn't turned mad or bored, and even enjoyed being wrapped to level 1 afterwards.
On the technical side, my latest personal tests hit me with a bug I thought I fixed long time ago: getting stuck in a ceiling, There are few places where this can happen in the game, but if it happens, you will remain stuck, possibly forever unless you use the power button to trash all your progress and reboot. Would a player feel like starting the game again in such conditions? I don't think so.
The thing is, the basis pretty hard to track, a bit tedious to reproduce and all my attempts to fix it have failed ... so far.
What I truly need is a tool that will allow me to identify the sequence of state transitions that led to the offending state when I finally manage to reproduce it. Another debugging tool that would complement Inspector widget for cases that cannot be solved by single-stepping on setting breakpoints.
Then let's make it easier to reproduce. Let's have e.g. a build of the game with much more lives, more hitpoints if needed, that drop you in a place where it is easy to try reproducing the bug. Let's go to that level with the DS, too, and try to get into the wall in as many ways as possible (runMe is neat for that because it let me launch any level), to get a better understanding of the conditions that make it happen.
Moreover, we're running on an emulator, here. And emulator typically have a free save-state mechanism. I'm unsure whether it works flawlessly with desmume, but it's worth giving it a try.
Bug is hard to track
Then let's make our best to get as much information as we can from a succesful run.
Granted, the ideal would be to have a curve plotted, annotated with state changes, in readable colors and where I could get any set of gob variable at any time by just pointing at a spot on the curve. But we don't have that. Granted, last time I had to use both ddd breakpoints and Inspector widget, but that doesn't mean I cannot do better this time.
So the ideal would be a time machine that let me replay things if I ever manage to produce the bug, and view what happened in slow motion, eh ? Wait ... that tool exists already. That tool is ... recording game + Inspector widget as an animation and replaying it!
Bug couldn't be fixed so far.
I thought I knew what happened by looking at the code that makes Verso bounce, because I know I had to fix the problem for Verso (the bouncing eraser). And the solution was to detect the impact's vertical speed to tell ceilings apart from floors, and prevent the 'fail [assuming on the ground]' from happening when what really happened was 'fail [on the ceiling]'.
So after I located something similar with the "HIT" state of Bilou, I thought I got rid of the problem. But since I don't have well-isolated test cases for the state machine (yet), seeing the bug again doesn't mean that the fix wasn't effective: simply that it might not be an isolated case.
InspectorWidget est en vacances,iprintf en arrêt maladie,ddd fait la grève du code optimisé, Qui reste-t'il pour sauver la journée ?Qui, sinon 0xbadb07 !
Ouaip, y'a une adresse-mémoire qui provoque un message sur le terminal quand l'émulateur la rencontre, avec la valeur posant problème, et même l'emplacement dans le programme. Alors j'ai rajouté comme un malpropre des accès direct à cette adresse pour envoyer un diagnostic d'erreur à travers le subespace de l'émulateur.Du coup, je peux corriger mon code. Ciao. (2017-11-27) [guru meditation][tiled][done]
cando grow?
Corriger le problème dans la machine d'état de Bilou pour lui éviter de se manger le plafond se révèle plus compliqué que prévu. Et pour cause: le bug n'est pas dans la machine d'états. Du point de vue du moteur de jeu, Bilou a deux tailles (de bounding box) différentes, et l'état "touché" est un des états où Bilou est grand. Du coup, puisqu'on peut se faire toucher dans à peu près tous les états, il n'est pas vraiment possible d'éviter un changement de taille par une transition conditionnelle. Il faut donc que je révise plus en profondeur le système de changement de taille (setbbox). Heureusement 1) je peux traiter les axes horizontal et vertical séparément; 2) je peux me servir du système "cando()" pour sonder le terrain avant de procéder au déplacement. 3) si je sais corriger jusqu'à la prochaine frontière de tile, je peux itérer pour étendre à des redimensionnement plus conséquents (à vérifier plus tard).
Tout ça m'aura demandé pas mal de gribouille pour m'assurer que je comprends bien tous les cas de figure et éviter de devoir mettre un grand nombre de tests excessifs pour traiter plusieurs micro-cas (parce que certaines valeurs sont nulles dans certains cas). Peut-être même que je n'ai plus besoin, désormais, du système qui transmet des consignes d'alignement (avec le centre, le bord supérieur ou inférieur) ... mais ça, ça demande à être prouvé, et on verra plus tard.
J'hallucine. J'ai pris une de mes plus vieille classes -- InfiniMap, responsable à la fois du scrolling et des collisions sprites/map. J'ai renommé ça en "CommonMap", appliqué les changements partout sauf aux sites de construction. J'ai ensuite déplacé le code utile dans un nouvel InfiniMap, déclaré l'une ou l'autre méthode comme purement virtuelle dans CommonMap ... et recompilé.
Et ça marche ! C'est du délire à l'état pur. Ok, j'avais une classe iWorld mais je ne m'attendais pas à ce que le truc ne réclamme aucune autre intervention. Nada.Allez, demain, je déplace le maximum de variables membre. (2017-12-19) [coding][cxx][scrolling][tiled][refactoring][uml]
whipseey
A lovely setting, charming character, platforming action in pixel art, and above all, a "whip/ninja rope" mechanics I'm in love with since Fury of the Furries and Mickey Magical Quest. You bet I'm following @whipseey in his quest recovering the Lost Atlas ^_^. And the fact it is drawing inspiration from the legendary Mr. Gimmick is just the perfect spice to complete the gift.
You have a lot of "one block wide" or "character width wide" platforms. I find this irritating. They put a lot of stress on the mechanics and they also slow down the gameplay a lot. Open the platform spaces up some more.
For the untrained player, these will put strong stress on the player's knowledge of the game engine's intimacy, like how much exactly gravity and momentum there is, so that one can land her favourite pink avatar on such a tiny spot.
One think I like to do on Twitter is to dream how I'd approach those short video sequences myself. Preferably in the original character's abilities set, a bit like how Myamoto and his team decided to make Popeye be able to jump over barrels in the early prototypes of Donkey Kong as it was the most natural thing to do if it wasn't a videogame.
And what would be the most natural thing to do if you barely missed a jump to a pillar while carrying a magical yoyo/whip/rope thing ? Well I bet I'd try to wrap the rope around the pillar to be safe again.
That in turn raised the question of "how would I do such a thing for Bilou in my own engine?" Should the 'whip' be an extension of the player's gob (that is, the internal software element ruling the sprite's behaviour) or be a gob of its own ? Should the pillar be made of special tiles or would the whip-end object (if any) be able to detect a pattern of solid-but-small-enough area ? Both approaches would be valid in each question, but as you may have guessed from the way I ask (or read in the sketches), I'd rather opt for the second answers if I was immediately ready to introduce that ;-)
C'est du tout bon. Une vraie pépite. Une fois encore, "retro-game mechanics explained" confirme que même quand on a pas l'intention de pratiquer le speed-run intensif, les découvertes des speedrunners sont une mine d'or pour qui s'intéresse aux techniques de programmation utilisées par les anciens de l'ère 8/16-bit. Et cette fois, c'est Super Mario world qui s'y colle.
When a tile is activated, it is deleted, and a sprite version of the block will displayed in its place. When that sprite returns to its initial position, it is removed, and another tile is set in its place (usually a brown block).
Si vous avez déjà un peu cogité la manière dont les consoles nintendo construisaient leurs images à base de mosaïques de "tiles" et des "sprites" par-dessus, vous aurez aussi deviné que pour faire sursauter un bloc-question quand on le touche, il faut effacer le bloc de décor (à base de tiles, donc) et le remplacer par un graphisme librement positionnable (un sprite) le temps de son sursaut, puis remettre à nouveau des tiles pour le bloc transformé. C'est sympa d'en avoir une confirmation depuis l'analyse du code (et oui, j'ajouterai ça dans ma todoux-liste un de ces quatres).
Which tile is activated during sprite/tile collision is determined by a point that is a mix (blue) between the the sprite's position (green) and its clipping box (red). If that point is not within the tile that was activated [...] there will be block duplication.
Un peu plus inattendu: en plus de leur zone active -- la hitbox, en rouge sur la carapace -- qui doit rester en-dehors des zones solides du jeu, les objets ont un point unique qui sert à déterminer quel bloc a été touché. Si on cogne un bloc-question avec une carapace lancée vers le haut, c'est d'abord le carré rouge qui va renseigner qu'il y a collision puis le point bleu servira à trouver avec quoi il y a collision. Chose intéressante, tous les objets subissent le traitement "repoussé par les murs" qui autorise Mario à contourner un bloc lors d'un saut plutôt que de s'y cogner méchamment comme une Giana sister. En revanche, le fait d'avoir mis le point-test en dehors de la zone de collision m'intrigue. Quelle est la raison ? ou est-ce juste un bug ? et cette possibilité d'avoir changé de position entre les deux opérations du test de collision (boîte et point) trahit-elle une optimisation du genre "on ne teste les blocs-question qu'une frame sur 4" ?
In order to reduce the number of distinct objects, some power-up blocks have different contents depending on their X coordinate on screen.
Mais il reste le plus croustillant. Le truc que explique qu'un bloc supposé contenir une clé peut tout d'un coup donner des ailes à yoshi. Visiblement, je ne suis pas le seul à avoir choisi trop peu d'information par bloc dans mon format de niveau, et pour pouvoir représenter tous les power-ups et bonus possibles dans Super Mario World, l'équipe de Myamoto a choisi d'utiliser la position du bloc au sein du niveau pour choisir quel objet serait offert au joueur. Pas la position absolue, hein, mais le fait qu'il soit sur un bloc pair, multiple de 4, impair, etc. C'est à la fois génial et complètement déroutant. Dans un commander keen, je ne me serais jamais attendu à un truc du genre "si le bonus est au 2eme étage du building, alors c'est une glace à 2000 points. S'il est au 1er c'est un donuts à 1000 points et au 3eme un nounours à 5000". Mais ici, dans un contexte où les blocs-question sont souvents présentés alignés comme les gobelets d'un jeu de hasard, le truc prend tout son sens. Il reste à considérer le "numéro" stocké dans le niveau non plus comme un identifiant d'objet à créer mais plutôt comme l'identifiant du générateur d'objets correspondant. Au moins, ils ont évité les contraintes du genre "un niveau peut offrir soit les ailes, soit une clé, soit un ballon, mais jamais une combinaison de ceux-ci." (2018-09-07) [tiled][collisions][mario][special block][SMW][level data]
Bon, j'ai une grosse révision de mon moteur de jeu en cours. Jusqu'ici, les propriétés du niveau étaient conservées dans les bits "palette" du niveau, soit 4 bits par mini-bloc de 8x8 pixels (les tiles, pour les intimes. Prononcez avec de l'aï comme dans light). Pour "School Rush" et ses niveaux de 16 écrans de large pour 1 écran de haut, on parle de 8KiB de données. Un niveau de Commander Keen (mettons la machine infernale des Shikadis) avec ses 1200x1200 pixels nécessite 22500 de ces tiles.
Pourquoi tous ces chiffres ? parce qu'au coeur de la révision, il y a le besoin de faire sortir ça des bits de palette pour pouvoir utiliser librement les changements de couleurs si utiles dans School Rush tout en permettant de nouvelles mécaniques de jeu. Jusqu'ici, j'ai pu m'en sortir avec des blocs spéciaux (bonus, pics) qui utilisaient les "codes couleurs" des 4 tiles qu'ils couvraient pour encoder un numéro suffisamment large. Mais quand on commence à réfléchir à faire de l'eau, des ventilateurs ou des tapis roulant, ce système devient bancal.
En particulier, il était basé sur le fait qu'un groupe de 4 tiles sont liées par les numéros de tile (en mémoire vidéo) utilisé parce que l'éditeur de sprites fonctionne comme ça. Impossible donc de définir un bloc-bumper sur un graphisme qui serait fait d'une moitié de gomme et d'une moitié de lettre. ça, c'est plutôt un avantage. Impossible aussi de rendre une pointe de crayon blessante si elle est sur 'l'arrière-plan'.
Faut-il donc que je continue avec ce système (données 4-bit) sachant que je travaillerai maintenant sur un tableau séparé ? Puis-je me permettre une extension à 8-bit par tile ? et si oui, comment organiser au mieux les données ? Est-ce que ça m'impose d'intégrer de la compression ? A mon avis, avec 22Ko pour une map type "Commander Keen" et sur une bécane qui dispose de 4Mo de mémoire principale, ça reste parfaitement jouable. Oui, c'est loin des techniques de trapéziste de l'époque des consoles 16-bit, mais c'est justement une des raison de choisir la DS à mes yeux: elle permet de se concentrer sur le jeu lui-même, avec des contraintes, certes, mais des contraintes simples à apprivoiser.(2019-01-13) [tiled][newmeta][scanned][CONVEYED][palette]
Sandopolis Zone and Mega-tiles.
Ok, au départ je cherchais des références pour les couleurs et le style de la "desert/pyramid zone" de mon prochain jeu. Et je venais de découvrir l'existence de Sandopolis, région d'un jeu Sonic jusqu'à laquelle je ne suis probablement jamais allé.
Un beau gros .zip dans lequel était supposés se trouver tous les décors, mais au lieu d'une "tilesheet" habituelle, je tombe sur une collections d'images de 128x128 pixels. J'étais resté un peu intrigué mais sans plus. Puis en revoyant la vidéo "making of Sonic 2" de splashwave, le doute n'est plus possible: il y a un niveau intermédiaire de map, et ces blocs de 128x128 pixels sont bels et biens des morceaux de puzzle construit à la main pour servir ensuite de de bloc de construction dans un niveau. On va les appeler des "mega-tiles".
Avec les mega-tiles, on peut atteindre ainsi un taux de compression proche de 100 pour 1. Les niveaux de Sonic avec leur taille surréaliste de 10,000 x 2,000 pixels tiennent en réalité dans moins de 64K de mémoire.Et si on regarde un niveau construit avec suffisamment de recul, on peut commencer à trouver des correspondances. Commencer seulement, parce que c'est loin d'être complètement évident ... et j'aurai eu recours au mode "soustraction" de Gimp pour les mettre en évidence.
Et si on ne s'en rend pas compte en jouant, c'est grâce aux différents éléments qui eux ne font pas partie de ces blocs : les télévisions, les anneaux, les ennemis ... Et parce que contrairement à un jeu "type Mario", la disposition des blocs de taille 16x16 au sein du jeu a nettement moins d'importance. J'entends par là que Mario interagit avec un bloc de brique ou un bloc-question individuellement. Et le fait qu'un saut soit possible ou non se décide à un bloc près.
Pas dans Sonic. Selon qu'on va plus ou moins vite, on passe ou non. Selon qu'on trouve le bon bumper ou non, on va assez haut. Et quand on n'est pas assez haut, il y a de toutes façons une route inférieure à suivre.
Mes cogitations pour rénover la gestion des maps dans mon moteur DS commence à porter ses fruits, au point que je peux commencer à réfléchir à l'impact que ça aura sur l'éditeur de niveaux. ça me tient à coeur parce qu'avec le système actuel, je ne vois de bonne solution pour réaliser les cours d'eau souterrains que mon frère avait mis dans les niveaux historiques de la Grande Aventure. Je me dirige vers un système avec uniquement un byte par tile de 8x8 pixels, et quatre grande classes de blocs:- les morceaux deblocs interactifs, provoquant des collisions auxquelles le personnage doit réagir (pics, bloc-question, bonus, portes ...)- les zones non-solides mais succeptibles d'affecter le comportement de certains morceaux de code (eau, échelles, courant d'air ...), dites "medium"- les blocs de "sol" (solides) qui encodent la forme des pentes- les blocs solides qui encodent les propriétés physiques (friction, déplacement forcé, etc.)
ça signifie que la petite palette de blocs utilisée dans LEDS va devenir plus complexe, avec un bouton "montrer les autres options", et que je pourrais bien en avoir trois tranches plutôt qu'une seule pour éviter les opérations de navigation fastidieuses. ça signifie aussi que c'en est fini de l'encodage de petits "0" et "2" dans la map pour former un code entre 1 et 256 en base 4: l'éditeur de niveau ne pourra plus proposer que les blocs qui auront été décrits, ce qui devrait au passage rendre les choses plus lisibles et plus faciles pour des gens qui auraient envie d'utiliser LEDS pour leur propres projets (on peut rêver, non?)
Autre truc intéressant: puisque l'information concernant un bloc spécial (mettons des briques cassables) n'est plus encodée que sur 1 tile, je vais avoir besoin de tiles qui disent "suite du bloc, cf. à droite" ou "suite du bloc, cf. en haut", etc. Chose qui devrait être plus simple à étendre à des blocs interactifs de 24x24 ou 32x32 si le besoin s'en fait sentir...(2019-03-22) [level editor][tiled][newmeta][special block][slopes]
a + b = idea
+
+
= idea.
Il y a encore des choses dans l'étude des moteurs de jeux de plate-formes qui m'intéressent suffisamment pour me garder accroché au dévelopement de 'libgeds'. Des mouvements style "corde ninja". Des rebonds sur les murs. Toutes des choses que j'espère bien voir figurer dans 'infinite pyramid', mon prochain projet. Il y avait par contre un truc qui ne collait pas avec le thème de la pyramide: les zones qui forcent Bilou à avancer dans une direction précise. C'est le cas avec la rivière que j'aimerais introduire dans la version revisitée du niveau 1-1 de Bilou, à la placedu tapis roulant que mon frère avait dessiné en guise de passage à sens unique. Parce que cette rivière justifie la révision du système de "propriétés des blocs", en cogitation depuis le début de l'année. Mais c'est bon. Je me suis retrouvé avec les méga-tiles de Sonic juste à côté de Bouli expliquant le nouveau système et ça a fait "a + b = i * d" : je n'ai qu'à mettre des rivières de sables (comme tout le monde?). Pas oublier non plus les escaliers-qui-deviennent-des-tobogans du chateau gadget. Mais à condition que ce soit fun!(2019-08-21) [todo][tiled][newmeta][pyramid][desert zone]
FlatWorld : public iWorld
Bon, je laisse un instant les commentaires sur Ori et Link's Awakening de côté, les pixels studies et compagnie. Les enfants commencent à me demander s'ils peuvent essayer les niveaux dans la forêt de Bilou, et j'ai une idée un peu plus claire du moteur de jeu que je voudrais avoir pour pouvoir le réaliser. La première étape va être de faire en sorte que les propriétés du niveau soient conservées dans un tableau séparé, plus dans l'emplacement réservé aux couleurs d'une des deux couches de graphismes (une des plus vieilles fausses-bonnes-idées du projet). Il faudra adapter l'éditeur de niveaux, mais ce sera pour la deuxième étape. Avant ça, j'utiliserai un "extracteur automatique" pour les "vieilles" maps (comme celle de School Rush et Apple Assault). Je vais essayer d'avoir un bon design de classe dès le départ ce coup-ci, avec un type pour les données du moteur physique, l'implémentation qui reste un détail et iWorld qui est le seul élément connu du reste du code.
J'ai voulu réessayer de faire tourner ça dans SchoolRush pour voir, mais je n'arrive qu'à avoir un écran bleu quand je compile avec le "dernier" devkitpro. Un conflit avec EFS, semble-t-il ... ou bien j'ai fini par oublier comment je devais invoquer le programme, mais
ni --load-type=1 ne semble marcher
ni un build plus ancien
ni --cflash-path=SchoolTest (et recherche dans les fichiers)
Un premier problème que je parviens à identifier, c'est lseek(..., SEEK_END) qui renvoie -1. Pas bon, vu que le code d'efs va chercher à comparer la taille trouvée avec celle pré-enregistrée. Et si ça foire avec lseek, c'est apparemment parce que open("fat:/SchoolTest.nds") échoue avec un beau ENOENT ... pourtant ce nom provenait d'argv[0]. Bref, c'est de nouveau la saison des guru meditations... Par contre, le commit précédent marchait sans problèmes sur 'grizzly', mon vieux laptop de 2007 qui utilise toujours un ancien devkitpro. Et mon nouveau code passe sans problème la découverte du système de fichiers embarqué quand il est compilé sur grizzly. Au point que j'en viens à me demander si j'ai jamais fait un build de SchoolRush sur le nouveau système... Fort peu probable, en fait vu que le système de highscores dépend du stylet (décembre 2018) alors que je n'ai corrigé le bug d'intégration du stylet avec le nouveau devkitpro que 6 mois plus tard.
Je sens que je vais archiver le setup devkitpro de grizzly sur mon nuc-à-tout-faire, moi.mais, en utilisant grizzly-desmume-cli --cflash-path=directory/to directory/to/SchoolTest.nds, je peux faire tourner le code (c'est l'implémentation pour "cartouche GBA" qui ne marche plus, apparemment).et en débuggant ce code-là, il apparaît que le destructeur d'InfiniMap se voit appelé lors du premier getflags()... c'est supposé être un iWorld, mais il a une vtable de Vblcallback ... Et si desmume sur grizzly ne se laissait pas débugger avec ddd c'est parce que ... J'avais chargé l'éditeur de niveau dans ddd pendant que l'émulateur tournait le moteur de jeu et SchoolRush. Eh oui. Je vous en prie, balancez votre vanne préférée sur la quarantaine dans les commentaires. (2019-11-26) [guru meditation][tiled][desmume][dldi][devkitpro]
FlatWorld.artmap
Bon, bin j'ai finalement réussi à trouver les erreurs que j'avais glissées dans mon code et à les corriger. Je peux donc reprendre une des "maps" de School Rush et la convertir à la volée pour qu'elle tourne avec la nouvelle classe "monde" et ses données de collisions séparées des blocs de VRAM du fichier .map. Par contre, ce faisant, j'ai cassé la possibilité d'animer le contenu de l'écran en réaction à une collision avec un bloc spécial.
Je dois reconnaître que j'y vais à tous petits pas, là. Entre autres à cause de la faible quantité de temps libre qu'il reste après les transports et les opérations de maintenance domestiques ... et encore, je suis loin du compte.
Bref, on va commencer par ré-établir un lien entre la "méta-map" et les map-à-images puis il faudra ajuster les changeblock() et autres clearblock() pour que tout passe. Mais caser ça avant St-Nicolas ? Je dirais qu'on est à un niveau d'improbabilité de 3.14 ou pis.
(2019-12-06) [tiled][newmeta][uml][translate me]
Rush to Flatworld
Avec le recul, ça me fait un peu penser à un donjon Zelda où on viendrait de trouver l'objet-clé. J'ai un nouveau mécanisme pour gérer les types de blocs sur DS, conquis après une lutte opiniâtre contre les vilains bugs (pensez aux wall masters) et les émulateurs récalcitrants (grosses statues à déplacer vers une dalle-interrupteur au hasard dans la pièce). Et du coup, je dois retrouver les différents endroits où je peux à présent reprendre le développement. Editeur de niveau amélioré ? Programme de conversion vers un nouveau format de map ? Plus de types de pentes ? chacune de ces "petites clés" supplémentaires seront nécessaires au final pour pouvoir faire une version jouable de la maquette "desert zone" que j'ai en tête, et un bon nombre est aussi utile pour débloquer une conversion correcte de la "green zone". Mais là, je suis dans la salle avec trois portes et je dois choisir la bonne.
Je me rends compte qu'il y a un truc à propos des pentes que je n'ai apparemment pas convenablement expliqué sur ce blog. Voyez donc cette "capture d'écran" de l'éditeur de niveau avec les types de blocs qui sont révélés en bleu transparent. Il est assez facile d'y voir les zones considérées comme solides, et même la petite pente au niveau de la souche d'arbre (allez, je vous la marque en vert flashy). Puis il y a un troisième élément qui revient assez souvent, ce sont ces lignes horizontales, notamment dans la branche (je vous les marque en rose ou en jaune, petits veinards ;)
Elles correspondent au type "jump-thru", qui a la particularité de laisser passer ce qui monte mais pas ce qui descend. Aucune magie là-dedans: c'est simplement le "GravityController" qui ajuste le test effectué selon qu'on monte ou qu'on descend. Ce type agit aussi comme du sol sans agir comme un mur: si on arrive dans la branche par le bas avec une vitesse horizontale, on la conserve. Là où ça devient intéressant (ou louche comme une passoire sans trous), c'est pour les blocs "jump thru" qui sont marqués en jaune. Non, il n'y a pas de passage secret, et on est jamais sensé sortir de la souche.
Imaginons un instant qu'on ait un personnage occupé à monter cette pente. Il a un point de contact (en vert flashy) qui doit rester sur la pente, et une zone de collision (en bleu). Pour qu'un déplacement soit valide, l'ensemble des blocs sur lequel la zone de collision arrive doit posséder les bonnes propriétés -- par exemple "on peut passer à travers" ou "on peut nager dedans". C'est le principe de la fonction cando() dans mon moteur de jeu. Mais dans le cas d'une pente, on voit clairement que la zone bleue peut "déborder" sur les blocs juste à-côté des blocs-pente. Si on les rends solides comme des murs, le personnage restera bloqué en bas de la pente... d'où la réutilisation des blocs au-travers desquels on sait tout faire sauf tomber.
(C) The UnDisbeliever
why we need more than slopes & blocks.
Ils permettront aussi qu'un personnage dirigé par une "IA" qui sonde la map autour de lui ne pense qu'il arrive au bord d'une plate-forme en trouvant un tile "creux" quelques pixels sous son point de contact. Voilà, j'espère que c'était intéressant ;)
Ok. Ajouter des pentes dans un jeu de plate-forme, ça devrait ne pas être compliqué, mais dès qu'on modélise son niveau à l'aide de dalles pré-définies (toujours les bons vieux tiles), ça cesse d'être simple. J'en ai déjà beaucoup parlé (cf. le tag) mais sans jamais vraiment (reality check: si) présenter la solution que j'utilise depuis Apple Assault: la fonction doslopes.
L'algorithme derrière doslopes travaille uniquement avec le 'hotspot' des sprites, et veille à garder ce point en contact avec le sol tout en appliquant un déplacement horizontal donné (qui peut s'étendre sur plusieurs tiles). Il utilise aussi une fonction annexe -- groundheight(x,y) -- qui donne la hauteur du sol pour un tile du niveau à partir de la base de ce tile. On y reviendra.
L'algorithme est constitué d'une boucle optionnelle (en jaune) pour atteindre le tile de destination puis une phase d'affinage (en vert) qui calcule la position du sol pour la position horizontale voulue.
En sortie, on obtiendra un déplacement vertical correspondant au déplacement horizontal donné. A chaque déplacement intermédiaire, doslopes s'assure que l'on est pas en train de s'envoyer dans un mur à l'aide de la fonction cando -- la base de la gestion des interactions sprites/niveau.
Le principe de base, c'est de regarder la hauteur du dernier pixel d'un tile pentu juste avant de le quitter. Il peut avoir une 'hauteur = -8' ce qui fait positionne le joueur juste au-dessus du tile (comme on le fait normalement pour du sol) et permet du coup de regarder le bon tile pour la suite de la pente au tour suivant.
(2020-02-01) [tiled][scanned][slopes]
Higher Order Blog ...
Un tout bon blog, qui continue à nous proposer des conseils pour implémenter un jeu de plate-forme 2D depuis facilement 2007, époque où j'ai dû le rencontrer pour la première fois. Mais en repassant dessus il y a quelques mois (magie Twitter), je me rends compte qu'il y a encore un bon nombre de fonctionnalités discutées que je n'ai pas encore implémentées dans mon moteur à moi (échelles, plate-formes dont on peut redescendre), et d'autres qui sont en cours de révision (pentes, plate-formes uni-directionnelles). Je repasserai dessus dans les jours à venir (j'espère)
Mais une particularité de l'article de Rodrigo Monteiro, c'est qu'il ne se limite pas aux jeux basés sur des tiles: il discute aussi le cas des niveaux modélisés par des vecteurs (Braid), à positionnement forcé sur des tiles (Prince of Persia, Flashback) et basés directement sur un bitmap (Worms). Et là, je trouve qu'il a peut-être été un peu léger en oubliant de mentionner Lemmings. Bon, d'accord, l'article est bien plus ancien que le talk de Mike Dailly présentant le fonctionnement (et l'éditeur de niveau :P) de Lemmings 1 et 2. Bon, on est d'accord: ni Lemmings ni Worms ne sont à proprement parler des jeux de plate-formes, mais ils restent en vue latérale avec de la gravité, et à voir la physique du personnage dans Fury of the Furries, je dirais qu'on pourrait bien en avoir un troisième.
A noter que traiter la map comme un gros bitmap n'implique pas forcément de stocker la map comme un gros bitmap. En fait, pour la majorité des machines d'avant le CD-ROM, ç'aurait été tout simplement impensable même avec des niveaux de taille modeste comme ceux de Lemmings (5 écrans). Pour faire tenir tout ça sur une diskette, les gens de DMA design ont construit leur niveaux sur base 1) d'une palette de 'brosses' (des bitmaps de taille quelconque provenant de Deluxe Paint) et une série de commande 'ajoute la brosse A aux coordonnées (X, Y)'. L'éditeur permettant d'ailleur de garder une idée du nombre de 'coups de pinceau' utilisés jusqu'ici dans le niveau.
Bon, vous l'aurez compris, cette technique bitmap pour les données d'un niveau marche surtout bien pour les jeux où l'environnement est complètement destructible (chose qui devenait moins vrai avec Lemmings 2, prévu pour consoles 16-bit) et à un style graphique plus proche d'un Rayman que d'un Mario. D'une façon assez amusante (mais foireuse), c'est aussi ce qui était utilisé pour la version BASIC de Bilou's Adventures ;-)
Il y a pas mal de choses qui restent à arranger avant de pouvoir cocher la case "démo avec les trois salles de test", et ça malgré toutes les choses qui ont pu être corrigées pendant les vacances. Alors pour ce week-end, pourquoi pas s'attaquer à l'atterrissage sur des pentes ?
Jusque là, le seul bout de code qui s'intéresse au pentes, c'est celui qui contrôle le comportement "marcher". Pour le reste du moteur, une pente, ce sont des pavés carrés comme tout le reste. Du coup, si vous faites un p'tit saut pendant que vous étiez sur une pente, il y a de fortes chances que vous finissiez en flottant au-dessus du sol ^^".
J'ai passé la journée d'hier à faire des va-et-viens entre le débuggueur et le compilateur pour essayer de comprendre ce qui se passait, mais c'est sans espoir tant qu'on ne saura pas tomber à travers les tiles qui constituent les pentes... A cogiter.
Mais même avec cette nouvelle propriété et même avec des blocs carrés qui disent que "oui, en vrai, on est des pentes, mais des pentes plates. haha", ça ne marche pas encore. La solution que j'avais prévue suppose que le sol sur lequel on veut atterrir est constitué d'une brave rangée de tiles de type "pente". Mais comme vous pouvez le voir sur mon petit croquis, il y a pas mal d'autres combinaisons qu'il faudra aussi prendre en compte.
J'ai une idée pour remplacer ça ... il faudra que je la teste. Elle n'est pas sans me rappeler (ce que j'ai compris) du moteur de Sonic, d'ailleurs: considérer toute la colonne de tiles par-dessus la nouvelle position souhaitée pour le "hot spot" et calculer la "hauteur du sol" dans cette colonne.
Là, je pense que je le tiens. Il y avait un défaut majeur dans mes premiers essais: considérer qu'on peut tomber à travers une pente en lui donnant la propriété "F_FALLTHRU" implique automatiquement qu'on ne peut plus marcher dessus: le contrôleur "walker" s'assure que le sol est solide en testant s'il serait possible de tomber à travers ... pas beaucoup: juste quelques pixels, mais si ça marche, ça suffit pour indiquer que marcher n'est plus possible.
Mais la technique utilisée pour les
(F_START_FALLING) et un autre "autorise de continuer à tomber"
(F_FALLTHRU). Un pour le contrôleur de marche (walker), l'autre pour le
contrôleur de gravité.
Mais ce n'est pas de ça que Bouli veut vous parler. Une fois le problème des propriétés réglé, il reste à calculer correctement la distance jusqu'au sol. Supposons qu'on parte d'une position (ox, oy) et qu'on veut déplacer le "hotspot" (le point qui doit rester en contact avec le sol dans une pente) en (hx, hy). Déterminer si on a atterri sur une pente, c'est comparer la distance hy - oy avec la distance jusqu'au sol, à partir des résultats de tile.groundheight().
Seulement, ce nous donne une altitude par rapport au bas du tile. -8 pour un tile entièrement solide, -2 pour indiquer que les 2 pixels du bas sont solides et qu'il y a ensuite de l'air, 0 pour un tile constitué uniquement d'air. Une fois qu'on a relu un peu sa doc, c'est vite traité. Par contre, le début du vecteur-mouvement (en bleu foncé sur le schéma) peut se trouver n'importe où dans le tile de départ. Si on est au 4eme pixel d'un tile dont seul le dernier pixel est solide alors seuls les mouvements tels que hy - oy < 3 pourront se faire entièrement.
J'étais tout content de voir que Bilou savait aller dans l'eau, et je ne me suis pas rendu compte qu'il y avait un soucis avec les bonus: on pouvait carrément marcher dessus. Quelque-chose lié à la nouvelle propriété F_START_FALLING, sans doute ... sauf que non. c'est surtout lié aux bytes 'regarde à côté'.
C'est que l'ancien moteur de jeu utilisait le numéro du 'bloc spécial' pour décider s'il devait être solide ou non, et la fonction qui indique la 'hauteur du sol' essayait toujours de faire comme ça. L'ennui, c'est que j'ai utillisé les codes 0xfc à 0xff pour les fameux 'regarde à côté' qui permettent aux blocs spéciaux d'être des blocs, et pas juste des pavés de 8x8.
Pas le choix, donc: ici aussi, il faut retrouver le 'coin actif' du bloc spécial et aller chercher ses propriétés dans le BlockInfo correspondant.
Deuxième farce (voir l'animation): une fois le bonus effacé, il a laissé derrière lui un bloc à travers lequel il est possible de continuer à tomber, mais aussi de continuer à marcher. La faute cette fois au tableau des propriétés pré-encodées pour les blocs à définition indirecte (prévus pour les physiques particulières, essentiellement).
While thinking at 'how to implement conveyer belts', I end up investigating some currently incomplete code in special blocks handling. They have correct implementation of their collision areas, but since the migration to the new tile types engine, they can no longer act as 'doors' because they all have the same cando() properties.
There's some code in the parser to change that, and some code in 'FlatWorld' to return BlockInfo::props when we encounter one ...
Wait ... BlockInfo::props is precisely what this last `siscanf()` updates ... so ... that means the code is not incomplete at all ? it just needs to be tested ?
Bon, j'ai un p'tit Bilou suspendu sur une page de mon cahier-agenda depuis le début 2024 ... j'en ai un autre sur la bannière du blog ... je crois que l'appel du pied est assez clair: il serait temps de prototyper un peu ça, d'autant que cette fois ci, ce ne sont pas les coins de niveau où s'accrocher qui manquent dans le livre-secret-du-level-design.
Et pour "commencer simple", l'idée était de faire subir à Bilou le comportement des éponges. tout en réutilisant un des états codés pour l'interaction avec les éponges: rester suspendu (la feinte). Chose qui m'a tout de même demandé de me replonger dans pas mal de gobscript, parce qu'à la base, il est prévu que Bilou s'accroche *par-dessus* les éponges, et pas qu'il pende en-dessous. Le côté "pendu" n'a été rajouté que dans les derniers 20% du projet, entre deux séances de playtesting parce "bin, oui, c'est vrai au fait: pourquoi Bilou ne pourrait-il pas rester suspendu s'il était trop bas pour escalader l'éponge au moment ou la joueuse a appuyé sur (B) ?"
Mon idée était ensuite d'utiliser un type de bloc spécial pour servir de point d'accroche, et d'aller "poker" quelques blocs de ce genre à des endroits bien sentis de la map "green" de la démo. ça non plus, ça n'a pas fonctionné comme j'espérais. Entre autres parce que quand Bilou cherche à s'accrocher à quelque-chose pendant sa pirouette-en-l'air, il cherche parmi la liste "EVIL" ... et les blocs interactifs, eux se présente automatiquement comme des "HERO".
Bon, ce n'est pas bien grave: puisque je voulais que Bilou s'attache au point d'ancrage, il fallait de toutes façons que j'introduise un GOB (game object) généré à partir du bloc spécial, un peu comme les scintillements qui suivent Bilou quand on ramasse un smiley bleu. Ce gob pourra être dans la liste que l'on veut. Pour éviter qu'on ne génère des quantités ridicules de gob-points-d'ancrage, on va (enfin) utiliser une animation de la map elle-même pour désactiver puis réactiver les propriétés du bloc pendant que le gob est présent. Exactement le comportement des blocs-question de SuperMario quand on les coup-de-boulise.
Alors oui, je vous balance le script directement dans le browser. Parce que ça ne fonctionne pas. Enfin, après avoir corrigé la gestion de l'animation pour ne pas dépendre des changements d'affichage (qui expliquent le bout de terre manquant dans le screenshot là-haut), ajouté une "bbox" à l'animation (pour que le code sache qu'il ne doit manipuler que 8x8 et pas 16x16), je finis par avoir un objet qui est créé tandis que le bloc devient inactif puis se réactive ... le hic, c'est que il faut pour ça que le "corps" de Bilou soit en contact avec le bloc interactif alors que ce sont ses *mains* qui vont essayer de l'attraper. Et la zone à attraper est réduite à 8x8, de toutes façons mal positionnée... bref, je n'y arrive que 1 fois sur 5-6, donc le joueur n'y arrivera pas.
Il vaut bien mieux que je me décide à dessiner un graphisme dédié (une racine ?) et que je fasse juste un GOB dédié... il y aura des import/exports mercurial à faire ...
Refaire Super Mario World dans le game-maker "Godot" ... j'ai envie de dire à la fois "tout ça" et "rien que ça". Pourtant, l'idée de Wye n'est ni de proposer un Mario World Studio ni son Mario World 3. L'idée, c'est de comprendre le fonctionnement du jeu d'origine en le reconstruisant dans un nouvel outil tout en apprenant l'outil lui-même. Et ça, ça me parle. C'est le genre de bouquin que je dévorerais mais ici, ce sont des vidéos youtube.
"In Super Mario World, Mario was considered fully underwater if both his head and body interaction points are touching water tiles. [...]
"When Mario body is in the water but not his head, that means he is near the surface."
Du point de vue des collisions avec le niveau, Mario n'est pas une boîte. Il est ... une sorte de constellation de points qui ont chacun leur préférence sur le type de terrain 8 en tout, mais dont certains ne seront évalués que dans certaines conditions. Pour déterminer s'il faut nager ou non, ce sont par exemple ce sont les tests du milieu et de la tête qui entrent en ligne de compte. Et pour déterminer si on peut sauter hors de l'eau ? Bin il faut que le corps de Mario soit dans l'eau mais sa tête hors de l'eau. On évite en fait les complications du type "la surface est à la fois de l'air et de l'eau" dans lesquelles je me suis embarqué.
Il y a sous l'arbre creux une sorte de pente rendue glissante par une cascade sous-terraine. Mon frère l'avait utilisée pour créer une sorte de sens interdit entre deux morceaux du niveau. Alors j'ai déjà des pentes, donc ça aurait dû être une simple formalité, mais comme vous l'aurez sûrement deviné par le titre, ça n'a pas été sans mal Premier couac: en voulant rendre ce "tapis roulant" plus rapide que les chutes de sable, je suis sorti des limites d'un tableau, ce qui a eu pour effet d'appliquer l'effet geyser à l'ensemble du niveau ^^".
Plus contrariant, une fois arrivé en bas de la pente ... rien. On ne tombe pas dans l'eau, on reste juste là, sur le rebord. Parce que c'est le pixel sous le *milieu* de Bilou (le "hotspot") qui détermine si on se trouve sur un tapis roulant mais qu'il faut qu'il n'y ait plus rien sous lui sur toute sa largeur pour qu'il commence à tomber. Je dois donc rajouter un autre type de tile qui pousse comme un tapis roulant tout en étant pas du sol.
Il restait un dernier soucis. Un couac lié à un changement plus récent et qui fait qu'après avoir bien emmené Bilou au milieu de nulle-part, il ne tombe toujours pas. tout ça parce que j'ai donné au nouveau tile possède bien la propriété F_FALLTHRU, utilisée pendant que Bilou tombe, mais pas F_START_FALLING utilisée pendant que Bilou est au sol pour voir s'il pourrait tomber ^^"