samedi, avril 19, 2014

La p'tite encre qui monte, qui monte ...

Quelques notes sur l'encre-qui-monte que je numérise avant de passer à l'implémentation. L'idée étant d'utiliser soit un polygone soit la fonction "fenêtre/cadre de vue" pour la partie noire ("Master Ink" dans mes notes") et des sprites pour la partie animée (les "wavelets").
Si je veux avoir quelque-chose à proposer pour la NeoCompo de l'été, il est temps que je m'y remette, n'en déplaise à Gomez et son fez. Donc:
  • permettre au contenu de la mémoire vidéo des sprites de recevoir des étapes d'animations successives à un seul emplacement (par copie à chaque étape) pour une synchronisation parfaite des ondelettes;
  • ajuster CopyCoords pour que MasterInk ne suive la caméra qu'horizontalement et que les wavelets ne suive MasterInk que verticalement.
  • Mettre en place la machine d'état avec ces éléments-là
  • Faut-il une "caste" spécifique pour les vaguelettes (pour réduire le coût des collisions) ? 


vendredi, avril 11, 2014

Critical Link #1 : replay value.

http://zelda.wikia.com/wiki/Turtle_Rock_%28Link%27s_Awakening%29
Z:LBW (Link Between Worlds, 3DS) étant terminé, ma fille a réclamé à nouveau Z:LA (Link's Awakening, GameBoy). L'occasion pour moi de me donner un défi assez inhabituel pour moi et inspiré de mon analyse de la difficulté de Mario World: terminer le jeu en obtenant le moins d'objets possibles (y compris sans la "master sword" et sans le boomerang capable de transformer les anti-fées en zentilles fées). De quoi redonner un peu de saveur aux donjons 7 et 8, et de me rendre compte que l'Aigle déboule de la gauche quand je me tiens à droite de la tour et vice-versa. Après ça, j'aurai probablement fait le tour des expériences de jeu possible. Celà dit, un bon livre, ça se relit avec plaisir au bout de quelques années.

When a game is story-driven, why would you replay it ? As surprising as it could sound, and despite Z:LA (link's Awakening) is the most scripted Zelda episode ever, it's also the video game I re-played the most. Just like I could read again (and again) a good book, knowing the sequence of elements in Z:LA doesn't prevent me to play it again. Partly because text-reading is nicely balanced with the action. Compared to previous episodes, this purely-guided content was a unique introduction: the original Legend of Zelda had most its overworld open from the start and dungeons could be entered in no specific order.

Z:LA, which item you need for which place.
Z:LA, c'est peut-être le premier zelda où l'ordre des donjons est totalement imposé. En fait, pour pouvoir s'approcher d'un donjon, il faut systématiquement au moins avoir obtenu l'objet du donjon suivant, et les interactions avec les PNJs vont régulièrement imposer d'avoir vaincu un boss pour continuer à avancer.

Z:LTTP (link to the past, SNES) sits somewhere in-between: early dungeons in the Light World must be completed in a specific order, but once you grabbed the hookshot, you can swap dungeons labeled "3" and "4", as well as "5" and "6" can be swapped too. So having all the items and (almost) all the dungeons available thanks to the rent-an-item approach in Z:LBW (Link Between Worlds, 3DS) is more of a "back to the roots" of the series than an absurd gameplay deconstruction. It also means Z:LBW is designed to be played several times. Your first pass in Lorule will mostly be a matter of hazard: you'll enter one of the dungeons fairly blindly and possibly find the hidden item it holds. That (defensive) item will affect how you'll experience the next dungeons: better shield, improved sword or stronger mail ? Chances are that on your next pass, you'll try to optimize that and maybe challenge yourself with a harder dungeon with fewer hitpoints for a higher reward (having the mirror shield as your first Lorule item, for instance).

Only first 3 dungeon give you explorer "keys"
Même dans Z:LTTP (link to the past, SNES), où les donjons du monde des ténèbres ont été numérotés de 1 à 7, l'un ou l'autre peut être attaqué avec de l'avance, et parfois même fini dans le désordre. Chose remarquable, deux des derniers donjons sont accessibles malgré le fait qu'il manquera à Link un objet indispensable pour terminer le donjon en question. Z:LBW est en quelque sortes un retour aux sources: une fois le monde des ténèbres atteint, l'ordre pour attaquer la plupart des donjons est laissé au choix du joueur. Si lors du premier passage, le résultat sera forcément aléatoire et peut-être décevant, celà offre en revanche l'opportunité d'une grande variété de jeu, car si tous les objets "aggressifs" peuvent être loués à tout moment, les objets défensifs, eux, sont cachés dans les donjons, et il se pourrait bien que vous sortiez du donjons sans avoir trouvé le bouclier-si-brillant-qu'on-se-voit-dedans ou la cotte-de-maille-qui-m'aille. Il me manquait d'ailleurs deux minerais de mes deux premiers donjons.

And when you'll consider -- just like I've done last week with Z:LA -- that you would try to speed-run the game and see whether you have the skills to face Ganon with no magic bottle and only your regular (unimproved) master sword and your godfather's shield, remember: the game can also be played in "hard" mode once beaten. Any single ennemy will now cost you two hearts on contact. Yup. Two. And you only have three of them to start with. If you get hurt twice in a row, you're already dead. I guess that will make Z:LBW the most replay-able game of the series.

Enfin, et sauf erreur de ma part, c'est une grande première dans un jeu Nintendo depuis Kirby's Dream Land, pour ceux qui se lasseraient de ré-explorer [lo|hi]rule où que la chasse aux coquillage-surprise avec des 'p'tits gorneaux' dedans laisserait froid, il reste une arme ultime: le mode difficile. Même ennemis, même disposition et tout, mais le moindre contact avec un des monstres qui peuplent la map dès le début du jeu vous coutera désormais deux coeurs. Comme votre barre de vie n'en offre que trois, celà signifie qu'encaisser deux coups successifs se traduira par un game over. Et reprendre un petit coeur ne suffira pas pour vous sauver la mise. En fait, en mode difficile, jusqu'au 2eme donjon, soit on a tous ses coeurs, soit on est à un pas de la tombe.

samedi, avril 05, 2014

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 ...

This is how collision between a Game OBject and the tiled world occurs in my current Game Engine for DS library.  Scribbled note next to the dotted line says "collide(c)" and comments "current code enforces
  • block disappears only if extra & 0x80 is set
  • block reacts only with HERO when extra & 0x40 is set.
where "extra" is the  8-bit reconstruction of the free bits in each special tile
.

mercredi, avril 02, 2014

Ça suffit, les crash!

Rhaa! encore une chouette petite animation perdue à cause d'un bug que j'ai tardé à corriger dans AnimEDS! Ça doit bien être la dixième du genre, où je me dis "décidément, c'est bien confortable et rigolo, comme manière d'animer mes personnages" et 30 secondes plus tard, j'ai un gros écran rouge qui bloque tout parce que j'ai mis un pied dans un "comportement non-défini" de la bibliothèque standard.

En plus, comme je suis actuellement franchement patraque, il m'aura fallu pas loin de trois commit pour y parvenir. Donc je vais remettre mon fauteuil en position relax, et on reprendra les animations ce soir.

I hope with today's fix, I won't get such guru-meditation screens for having clicked within the first 8 pixels of the timeline. It's getting on my nerves to lose the most adorable animations I'm working on again and again. (Sorry for such a short english version. Sickness.)

samedi, mars 22, 2014

Stack'm'up!

totally totem !
Commencer par les choses simples. C'est ce qui m'a permis de progresser en fin un peu sur le systèmes des plate-formes dans mon moteur de jeu, malgré des semaines bien remplies. L'objectif: permettre d'empiler des dumbladors l'un sur l'autre pour que plus tard, Bilou puisse lui aussi grimper sur un dumblador puis sur des objets qui bougent.

C'est un bon premier pas, même si dans l'immédiat, le taille-crayon du dessus ne se rend pas compte du "démarrage" de celui d'en-dessous, ce qui m'a permis de l'envoyer à son tour par-dessus le premier.

that was too much for one chunk.
At last, I've got one more step performed on the game engine. Too many times, I picked up my laptop after the kids when to bed to end up watching things on youtube or hunting for a new car. Somehow, getting at once into "Bilou riding some platform" was intimidating and I couldn't get things in place. When picking up my notes again this morning, I wanted to come back to small steps that can be chunked with a single tea cup. How about having Bilou able to stand on a stunned blador and call it a milestone ? As I started converting blador.cmd into the new cpp-assisted format, it turned out that I could get something even simpler to check the bare mechanisms: having stunned blador stacked onto each others. Well, tonight it's done.

Oh, and I'm afraid you'll have to wait somewhat for my "Critical Link" series comparing the gameplay of 2D Zelda games: my scanner has apparently entered the dark world of non-functioning appliances.
PS: it's easy to explain the fact that the "stacked" blador stays in place: I was missing the "attach to path" statement. More annoying: the new GameObject::ride() method was built under the assumption that Gob's movement in (dx,dy) would be consumed, which is not the case with dumblador's stepped movement.

dimanche, mars 16, 2014

un bout de code ... d'alignement ?

Je range ... Et au milieu de tous les brols qui reviennent de mon ancien bureau et qui ne trouveront pas de place dans le nouveau, je tombe sur une tentative d'illustration du roll-jump de DKC returns précédé d'un morceau de code:

while (! cando(dx, dy)) {
  if (dx!=0) {
    dx=(dx>0)? (((x+w+dx)|7)-x)
               : (((x+dx) & ~7)-x);
  } else {
     if (dy==0) return FAIL;
  }
  dy=(dy>0)? (( y+h+dy ) | 7) - x)
             : (( y + dy) & ~7) -y);
  }
}
return NONE; 

