Friday, June 28, 2013

Incoming week-end ?

I cannot tell yet whether there will be significant development this week-end. I had a sort of marking-hangover last week-end and I barely managed to fix some bounding boxes and import some "school2.cmd" level to support the 20-year-old map I want to revive.

Ça ne donne pas l'air de bouger fort, hein, ces dernières semaines. En fait, j'avais commencé à ajouter les gommes sauteuses le week-end dernier pour me rendre compte qu'il y a toujours un sale défaut dans l'éditeur de niveau qui a tendance à bousiller la liste des monstres à chaque sauvegarde. Si je parviens malgré les valises que je traîne sous mes paupières à faire un peu de développement sur le projet Bilou ce week-end, ce sera donc très certainement dans 'LEDS' pour modifier la structure de données qui maintient le "gobscript" en mémoire ...

I'd have loved to include the pendat over the week, but I still have a dormant bug in LEDS which has been left pending since Easter's fixes. Now I need it fixed or I'll have to beam level scripts to the PC and back after every save to fix duplicate "gob% := state% (x,y)" statements.

Meanwhile, I had a nice idea for editing such blocks of text in a convenient fashion ... which led to an alternate design for processing the .cmd files that could solve the issue with monsters duplication. So if something happens in the svn tonight, it's likely to take place here.

Tuesday, June 25, 2013

7 ans!

Ça fait maintenant 7 ans que new Super Mario Bros a été annoncé, donc également 7 ans que j'ai attaqué réellement le développement sur DS. On est clairement à un autre rythme que pendant la période "PPP Team" de '93 à '97, mais je suis tenté de penser que le jeu vidéo s'est maintenant relativement affranchi des impressionnantes innovations techniques, même si -- comme pour le cinéma -- il restera des équipes pour qui la surenchère à coup de texelshading sera fondamentale. Une histoire originale et un gameplay réussi, c'est intemporel.

Donc je continue.

Et où en est-on ? À quelques lignes de code d'une démo d'un niveau dessiné en '93 qui prendrait enfin vie... avec des gommes qui rebondissent, des encriers qui crachent de l'encre, etc. À un outil qui devient confortable pour faire le type d'animation dont je rêvais avant de voir dans les démos de Rayman 1 tout le potentiel incroyable qu'il recelait. À une branche de la possibilité d'avoir des structures mobiles dans le niveau. Mais toujours du mauvais côté de la barrière polygonale de la 3D. Il faudra tôt ou tard que je la franchisse, si je veux que Bilou puisse construire des ponts en poussant des bouquins.

Il y a 6 ans, j'imaginais reprendre presque tels quels les niveaux et les monstres envisagés quand on étais ados. Maintenant, ils passent dans le grand chaudron du gameplay puis sont décantés au form-fits-function pour offrir quelque-chose de vraiment intéressant qui se démarque des "n-ième platformer où il faut récolter les items cachés".


Friday, June 21, 2013

Le stylet ultime ^_^

Je dois bien l'admettre, quand mon frère m'a prêté sa DS lite il y a déjà plus de 4 ans, j'étais resté à mon ancien stylet de DS "phat", bien plus fin dans la main, pour faire mon pixel art. Puis au moment où je passais moi-même à une DS lite (aux couleurs nettement mieux rendues) ma fée m'a trouvé un stylet ultime qui depuis ne me quitte plus ...

Le stylet à pointe transparente.

Je croyais l'avoir définitivement perdu, mais ce matin, je l'ai retrouvu ^_^

Howdy! I recovered the Ultimate Stylus of Pixel Art ... the one my Fairy gave me, with the transparent tip so that you accurately see where you're pointing. I'm really glad it wasn't lost.

Btw, I've got a student who just purchased a devkit for the original PSX. Anybody has links to share about homebrewing on that console ? http://code.google.com/p/psxsdk/, maybe ? (via)

