Showing posts with label MyEngine. Show all posts
Showing posts with label MyEngine. Show all posts

Monday, February 10, 2020

GameEngine design: script-to-code

Somewhere in 2009, I added support for actions triggered by the script-part of my game engine but implemented in C++. This is the cornerstone of "GEDS" game engine. This is how run-time monsters spawning and sound effects work. This is also how level load request are processed.

The design survived the years, so let's see how responsibilities are split.

  • Anything that will start invoking code upon game event (collision, new input, animation completion, etc.) is captured in a *Gun instance. The instance carries all the parameter needed for firing a specific kind of code. E.g. An instance of the LevelGun capture the specific level script to load. An instance of GobGun captures which type of monster to spawn, etc.
  • Configuring the *Gun instance happens at script parsing time. In other terms, most of the *Guns could actually be constant objects.
  • The c++ code instanciating an *Gun must be located from the name of the action on the script (with a using [action-name] ([arguments]) as [holster-slot-number] statement). That requires every type of "gun" to have its associated "factory" class, so that an instance of each "factory" can be registered into an std::map to be the link between actionname and the *Gun constructor.
  • Amount of support code for a new *Gun or a new *GunFactory is minimal. Each class have only one (virtual) method, either create() or shoot().
  • *Gun instance typically forget about what they 'shot' as soon as shooting is done. Often, the created object is registered as an animated item at the game engine, which will take care of running its code periodically. This may lead to the need for an additional "progress" object when there isn't any yet, like with the TrackSequence following the instructions (e.g. shooting more guns) as a sound track unrolls in the music player.
  • when processing script expressions, a palette of "guns" (the multi-slot holster, if you want) is received in addition to the set of variables accessible to the script. However, the transitions between states are the one capturing those palettes.
  • The GameScript is responsible for recording every *Gun instance created, so that they can all be reclaimed when the level is destroyed and we switch to something new.
  • There is one guns palette per "state machine file", with the constraint that all the states used by a single "character" in the game have to be described within the same file, this means that a "character" can only use up to 16 different sounds+special effects.
  • The GameScript has the ownership of all these palettes. Reusing guns from parent's palette is explicitly requested. The GameScript knows when to stop using a given palette for new transition and switch to a new/old one because it has seen input/end statements. There is room for improvement here.
Lots of guns. But anyone in GEDS can at most carry 16 of them at a time.
Clearly, the major limit comes from fixed-size palettes of gun, which itself comes from the byte-code nature of the (parsed) script expression where only 4 bits are dedicated to the storage of which-gun-to-use. This is perfectly ok for a Mario-like game, where the distinct number of actions per character remains low. I would definitely need to extend this (and other parts of the game engine like sprite memory management) if I was to create a run-and-gun game where you can pick up lots of different weapons, each requiring a different kind of amno sprite and sound effect.

Saturday, August 19, 2017

Les autres pentes

Du point de vue du moteur de jeu, un niveau est avant tout une grille dont chaque case reçoit des propriétés: solide, solide uniquement quand on tombe, pente, spécial, etc.