Some code snippet I found on a flying sheet of paper while cleaning up and shifting some books around. It should help dealing with alignment along walls and floors while being carried by a platform. Time constraint do not allow me to integrate it into the source code so far, and I've been wondering where it could be for about 2 weeks, so I'll just keep it here for now. It should complement the newly introduced GameObject::ride() function.

Il doit s'agir de la logique d'alignement impliquée dans le déplacement "sur une plate-forme", gribouillé une fin de soirée, mais comme je n'ai aucun schéma qui l'accompagne et que j'ai été avare en valeurs symboliques, on verra ça plus tard: c'est l'heure des tartines.

jeudi, mars 13, 2014

encre ? yeah !

Je ne veux pas me contenter de flaques d'encres immobiles pour la School Zone de Bilou. Je veux pouvoir en profiter pour construire des puzzle autour du principe des vases communiquants. Je veux aussi pouvoir utiliser un seul niveau qui monte pour tout le niveau, histoire de proposer des niveaux à terminer dans un temps limité.

So far the ink is static. Puddles where you shouldn't fall in or some bottom-of-level deathly trap. I want to go beyond that. One of my favourite puzzle elements in Fury of the Furries was involving sand or water level that you raise or lower to transform the level. It re-appears in two of the levels I had envisioned for the School Zone, and it would be the base for both deep-ink-pit and rush-to-completion arcade-games I have in my furnace. Several elements need to be sorted out to get that working, though: rendering, interaction with monsters and implementation of the levels equilibrium mechanics.