Oh, and Nocash, the guys who wrote the 'DS&GBA tek' reference I'm using almost daily (when brewing) also built up PSX SPeX :)

Thursday, June 20, 2013

DpadController::timeout

Back in 2010, I had introduced a tweak to the DPAD reading so that you wouldn't bounce again and again by just holding the JUMP button down. That seemed efficient enough. Whenever a button (DPAD excluded) was pressed, a timer was started, running for 30 frames, and as long as this timer hadn't reached zero, the transitions of your state machine would read the button as "pressed". That allowed players to do special bounces when bopping on applemen wihtout requiring 1/60sec. precision. That's called "input buffering" anywhere else in gamedev documents.

Comparé à un clavier ou à un joystick de PC, la saisie des mouvement du joueur sur le DPAD de la DS est une vraie partie de plaisir. Pensez un peu: un emplacement mémoire qui contient directement l'état de chaque bouton de votre console !
Celà dit, s'en servir pour alimenter la logique d'un jeu de plate-formes comme Bilou demande un tout petit peu de subtilité quand-même. Selon les situations, la question n'est pas uniquement "est-ce que le bouton de saut est enfoncé" mais plutôt "depuis combien de temps le bouton de saut a-t'il été enfoncé ?"

Shortening the "time window" (::delay) to 10 frames actually reduced the maximum height for jumps! After rising for 10 frames, the game logic is indeed notified that the key was released, and switch to "end-of-jump". Annoying. I already had the feeling that the "action" button may need to be held and thus be "timemask-free", but the issue is actually deeper. It means whether you use the timer mechanism or not depends on the state you're currently in.

Eh, c'est que rebondir sur la tête d'une tortue pour aller plus haut, c'est bien rigolo, mais s'il faut enfoncer le bouton précisément dans le 60ème de seconde où la collision a lieu, ça devient franchement trop exigeant. En revanche, voir son personnage faire un deuxième saut spontanément en arrivant au sol, c'est perturbant. J'ai donc depuis un moment déjà un mécanisme qui décompte les "images de jeu" depuis que le bouton (croix directionelle exclue) a été enfoncé, de sorte que la logique du jeu regarde surtout si ce décompte est arrivé ou non à zéro. Mais pour que Bilou puisse s'accrocher aussi longtemps qu'on le veut aux éponges, il faut en plus que je puisse définir, état par état, quel(s) bouton(s) ont le droit à un "décompte infini".

While moving up in a jump, you want to check simply whether the button is held or not, and keep pushing up as long as the button is pressed. When going down, however, the timer mechanism is applied so that you only get your special bounce if you hadn't pressed the button too much in advance. Similarly, the "action" button uses the timer when you're falling down, but as soon as you switch to the "grabbing spongebop" state, it just checks whether you're still holding it down. Same would go for mario-like running, for instance.

Tuesday, June 18, 2013

Kung-f00: surviving in the kernel

There are two level of programming in an environment like the Linux Kernel, imho: survival and release. Survival programming is what you will do when you need to prototype or a proof-of-concept. Release programming (or refactoring) takes place when you want your code to be accepted in the kernel as a patch or another driver. Here are some ancient, half-forgotten programming techniques that could help you survive.
Initialization

If you have been raised in OOP, you may need to un-learn a few things to survive in the kernel. Especially your habits on constructors. Every time you can define an initial value at compile time rather than at run-time, you save yourself from the need to check your initialisation has indeed happened, and you avoid the risk that some mistake you’ve made could prevent the system from booting altogether. You don’t necessarily need to pollute the top-level name space for that:

{
   static int reported=0;
   do_swap_page(p);
   if (!reported && my_page(p)) {
       printk("my page got swapped.\n");
       reported=1;
   }
}

The static reported variable will have block-limited scope (so you could have as many such block as you need, and always use the name ‘reported’), but program-wide lifetime (and thus its value will persist across function calls). I believe this saves us from many __init functions.

just useless ...

