Friday, April 30, 2010

On se refait un Zelda ?

Ils sont nombreux, sur Internet, spriters, game-makers et même programmeurs, fan de tout poils à se lancer dans l'aventure de "mon propre Zelda"... Mystery of Solarus, 3 Shards of Light, Occarina of Time 2D ... La liste est sans doute encore bien plus grande. Et à bien y regarder, hormis le fait que j'utilisais Bilou comme personnage, mon propre projet de RPG était à 90% calqué sur Zelda: Link's Awakening. C'est inévitable: Zelda -quel qu'il soit- est un jeu qui marque et une fois l'aventure terminée, on en veut toujours plus (constat qui s'étend à tous les univers de Fantasy réussis, d'ailleurs). Dommage, d'une certaine manière que toute cette créativité ne soit pas accompagnée de graphismes originaux, mais tout ceux qui se sont essayés à la programmation de jeux savent à quel point il est difficile de trouver une équipe de graphistes qui vous suive dans votre aventure.

Par curiosité, j'ai regardé d'un peu plus prêt le projet "Solarus", dont le moteur "deluxe" est open-source. Codé en C++ avec du Lua pour le scripting -- une approche assez en vogue dernièrement -- il constitue une bonne base de comparaison vis-à-vis de mon propre moteur de jeu. Par exemple

-- Function called when the player wants to talk to a non-playing character.
-- If the NPC is the monkey, then the monkey sound is played and the dialog starts.
function event_npc_dialog(npc_name)

if string.find(npc_name, "monkey") then

if not savegame_get_boolean(24) then
-- monkey first dialog
play_sound("la_monkey")
start_message("outside_world.village.monkey")
else
play_sound("la_monkey")
start_message("outside_world.dungeon_2_entrance.monkey")
end
end
end
trouvé dans map0003.lua, propose une série de fonctions-scripts qui devront être appelées dans des cas précis. event_npc_dialog, par exemple, est d'office appelée par le code C++ lorsque le joueur tente d'interagir (bouton A?) avec une InteractiveEntity de sous-type NON_PLAYING_CHARACTER. Qui l'eut cru, hein ? Il vaut donc mieux que cette fonction soit définie si vous avez quoi que ce soit dans le fichier .map qui soit interactif. Un fichier complémentaire (map0003.dat), généré par un éditeur graphique, fixe les coordonnées des différents NPC en donnant, ici via la chaîne "monkey", le nom du personnage qui sera passé en argument à event_npc_dialog.


Il y a donc toute une série d'action-clé, véritables "point d'ancrage" dans le code C++ pour définir les interactions qui régissent les donjons et les énigmes d'un zelda. event_hero_on_sensor, event_switch_enabled, event_message_sequence_finished, etc. Dans l'autre sens, le script va essentiellement faire des appels à des fonctions C++, auxquelles on aura assigné un "nom Lua". P.ex.
lua_register(context, "play_sound", l_play_sound);

int MapScript::l_play_sound(lua_State *l) {

MapScript *script;
called_by_script(l, 1, &script);
const SoundId &sound_id = lua_tostring(l, 1);

script->game->play_sound(sound_id);

return 0;
}