Côté implémentation, j'ai écarté l'utilisation d'un plan dédié (je n'en ai plus). Combiner une ligne de sprites pour l'animation de la surface avec un remplissage de l'arrière-plan par des pavés tout noirs pourrait faire l'affaire quand l'encre va toujours vers le haut, mais pour des vases communiquants, qui pourrait "réparer" la map quand l'encre descend ? ... A moins que la map complète en mémoire ne soit jamais dégradée, et que seule sa copie à l'écran ait des pavés noirs. Mais Infinimap n'est pas conçu dans ce sens-là. Des gros sprites partout, ça ne marchera pas bien. Le plus souple, ce serait sans doute de règler ça à coup de polygones.

Rendering still has a few options possible on the DS, but in my current setup, the constraints start to pile up. I cannot have a dedicated BG layer. I don't have any left since I introduced 3D objects, which need their own layer. I might be using 3D polygons when the ink sits "between" the two layers of the playground -- that's for Deep-Ink-Pit and Bilou's Adventure. When the ink rise up through the whole level as in Rush-to-Completion, I'd better use the "window" feature (or possibly a raster interrupt to affect background priorities on the fly). For the animated surface of the ink, sprites are the best I can think of.

Ensuite, il y a les personnages. En remplaçant les codes du bloc d'encre sur la map du niveau d'*deline, j'ai pu faire en sorte qu'un taille-crayon lancé dans l'encre fasse des gouttes. C'est aussi un premier pas pour faire en sorte qu'une éponge qui tombe sur l'encre flotte (enfin, à moins que Bilou ne se tienne dessus assez longtemps).