Do you need a reference to some kernel object X? Save yourself the problem of figuring out whether that object will be ready when your __init function would be called. Use a NULL value as initialisation and ensure that you properly use lazy initialization
   if (!backing_device) backing_device=find_swap_device(0);
Downside is that you’ll have to repeat this line in every access point of your code. That’s unacceptable at the scope of a corporate business logic, but perfectly maintainable at the size of a prototype.

Do you need N bytes of memory to be available and can’t afford using kmalloc because your lazy initialization could take place at a moment where it could require memory that isn’t available right now (yup, even the kernel could be low on memory) ? Well, just be plain 8-bit style and go
   byte mySpace[N];
that will be set in the bss segment of your kernel, wiped with zeroes by the loader, and don’t take extra space on the vmlinux kernel image.

Probe System Call

Ideally, when you want a feature to be optional in the Linux Kernel, you wrap it in a module that can be loaded and unloaded. The price to pay for that is a more sophisticated template to follow and significant attention to memory management to ensure whatever your module doesn’t let dangling pointers when it’s gone. Core kernel functionalities typically get enabled/disabled through system control variable in /proc, just like support for the “magic SysRq key”. That’s the good way to go for a release. During prototyping, we can be old-school and define a new system call that we’ll use for invoking late initialisation in contexts where the initialisation, and otherwise enable communication from our user-world.

The /proc filesystem requires a significant amount of support data structure to work properly, yet if using /proc/kernel/myprobe is fine and if a mere int is enough for your communication needs, it comes down to
  • locate the appropriate place to add a entry in kern_table[], with .procname=”myprobe”;
  • mimmic sysrq_sysctl_handler, and add your own static int to receive the value decoded by proc_dointvec;
  • use a clone of the sysrq_sysctl_handler() function: let proc_dointvec deal with the buffer that comes from user-space, and just launch your code logic if the user is writing to your control variable.
Dynamic Allocation within a Table

You’re used to linked list, hash tables, trees, stacks and other dynamic data structures but realise that allocating cells wouldn’t be a wise idea in the kernel ? Congrats. Kernel developer put significant effort in crafting the slab and kernel object caches to reduce allocation costs, but at a prototype scale, it isn’t clear it’s worth learning how to master it. The “16-bit style” solution to this, given that you have a static table of N elements and that “N shall be enough for everybody and we guarantee there will be at least N slots available” is to embed a linked list of free slots within your table.

You don’t even need pointers for that: simply one field in your table slot that can hold an integer index to the next free slot.
int free_slot=-2;
int allocate_slot() {
   int al;
   if (free_slot==-1) return -1; // all slots used
   if (free_slot==-2) { // lazy init: chain all entries
      for (int i=0;i<N-1;i++) table[i].next=i+1;
      table[N-1].next=-1;
      free_slot=0;
   }
   al=free_slot; free_slot=table[al].next;
   return al;
}
Now you can focus on the real problem of guaranteeing that this table won’t suffer concurrent allocation issues :P