Les chaînes de texte constituent un élément essentiel de tout ce système, identifiant systématiquement les entités. Pour un Zelda (où le temps de réaction des NPC n'est pas critique pour le jeu) sur PC (avec 1GHz facilement), ce n'est pas un soucis. Sur console portable, l'issue d'une telle approche est incertaine. Au passage, Lua prend en charge lui-même les constructions telles que if-then, les manipulations de chaîne et les expressions numériques. Ouf.

A noter que la "logique" d'une séquence se retrouve forcément éparpillée entre "parler au NPC", "fin de dialogue", , "fin de mouvement du NPC", "item ramassé", etc. Et également mélangées avec les interactions des autres personnages présents sur la même map. On peut s'en sortir, bien sûr, mais de là à dire que le modèle de programmation est transparent, je ne m'y risquerais pas. C'est une tournure d'esprit à prendre, probablement assez proche de la programmation d'interfaces graphiques sous winwin.
-- Function called when the monkey has finished jumping
function event_npc_movement_finished(npc_name)

if monkey_jumps == 1 then
-- first jump finished: wait a little amount of time before jumping again
start_timer(300, "monkey_timer", false)
elseif monkey_jumps == 2 then
-- second jump finished: start the last jump
play_sound("la_monkey")
npc_jump("monkey", 1, 64, false)
monkey_jumps = 3
else
-- last jump finished: remove the monkey from the map and unfreeze the hero
npc_remove("monkey")
unfreeze()
end
end


Si je n'en suis pas encore au stade où je peux proposer une alternative en "GOBscript Bilou" à un comportement de ce genre, je voudrais le contre-balancer avec le système des "gendoors/killdoors" de rayman designer. Une série d'objet dans le jeu peuvent être reliés à un "compteur" (représenté par le magicien) de sorte que lorsqu'un d'eux (ou tous, c'est selon) entrent en contact avec Rayman, une autre série d'objets (ici, un bonus et un clown ennemi) sont créés ou détruit. Un bon nombre de "salles-à-énigme" dans les donjons de Zelda peuvent se réduire à ce genre d'interaction. Un simple lien, créé graphiquement entre les objets, a donc suffi ici, là où plusieurs fonctions interviennent dans l'approche "solarus-dx".

Suite au prochain épisode ... il est temps que j'aille mettre ma fée au lit ;)

Wednesday, April 28, 2010

Maintenant, poussez !

Armé de mes débuggeurs, je suis à la chasse aux petits "glitches" dans le jeu. Bilou reste parfois bloqué en bas d'une pente sans pouvoir continuer ... ou bien il ne sait pas s'introduire dans des conduits un peu plus étroits, etc. Sans doute des effets combinés du mouvement contrôlé par les animations et des conversions virgule-fixe-vers-entier.

If you have tracked updates of the last 'todo' post, you know I'm busy hunting and fixing all behaviour glitches in the current demo before I move forward to the Apple Assault mini-game. It's not that nice to have a gameplay where your hero might remain stuck at the bottom of a slope or where he's remaining stuck in narrow corners. Many of these oddities are side-effects of animation-controlled moves, which is intended to make Bilou better "fit" his world.

I had the opportunity last night to proceed to instruction-stepping of one such issue (g++ -O3 overdid it: it's impossible to check whether the algorithm is properly working at source code level anymore), which ended up with "Bilou's behaviour isn't buggy: it's incomplete!"


Après un retraçage complet au niveau assembleur (l'optimiseur de C++ fait trop bien son boulot: ce n'est plus possible de suivre l'algorithme au niveau symbolique), j'en arrive finalement à la conclusion que mes efforts pour "corriger le tir" sont vains: le déplacement de Bilou n'est pas buggué, il est juste incomplet. L'animation prévoit que l'on se déplace par pas de 2 pixels. Pas moyen d'y déroger. Si bilou n'est qu'à 1 pixel du mur, il ne s'en rapprochera jamais plus, mais du coup (cf capture d'écran), il ne saura pas sauter puisqu'il n'a pas complètement le champ libre vers le haut >_<.

Bref, pour résoudre ce problème, il me faudra une animation supplémentaire "pousser le mur" (que je pensais toutefois introduire plus tard) dans laquelle le déplacement est indépendant de l'animation (bin oui, quand il pousse, Bilou peut "patiner" sur place, ça ne gène pas). Du coup, je peux déplacer Bilou d'un-demi pixel à la fois s'il le faut pour qu'il vienne se coller au plus proche du mur ... sans y rester scotché ^^".

The 'walking' animation currently imposes that Bilou moves by two 2 pixels increments only. Whatever smaller speed I enforce at "controller" level or in the doslopes() function, they'll be accumulated until a move of at least two pixels is allowed. Of course, if you're only one pixel ahead of a wall, the move is impossible, and therefore it is cancelled. This happens at a point in the code where reasons of the move have been lost, so we're stuck in a dead-end situation. That gets even more troublesome if, such as on the screenshot, Bilou is trying to take a narrow corner: he can't move forward to align with the wall, but he can't jump until that alignment occurs.

Altogether, it looks like I'll have to introduce "wall-pushing" animation much earlier than I initially thought just to ensure that alignment to walls is possible. In such a "push" animation, I can decouple movement from animation frames and therefore I can move Bilou by half-a-pixel if needed, and noone will contest/modulate decisions made by controllers.

Monday, April 26, 2010

Le journal de Link -- fais 'tchô, châl !

Après le bois, la glace et l'eau, c'est sans grande surprise que j'aborde le Domaine du feu -- terre des montagnes, des pierres qui tombent du ciel et des Gorons. Fort heureusement, la difficulté du jeu est dosée pour les gamins, et c'est sans réelle difficulté (comprenez, du premier coup) que je livre la glace au village Goron. On m'a épargné le quizz-du-jeune-étranger-qui-veut-devenir-Goron. C'est déjà ça. Le personnage de Ptigoron dans Z:PH est un élément très sympa du jeu (qui n'est pas sans rappeler le "travail d'équipe" de Link & Zelda dans la tour des Esprits, d'ailleurs), mais si j'avais voulu jouer à "Qui veut gagner des millions", j'aurais acheté "Qui veut acheter des millions", non?

 Le temple du feu lui-même contient quelques éléments intéressants, qui dépoussièrent un peu nos méninges ou nos réflexes. Tir à l'arc depuis un charriot minier, tortues tournoyantes qui ont des allures de miniboss. J'ai un peu l'impression que le bestiaire s'appauvri, en revanche. Un nombre incalculable de variantes de "chu-chu" et de chauves-souris mais c'est peut-être simplement parce que je connais déjà la plupart des monstres rencontrés de par mes Zeldas précédents... ou que je considère les monstres plus innovateurs comme des miniboss ^^". 

Je dois reconnaître que les concepteurs du jeu ont fait des efforts pour que chaque approche d'un temple avec le train soit varié. Par contre, plus on avance dans le jeu, et plus les contrôles du train posent problème. Dans Z:PH, on pouvait encore bien naviguer en cercle autour d'un adversaire de façon à le garder en mire. Z:ST et son déplacement linéaire n'offre pas une telle souplesse, et il faut jongler entre la caméra et le canon pour s'en sortir. Le même style de problème de maniabilité apparaît quand on souhaite utiliser l'arc ou le lasso dans un coin où apparaît une icône (et en particulier en haut à droite de l'écran). Dommage. 

Reste que le ryhtme global du jeu -- une fois sorti d'un donjon -- est *lent*. Même un trajet court en train prend une plombe ... les tornades-à-grenouilles ont été remplacées par des portails magiques, mais ceux-ci sont à des emplacements fixes. Si ça raccourcit "un peu" les distances, ça ne suffit pas pour maintenir la tension. A ce train-là (sic), faire des échanges de marchandises entre villageois pour gagner des "gemmes de force" ne me tente pas vraiment. Dans la même veine, les villages et sanctuaires font vides ... presque dépouillés. La faute au maniement au stylet qui impose un espace plus dégagé pour le jeu ? La faute à la 3D sur-simplifiée sur une console pour laquelle Link consomme déjà un nombre important de polygones ? Les concepteurs ont-ils tenté de se rattraper via les huttes aux multiples détails (mais à l'interaction quasi-inexistante >_<) ? 

Le premier passage à un endroit peut donner du fil à retordre, mais une fois le parcours prévu effectué, revisiter un emplacement n'a plus aucune saveur. Ou alors, c'est juste les fontaines des fées qui me manquent cruellement, de même que l'apparition saugrenue d'un PNJ "perdu" au milieu des montagnes de la mort ou de la forêt interdite. C'était, je pense, une des forces de Z:LA. De son côté, Z:MC "cachait" des minish un peu partout, renforçant l'effet "multi-couches" du gameplay (le même lieu correspond à des expériences variées au fil du jeu).

Sunday, April 25, 2010

InspectorWidget : playfield.

Quelques nouvelles de Bilou, comme promis. Mon débuggeur intégré au moteur de jeu reçoit sa touche finale, puisque je peux à présent visualiser le contenu de la map dans la "zone centrale" (celle où les personnages sont représentés par des carrés et des barres colorés). Je peux observer les propriétés de chaque "tile" du jeu (via 4 chiffres hexa disposés en carrés) et je me suis même offert quelques aides visuelles pour les cas les plus courants (blocs, sol, pentes ...)

At last some news of Bilou dev'ing. The little blue ball hasn't been forgotten, as you can see now. It's just that some of the tweakings I had to do on Inspector Widget were hardly worth of note. But now, embedded debugging facility is virtually complete, including the last-but-not-least ability to render the playfield. I can have near-real-time peek at every tile to know its precise type (through hex values), plus easy-to-read hints for major classes (blocks, floor, and slopes). I can also "hop" from bilou to baddies when inspecting, proceed step by step, trade "controllers report" against GOB internal variables (such as speed, bounce counts, etc.). Now, i'm off to test that on the Real Thing...

L'inspecteur, puisque tel est son nom, peut aussi passer de Bilou à un autre personnage, basculer de l'affichage "contrôleur par contrôleur" à la liste des variables internes du personnage (vitesse, compteur de rebond pour funky funghi, etc.). Reste à tester tout ça en "live" sur DS.

Saturday, April 24, 2010

Le Journal de Link ... et si je plaquais tout pour aller chasser les lapins ?

Le temple de l'océan n'a pas tenu ses promesses. Après les combinaisons innombrables offertes par le grapin de Z:PH, le "serpent" n'est pas si enthousiasmant. J'avoue aussi avoir un peu tourné en rond, et puisque les monstres sont définitivement morts dans ces opus sur DS, le donjon devient très vite vide. Je n'ai pour ainsi dire pas eu l'impression d'être sous l'eau, non plus... et le boss m'a fait l'effet d'une redite de Gollum ... euh ... Bellum.

The Ocean Temple wasn't on par with my expectations. I remember so many use by Z:PH 'grappling hook', and this snake isn't as interesting, unfortunately. I've got lost in the temple at some point, which quickly made it feel empty, since there is absolutely no re-spawning of the monsters in this episode. It's also a bit sad that I got no feel of being underwater -- just like the forest temple did not gave me the feeling of being in the forest.

Back in the tower, I was hoping to show mechanics mastery again and even more Zelda/Link interactions, but unfortunately, we're back with even more turn-based puzzle solving this time. I wanted to do something else up to the point where I eventually turned to a solving guide to move on.


And now, I'll have to relay ice from migloos to goron. So much for the adventure. Maybe I'll leave them all and go for some rabbit hunting, after all.


Idem pour le nouveau passage dans la tour. Je m'attendais à devoir faire preuve d'une maîtrise des mouvements, encore plus d'interactions link-zelda, mais en réalité, le nouveau pouvoir des spectres de cet étage force un système encore plus "tour-à-tour". Comprenez, "Zelda va à un point A, puis link à un point B pour taper sur un switch, qui ouvre le passage à Zelda vers un point C où un chassé-croisé nous permet de continuer à avancer". J'ai fini par m'en remettre à la soluce parce que j'avais envie d'être dans autre chose, mais ça a continué ainsi jusqu'au quatrième glyphe.

Et là, me voilà à devoir faire le train-frigo entre les migloos et les gorons. Ca a l'air tellement passionnant que je vais plutôt aller donner le bain à *deline, tiens.

Friday, April 23, 2010

Le journal de Link : En route vers l'Océan

Mon sac de bombes en poche, et avec la princesse Zelda qui a pris possession d'un fantôme-torche, le troisième étage de la tour des Esprits devient plus accessible. Pourtant, c'est en méditant l'énigme d'une autre façon que j'ai trouvé la troisième goutte de lumière. Je m'attaquais à la mauvaise "torche isolée" dans le mauvais "coin isolé" ^^". Et cette fois, fini le "tour-à-tour": il a fallu se synchroniser bien plus pour emporter la clé électrifiée tout en écartant les monstres de notre chemin. A propos, figurez-vous que notre vaillante princesse, qui traverse les rivières de lave dans son armure, se retrouve tétanisée par quelques rats! Heureusement que je suis là ... euh ... dites-lui pas que j'vous l'ai dit, hein?


I'm back again, third level of the Spirit Tower, with a bag-o-bombs on my back. And the princess took control of a torch-spectral knight. But I'd better think twice about the hints next time: she doesn't find it funny when I'm going for the wrong 'isolated torch' in the wrong 'isolated corner'. And enough turn-based action this time: we had to work as a real team to take that electrical key to the door while cleaning up monsters away from our path.

Oh, by the way, can you believe that our heroïc princess -- who goes straight into rivers of lava -- just ends up scared by rats to the point she doesn't move at all ? don't tell her I told you ;)


Qu'importe ! Me voici en route vers l'Océan, et avec mon premier passager (enfin, en dehors de mon mentor Alfonso et de la princesse, bien sûr). Le train-train quotidien demande plus d'attention quand on doit respecter la signalisation que quand on se sert des poteaux pour s'entrainer au canon... mais ça ne rend pas les choses plus palpitantes pour autant.

Qu'importe. J'ai parqué ma loco pendant que M. Woodcraft répare le pont ... et je suis sur la piste du trésor du vieux Linbeck, le grand-père pirate du marchand de la côte... Haa haaaa! ... Il me manque juste une pelle pour déterrer le trésor une fois que j'aurais résolu cette nouvelle énigme de lumière et de pas vers le nord-ouest. Mystère...


/Link

P.S.: ah, oui, j'ai croisé un certain "PypeBros." aux cheveux en bataille qui me dit de vous dire qu'il n'abandonne pas Bilou, que la dernière "tout-doux-lisse" vous tient informé de l'enchaînement des micro-progrès, et qu'il vous en dira un peu plus dès que son zoumeur de tailles sera au point. Comprenne qui pourra.

Tuesday, April 20, 2010

Spirit Tracks ... journal de Link

Sans être d'une grande difficulté, le donjon n° 2 est plutôt plaisant, avec ses énigmes à cloches. On a pas encore le côté "jongleur d'items" du donjon des glaces de Z:PH (un de mes préférés dans ce jeu), mais on voit que les concepteurs ont cherché des approches innovantes tout en étant suffisamment intuitives. Pas de miniboss cette fois (ooh), juste quatre loups. Par contre, le boss m'a eu: j'étais tellement pris à essayer de lui mettre sa raclée que j'ai complètement loupé le fait qu'il ne me restait plus d'énergie :P

La deuxième tentative est la bonne. Même si je trouve exagérément simple la présence du téléporteur juste avant le boss, je ne m'en plains pas. Et il m'en aura quand-même coûté une précieuse potion rouge (vendue en flacon jetable. Pfeuh! tout fout l'camp)... Ce n'est que pour me retrouver bloqué dans le noir un peu plus loin face à une énigme obscure dans un troisième étage de la tour des esprit pour le moins ... énigmatique.

PS: vous l'aurez compris, je ne visite la soluce qu'à posteriori pour avoir des screenshots à poster. J'ignore complètement quels seront les prochains items, même si je n'ai pas pu m'empêcher d'aller voir la liste des donjons pour m'assurer que le jeu est assez long... Et ce sentiment d'inconnu est pour le moins délicieux d^_^b ...

Monday, April 19, 2010

Spirit Tracks : Quest on Rails ?

Bien que fan de la série "Zelda", je n'ai pas été tenté par l'annonce de Spirit Track, lui préférant même un Professeur Layton. Mais là, je l'ai en prêt, donc je teste ... Une semaine de jeu sporadique, mais déjà quelques points qui fâchent.

If I got it right, the project director of Phantom Hourglass thought that the game was too complicated for the people discovering Zelda with the DS, and he therefore adjusted the bar with Spirit Tracks. I'm unsure this is a wise move, but who am I to tell Nintendo how to make good games, after all. Yet, to my own 30-year-old-child-soul-in-a-man-body, I can't help but pin-pointing the things I'm still unhappy with after one week of testing (reaching dungeon #2)

exit l'overworld ?
Il n'en restait déjà pas grand-chose sur Phantom Hourglass ... Le voilà presqu'intégralement remplacé par une seule et unique map ... The overworld is gone, and with him the multi-layered, teasing and memory-demanding core gameplay of 2D Zelda series. All you're left with is a train network map.

A couper le souffle ?
Le ventilateur magique (version améliorée de l'amphore magique des Minish ?) pourrait être sympa comme premier objet, si ce n'est que la combinaison L+click+souffler ne vient pas aussi facilement que le L+tracer qui activait le boomerang. Par contre jouer de la flûte de pan au micro est plus sympa que utiliser les boutons du gamestick N64 comme un occarina ... The magical Whirlwind surely reminds me of Minish Cap Suck-o-Magic, but it gave me the feeling that L+touching a place while blowing the mike wasn't very convenient for fights. Yet, it hasn't failed me while facing the boss. Re-playing Phantom Hourglass between temple #1 and #2 surely didn't helped.

Inter-Temple Express, anyone ?
Les trajets en trains forcent un rythme lent où le joueur ne peut pas décider d'ajuster le niveau de risque (en cas de combat) à son impatience. J'ai un peu l'impression de jouer à Hugo Délires, pas à Zelda ... Et qu'est-ce que c'est que ces graphismes éhontément "cheap" des passages en train, en particulier dans la forêt aux arbres tout plats >_<
Train travels are pretty boring so far. Repetitive and slow. Surely, they haven't revealed all their hidden power, but there is no way to trade risk against speed as you'd do on a "regular" overworld (where you can decide to jump into fight or to slash-and-run if you feel skilled enough to dodge hazards). And in some places (lost woods?) better-defined graphics were necessary to convey a convincing immersion. That part is failed, imho.

Spirit Tower
Je craignais une redite du temple du roi des mers, les Spectres ont beau être les mêmes, la tour des esprits fonctionne visiblement différemment ... en particulier, elle n'impose pas que l'on revisite le 'donjon' depuis le début à chaque nouveau passage, et le temps y est illimité. Aurait-on perdu l'élément majeur qui rehaussait le gameplay de PH ?
I was somehow reluctant about it... That sounded too much likely to the Oceans' King Temple in PH. And yet, the gameplay is totally different. You can defeat phantoms, Zelda can then invest them and give you a hand (it was about time !). On the other hand, you don't seem to have to re-visit parts, neither are you time-limited. Imho, that was the very core of Z:PH and the most exciting part of the game. I have to admit that my 10-year old nephew isn't exactly of that opinion, which gives rise to nice inter-generational gaming experience.

Here comes the Migloo again
L'intrigue a beau se dérouler dans le monde du roi des mers, je trouve le recyclage des migloos (avec le même genre d'énigme de village digne du Pr. Layton) lamentable. Point.
Come on! The same migloos again, playing the very same role. That's cheap. It's not that I dislike a little brainteasing. Bis repetita non placet.

Les donjons,
Eux, en revanche, ont l'air nettement plus costauds, avec un miniboss et pas mal de surprises dans le "temple de bois". Je regrette, par contre, qu'ils n'aient pas plus "ajusté" le look du temple avec son thème. A l'exception de la salle du boss, le "temple de bois" n'est qu'un donjon générique à la teinte vaguement verdâtre. On est loin du raffinement de Minish Cap.
Dungenos -- despite their vaguely-customised appearance -- seems to be much more solid than their counterpart in Z:PH. Minibosses, innovative elements (key keepers). I'm still missing the aesthetics of Minish Cap, but it seems to play nicely. They are my highest hope for the remaining rental weeks.

Bref, tout celà renforce mon impression que pour un vieux briscard comme moi, ce jeu ne vaut pas les 40€ demandés, mais vu qu'il ne me coûte que 2€ pour 1 mois à la médiathèque, autant lui laisser sa chance et pousser l'expérience jusqu'au bout.
Altogether, it seems that once again, Z:ST is a "don't buy" for me. It lacks exploration and freedom that made the previous one exciting and challenging. It's slowly drifting from magic to steampunk-fantasy ...

Thursday, April 15, 2010

Bat Attack!

Almost everything is in place to build Apple Assault except arenas. Yet, I only had "little stars" to check I can shoot something... So I did a silly thing and started shooting berrybats out of applemans when the appleman is surprised to see Bilou. You may think of it as a performance test ... somehow.

Eh bien voilà: techniquement, le code est prêt pour un "Apple Assault". Je n'ai plus qu'à faire quelques "arènes" à coup de level editor. En attendant ... eh bin, j'ai bidouillé le code de l'appleman pour qu'il jette de petites étoiles et des berrybats lorsqu'il apperçoit Bilou. Ca ne sert à rien, mais c'est marrant, et ça me permet de tester les performances du moteur de jeu. De ce côté-là, rien à signaler.

Par contre, dès que j'appuie sur "START", c'est la galère. Avec autant de GOBs qui apparaissent, et vu le comportement des BerryBats (suivre Bilou), je suis régulièrement en contact avec une demi-douzaine d'entre-elles ... Et InspectorWidget essaie de remettre à jour son affichage à chaque fois, avec un "trashing" inévitable, puisqu'il ne peut m'en montrer plus que 3 à la fois. Je prends donc note de ces 2 ou 3 petites choses à règler mais qui n'empèchent pas pour autant de se lancer dans la réalisation du mini-jeu.

A few things have to be fixed, though they do not prevent the Apple Assault game to be started.

  • [done] Appleman remains too often "stuck in the air" after a fall.
  • [done] InspectorWidget should not break more than once per frame when a new monster enters the collision area. Plus I implemented a simple way to switch the focus to other GOBs (just click their "headline").
  • [wish] I need to randomize somewhat the behaviour of berry bats, which tends to "glue" to each other rather than tracking Bilou individually.
  • [done] make sure I can step-debug without having Bilou to jump. now START = debug, L (when debugging) = step and L+START = continue. That allowed me to inspect the stop and to figure out that the "slowing down" of Bilou was not accounting for walls. So you indeed receive a "stop" signal when hitting a wall, but still advance by one pixel into that wall when "stopping". That was enough to have Bilou then "glued" to the wall. It interferes with usual "L+click" commands, though... maybe I should've used R+START.
  • [done] When something land on ground, its speed is adjusted so that it exactly hit the ground. The "impact speed" -- that we'd like to use for bouncing -- is lost. We should keep that in a specific var. Bounces make Bilou harder to control, though.
  • [done] there is a 1-frame lag between sprite positioning and screen positioning that gets visible when falling at high speed (baddies get 'sucked' by the ground). We may want to delay camera move by one frame so that computed GOB coords is indeed valid.
  • [wish] replacement of a "gun" by a new one doesn't seem to work very well. I've got enough "spare guns", though.
  • [done] I miss something to perform x-align-against-block when a horizontal move is cancelled. That's why it's so hard to climb in the tree. There's interference with animation-controlled movements here.
  • [done] Work out a fail-proof initial state for Appleman and Funky Funghi. such failures interfere with game debugger.

Wednesday, April 14, 2010

oamstack from XeO³

Bion, ajouter des sprites dans tous les sens, c'est sympa. Continuer à avoir des sprites dans le 2eme niveau, c'est mieux. L'ennui, c'est que le GuiEngine, comme son nom l'indique, a été au départ conçu pour gérer des interfaces graphiques (SEDS et LEDS), et pas des jeux, ce qui signifie qu'il n'y a pas la possibilité à ce niveau de libérer des sprites -- ou plus précisément, les OAM, c.à.d. les zones en mémoire vidéo qui décrivent l'emplacement et les propriétés des sprites. Qu'à celà ne tienne: m'inspirant du "stack allocator" pour 6502 du projet XeO³, je rajoute une petite surcouche ...

I was making sure that sprites could be reclaimed and that you keep seeing little stars even if you got hit > 60 times ... Just one problem here : Engine::allocate is the only way to inform the GuiEngine of how much sprites it should sync to the DS video memory ... As the level is reset, that number is reset to 0, but since all the Gobs of the level have just been "pushed" in the oamstack ... well ... Engine::allocate() is never called and thus no sprite show up ... at all. Trivial to fix, but reminds me that you should always think twice when you alter the behaviour of something ... And that was a nice occasion to mention Dailly, Kekule & Russel's work on XeO3 : the Ultimate shoot'm'up for Commodore Plus/4 (which has some armalyte taste, if you ask me) and how this code gets inspiration from the 6502 "stack allocator" for bullets in that game.

Ca n'a pas tout à fait marché, mais presque: le GuiEngine retient le nombre de sprites qu'il a effectivement alloué et ne copiera en VRAM que ceux-là. Or, lorsque le niveau recommence, tous les OAMs précédemment restent dans la "pile de sprites recyclés" alors que le GuiEngine pense qu'il n'y en a aucun en service. Le résultat ? Bin le jeu tourne, mais plus aucun sprite ne s'affiche. C'est plutôt bête comme chou à régler, donc je vais aller règler ça pendant que vous découvrez en détail cet étonnant projet XeO³ mené par Mike Dailly, un (ancien ?) programmeur de chez DMA design dont je suis le blog depuis un moment.

edit: Btw, yes, this implies u8 oamstack[128] because there is at most 128 OAMs per screen and that the game is only on one screen. I can't think of another allocation scheme that would save me more and don't disturb run-time operations. I'd love to extend the mechanism to GameObject structure themselves (which have higher allocation overhead)

edit++: funny, they seem to use the same kind of "script-encoded-into-asm-constants" approach than I used in 2000 for Out'm'UP :).

Sunday, April 11, 2010

Une bière fraiche ! Dans un verre propre, nom de ... !

Avec le départ du professeur Ribbens, c'est sans doute une page de l'histoire de Montef' qui s'est tournée, quelques années (mois ?) seulement après le début de ma carrière. Comme pour beaucoup des cours que j'ai suivi, j'ai peut-être bien eu la chance à avoir reçu du maître du Scheme en personne une initiation aux techniques des continuations et du "data-driven programming". Et sa rétrospective sur les LISP machines et techniques de garbage collection était tout simplement magistrale.

In my 3rd year at University, I've finally been taught a programming language that forced me to re-think everything I thought I knew : LISP (and I practiced mostly its Scheme dialect). Beyond the charismatic character of Pr. Ribbens whose motto could more or less be translated in "brew sana in f**ing bottlore sano", it introduced me to "data-driven programming" and design of language-specific processors. To make a long story short, "data-driven" is what you feel you should be using when you start going beyond 10 rooms in a Lone-Wolf game : keep the code short, simple and generic, and have it proceed through structured data in order to obtain the desired result.

C'est de "Data-driven" justement, qu'il est question ici, puisque j'ai décidé de ne pas directement *coder* la logique de mon jeu, mais de la décrire par des structures de données traitées par un moteur qui reste plus simple et plus générique. Une approche qui me ralentit peut-être par moment mais qui me titille : je veux en avoir le coeur net et vérifier par moi-même si oui ou non il sera possible de construire un jeu de plate-formes sophistiqué de cette manière.

Les "livres dont vous êtes le héros" sont sans doute le meilleur exemple possible de "data-driven programming": tout programmeur qui a un peu roulé sa bosse "sent" bien qu'il y a moyen de faire mieux que

sub Salle42
print "au détour d'un couloir obscur vous entendez un bruit sourd ..."
print "1. vous dégainez Voleuse de Vies"
print "2. vous vous avancez dans les escaliers"
input "votre choix"; choix
if choix = 1 then Salle44()
if choix=2 then VousEtesMort()
. Programmer une fonction par salle / évènement (en fait, par numéro de "chapitre" dans le livre) serait extrèmement pénible, la logique du jeu se retrouverait noyée par des éléments de second ordre comme "est-ce que le clic se trouve dans la zone s'avancer dans l'escalier ou dans dégainer la Voleuse de Vies?" Sans parler de la difficulté à gérer les modifications du scénario. On préfèrerait de loin pouvoir stocker toutes les "données" du jeu dans un format à part ... un fichier texte avec des "macro-commandes" pour les vieux patchs de mon genre (et leurs mentors) ... un document XML pour les afficiandos de l'UTF-8 et autres codeurs post-moderne. Une S-expression pour les fans de "recueil de petits problèmes en Scheme", je présume. Peu importe, finalement, la forme: ce qui comptera, c'est le fond, la sémantique, le modèle sous-jacent.

My "game script" and the state-machine-based-monsters is deeply influenced by this technique. Unlike your regular scripting language (Lua ?), building a data-driven game engine means that you're building with line of code a software microsystem that will process data in a specific context and for a specific purpose. You are free to define the line between code-bound function and data-driven function, and placing that line at the right place will be the key to efficient processing.

Bref. Pour mon jeu de plate-forme, le "modèle" de données est un peu plus complexe, fait en partie de pixels et de commandes qui décrivent les machines d'état des différents intervenants. Une réminiscence du cours "Ingénierie du Logiciel Orienté-Objet" et de ma confrontation avec l'UML, et dans une moindre mesure, avec le formalisme des automates à états fini. La frontière entre data-driven programming et langage de script complet (cf. microLua pour DS) est sans doute ténue ... Elle explique sans doute la décision parfois curieuse de garder certaines choses "en-dehors du script". La détection des collisions, notamment, ou la prise en charge des animations. J'ose espérer que cette volonté de "rester juste un cran en-dessous d'un DS-Basic" me permettra de garder un moteur de jeu suffisamment efficace.

Avec mon "InspectorWidget" désormais opérationnel, on se rapproche aussi d'un éditeur graphique pour ces machines d'état ... Et certains "défauts" du modèle actuel deviennent flagrant. Par exemple, Bilou saute, cours, nage, attend ... autant d'états que je dois déclarer et que je relierai ensuite les uns aux autres par des transitions, p.ex. "lorsque les boutons changent, si le bouton 'saut' est enfoncé, alors modifier la vitesse verticale et passer dans l'état 'saute'". Par contre, pour que l'appleman puisse être assomé quand Bilou tombe dessus, il faut que tous les états de l'appleman mêne vers l'état "appleman assommé" si la 'bonne' collision se produit. Il est facile d'en oublier l'un ou l'autre en cas de modification, et c'était d'ailleurs en partie la raison pour laquelle il était si difficile de s'en défaire dans les démos précédentes du jeu.

UML state machine formalism was one of other things I learnt that very same year, and it looked like a powerful way to express behaviours while avoiding repetitive boilerplate code to be described ... So my current data model for GOB behaviours is mostly implementing that. So far, it has the limitation that a transition is always flowing from exactly one state (to exactly one other state). It's expressive enough (that is, there isn't a behaviour you cannot implement with that), but it's not code-friendly in that when you actually want to implement a transition that should apply from (almost) all other states to a specific state, you can quickly forget something. It actually occured with the Appleman that couldn't be stomped when walking to the right simply becaused I missed some state20->state33 on hit0 [t] statement.

