lundi, juin 17, 2013

Spongebop Rodeo

I now have a (partly-)working prototype where Bilou can grab a Sponge bop and stay hooked while swinging, but I have to hard-code the offset between Bilou and the Sponge as arguments of the copycoords controller. Not so elegant. Plus, if I also allow to grab on Bladors (granted, that's a silly idea, but it helped for debugging :), those offsets are no longer correct and we're rather grabbing some point in the air over Blador's top-right corner >_<

Bien. Bilou peut maintenant s'accrocher aux éponges en balance. Il y a encore des soucis non résolus avec l'animation prévue à cet effet (d'où l'absence de démo jusqu'ici) et un désagrément mineur: il m'a fallu ajuster à la main et préciser dans les paramètres du comportement "accroché à X" la position relative de Bilou et de l'éponge. Au moment d'ajouter le comportement "assis dans un encrier", ça me démange un peu.

I initially had plans for making those "grabs" act on an are, and it would make sense to actually state "copycoords(a.bottom=b.top ; a.center=b.center)", but I've just said that accessing GobAreas within a controller isn't practical.
Expressions handling a collision have access to some extra-context variables (wc-wf) which were just involved in that "repel" behaviour that made inkjet "solid".


  • xcontext[0] - wc -- collision flags
  • xcontext[1] - wd -- unused (0)
  • xcontext[2] - we -- X-axis center-to-center distance
  • xcontext[3] - wf -- Y-axis center-to-center distance
  • xcontext[4] -- not in GobScript - X-axis area overlap
  • xcontext[5] -- not in GobScript - Y-axis area overlap
 The idea would be to use some GobScript to compute/force the desired location, possibly record it in some GOB variables and have the CopyCoords controller simply enforce that offset from the GOB variables.

Il y aurait bien des solutions techniques pour automatiser ces coordonnées relatives en "alignement vertical, centrage horizontal", à la façon des éditeurs de diagramme ... il y aurait même un "chemin de moindre résistance" pour construire ça dans le contexte actuel. Mais soyons honnête: ce n'est *pas* un élément nécessaire pour le programme. J'ai un seul objet auquel Bilou puisse s'accrocher de la sorte (l'éponge) et je dois de toutes façon utiliser une autre animation pour Bilou-dans-l'encrier (donc, autre état et autres paramètres). Ce serait donc de la généralisation intempestive et prématurée! Caramba! Ça le ferait pas!

I could thus extend the "interaction opcodes":


  • A[p]: attach [with path] evaluating object to context object (works in hit and found)
  • D: detach evaluating object (works in any transition)
  • R[xy]: repels context object away from the evaluating object (works in hit and found) [only along x/y axis]
  • L[xy]: line up the evaluating object with context object (mirrored repel)
  • C[xy]: center the evaluating object with context object (wish)
Now, let's be honest. That's a "wish", not a "todo". I only have one grabbable ennemy so far, and only Bilou grabs it. "hard-coded" offset are just fine in that context, and alternative are "premature generalization". I thought about all this because I'll also need a "copycoords" when Bilou sits in an inkjet, but that will be another animation and another state, so other copycoords offset is just fine.

samedi, juin 15, 2013

NSMB Editor !


The fact that I've just beaten the game without using a Mega Mushroom against final Bowser on *deline's request shouldn't let me forget that new Super Mario Bros on Nintendo DS is now 5 years old... Much enough to allow level editors to have been created. And I'm really glad that NSMBe works fine on my linux, since it's a mono-friendly Csharp program ^_^. Much easier than running Lunar on Wine.

 As you guess, I'm not that interested into crafting alternate levels for nsmb, although thanks to my ds linkers, I could even play the modified ROM on the Real Thing. But I'm deeply curious to discover how the thing was built. As I had expected, many of the special platforms you can walk on are "sprites" (either made of OAM or using 3D hardware ? I still couldn't tell). Obviously, not all level may use all the sprites, but what is interesting is that they have been organised in swappable "banks". You can't have e.g. giant Bowser and Nessie starred in the same level because they both belong to bank 5. But you're free to chose wheter you complement them with Hammer Bros. or Chomp Chomp, with mushrooms or tornados or moving platforms, etc. Spawn points, door/pipe entrance are also managed as "sprites".

 Coins and blocks belong to the "ground" layer. Even item-providing blocks. I trust that to be made of good old tiles, as there can be only one layer of 3D objects and that sprites already use it... yet it remains to be proved. One reason that make me hesitate is that you edit it in terms of "objects" and not merely by laying out tiles on a grid. Each object can be individually selected, moved along, dropped, resized, etc. It is very efficient, compared to what LEDS currently offer, so I'm not surprised. It leads to funny things such as many different patterns of 'coins' so that you could have vertical, horizontal or dithered coins arrangement with a single 'object'.

And that doesn't seem to be a mere side-effect of the editor: the level has those objects, truly. If you click on the top edge of a platform that's already part of the design, you select the whole top. I can only imagine that this helps storing levels using less memory. Given that there's a lot of empty space and repetitions in NSMB maps, storing the level as "paint commands" (fill x1,y1 - x2,y2 with object #12) where object12=[start at tile 42, width=3, height=4] is much more compact than storing the whole map, and allows easier edition cycles. That would also explain the presence of "erasors" patterns that wouldn't make any sense if the level was 3D-rendered (afaik). When the level is loaded, the painting list is streamed from the ROM card (unlike previous Nintendo console, the DS doesn't have direct access to game ROM through its bus, iirc), a large chunk of RAM is allocated, and the commands are applied to know what to map on-screen. This in-RAM map would then be dynamically updated as coins are taken, blocks are used and bricks are destroyed... it all make sense.

Alternatively, you could just 'unpack' part of the level by applying a clip mask on the "paint commands", and have a level-sized bitmap for "interactive tiles".  Both coins, bricks and blocks are binary (original/used) so you'd mix that information with a paint command and still be able to retrieve the start of the level as the player left it although it wasn't permanently available as a tile map in RAM.


jeudi, juin 13, 2013

Alternate ... for what ?

Reading again the "Alternate Path" post from Kirby Kid (on my cybook, from his 4-years archive) raised the question "Why would players put the effort to follow your alternate route  ?" ... And it seemed to be a deeper question that I'd have thought at first.

It boils down to rewards you can offer to a player, and their value. I bet you wouldn't quite feel thankful to the game designer if an alternate route you found by careful exploration led to a mayhem of fireballs, then to a bunch of spikey monster falling from above ... just to discover a "recover health now" item and an exit to level 1-2.

Things you could receive that would be really worthy of your effort are

  • Control over your avatar's destiny. Whether or not some level is mandatory, whether you need to pass this boss. Shortcuts are an obvious representative of this.
  • Control over your #1 ennemy. I don't necessarily mean the final boss, but the #1 source of contrary motion in the game, the one thing that you're always fighting against. In Mario, this is gravity and this is why P-wings, feather cape, super leaf and blue yoshi a such a high reward in the series.
  • Control over your playtime. Extending through 1-UPs, control through save points or codes.
  • Forgiveness for player's mistakes, either past (health recovery) or future (potions, shields, starman).
  • Make the impossible possible. Grant unique abilities. I think about the Hammer Bros. suit.
When thinking about it, the closer you are to the core mechanic of your game, the most likely your 'secret' will be important in the eyes of the player. I also note that the same type of thing may have a different impact depending on whether you're given on the time where you can use the item.


 Heal now, Heal later, Heal whenever you want, auto-Heal when truly needed or get additional heal forever. The same function -- healing -- but the more control you have over its timing, the greatest it feels. Sorry for picking SMB3 again, but it offers the same range of effect on level skipping. With its unique "overworld", the player receives many items to skip levels through hammers (enable access to pipes), cloud (fly over a level, but it remains closed, so you're better succeed on the level just after), music boxes (stun Hammer Bros. and skip encounters). Even the traditional "welcome to the warp zone" (which always happens at specific location) is replaced with a warp whistle (warp whenever you're bored with your current game).

SMB3 also offered similar timing control with power-ups. Some are given immediately, other are given as playcards that the player stocks and use before entering levels. nSMB wii used a fairly similar technique. nSMB and SMW had even finer control with the ability for the player to summon one power-up whenever you want -- but just one. Feel like this final bowser battle would be too difficult for you ? Just make sure you stock an Ultra Mushroom before entering the castle.

Other approaches have been tempted by game designers, such as alternative endings, mini-games and such. It is however more dangerous to go that way. An alternative ending needs to be quite carefully crafted so that the players feel rewarded for it. It works fairly well in Cave Story, where saving the character you emotionally engaged with sounds worth the effort. It failed in Commander Keen : Goodbye galaxy, imho, where all your effort in finding and beating the secret level just ends up into a low joke.

Same goes for a minigame. Why would "whack-a-mole" game be a "reward" for exploring in a platforming game ? You need a franchise that is terribly strong for that to work. Rayman 1 on PC offered a homebrew-quality arcanoïd clone when you beat the platformer. That's so much disconnected ! So cheap compared to the whole stock of P-Wings you'd get for beating Bowser in SMB3! But of course, Rayman lacked a "floor skill", so if you've reached Mr. Dark, you have nothing left to explore because you needed to collect all the cages to be allowed in Candy Castle.

I do feel, though, that for a game where mystery is a significant part of the game design, unveiling back-stories could be perceived as a worthy reward. I'd just make sure that those extra material can be "consumed" when the player feels so (i.e. available through the game's main menu) rather than forced as a cut scene.

vendredi, juin 07, 2013

walking on platforms

I was somehow 'home alone' Wednesday evening. But my eyes were stabbed by my laptop's backlit. Perfect mood for some thinking on game engine features with some pens & paper. Theme ? How to enable Bilou walking on platforms. I initially hoped that a GOB reference would be sufficient to "walk on a platform", but that would restrict us to "platforms that have just the shape of the object's bounding box, while there is much more I intend to do with a generic "dynamic path" abstraction!

Cette fois, je pense que je tiens enfin le bon bout. J'ai réussi à fusionner la notion de "chemin" modifié dynamiquement (pour les cordes qui se balancent, entre-autres), et de plate-formes.Tout ça en prenant le temps, pour une fois, d'y réfléchir en cherchant quelles sont les questions auxquelles il faut apporter une réponse plutôt que d'essayer de construire une solution en partant d'une page blanche. Un système efficace qu'il faudra que je réutilise plus souvent.

And for once, I didn't started building a solution, but rather by writing down the questions that need to be answered. Once again, it proved being a powerful exercise. The questions were:

  • Is the "path" object permanent ? sometimes
  • Is the "path" object redundant ? only in the basic case
  • Could a GOB walk on more than one path (at once) ? no
  • Who 'own' a path ? usually, a GOB.
  • How many path can be owned by an owner ? I don't want to limit.
  • Can we be both 'on path' and owning a path ?  Yes (see picture above)
  • How could the owner of a path force objects on that path to lose their reference ?
Now, it's time to be more precise. The path is redundant with collision box in the very case of an horizontal platform. Yet, collision boxes (GobAreas) are not easily accessed from a GameObject reference. For once, I recall of Super Mario World's slanted platforms in the Chocolate Zone. It's still linked to a GameObject, but it will require a dedicated y=f(x) function.

La clé, c'est d'arriver à la formule "l'objet GPath est redondant avec la GobArea -- les zones rectangulaires utilisées dans la détection de collisions entre sprites -- dans le cas précis où l'on veut que le chemin aille de (gob.x+area.left,gob.y+area.top) à (gob.x+area.right, gob.y+area.top)". En d'autre termes, GPath est une abstraction générique, et le GobArea contient les données nécessaires pour cette abstraction dans un cas précis, mais on ne souhaite pas que le code utilisateur soit exposé à ces données. L'exemple-type d'une classe implémentant une interface ^_^. Il me faudra bien sûr d'autres implémentations si je veux des plate-formes inclinées, rotatives, basculantes, et tout ce que NSMB a apporté aux jeux de plate-formes ... mais le code de base "marcher sur une surface" restera là.

I also think the "path" we walk on must be potentially dynamic, that is, manipulated by a controller, for swinging ropes (example of a vertical path) or those NSMB dancing giant mushrooms. Writing it down as "only in the basic case (GobArea), but as a function of what I already have" made the bell ringing: GobArea is a specific implementation of the GPath interface. Virtual methods and inheritance are what I need to avoid cluttering controller's code with interpretation of coordinates array.


It also nicely solves the "ownership" (and thus allocation) issues. I know that things like huge mushrooms would have their Path allocated with level-scope, and then manipulated through a controller. The Gob that runs that controller is likely to be permanent as well.

Path for a platform, on the opposite side, are required only as long as a character interacts with the platform. By making GobArea ISA GPath, and stating that all coordinates resolutions are relative to the owner's coordinate, it means that all the platforms in a current state may share the same "path" information (offsets to their local coordinate) and  function implementation. Nice. They stop being "transient/temporary" structures and get level-wide scope as well: the allocation problem disappears ^_^. It do mean, however, that the "gobs" that control movement of multi-segment, dynamic paths will not be able to be themselves on a path, as they'll need their "path" reference to define what path they're altering ... unless I find a better solution, I can live with that.


L'autre bonne nouvelle, c'est que celà règle également les problème de "responsabilité" entre objets. Je trouve cette notion de responsabilité fondamentale en C++: une fois qu'on a pu définir qui est le seul et unique responsable d'un élément, alors on peut gérer beaucoup plus facilement son allocation, surtout lorsque les durées de vie coïncident. "Un chemin pour marcher sur une plate-forme", à priori ça a une durée de vie assez brève: du moment où Bilou entre en collision avec la plate-forme jusqu'au moment où Bilou s'en détache (de son chef ou de celui de la plate-forme, d'ailleurs). Un vrai cauchemar, surtout si on commence à se dire qu'il serait sympa qu'un autre ennemi arrive à la rencontre de Bilou sur cette même plate-forme!

En revanche, dès que GPath n'est qu'une façon de regarder la zone de collision (GobArea), tout s'arrange. Les "GobAreas" ont une durée de vie identique à celle du niveau et sont sous la responsabilité des états des machines d'état (et donc partagées par tous les objets d'un même type). C'est tout à fait satisfaisant si on considère qu'elles définissent des fonctions y=f(x) dans des coordonnées relatives à l'objet auquel Bilou est attaché.


All I still need in addition to the existing Attach, Detach and AttachToPath 'interaction opcodes' in GobExpressions is a 'IsAttached' test that will allow me to craft "force-detach" collision boxes that won't detach objects from other platforms ^^".

mercredi, juin 05, 2013

Deep Ink Pit gameplay

 Nous voici à la frontière entre le "monster design" et la conception du gameplay. L'aspect et l'idée de base pour spongebop sont définis, mais il reste à préciser comment le joueur va interagir avec ce "monstre".

I recovered and digitized some sketches that describe the intended gameplay for Deep Ink Pit. I make the distinction against "monster design" as we start here thinking in terms of user input, skills required and goal to achieve.
The core idea is to bounce from one Spongebop to the next to climb up as high as possible. That leaves us 3 things to define: what happens by default when Bilou and a Spongebop collide, how can he climb up higher and is what are the contrary motions.


 Sans action de la part du joueur, Bilou rebondit sur les éponges, mais toujours des petits bonds. Pour s'élever plus haut (et donc progresser dans le jeu), il faudra ré-appuyer sur le bouton de saut au moment du contact. Chaque rebond augmente la portée du saut suivant, que l'on reste sur la même éponge ou que l'on enchaîne les éponges.

If the player stays idle, Bilou will bounce again and again on the sponge's "head". To gain jump height, it is necessary to press the JUMP button timely when you hit the sponge. By chaining timed jumps, the height for the next jump keeps increasing.


Au départ, j'avais prévu qu'un trop grand nombre de saut sur une même éponge casse son élastique, mais en fait, cela n'apporterait rien au gameplay puisqu'on a déjà l'encre qui monte dans le rôle de la "motivation pour avancer".

Spongebop's thread may break. I originally thought it would break "after n bounces", but that doesn't really convince me. I'd rather have the thread breaking if Bilou comes with an "excessive" down speed".

Je garde donc ces fils cassables mais uniquement si la vitesse de Bilou est trop grande lorsqu'il heurte l'éponge.

That fundamentally means that there won't be anything like "walking on a spongebop", despite its appaerance in "platforms" posts. Still, for "the adventure", I know that players will need help, as staying aligned with a swinging sponge proved feasible, but demanding. Imho, the proper approach would be to allow Bilou to GRAB the sponge by pressing the 'PUNCH/THROW' button timely (on contact) and holding it. That means Bilou wouldn't be capable of grabbing sponges when carrying something, which imho is an interesting cancel for widening the skill range the game will test. And for Deep Ink Pit, we could easily imagine a "enable GRAB" power-up to be collected.

Reste à intégrer le balancer des éponges, histoire de rester cohérent avec l'aventure. Il sera pratiquement nul au départ pour augmenter au fur et à mesure de la progression du joueur.
Pas moyen de "marcher" sur les éponges donc, au final, mais ce serait sympa de s'y accrocher de manière plus ferme, et puisque c'est le bouton B qui permet d'attraper les taille-crayons pour les lancer, autant faire de B le bouton par lequel on s'agrippe pour suivre le mouvement. Du coup, celui qui (dans le jeu complet) voudra passer une éponge en gardant son taille-crayon en main devra le faire "à la dure" sans compter sur son bouton B. C'est un "cancel" dans le jargon de Kirby Kid :P

mardi, mai 28, 2013

extractanim.pl

Maybe you wonder why, after two teasers on "the great walking pendat", you still can't see it animated ? Well, the thing is I still haven't got any competent animation exporter so far. All the previous animations from AnimEDS were either hand-generated from static snapshots, or screencasts from either AnimEDS in desmume or from the game engine.

But that screencast tool (byzanz-record) shows its limits when one wants pixel art-level animation, and not merely a gameplay teaser: it's introducing dirty pixels all over the place.

So I refreshed my brain with API of the Imlib2 library I used for spr2png a while ago and started a proper animation ripper for my own .spr file format. I plan to first build static frames in .png and let a post-processing step build an animation out of that. I bet swapping will be the most annoying feature to support.

Stay tuned ...

hmm ...

edit: Well, the tools have progressed, that's sure. I still need to hack support for 32x32 blocks, though. On the other hand, it's pretty screwed for the pendat: I used pencil tiles as a quick replacement for pencil sprites content, but that's not a valid approach in the game engine, and that isn't either for the extractanim.pl tool :P

But with the "apply change to this frame and all the following" button, replacing the page and recovering graphics is a matter of 5 minutes with the DS in hands ^_^


vendredi, mai 24, 2013

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.

My nephew had pointed out that Bilou could get stuck in the "School Zone" quite a while ago, and while I was checking that I fixed that properly, using step-by-step mode in Inspector Widget, I realised that Bilou would also temporarily "stick" to book corners before falling down. In stop motion, one could have noted that it flickers between walk and idle 2 or 3 times before actually jumping off the cliff. I want a rules-perfect engine, not a "we can play it out", so I invested several evenings figuring out what was actually wrong.

As in many engines, I handle slopes using a 'hotspot' pixel that must stay on the 'curve' defined by the slope. The rest of the character (the dark box) may enter slope tiles or be in the void, what it doesn't cando() is entering those black, solid tiles (which is checked separatedly). Making sure this looks nice is the level designer's problem, not the engine's. Within a tile, when the horizontal coordinate is changed by the 'walking' behaviour, we retrieve the ground height for the corresponding target pixel and align the hotspot there. With that approach, we may end up in a tile that has no ground at all (gh=0) when switching to the next (horizontal) tile. In that case, the alignment is performed on the tile just below.

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.

Now, when we instead have a sloped edge, as with books, both tiles are AIR and have no "ground" to align on. The do_slopes function then consider there is no slope to follow and let the walker::think behaviour function to adapt accordingly. Walker's behaviour is then to walk as long as possible, testing whether Bilou cando a move downwards (meaning he can fall). That would be all fine if Bilou was aligned on the blue line by then, but he isn't. he's still one pixel above, because that's where the slope ended.

Would have it implied that Bilou started falling earlier, I wouldn't have minded. But that's not what the state machine decides. from its perspective, the backward testpoint is still on the ground and thus the FAIL even is translated into a transition to idle state rather than towards fall.

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.

The actual solution is of course to ensure that we're aligned on ground when there's no slope. Only if we cando the move downwards *while being on a tile boundary*, we will claim it a FAIL move that should switch to some other state. But to realise that, significant upgrade on InspectorWidget's precision was required. I even was tempted to turn this post into another "Inspector Widget's novel" post, but the implication on the game engine and behaviour coding was too high, and I'd rather went for a tutorial shape.

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.