Monday, June 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.

    Saturday, June 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... omg! 7 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.

    Cette fois j'ai battu Bowser sans utiliser de Mega Mushroom (interdiction édictée par *deline). Et je réalise tout à coup que "new" Super Mario Bros a ... 7 ans. Bien assez pour que les éditeurs de niveaux aient fleuri, et j'ai même réussi a faire un peu tourner NSMBe un tout petit peu sous Linux. J'avoue que je n'ai pas l'intention de créer mes propres niveaux à NSMB. C'est un exercice qui ne m'a jamais vraiment attiré. Par contre, je suis curieux de voir ce que je peux apprendre sur la structure du jeu.

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

    Comme on pouvait s'y attendre, un grand nombre de plate-formes sont en réalité des "sprites" (soit avec des OAM, soit avec la couche 3D. Difficile à dire), même celles qui ne sont pas forcément mobiles, mais dont le comportement est suffisamment différent du sol. Les portes, les début de niveaux, les entrées/sorties par des tuyaux, sont eux aussi gérés avec des sprites.

    Les sprites sont organisés en "bancs" que l'on peut utiliser ou non dans le niveau, certains bancs en excluant d'autres (pas de bowser et de nessie ensemble: ils sont tous les deux pour le banc n°5, par exemple). Par contre, on pourra les compléter avec des Hammer Bros ou des chomp chomps, comme on préfère.

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

    Les pièces d'or et les bloc-question font partie de la couche "sol". Idem pour les blocs-à-power-up. Ils sont sans aucun doute fait à partir de bons vieux "tiles". Chose assez inhabituelle, en revanche: on les édite et tant "qu'objets", pas juste en plaçant des pavés sur une grande grille. Chaque objet peut être sélectionné, déplacé, redimensionné individuellement, ce qui s'avère très efficace pour la conception rapide de niveau comparé à ce que LEDS propose. Du coup, on retrouve des éléments assez curieux comme des "pointillés de pièces d'or" qui permettent d'avoir un "damier de pièces d'or en diagonale" avec un seul objet.

    Tout ça n'a pas l'air d'être juste un choix de fonctionnement de l'éditeur: les niveaux eux-même contiennent ce genre d'objets: si on clique sur un "bloc" du dessus d'une plate-forme, on sélectionne directement l'entièreté du sommet de la plate-forme. Je présume que c'est une technique pour réduire l'espace sur la cartouche: le contenu en mémoire du niveau sera reconstitué en appliquant des commandes du type "remplir (10,10)-(20,20) avec du sol" puis "remplir (10,9)-(20,9) avec de l'herbe", etc. J'y crois d'autant plus fort quand je vois des motifs "effaceurs" qui n'auraient aucun sens dans un système utilisant des textures et des polygones.

    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.

    Thursday, June 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.


    Dans son article sur les chemins alternatifs, Richard Terell nous fait une synthèse des règles qui semblent définir le fonctionnement des chemins alternatifs dans la série Metroïd. 1) les alternatives sont moins fréquentes que les "sorties normales". 2) les alternatives doivent être masquées, et si possible réservées au joueur maîtrisant mieux les mécanismes du jeu. 3) le joueur sur un chemin de traverse doit pouvoir se rendre compte d'où il est par rapport au monde normal. Dans son incarnation ultime, un chemin alternatif peut même se révéler un tel raccourci qu'il évite complètement certaines parties du jeu. Moi, je n'ai encore jamais joué à un métroïd, mais ce dernier élément me fait immédiatement penser à la Star Road de Mario World et à la "technique secrète" de Hero Core qui permet de se frotter au boss final sans avoir affronté aucun des boss intermédiaires!

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

    made it possible.
    • 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.


    Mais au fond, que cacher dans son passage secret qui en fasse un détour qui vaut la peine ? Il y a bien sûr le raccourci, qui est une des façons de contrôler ce qui est nécessaire et ce qui est optionnel dans le jeu. Dans le raccourci, c'est le designer qui prend la décision "on garde le niveau 2-5 à sa difficulté actuelle, mais la porte d'entrée permet aussi d'aller directement au 3/4 du niveau. Ça vous va ?". SMB3 offrait des jokers (nuages, boîte à musique, flûte, etc) qui permettait au joueur de décider quelles bataille il allait éviter pour un contrôle encore plus grand.

    Le deuxième type de récompense est un contrôle supplémentaire sur le comportement de l'avatar. Il y a les power-ups qui sont nécessaires pour accéder à certaines parties du jeu, et ceux qui conservent vos capacités d'origine, mais en les modulant. Nager mieux, planer en tombant ... plus on touche au coeur du gameplay (la gravité dans un Mario), et plus l'effet est important.

    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.


    Same heal feature, with modulated effect in time.

     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.

    Friday, June 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 ^^".

    Wednesday, June 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