Je cherche donc depuis quelques semaines à ajuster le modèle de manière à pouvoir justement exprimer "depuis tous les états, ..." ou "pour tous les états où X est au sol, ..." et donner une transition unique qui s'applique à un groupe d'état d'entrée. Comme prévu de longue date, d'ailleurs (cf. ce schéma du comportement de l'Appleman). Outre l'économie de temps de parsing au démarrage du niveau et de quantité de mémoire (à mon avis négligeable), celà permettrait de pouvoir d'un seul clic dans le débuggeur forcer un point d'arrêt sur "je vais me faire blesser", quelque soit l'état actuel de Bilou. Pour l'instant, avant le debugging, il était nécessaire de passer tous les mouvements de Bilou en revue pour cliquer sur "transition vers l'état n° 15" depuis chaque état. Pas franchement folichon.

I haven't added such "group transitions" yet, but at least I made a nice step towards it by letting states and animation be defined in the context of .cmd files, so that each monsters' state machine can be written without having any knowledge of what other monster do and how many animations they use. Then, only a small amount of the states are "imported" by the master (level) script.

I'll have to adapt the level editor accordingly, but it should make InspectorWidget more friendly to use as the 'kind' of monster is now part of the state's name. Moreover, once the group transitions are in, activating a breakpoint on "jumping->hit" should equally activate "standing->hit", "walking->hit" etc. as long as you used a group transition for "{jumping, walking, standing, ...} -> hit"


Un premier pas dans cette direction, ç'a été de donner à chaque fichier ".cmd" (généralement un par personnage ou ennemi) son propre "répertoire" d'états et d'animations, désormais indépendants de ce que font les autres. Seuls certains de ces états seront "importés" par le script qui définit le niveau en cours pour placer les intervenants sur la map. L'éditeur de niveau devra être adapté, bien sûr (eeh oui. Faire et défaire ... that's the question), mais ce sera pour un mieux: plus besoin de passer en revue toutes les positions de Bilou, ni de savoir lequel des états de l'appleman convient comme état initial. Les groupes de transitions devraient s'y greffer plus agréablement.

Et InspectorWidget lui aussi en bénéficie, puisque les monstres sont maintenant nommés "fu01", "ap07" ou "wo00" plutôt que d'être représentés avec un simple nombre.

Thursday, April 08, 2010

Gotcha!

Bon, bin voilà. J'ai l'affichage des zones de collisions et j'ai du coup pu mettre le doigt sur le problème observé avec les Applemen qui a motivé tout ça. En bleu, la zone "fragile" de Bilou, en vert sa zone d'attaque. En rouge, la zone d'attaque de l'Appleman et en bleu clair sa propre zone "fragile". Pour pouvoir assommer l'Appleman, il faut que vert et bleu-clair se recouvrent sans que bleu et rouge ne se touchent. Le carré bleu clair au milieu de la zone d'attaque de l'appleman est évidemment une erreur dans le script du jeu ...

Well, here we go. Collision areas are displayed, updated as I proceed with step-by-step detection and coloured depending on their role. Blue is Bilou's weak zone, green is Bilou's attack zone. Red is monster attack and cyan is monster weakspot. Only 4 zones can be shown at once with InspectorWidget, so you've got to click the "a" and "p" of the appropriate monster to trigger the correct display ... that's still manual. You also have to "guess" who's who using monsters coordinates and state number.

I bet i could keep improving the UI, put coordinates in smaller font around the sprites, and have the sprites of the monsters shown in the "boxes" as on the sketch'up. But who'd care ? I pin-pointed the bug that made the applemen so hard to defeat: with the "weak spot" (cyan) in the middle of the "attack area" (red), how could I possibly stomp it without having Bilou (blue) entering that attack area ? That's only possible if Bilou's speed is high enough, etc. I think I'll rather fix *that* instead, and then keep on working towards either Nuts'n'Bolts or Apple Assault project.

ATTR0_ROTSCALE : partie I

Tout comme le GBA et la SNES, la DS est capable de zoom et "rotations" sur un sprite. Une fonction quelque peu obscure, sans doute parce que liée à une implémentation hardware alors qu'on serait tenté de penser "transformations géométriques" (comme dans le cas d'OpenGL) ou "programme de rendu". Eh bin non. Puisque je projette de me servir de sprites aussi pour la visualisation des zones de collisions pour mon InspectorWidget, voilà un peu de bidouille pour identifier les quelques points importants à prendre en compte. Mon "sprite de base" est une sorte de grille/radiateur noir dans une zone de 16x16.


Engine::sprites[oam].attribute[0]=ATTR0_ROTSCALE|ATTR0_ROTSCALE_DOUBLE|
   ATTR0_TYPE_BLENDED|ATTR0_SQUARE|y;
Engine::sprites[oam].attribute[1]=ATTR1_ROTDATA(oarot)|ATTR1_SIZE_16|x;
pSpriteRotation sr = Engine::spriteRotations + oarot;
sr->hdx=0x100; sr->hdy=0x0; // 8.8 fixed point : 0x100 is 1.00 here
sr->vdx=0x0; sr->vdy=0x100;
ROTSCALE_DOUBLE affecte la manière dont les coordonnées sont interprétées. C'est la zone de visualisation -- de 32x32 ici, matérialisée par les # blancs -- qui aura ses coordonnées en (x,y). Le sprite proprement dit, lui, verra son centre coïncider avec le centre de cette zone.
Si je donne les paramètres (hdx,hdy,vdx,vdy) = (1,0,0,1) -- ce qui correspond à un affichage sans aucune transformation -- mon sprite apparaîtra donc w/2 pixels plus à droite et h/2 pixels plus bas que les coordonnées (x,y) que je lui fixe.

A noter que [hv]d[xy] indique "de combien avancer (en X et Y) dans la "texture" du sprite lorsqu'on avance d'1 pixel Horizontalement ou Verticalement sur l'écran. Du coup, un zoom grossissant s'obtient avec des valeurs de hdx plus petites que 1.

Si je zoome (0.5,0,0,0.5) puis (0.25,0,0,0.25), le centre de mon sprite continue effectivement à coïncider avec le centre du "viseur". En revanche, impossible de sortir de la "zone de visibilité" de 32x32, même avec un facteur de zoom (4x) qui devrait m'afficher un sprite de 64x64, je ne vois plus que le centre de l'image.

En clair, le hardware permettra sans problème de légère déformations, comme celles subies par Morton Koopa Jr., et n'importe qu'elle réduction de taille (jusqu'à 256 fois plus petit ;), mais pour un zoom prend-toi-ça-dans-les-dents tels qu'on en a vu dans le combat contre Bowser dans SMW ou dans Turtles in Time, il faut ruser...
  • soit en utilisant plus de sprites,
  • soit en prenant des sprites plus gros que ce qui est nécessaire (p.ex. 64x64), zoomés de manière logicielle et rapetissés artificiellement par le hardware -- probablement la solution pour mes zones de collision,
  • soit (à mon avis plus plausible) en utilisant un fond plutôt qu'un sprite pour le boss. Ca s'est régulièrement vu sur NES où les sprites avait des tailles ridicule (8x8 ou 8x16 :P)
PS: tests réalisés sous Desmume 0.9.4 (linux) et confirmés par le hardware.

Wednesday, April 07, 2010

It's getting operational.

It's not quite progress that can be "shown off", but it's progress nonetheless! The "monsters" area of the integrated debugger -- the so-called "InspectorWidget" can now be used to set traps "on new monsters encounters". It also displays state and GOB numbers, coordinates and indicates how many active and passive collision area the GOB has. When clicking one of the letters representing an area, its world coordinates are displayed, allowing for comparison against hero's areas. Now, the only missing feature is a graphical rendering of the situation ...

Avec le code de "support" mis en place hier, le débuggeur interne de mon moteur de jeu -- j'ai nommé InspectorWidget -- avance bien. En quelques clics, je peux forcer le jeu à s'interrompre chaque fois qu'un nouveau monstre s'approche de Bilou, continuer le jeu pas à pas ou en continu, programmer des points d'arrêts sur les transitions d'état de Bilou, et demander les coordonnées des zones de collisions de tous les intervenants. Fonctionnellement, c'est quasiment complet. Je n'ai plus qu'à coder l'affichage, histoire que les zones de collisions et les sprites apparaissent comme dans la "version papier" ... parce que les chiffres dans tous les sens, ça n'est pas très sexy, et ça n'accélère que moyennement l'analyse.

Tuesday, April 06, 2010

un rien de mise au point

Bonne petite journée bien remplie ... Le nouveau système de debugging pour Bilou avance, avec déjà la possibilité de placer des "breakpoints" sur les transition de la machine d'état, et l'affichage des coordonnées des zones de test. Je n'ai pas encore toutes les fonctions souhaitées, bien sûr. Je dois encore rajouter la possibilité d'interrompre l'exécution lorsqu'un nouveau monstre apparaît dans le voisinage immédiat de Bilou, histoire de pouvoir faire un break *aussi* sur les transitions des monstres proches.

Par contre, le jeu est actuellement interrompu immédiatement après la collision qui m'intéresse -- et non pas immédiatement avant, comme je l'aurais voulu :-/

At last a little day off. I've been working a bit on the in-game debugger for Bilou, giving it the ability to set breakpoints on state transitions. That's not yet covering 10% of the desired features, but it can already be somehow handy. Unfortunately, the game is interrupted *right after* the collision of interest, rather than *right before* it happens... I need to think about that a bit more, but first, some dish-washing is waiting for me: I've got some friends at home last Friday, and the glasses aren't clean yet...