(note to English reader: the handwritten text on the pictures is part of the post's text this time).

... A grid, with tiles telling "solid" or "sloped", or "jump through, but don't fall". And sometimes just "special block".


Les "blocs spéciaux" ne sont pas compris par le moteur de jeu lui-même (d'où leur caractère "spécial") mais décrits dans les scripts qui définissent les règles du jeu.

Ainsi, quand Bilou touche par exemple un Bonus, le moteur de jeu récupère quelques bits d'information pour chaque pavé (8x8) de la grille constituant le bloc (16x16) et reconstruit un numéro de bloc spécial (de 0 à 255) qui permettra de retrouver les instructions à exécuter (faire un son, déclencher une animation, donner des points, etc.)

... each "special block" tile provide two bits that are combined to define the "block number" used to retrieve collision properties, hit box and script expression that makes a bonus play a sound and spikes hurt Bilou. The game engine itself, to some extent, doesn't know anything about the special blocks except how to delegate block/object collisions to the appropriate script sections.

Quand Bilou touche une pente, en revanche, le moteur de jeu sait qu'il doit aller regarder une petite table indiquant la hauteur de chaque pixel de la pente. A priori, la forme de cette pente pourrait être n'importe quoi. Mon éditeur de niveau connaît les pentes à 45° et à 22.5°

The height array tells how high is each pixel in the tile. The game engine knows very well what to do, this time. Some tuning could provide an alternate height array to support a different kind of slope, but I haven't used that so far in the game.


Mais voilà: j'aimerais pouvoir avoir plus de souplesse: des escaliers, des coudes, par exemple. Un petit schéma de rien du tout qui traîne depuis des années (2010?) sur un bloc de brouillon en atteste.
"Le mieux serait d'avoir deux boutons 'pente' [dans l'éditeur de sprites] qui forcent le remplissage d'une ligne de 64x16 pixels avec des pentes suivies (les modèles)"
 ajoute encore le calepin.

That "sprite editor / stripe of sloped tiles" approach hangs around in draft mode for years. Because each tile has a unique address in video memory, I had plan to use that address to extend "advanced slope-to-the-right" into "start-of-slope, 0° to 22°". The issue is that the Sprite Editor has no real control of which blocks goes where in memory. Just filling a row of the tile sheet with slope patterns doesn't guarantee that the corresponding tiles will all receive consecutive (and not even aligned either) numbers in the tile set. The solution was in realising that the Level Editor is the component that should solve the more-slope-types problem.

En effet, la technique que j'envisageais alors consistait à utiliser comme information supplémentaire la position du graphisme au sein d'une ligne de blocs dans l'éditeur de niveau (une "tranche" de 16 pavés de 8x8 successifs). On pourra alors convertir l'information de la grille "pente compliquée vers la droite" en "transition douce de 0° à 22°" parce qu'on est dans le 6eme pavé sur une tranche de 16.

Sur les dernières années, ça n'a jamais été mis en service et mes derniers tours d'horizon dans le code de l'éditeur de Sprites me confirme que travailler "par tranche de 16 pavés" serait un vrai casse-tête (le mode et le moment de l'allocation des pavés dans la mémoire vidéo n'étant absolument pas prévu pour ça).

Et cet été vient une idée qui jette un autre éclairage sur ce problème: "Les pentes douces, c'est un problème pour l'éditeur de niveaux. Ce n'est pas quelque-chose à régler dans l'éditeur graphique.

Et l'éditeur de niveau sait par contre traiter des groupes de chiffres pour montrer qu'un bloc spécial est soit un bonus, soit des pics, etc. En prenant donc un type de pavé qui dit "pente compliquée, regardez les chiffres en-dessous pour en savoir plus", je peux diriger le moteur de jeu vers une autre table de hauteurs qui définit la pente (parmi 16 pentes possibles), mais aussi éventuellement fixer le coefficient de friction (sol dur, glissant, boue-qui-ralentit, etc.)

"Special Slope" type merges the behaviour of "sloped tile" and "special block": it will have less bits available to encode the type of slope (e.g. "only" 16 possible slopes), it will require collaboration of multiple tiles, but instead of creating a BlockArea and trying to match collisions, it will just provide an alternate height arrays.

Il me tarde d'essayer ça ^_^

Saturday, April 29, 2017

Adding the MapAnim

 Allons-y donc pour l'ajout d'un nouveau type d'animations dans mon moteur de jeu: la modification du contenu de la map elle-même dans le temps. Jusqu'ici, c'était limité à "faire disparaître le bloc en (x,y)" ou "retire-lui ses propriétés, mais laisse-le visible" (pour les bonus cachés). ça peut marcher pour certaines choses, mais ça suppose que les éventuels effets liés à la disparition soient entièrement pris en charge par un sprite supplémentaire. Idéal si l'image doit quitter son emplacement (un morceau de pont qui tombe, un décompte des points qui monte, etc), celà dit.

Let's have one more way of animating things in my game engine. One that would be suited to special blocks, and that update the map content at a specific location over time. The only thing I could do so far would be to shoot a sprite-manipulating game object and make the block disappear. That's mostly appealing if you want a picture to move away, like broken bricks, falling logs and the like. 
The "BlockAnim", used to animate the ink, is not interactive and apply simultaneously to all the instances on screen. It can make SMW coins spin, but it cannot leave a trail of sprinkling sparkles behind.

Now, let's resume the coding, I'll keep you updated on the outcome. I already love the animation I designed last night for the bouncing erasers ^_^.

Il y avait aussi les "blockanims", qui permettraient par exemple de faire tourner les pièces de Super Mario World sur elles-même. On procède alors par une mise à jour de la mémoire "tiles" et toutes les instances de l'objet sont mises à jour simultanément sur l'image à l'écran.

Le nouveau "MapAnim", lui, correspond plutôt aux besoins pour faire bouger un buisson devant lequel on passe, mettre des petites étoiles là où il y avait un bonus, etc. Comme vous pouvez le constater sur les diagrames, le mécanisme qui permet de mettre à jour le niveau (les données gérées par la classe InfiniMap) n'est pas particulièrement simple. Il met en jeu une description de chaque type de bloc spécial (les BlockInfos) et un objet temporaire capturant "tel bloc à tel endroit" (le BlockArea) qui permet d'interagir avec le personnage (GameObject) qui vient de passer par là. C'est finalement InfiniMap lui-même qui procède à l'effacement sur base du résultat du test de collision. La seule bonne nouvelle, c'est que c'est assez souple pour permettre des (petites) portes, des bonus, des bumpers et même les guides invisibles qui font faire demi-tour aux encriers.


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

Monday, December 02, 2013

The art of Blading

For g88 release, carrying bladors was functionally correct, but still a bit cheap, as the carried blador could move through walls, ceilings and such. It would be an acceptable simplification of physic rules in-game if that did not meant you could "throw" it while it overlap solid structures, which would let it "stuck" there as soon as regular physics takes over to let it be thrown away. I want the game to feel professional, and demonstrate that a simple scripting approach does not imply half-baked behaviours. It's thus time to prove it.

Un taille-crayon qui passe à travers tout pour qu'on puisse le lancer n'importe où, ce n'est pas très réaliste, mais c'est acceptable dans un jeu vidéo. Un taille-crayon qui reste bloqué dans un mur quand on le lance, ça, ça fait tache. Surtout quand le joueur aurait théoriquement dû pouvoir le ramasser et l'emporter plus loin. Premier pas pour corriger ça, interdire à mon taille-crayon de passer à travers les murs (ce qui demandait un réglage spécifique du contrôleur freemove). Résultat: Blador suit maintenant Bilou "de son mieux", ce qui amène parfois des résultats curieux comme un saut quantique à travers un mur contre lequel il est resté scotché pendant 15 secondes lorsque Bilou émerge enfin d'un passage secret.

The first step is to make blador using freemove(1) instead of using freemove(0) when carried. Freemove is the controller that allows free movement typical from airborne ennemies such as the berrybat. The only thing it does before validating the desired movement is checking that you cando(m) where m describe some properties of the environment - like being something you can MOVETHRU. 0 is considered as a special case where you can always do what you want -- perfect for ghosts or sparkles that will fall of the screen. So now, blador can't get into walls, but curious things occur when Bilou gets into narrow passages where Blador cannot follow: Blador just sits where it is, and if at some point Bilou reaches a new position where Blador could fit above its head, Blador gets quantumagically teleported to that new location (yes, again).

Pour corriger ça, il faut que Bilou puisse être informé de la situation du Dumblador qu'il transporte. En '97 j'avais donc pensé augmenter mon "ulitmate game maker" d'un système de messagerie entre les personnages. En fait, ici, on va faire beaucoup plus simple: tout comme l'encrier possède une zone-test (hitbox) qui repousse Bilou lorsque celui-ci l'approche, le Dumblador transporté va disposer de murs invisibles qui empècheront Bilou de s'en éloigner de trop tant qu'il le transporte. Eh oui: c'est aussi simple que ça.

Enfin, presque. Il y a quand-même une subtilité dans le cas du saut. Si je me contente de forcer Bilou à faire du sur-place au milieu d'un saut juste parce qu'un taille crayon a la tête dans le plafond, ça fout en l'air la crédibilité de la physique du jeu pour donner un effet Badman/RSD Game-Maker carrément pathétique. Il faudra donc que je traite cette collision aussi du côté de Bilou en forçant une remise à zéro de la vitesse, exactement comme le contrôleur "gravity" l'aurait fait si on s'était vraiment retrouvé directement dans un mur.

I like how Bilou/Blador distance is stretched or squished when colliding the environment, but I need more control over it. And that's just a perfect fit for the "repel" action introduced with solid inkjets. To avoid having Bilou going too far away from the carried Blador in a narrow tunnel, I just need some repelling-areas acting as virtual walls at appropriate distance from its position.
The same approach allows Blador to take some room between Bilou and the ceiling, but here I need some additional action. "Repelling" simply adjusts coordinates and does not affect speed. The result was that Bilou would keep floating along the ceiling as if he virtually kept jumping. Even weirder: he might have resumed moving up as soon as he'd have moved away from what was blocking Blador (just as in Badman II, actually).
To avoid this, Bilou needs to react on the collision and clear vertical speed. Additional tests are needed to ensure the blocking object (active GOB) is indeed over Bilou's head and that he's still moving upwards. The center-to-center distance (dy) is available through GobScript wf variable read and is positive when the active object is above the passive object (and yes, the sign of dx and dy is related to passive/active roles as the distance is defined only once, and then reused in both passive and active objects transitions).


Il me reste donc "simplement" à corriger l'ordre d'affichage de Bilou et du dumblador (tant qu'à faire, prenons-le en mode "hotte de père Noël" plutôt que "Sac de courses devant le nez") et à m'assurer que les mains de Bilou apparaissent sur le taille-crayon pendant qu'on transporte le crayon.

It leaves me with one last aspect to fix: having Bilou's hand properly shown as grabbing the blador while it is carried, knowing that hands will have to be aligned with blador rather than with Bilou if we want to keep stretch/squish.

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.

    Tuesday, May 22, 2012

    Dumblador turns back

    Here it comes. Some final tweaks on dumblador's "walk to the right" animation last night and I now have a CompoundGob that turns back when it encounters a wall (how sweet ^_^) Dumblador avance et fait demi-tour. L'occasion de revenir sur le fonctionnement des personnages dans mon moteur de jeu. Accrochez-vous un peu: le 'gobscript' est conçu plus comme un modèle de bytecode en ASCII que comme un véritable langage de programmation, mais comme dumblador est tout simple, ça reste lisible (j'espère ^^") Please bear some GobScript since it's still a pretty simple and straightforward monster:

    anim1 = spr:0
    anim2 = spr:3
    statL0 :anim1 {
    using walker
    selfmove
    test 0 (2,4)-(8,14) 0001
    area 0 (0,0)-(8,4) 000A
    }
    on commence par importer des animations depuis le jeu de sprites (il suffit de connaître leur numéro, ici n°0 pour avancer vers la gauche et n°3 pour avancer vers la droite) et on leur donne des identifiants locaux (anim1 et anim2, respectivement). Le comportement de dumblador va maintenant être construit comme une combinaison d'états réutilisant ces animations: L0 pour avancer vers la gauche et R1 pour avancer vers la droite.
    statR1 :anim2 {
    using walker
    selfmove
    test 0 (2,4)-(8,14) 0001
    area 0 (0,0)-(8,4) 000A
    }
    
    statL0->statR1 on fail (100 :0)
    statR1->statL0 on fail (100 ~ :0)
    statL0->statR1 on hit0 (100 :0)
    statR1->statL0 on hit0 (100 ~ :0)
    end
    
    The only thing you have to remember about your spritesheet is now the position in the animation slots where your things are. That's slots #0 (walk left) and #3 (walk right) for me. You give them animation identifiers for your monster (1 and 2, resp.) From that, you can define states walk left and walk right. Each state defines an animation being played and a behaviour. The combination of using walker and selfmove means that DumBlador will move forward at constant speed, according to what's defined in the animation, but not faster than the value defined in its internal variable v0 (x speed)
    Vous pouvez penser à un "état" comme à une action que fait un personnage: s'il est dans l'état "avancer", il fait son animation "un pas en avant" et utilise un morceau de code C++ un (contrôleur) identifié via using walker qui reprend les tests "y-a-t'il du sol? dois-je suivre une pente?, etc.". Le mot-clé "selfmove" indique que c'est l'animation qui définit de combien de pixel on avance à la fois pour une meilleur synchronisation. On reste donc par exemple dans l'état "vers la gauche" tant que tout va bien. Le contrôleur génère un "fail" lorsque le personnage arrive dans un mur. À ce moment-là il va falloir choisir un nouvel état. Les commandes du types statdepuis -> statvers on fail [condition] (action) permettent de construire les transitions qui seront testées dans ce cas-là. Ici, l'action consiste simplement à écrire une valeur constante (100 ou -100) comme nouvelle vitesse horizontale (variable n° 0). "walker" is what's called a controller for the GOB. It can also report events and indicate when it failed to perform the desired movement. The two on fail statements indicate transitions from "walk left" to "walk right" and back when it's no longer possible to move forward. That could be due to a wall or the end of a platform. With this simple script, dumblador "has no way to figure out". Another part of the current behaviour is that dumblador turns back when Bilou jumps on his "head". This is achieved by the two on hit statements. It's still necessary to provide the (relative) coordinates of the hitboxes manually. Here the "hit" statement corresponds to a passive area in the state definition, which is seen in dark cyan in the Inspector widget. "000A" is the bit mask that corresponds to Bilou's stomping action or AppleAssault's attacks. I could have used "0002" to make DumBlador impervious to punch attacks and reacts only to stomps. Enfin, pour compléter le comportement décrit par chaque état, les instructions test|area n° (coin1)-(coin2) avecqui donnent les zones de collisions (rectangulaires) qui vont permettrent d'interagir avec les autres personnages. La partie un peu "magique" se situe dans le nombre hexa avecqui. Pour chaque "type" de collision, il faudra choisir un des 16 bits disponibles et faire en sorte qu'au moins deux zones de collisions (p.ex. le corps de blador et celui de Bilou) utilisent le même bit (0001) mais dans un test pour l'un et dans un area pour l'autre, pour refléter l'asymétrie frappeur-frappé du moteur de collisions. Finally, the curious (100 ~ :0) are GobExpressions. It's a minimalist encoding of "set var[0] to -100". It works as those RPN calculators, where you type "2 40 +" to compute "40 + 2". Only simple parts are used here: push the constant 100, negate it (I don't have negative constants so far ^^") and assign it to variable 0 (mnemonic: Pascal uses := for assignments). Reusing a constant here is required as the walker controller is allowed to alter the speed in the final steps towards the wall. Would you like to give it a try yourself ?

    Friday, September 10, 2010

    External Actions

    using sound(16,56) as 0 Bilou shoot
    using sound(0,60) as 1 Bilou hit
    using shgob(state28:= 0 :1 1 :0) as 2 Bilou, right punch
    using shgob(state29:= 0 :1 1 ~ :0) as 3 Bilou, left punch
    using shgob(state19:=$100 :0 $100 ~ :1) as 4 Bilou, stomped star
    using shgob(state19:=$100 ~ :0 $100 ~ :1) as 5 Bilou, other stomped star
    using shgob(state0:=D) as 6 throw a fake appleman (?)
    using sound(17,64) as 7 Bilou bop
    using shgob(state18:= 0 :1 v6 :0) as 8 Bilou, shuriken
    using shgob(state0 is e:=D) as 9 throw an appleman



    ça fait un peu peur, hein ? C'est ainsi que je définis en "gobscript" les différentes classes C++ qui peuvent être appelées et quels paramètres leur donner. Il ne me restera plus qu'à dire "x4x5x1" lors d'une transition pour qu'on voit deux petites étoiles jaillir de bilou avec un son "ouch". Et si je vous mets ça en ligne, c'est parce que je vous concocte un "mégapunch" à placer entre le "coup de poing de base" et le shuriken dans apple assault ... wait'n'see.

    Did I scared you ? This is how you bind external C++ code to transitions in gobscript ... well, you'd still have to specify "x4x5x1" to have small stars shots while you hear an "aouch" sound. Why do I put *that* online, will you ask ? because I needed to have a better overview of those actions so that I pick the proper numbers for the "big punch" action I'm testing right now. Yeah. Big punch. Larger collision box, more apples dispatched, more fun. Wait"n"see :)

    edit: Oh Yeah! The "Big Punch" definitely roxx! I still have to fine-tune it, so that e.g. the master player can use it mid-air to knock-out applemen below him; but ... Man! It let me score 10005000 at the hardest difficulty level, and it makes me play less cowardly! I can't wait to have those additional tunes and share a next apple assault release with you!

    todo: add a "state%%->self on ... [condition] (action)" mechanism so that we can easily encode the rules for punching while walking, idle, jumping and falling ... and a prio % command in animtions to select the plane on which things are.

    Monday, May 10, 2010

    Bounce & Shoot

    Petite tentative de mettre en image les petits essais de ce midi, dans le cadre du mini-jeu "Apple Assault". L'idée est d'avoir une jauge d'attaque que Bilou peut remplir en rebondissant sur des monstres (représenté par les 2 transitions en haut de l'image) et qui lui permet de lancer une étoile (à remplacer par un coup de poing).

    Cette jauge n'est toutefois pas permanente: quand Bilou est au sol, elle retombe progressivement à 0, réutilisant le même mécanisme que celui qui fait ralentir les Applemen. Cette "vue synthétique" de toutes les interactions faisant intervenir une variable donnée pourrait bien venir compléter d'autres fenêtres de dialogue plus "classique" (éditer toutes les transitions de / vers un état) dans un éditeur de comportement tournant directement sur la DS ... à venir plus tard.

    This week-end has seen virtually no code written for the engine, but massive update and improvement on the "gobscripts" defining Bilou and the Applemen. Bilou now actually 'bounces' when he stuns appelmen, and you can bounce higher by pressing the jump button with the proper timing. In an attempt to get closer to the "apple assault" milestone, I was toying this lunch time with a bounce counter that could be use to enable the "shot of some placeholder" that is meant to become a "finishing move" that would clear the area of stunned (or running) applemen.

    Here's a conceptual mockup of how that job would have been done if the "behaviour editor for DS" was already implemented... or at least how you could visually provide hints on what is using that bounce counter by identifying all the predicates and action that use "variable #6". In order to add that behaviour, you'd have to start with the "falling" state, alter "monster found" behaviour, then browse neighbour states and decide what to do with v6 in order to implement the "depleting counter" that only let you "throw a star" shortly after you've hit the ground.