Spongebops and other monsters might have to interact with the ink. I made a first step with Dumblador getting "killed" when touching the ink and generating droplets. That required to change the tile properties so that monsters can also interact with the ink. Initially, I thought making spongebop floating on contact would require some intermediate "liquid" tile before the interactive "kill-Bilou" blocks, but it turns out that the same interactive block can also trigger state transitions such as fall/float for spongebop. neat.
That's actually very good news. It means I don't *need* the ink to be tiles in order to trigger the floating. A GOB with the appropriate collision mask will transparently produce the very same kind of state transition.

Je pensais au départ ajouter une couche de tiles "flotteurs" par-dessus les tiles qui blessent Bilou, mais le même code "F_INK" peut à la fois se traduire en une transition tombe/coule pour Bilou et tombe/flotte pour l'éponge. En en faisant une collision ordinaire (et pas un type de tile spécifique, ce que je garderais pour l'eau), ça veut dire aussi qu'un GOB qui possède la bonne zone de collision F_INK peut interagir avec les éponges, y compris pour l'encre qui monte et qui descend.

A partir de ce moment-là, je peux commencer à concevoir les réactions "vases communicants". Un objet-déclencheur peut forcer les deux GOB-surface à se mettre en mouvement. On peut aussi les lier l'une à l'autre pour leur permettre de s'équilibrer, avec une variante du contrôleur qui ajuste la distance entre Spongebop et son pivot. Reste un élément délicat: comment faire en sorte que les niveaux d'encre se mettent en mouvement simultanément ? Un coup de sonde à l'aide d'une zone de collision bien calibrée pourrait faire l'affaire (de la taille du bouquin central, par exemple), sauf que les zones de collision sont normalement définies pour un état donné, pas pour un objet donné.

If the part of the ink that's animated is allowed to be a GOB, the use case of ink-level-equilibrium turns closer to something GobScript can readily handle: it mostly comes down to two ink-surface objects that adapt to each other so that they reach the same level, possibly assigning them top speed that depends on the width of the pit they're in. Then, I have to figure out a way to kick them so that they start moving simulatenously (e.g. after Bilou opened some path between the two pits). I could build something inspired from Badman II boss rooms in RSD Game-Maker, with a lock-type block that shoots invisible trigger-sparks moving along the same kind of directional block as those controlling inkjets.

J'ai donc commencé à construire quelque-chose qui fait fort penser aux boss de Badman II: faire en sorte que la serrure génère des "étincelles déclencheuses" (invisibles) vers la gauche et la droite et placer le même genre de blocs-directionnels que ceux qui contrôlent les déplacement des encriers pour faire en sorte que les GOB-étincelles atteignent simultanément les deux surfaces pour les mettre en mouvement.

If that's funny to demonstrate that the a-priori simple engine can handle elaborated things, it would quickly turn into a nightmare for any sophisticated situation, like the twin-corks in Remi's level (where each cork might trigger raising ink from the other's position). You'd end-up crafting chuchu-rocket puzzles on your map and hope the timing you computed in your mind is correct when launching the level. As a second thought, I'd rather have the surface GOBs both attached to the "lock" and start raising when this link is lost. Yet, it would be nice to have test-zones that can be defined through GOB variables so that the level editor can precisely define a trigger region for each of them (but it's unclear I have true *need* for that in any of the level I designed so far).

C'est rigolo un instant, et ça fait plaisir de constater que les abstractions construites jusqu'ici sont suffisament Turing-esque (comprenez, expressives) pour ce genre de déviance. Mais soyons clair: si on veut passer à un niveau un tout petit peu plus élevé -- les bouchons jumeaux du niveau de Rémi, par exemple -- on peut rapidement se retrouver à construire un puzzle à la chu-chu-rocket qui va promettre de sérieuses migraines au moment de la mise au point.

Il y a bien une autre possibilité: quand un Gob (ici, la surface d'encre) est attachée à un autre (la serrure), il reçoit un évènement si le Gob de référence disparaît. On peut alors faire monter le niveau d'encre suite à la disparition du bloc-serrure -- ou du bloc invisible qui s'est fait détruire par le bouchon-qui-s'enfonce. La bonne nouvelle, c'est que l'éditeur de niveau supporte déjà ce genre d'approche. La moins bonne, c'est qu'un Gob ne peut être attaché qu'à un seul autre. On saura donc faire des niveaux avec 2 bouchons, mais pas avec trois. Cela dit, ça mériterait vérification, mais je pense bien que je dois pouvoir m'en sortir avec juste "2 bouchons" dans tous les niveaux dessinés jusqu'ici.