Saturday, May 23, 2009

parallaxe ... foireux.

Petit test de scrolling parallaxe ce matin (entre vidange du garage et achat de papier peint) qui est encourageant sans vraiment être satisfaisant. C'est ma première tentative de charger plusieurs tilesets différents en mémoire, et je dois maintenant combiner une "infinimap" (dont l'image est modifiée au fil du scroll) avec une "wrapping map" (dont l'image reste fixe mais "tourne en boucle").

Les pommes sur fond blanc témoignent d'une petite réorganisation de la VRAM. J'ai réglé tout ça un peu vite à mon goût et il faudra que je prenne le temps d'ajuster le code pour qu'il fonctionne correctement sans dépendre d'effets curieux du genre "if bg==2 ...".

I wanted to bring the real (parallax) background in my woods this morning, but it didn't came as 'fluently' as i hoped in the code. Things were not quite ready for mixing wrapping maps and large maps, not quite ready either to host several tilesets in vRAM or to load an odd number of colors. I had to put ugly patches here and there to show the feature. A proper way to "split" SpriteRam in several banks is required in the future, as well as a real "WrappingMap" class and an update of the map-related script parsing functions. Well, anyway, even though my camera isn't as neat as Kram's, it's more "sexy" this way than with the dark-red background.

PS: since the demo is now released, now, it's time for a new "todo list", reusing wishes from the former one.

  • [done] have the "level exit" activated when enough apples have been picked.
  • [done] increase homogeneity of collision detections
  • [done] pre-define values when initializing GOBs in script (e.g. set Funghi's counter)
  • [done] blocking ennemies: e.g. you can't pass through Funky
  • [done] bilou can tell whether he has been "hit" by an ennemy or an apple
  • [done] code cleanup : upgradable "block info"
  • [done] register "action on counter back to zero"
  • [think..., done] substrate for "shooting" (dust clouds, stars, etc.)
  • [wish] code cleanup: more states, more anims (array -> maps in GameScript)
  • [wish] support multi-palette environment (by now, i'm just lucky it works fine !) or 16 color backgrounds
  • [wish] provide a cleaner way to have parallax scrolling (and more maps)
  • [done] make sure we can change/end level!
  • [done] add sound fx support
  • [wish] add music selection in the GameScript!
  • [done] loading SpriteSet with odd number of colors properly
  • [done] execute scripts non-interactively in demos.
Note: le parallaxe figurait dans mes "todo list" depuis décembre 2007 quand même ...

Friday, May 22, 2009

Moon walk ?

As seen in former post: Scripting Sunday:

TODO: moving Bilou through the animation isn't the right way to go. Maybe "delay x" in the animation sequence to have the next frame triggered only when we move to the next pixel would be better
Faire marcher un personnage est loin d'être une tâche facile et en particulier, ça demande une synchronisation parfaite entre le déplacement du sprite et son animation. Le moindre décalage et le joueur aura l'impression que le personnage "glisse" sur le sol au lieu de marcher. En clair, le pied en contact avec le sol doit rester au même endroit par rapport au sol jusqu'à ce qu'il se lève. Ce genre de défaut était assez fréquent dans les jeux de mon enfance (hein, Eric ;). Une manière assez simple d'y remédier est évidemment de faire courir le personnage, de préférence "à la supermario"

La solution que je pensais y apporter était relativement simple: intégrer les déplacements dans l'animation à coup de "move x y" entre deux images. C'est comme ça que je déplace le wooworm et ça réussit plutôt bien. Le hic, c'est de combiner ça avec une vitesse éventuellement variable et le test des collisions qui a été ramené dans le contrôleur (qui ignore tout de l'animation en cours).

I've never been satisfied by typical game-making animation tool that just let you define a constant moving speed (e.g. one pixel per frame) and an walking animation that you try to match that. It always gave me the feeling that the hero is "sliperring" on the ground or walks with rollers. I initially planned to fix this by integrating moves to the animation itself (i.e. 'show frame A, then wait for 2 frames, move by 2 pixels horizontally and show frame B'). That's how woodworm works, but i couldn't simply extended to Bilou's walk... Not until a friend of mine suggested that i could also delay the animation _until sufficient movement has been accumulated to 'hop' two pixels away_. Here comes the specific code that does that (only when the animation is "self-moving", which would be typical from ladder climbing, walking and other "friction-based" moves.

C'est Pierrick qui m'a donné la solution a mon problème en racontant comment du temps du CPC il faisait faire des sauts "réalistes" à son petit bilou en modifiant la durée d'affichage à chaque emplacement vu qu'il lui était impossible de placer le bilou (eh oui, c'était lui) entre deux tiles (pas de sprites en CPC basic ?). Plutôt que de chercher midi à 14 heures (du genre "modifier la vitesse d'écoulement du temps pour que Bilou coure plus vite"), j'ai juste changé l'interprétation de "move x y" en "ne passe à l'étape d'animation suivante qu'une fois que le contrôleur aura 'accumulé' le décalage suffisant. Traduit en code, ça donne :

inline bool trymove(int dx, int dy) {
    forcechecks=false;
    if (selfmove) {
        int cflags  = cast==HERO?F_PLAYERTHRU:F_MONSTERTHRU;
        bool notyet = (dx>0 && cdata[4]<dx) || (dx<0 && cdata[4]>dx)
                   || (dy>0 && cdata[5]<dy) || (dy<0 && cdata[5]>dy);
        if (notyet) return false;
        if (cando(dx,dy, cflags)==cflags)  {
            x+=dx; cdata[4]-=dx;
            y+=dy; cdata[5]-=dy;
            return true;
        } else {
            cdata[4]=0; cdata[5]=0;
            return setstate(state->dochecks(x>>8,y>>8,world, cdata));
        }
    } else {
        x+=dx;
        y+=dy;
        return true;
    }
}
  • selfmove est défini par état : true pour monter à l'échelle, false pour tomber, etc.
  • cdata[STEPX] et [STEPY] accumule les valeurs de cdata[XSPEED] et cdata[YSPEED] (vitesses définies par le contrôleur) en mode "selfmove" (normalement, on a directement x+=cdata[0])
  • trymove(dx,dy) est appelé lorsqu'une étape d'animation utilse la commande "MOVE"
  • condloop et check permettent de vérifier les testpoints sur des frames données (p.ex. quand le personnage a de nouveau les pieds au sol).
Voilà. J'avais envie de démystifier ça. J'espère que ça sera utile à l'un ou l'autre.

A pair of per-object variables (cdata[])will thus be used to accumulate some intended move until that "step" size becomes large enough for the move x y instruction found in the animation list. The trymove(dx, dy) tells whether such a move is possible right now or must wait until more motion has been accumulated.

edit: De manière étonnante, Miyamoto avait lui fait le choix délibéré, dès Donkey Kong, de casser le lien entre animation et déplacement parce qu'il jugeait qu'une animation de marche réaliste "ne collait pas à l'action frénétique d'un jeu vidéo" (l'Histoire de Mario, p.240)

2024 #choice reality check: It is still there, and I like how it makes many character feel like they're in contact with the ground. But let's be honest, it makes the code a bit more complicated every year. It makes it possible to have characters whose motion accelerates and decelerates over one step with a constant average speed, but offseting the graphics for a truly-constant-speed could achieve something similar, and that wouldn't even be complicated for a compound sprite. So there I am. Maybe this was not a good choice, but I haven't replaced it yet.

Tuesday, May 19, 2009

Le Bilou Nouveau est arrivé...

Comme promis, voilà la nouvelle démo de mon moteur de jeu. Ce n'est pas encore parfait, bien sûr, tant au niveau technique qu'au niveau graphique ou gameplay, mais ça vous permettra de voir faire une meilleure idée de ce sur quoi je travaille.

L'objectif est de récolter toutes les pommes du niveau, et un petit "GREAT" vous félicitera si vous en trouvez 16. Vous pourrez vous faire "blesser" (aussi souvent que vous le voudrez) au sol, mais pour l'instant, vous êtes invincibles en saut. Et il me manque toujours un fond convainquant.

Bref ... Work in progress.

Here you finally get it: a new demonstration of my game engine with my most up-to-date pixel art. It's not perfect, of course. Neither graphically, technically or in regards to gameplay. But at least it gives a better view of what i'm working on.

So far, your mission is to collect as much apples as you get (and you can get all of them in one row), a message "**GREAT**" will show you you've got 16 of them. Note, too, that you'll never get hurt while jumping. And yeah, it'd be much better with a background of some sort.

download gedsdemo.nds



PS: dans cette version, il vous faut toujours appuyer quelques fois sur "Y" au démarrage de la démo pour lui forcer à "lire" le script du niveau. C'est une de mes priorités de retirer ça pour la prochaine démo, bien sûr.

PPS: last update: 2009/05/20: Kram reported a bug on real hardware. Fixed.

Greetings fly to: NODA (efs lib), OxTob (ntxm), the devkitpro team, Kram (DS2DS), CJ (R4), Pierrick & Tbob (music), lackey, victorX, pixelaroo, arachne, zeid, Helm and many more pixel pushers at Pixelation for artistic advices, sourceforge & blogger for project hosting.

Suivre le développement du projet ...
Read progress reports

Monday, May 18, 2009

Demo coming soon

Encore un peu de patience. Le plus gros du boulot est fait, j'ai juste à "packager" les éléments de ma plate-forme de développement SEDS/LEDS/runme en un petit .nds autonome. Et éventuellement rajouter une animation pour que Bilou puisse se faire écraser par les champignons.

Après ça, release d'une petite démo jouable que j'espère plus convaincante que celle de juin 2008. Comptabiliser l'énergie de Bilou ne devrait pas être trop difficile à ajouter, par contre je ne sais pas encore comment m'y prendre pour redémarrer le niveau s'il meurt, etc. Il est possible que je garde ça pour plus tard.

Just a few more days ... Apples works, collisions works, all i need is packaging of code and media files into a single, standalone ".nds" file that you can download and try on your homebrew-ready DS. Hopefully, i'll also add an extra animation so that funghis can stomp Bilou and you'll get a playable demo of what i've been up to since June 2008.

Stay tuned.

Friday, May 08, 2009

Game Engine progress report

Bonne nouvelle: le mécanisme de gestion des collisions entre sprites (re)prend forme. J'avais déjà fait quelques tests dans la version précédente, mais cette fois-ci, la réaction aux collisions peut être programmée: Bilou sursaute lorsqu'un ver le touche mais il peut écraser le ver en question en lui sautant sur la tête, comme dans Mario.

Good news from the Game Engine: sprite-to-sprite collisions management is (re-)enabled. I already had a former release where the screen flashed to red when Bilou hit a monster, but this time i can script the desired response to collisions. For instance, Bilou will be "hit" when touched by a Woodworm, but he can stomp the same woodworm by jumping on its head.

Il y a encore un peu de boulot avant de pouvoir faire une "release" de la nouvelle petite démo:

  • [wish] increase homogeneity of collision detections
  • [done] have apples animated once again
  • [done] have Bilou collecting apples
  • [patched] more states, more anims (array -> maps in GameScript)
  • [wish] have the "level exit" activated when all the apples have been picked.
  • [done] rationalise memory use in libntxm (less mallocs/realloc) and track array overflows
Et au passage, il faudra aussi que je fasse un peu le ménage côté "gestion de la mémoire". La libntxm étant au départ prévue pour un éditeur de modules, elle a tendance à faire des reallocs dans tous les sens (trop souvent source d'erreur à mon goût) et j'ai régulièrement des pointeurs qui sont devenus (?) invalides au moment de la destruction du mod en cours.

(wishes and cleanups kept in the current todo list)

Monday, April 27, 2009

Coding Funky Funghi

J'avais envie de mettre côte à côte "comment je me représente le comportement de Funky Funghi" et "comment je réalise ce comportement avec mes outils actuels.

state12 :anim15 {
  using stopper
}

state13 :anim6 {
 using gravity(24,1536);
 testpoint off (16,32)
}

state12->state13 on done [t] (v4 0 = 200 * 500 + ~ :1)
state13->state12 on fail [t] (v4 1 + 4 % :4 0 :1)

The gamescript for funky funghi is a good example of what i called "more sophisticated monster behaviour" in my earlier post about how to write the game logic. Eventually, such behaviours will be edited *on the DS* with a mostly-graphical tool, but the logic is already there: Funghi has two states (jumping and on the ground) to which i can attach different animations and "core behaviours" such as 'affected by gravity' or 'just stay in place'. It also has internal variables (loaded with v4 and stored back with :4, for instance) that both manipulates its internal speed (:1 sets vertical speed, for instance) and can be used as monster-specific state.

I reused the UML state machine model where each state transition can have a predicate that indicates whether the transition can occur (between [square brackets]) and an action that alters the object before it enters its new state.

Voilà donc à la fois un "mockup" futuriste de l'éditeur de monstres sur DS et le genre de script que je bricole en attendant pour quand-même avoir des monstres qui se balladent dans mes niveaux. Histoire que vous suiviez :

  • Funghi a deux états: au sol (12) ou en saut (13). Ca me permet notamment d'avoir une animation spécifique à l'état "au sol" et synchroniser la "déformation" du sprite avec la collision au sol (chose dont le RSD Game-Maker était incapable).
  • Comme tous les objets que j'ai jusqu'à présent, Funky Funghi respecte la convention que v0 est la vitesse horizontale et v1 la vitesse verticale. A priori, ces valeurs sont gérées par le contrôleur et utilisées par la classe SimpleGob pour mettre les coordonnées à jour. Elles sont aussi accessibles dans la machine d'état (:1 signifie "écrire la valeur dans v1)
  • Chaque "gob" possède 12 compteurs sous son entier contrôle. Funghi utilise le premier d'entre-eux (v4) pour compter ses rebonds. "v4 1 + 4 % :4" est une expression post-fixée (pensez 'calculatrice HP' qui doit se lire "prend v4, ajoute 1, calcule le reste de la division par 4 et stocke le résultat dans v4 à nouveau". Ce calcul est effectué chaque fois que Funghi entre en contact avec le sol.

The above script is a bit complicated because i decided to rule out flow control when i 'designed' the language (something i learnt in my thesis), yet i need here that Funky Funghi jumps higher every 4 bops. Plus, it's a bit ugly because i used post-fixed arithmetic (2 + 3 is thus written 2 3 +, as on a HP calculator). Hopefully, the existence of predicates allow me to compensate the lack of conditional expressions in actions by multiple transitions, each with a predicate and a separate action. See the code below, it's actually clearer (sort of) with similar efficiency.

A la fin de l'animation "déformation", la nouvelle vitesse verticale est définie. "500 ~ :1" donnerait à chaque fois la même implusion (~ est utilisé ici pour prendre l'opposé d'un nombre, donc v1 reçoit -500 ... eh non, je n'ai pas implémenté les constantes négatives. Sue me).
Pas d'expressions conditionnelles genre "si(test, réussi, échec)" comme on en trouverait dans Excel ici (ou test?réussi:échec pour les programmeurs); c'est une restriction délibérée, et sans doute la plus contraignante de mon évaluateur d'expressions jusqu'ici. Or, je dois décider de fixer la vitesse après rebond à 700 ou 500 selon qu'on est au 4eme saut ou pas. Ma première approche était de tenir compte du fait que v4 0 = teste la valeur du compteur de rebonds et laisse 0 ou 1 sur la pile selon le résultat, et de jouer sur la multiplication absorbante : t 200 * 500 + vaut 700 quand t=1 et 500 quand t=0. D'où v4 0 = 200 * 500 + ~ :1.

En fait, il y a plus simple puisqu'à défaut d'expressions conditionnelles, j'ai séparé prédicats et actions. Rien ne m'empèche donc de réécrire la machine d'état sous la forme suivante:

state12->state13 on done [v4 0 =] (700 ~ :1)
state12->state13 on done [t] (500 ~ :1)
state13->state12 on fail [t] (v4 1 + 4 % :4 0 :1)
Alors oui, bien sûr, c'est horrible à relire. L'idée n'est évidemment pas d'obliger l'utilisateur final de GEDS à écrire des expressions de ce genre-là, mais plutôt de se donner un "langage intermédiaire" (un bytecode, quoi) comme résultat d'un éditeur de monstres plus graphique.

J'aurai donc quand-même mis un an entre "l'idée sur papier" et le protoype qui tient la route pour la définition des contrôleurs (bon, j'ai fait d'autres trucs entre-temps, évidemment). Et l'intégration des "test de possibilité de déplacements" dans le contrôleur (reléguant les testpoints au rang de sophistication optionnelle) n'est pas la moindre des décisions.

Sunday, April 26, 2009

Petit à petit ...

Le gros du "refactoring" est fait. J'ai bazardé le plus gros de ma gestion de collision basée sur les "testpoints" en faveur d'un test de propriétés sur la zone occupée par le sprite. Et progressivement, je mets à jour les règles définissant le comportement des différents sprites pour en tenir compte.

Une petite modif' d'une classe GobController par-ci, une commande 'xs = 0' dans les transitions entre états par-là ... Ca prend forme. Et j'en ai profité pour rajouter un funky funghi, histoire de voir ce qu'il donne animé.

Most of the refactoring effort on world collisions management is done. I'm now (slowly) updating the state machine rules to take new controllers into account (these use 3-state logic : EVENT, FAIL, NOPE rather than just boolean logic) and fixing bugs due to inconsistencies here and there. Initially,

Comment ?? Vous ne savez pas qui est Funky Funghi ??

Ah oui, c'est juste, je ne l'ai pas encore bloggé ici. C'est la version revue et corrigée de Boppin' Toadstool, qui apparaît dans la bande dessinée et que j'ai pixelisé il y a quelques mois en Suisse. Au départ, je voulais me le garder pour vous faire la surprise, une sorte de petite animation exécutable sur DS où on aurait eu les sprites rejouant le scénario de la BD.

This also allowed me to introduce Funky Funghi, the remasterised poisonous, bopping toadstool into the woods. Funghi confirms that 32x32 sprites work well with the engine and that Gravity controller can apply not only to the hero (though it required a bit of tweaking ^^"). He's also the very first monster to use internal counters: much like the Mad Mushrooms of the shadowlands, Funky Funghi will do three small bops before he goes for a higher jump.

J'ai encore quelques petits bugs à corriger, mais le plus gros est déjà réglé. Début de semaine, figurez-vous que dans certaines circonstances indéfinies, Bilou s'envolait à vive allure, hors de tout contrôle jusqu'au moment où, sortant de la zone de jeu, il provoquait un magnifique "Guru Meditation" ... Comme vous voyez, on revient de loin ^_^

Mais là, il y a un rayon de soleil et je vais aller me faire une p'tite balade jusqu'à la bulle de collecte des verres.

edit: cliquez sur la photo pour regarder la vidéo youtube.