Showing posts with label testpoints. Show all posts
Showing posts with label testpoints. Show all posts

Friday, May 16, 2008

Faut que je repense les test-points

Bon, Bilou saute, il tombe, il rebondit ... tout ça c'est bien joli, mais il a tendance à se retrouver un peu trop souvent dans les murs à mon goût. Et en plus, son comportement devient un peu trop complexe à exprimer dans mon modèle de machines d'état. Pensez un peu. Rien que pour le saut, il me faudrait pas moins de 10 états si je veux que Bilou se "souvienne" de la dernière direction dans laquelle il est allé :P

Great. Bilou jumps, Bilou falls and bounce. It's all nice and funny, but he ends up into walls a bit too often to my tastes. Moreover, it's getting too complicated to express his behaviour only through that state machine approach: only jumping takes up to 10 states if we want to remember the last direction Bilou has been leading to ^^"

One of the reason why it's getting so complicated is that when jumping forward, there are additional testpoints to be checked, while these testpoints are disabled when jumping simply "upwards". The solution would be relatively straightforward: i need a new class of testpoints that would be conditionnal on horizontal speed.
Une des raisons de toutes ces complications, c'est que lorsqu'il saute vers l'avant, Bilou doit vérifier deux testpoints de plus (pour éviter de rentrer dans un mur) mais que ces testpoints ne sont pas souhaités lorsque le saut est simplement vertical (sinon, on ne peut pas sauter le long d'un mur ^^"). La solution est relativement simple, heureusement, il suffit de créer une nouvelle "classe" de points-test qui ne serait active que lors d'un déplacement horizontal. Cyril avait bien proposé le passage à un moteur physique plus complet (avec gestion de la friction, de l'élasticité des chocs et tout -- vous savez comment est Cyril ;) mais je préfère garder ça au niveau du script pour l'instant.

Autre petite modif' en cours: varier les types de blocs possibles. C'est joli, un éditeur de niveau qui vous propose toutes sortes de pentes, de blocs réactifs et ce genre de choses, mais si le moteur de jeu voit juste "ciel" et "roc", on est pas avancé. Enfin, ça, ça peut prendre encore un moment.

Friday, May 02, 2008

*bounce*

Un nouveau pas important pour la réalisation de Bilou a été franchi: j'ai mon évaluateur d'expressions, et son intégration au moteur de jeu est quasi-parfaite. En clair, cela signifie que je peu maintenant indiquer via mon scripteur de niveau que lorsque Bilou arrive sur le sol, il doit:

  • rebondir si sa vitesse est trop élevée
  • s'arrêter si la vitesse n'est pas trop grande.
Avant la DS, j'avais essentiellement testé deux mode de développement de jeux: le BASIC et le GameMaker de Recreational Software.

Côté BASIC, c'était la liberté totale pour les algorithmes et les comportements des monstres, mais les possibilités graphiques restaient restreintes -- même sur Pentium '90. Côté GM, c'était le constat inverse: scrolling dans des niveaux relativement grands, un nombre d'animation et de monstre présent à l'écran quasi illimité (au point que Pascal s'est même servi de monstres pour faire les bonus de notre remake de Pop'n'Twinbee). En revanche, le GameMaker était totalement incapable de gérer un saut un peu potable. Le simple fait d'avoir un sprite différent pour la montée et la descente du saut était impossible. Alors faire faire une cabriolle au perso quand il arrive au sommet de sa parabole (comme dans Bilou sur Basic), vous pensez bien ^_^.

About 10 years ago, i decided that neither QuickBasic nor recreational software's GameMaker could still fullfil my needs for creating games. I wanted parallax scrolling, compound sprites (think of rayman, but i already had it for Bilou in QB), and most importantly, more flexibility in creating monsters attack patterns.
I don't want to have to program them at the lowest level of machine code, i want their behaviour to be part of the game data, not part of the game engine.
When restarting game development on the DS, i decided to opt for something that would be state-machine inspired, but capable of reacting to the level map, the hero's position, etc. How to actually make it working came up later while reading the "making of Another World" .

Bref, à part des fioritures comme le parallaxe ou des sprites composés (à la Rayman), mon projet "Ultimate Game Maker" devait surtout permettre une plus grande souplesse de programmabilité : un scripting des actions : ne pas se limiter à des transitions "d'un état à l'autre" pour la gestion des monstres, etc. mais permettre plusieurs transitions depuis un état en fonction de l'état actuel du monstre, et une modification de certains de ses paramètres. Comme vous pouvez le voir sur l'image, je vous concocte déjà un petit 'appleman' bien particulier grâce à ce nouveau mécanisme.

C'est en lisant un making of du jeu Another World que l'idée est revenue au premier plan pour la DS. Dans Another World, Eric Chahi a choisi de disposer d'un environnement de développement pour son jeu qui ne nécessite pas de recompilation entre deux tests successifs, mais aussi la possibilité d'écrire toute la logique du jeu indépendamment de la machine considérée. Le jeu étant développé sur Amiga500 et constitué exclusivement de polygones rendus 'à plat' (à partir d'image filmées, il n'y a donc aucun calcul 3D), il n'est pas possible de s'en tenir à un "BASIC" traditionnel, trop gourmand en temps de calcul. Eric nous concocte alors son propre petit langage, mélange d'assembleur et de BASIC (oui, quand-même) dans lequel il va exprimer toutes les réactions du jeu (genre "quand le laser entre en contact avec la base du rocher, le rocher se décroche et s'incline en oblique").

Aucun élément compliqué n'intervient (pas de chaînes, pas de structures de données complexes): Eric utilise uniquement 256 variables entières pour représenter l'état du jeu, et le plus souvent, il ne les nomme même pas.

Même le maniement du joystick est géré de cette manière.

Another World was coded by Eric Chahi on an Amiga 500. He wanted cross-platform game logic for a game whose logic is *much* more complex than a shoot'm'up, and he also wanted to avoid recompilations between two tests. He thus naturally opted for some scripting language -- a mix between BASIC and assembly -- through which he controls animations and game variables (just integers).
Well, that's more or less what i'll do for my sidescroller game engine, except that you'll have per-object (monster, hero, switch...) variable in addition to the global (per-level) variables. In my case, i can even make the scripting simpler than Eric's "bassemblic" as it will essentially be used in predicates and actions of a state machine. So no control flow is needed at all.

Eh bien, je me suis dirrigé dans la même direction avec mon Game Engine, mais en donnant plutôt 16 variables par personnage (oh, il y aura aussi des variables globales comme dans Another World, rassurez-vous). Je me suis évidemment inspiré de mon interpréteur WASP qui m'a valu mon doctorat pour le bytecode, mais en simplifiant encore un coup: il me faut des expressions, pas des programmes ici. Donc exit toutes structure if-then-else ou les boucles, qui peuvent se coder par la structure de la machine d'état. Reste à inclure quelques instructions supplémentaires (p.ex. créer un nouveau monstre ou jouer un son), mais la base est là, même si mon 'langage' est encore plus moche que le bassemblic de Eric Chahi.

# gestion de la chute de Bilou.
state4 :anim0 {
   using gravity
   testpoint off (4,16)
   testpoint off (12,16)
}

state5 :anim0 {
   using stopper
   testpoint on (4,16)
   testpoint on (12,16)
}
# si la vitesse est trop élevée, on rebondit en la réduisant de moitié.
state4->state4 on fail [v1 $40 >=] (v1 2 / ~ :1)
state4->state5 on fail [t]


En avant. Essayons de voir si j'arrive à fixer la caméra sur Bilou pour le promener dans le niveau. Et en même temps, je vais tenter de faire un petit .nds de démo pour que vous puissiez tester ça sans devoir comprendre le fonctionnement de mon 'runme'.

2024 #choice reality check: those scripted state machines have their good side but also their downsides. Over 15 years later, I still don't have any DS editor for them because they rely more and more on CPP macros to make them "easier to write", although that's not perfect either. And they make bugs complicated to fix as soon as they mix scripted expressions and sophisticated C++ algorithm like with slopes.

Tuesday, April 29, 2008

GameObject : public Animator

Bin voilà. Hier soir j'ai fait le point sur tout ce qui tourne autour de ma classe GameObject qui assure la gestion des monstres et compagnie. C'est déjà un peu le méli-mélo, et je dois rajouter là-dessus des objets multi-sprites (pensez à une chenille, et vous êtes dans le bon) et surtout les contrôleurs, capables d'influencer le comportement des monstres en assurant ce que les petits scripts et les machines d'état ne sauraient pas proposer.

Je m'explique.

Prenez un sprite comme le petit ver. Il a principalement deux états (avancer vers la gauche ou avancer vers la droite). Chacun de ces états est lié à une animation (qui entre-autres le fait bouger) et définit les conditions sur les testpoints nécessaires pour rester dans cet état (pas de mur devant lui, et du sol en-dessous). Lorsqu'un des testpoints n'est plus bon, on cherche une transition à appliquer pour passer dans un nouvel état (en l'occurence, il y a une seule transition, vers l'autre état).

En gros, c'est super-simple.

I shoot a picture of my attempt to summarize the OO design of my GameObject class (later on called "GOB" through code and blog) and everything sitting around. Let's see that with a simple example such as the WoodWorm, that simply move forward and backward on platform. The worm has mainly two states depending on its direction. Each state has an associated animation and defines conditions expressed on testpoints that must be met so that we can remain in that state. Namely, when moving to the right, the worm must have some ground behind him and no wall in front of him. When testpoints are no longer valids, we look for a transition that will lead us towards a new (valid) state.

L'appleman, lui, est un peu plus sophistiqué: il se balade également au sol (avec les 2 même états), mais lorsqu'il arrive au bout d'une branche, il peut décider de sauter au sol pour attaquer Bilou si

  1. Bilou est en-dessous de lui
  2. Bilou n'est pas trop loin.
Le contrôleur, dans le cas de l'appleman, est passif: il se contente de lui rapporter sa position relative par rapport à sa "cible" (Bilou) et c'est via les prédicats des transitions (que je n'ai pas encore implémentés :P ) que la décision de faire demi-tour ou sauter se prendra.

More sophisticated GOBs, such as the Appleman, have a behaviour that does not depend only from test points. When reaching the edge of a cliff/platform, the Appleman will either turn back if Bilou is not in its attack range or jump off if Bilou sits below him in attack range. I intend to build that by means of controllers: C++ objects attached to a state that affect either directly (altering speed) or indirectly (indicating the presence of Bilou) the behaviour of GOBs. Even reading the Dpad and moving the hero accordingly can be achieved by a controller.
This should give us flexibility while reducing the amount of CPU power thrown at interpreting scripts.

Si on prend maintenant notre copain AppleBat, il dépend aussi de la position de Bilou pour son déplacement, mais il accélère et suit Bilou. Pour faire ça proprement, je vais laisser à son contrôleur le soin d'ajuster sa vitesse automatiquement pour se dirriger vers la cible. Tout ce que le script aura alors à prendre en compte, c'est le choix de l'image utilisée (pour que l'applebat continue à regarder vers Bilou quand il le dépasse).

Le reste, à mon avis, c'est du détail de bricolage de colle, donc je vous laisse le soin de déchiffrer mes gribouillis en cliquant sur les images si ça vous intéresse.

En d'autres temps, d'autres lieu, tout ça aurait pu faire une bonne autre lettre à Gédéon ;)

edit: partly obsoleted by this post and that post.

Tuesday, January 01, 2008

"Je vous présente les bordures!"

Je viens de retomber sur une lettre -- datée du 8 janvier 1900 -- que je n'ai jamais envoyée à son destinataire, Gédéon (qui signe de temps en temps un commentaire du nom de 'Ged' sur ce blog). Il faut dire qu'après la Inscene '99 et la collaboration de CJ à son jeu "insane bugs" (une sorte de remake de micromachines), on avait entamé une correspondance sur des sujets traitant de la réalisation de jeux vidéos, que j'illustrais assez abondamment de mes Bilous, évidemment.

edit 2021: finally translated.

A cette époque, en pleines études, je codais surtout pour les TP de l'université, mais je n'avais pas énormément le temps de mettre en oeuvre tout ce à quoi je réfléchissais pour mon "Ultimate Game Maker"

Voilà ce que celà disait:

Hello, Gédéon.

Je crois bien que j'ai trouvé un truc cool pour modéliser les niveaux dans Bilou! un truc plus smart que ces "tests de couleurs" puants que j'ai utilisé dans la version BASIC du jeu!... Je te présente les bordures.
L'idée est la suivante: tes sprites possèdent un certain nombre de "testpoints" et les déplacements ne sont possibles que pour certaines valeurs de ces test-points (pas question de faire marcher Bilou dans le vide, hein!)

D'autre part, tu plaques un peu partout dans ton niveau des bordures. Ces bordures sont des éléments logiques qui ne correspondent à aucun affichage. Elles sont liées logiquement de telle manière que l'on peut facilement savoir quelle bordure est située à gauche, droite, au-dessus ou en-dessous d'une bordure donnée. Les bordures sont également séparées en 4 classes: plancher, plafond, mur-gauche et mur droit.

Ca va, je ne vais pas trop vite ?

Bon. Voyons comment ça marche. Lorsqu'un sprite crèe ses test-points, il leur fournit à chacun une classe correspondant à la classe de bordure avec laquelle ils réagissent. Du coup, on s'empresse de lier chaque test-point à la bordure de même classe la plus proche (euh, ça ne devrait pas être trop sorcier).

Voilà un aperçu de la chose en cours ... tu peux facilement savoir si tu es "dans le mur" ou pas: il suffit de regarder de quel côté de la ligne le point se trouve. Dès que ton point sort du "champs d'action" de sa bordure, on cherche quelle est la nouvelle bordure et on y relie le testpoint (après quoi on effectue le test habituel).

C'est-y-pas merveilleux, tout ça ?

Allez, une dernière idée qui m'est venue comme ça, en écrivant cette lettre (pour être sûr que tu n'arrives pas à dormir cette nuit ;-)
Au lieu des testpoints, tu pourrais faire des "bordures" pour les sprites aussi (en fait, à partir de la bounding box), mais tu risquerais d'avoir plus de calculs à faire ...)

Enfin, ça m'éviterais d'avoir des bugs à la "Crazy BriX".
Allez,

Bion, depuis, l'eau a coulé sous les ponts. Le coup des "bordures" était bien alléchant, il permettait de passer à la 3D avec moins de prise de tête que si on doit procéder pixel par pixel (c'était le cas dans Bilou en Basic, comme je le mentionnais dans la lettre), mais par contre je n'ai jamais trouvé le véritable "truc pas trop sorcier" pour déterminer quel est la prochaine bordure du même type, en particulier lorsqu'il y a plusieurs plate-formes les unes au-dessus des autres (classique dans un jeu de plate-forme, évidemment).

Pour la version "DS", je me rabats principalement sur des tests "tile par tile" (et prout pour la 3D), et les "bordures" serviront principalement pour des plate-formes mobiles, nuages, et autres joyeusetés ... Une sorte de manière de passer outre les limitations typiques des jeux "game maker".

Voilà. Bonne année à tous.

Monday, December 17, 2007

Des pentes et des testpoints ...

Je relisais ce document sur la réalisation du jeu M.C. Kids, et j'ai enfin compris pourquoi ils avaient eu tant de mal à prendre en compte les pentes dans la "gestion des collisions". (J'ai un peu du mal à parler de détection de collision entre sprite et décor, vu que j'ai utilisé ce terme pour les collisions sprite-sprite pendant des années ;)

Bref. L'idée globale des testpoints, c'est s'assurer que le testpoint situé sous le personnage soit toujours à la frontière entre le "ciel" et le "sol". Ultra-simple sur des horizontales, un peu plus subtil sur une pente (mais grosso-modo il suffit d'ajuster le déplacement vertical en fonction du déplacement horizontal). Le hic, c'est de passer d'une surface horizontale à une pente. LE hic, c'est que la pente commence devant notre personnage alors que le point-test que l'on doit garder sur cette pente est sous le personnage (ici, yelloworm). En clair, notre worm va soit s'arrêter avant la pente (s'il la considère comme un mur), soit rentrer dans le premier tile puis s'arrêter au premier bloc de "mur" complet.

Dans M.C. Kids (et selon les dires de l'auteur, dans SMB3 également), ils résolvent le problème en introduisant un nouveau type de tiles: le "bas de colline", qui est placé dans le sol, dans le prolongement de la pente à amorcer. Ca marche, bien sûr, mais personnellement, je trouve ça un peu "bricolage". Un autre point qu'ils mettent en avant, c'est l'importance de garder un nombre de "test de map" fixe et le plus réduit possible -- de préférence un tile par testpoint.

Mais nous avons de toutes façon un testpoint devant notre personnage: celui qui sert à déterminer si oui ou non on arrive dans un mur. Et il est important de s'assurer qu'il soit suffisamment bas pour qu'il stoppe Bilou (et le yelloworm) même si le mur est très bas.

Du coup, on peut en profiter pour détecter le début d'une pente quand on arrive un tile avant. Il nous reste alors à ajuster la vitesse verticale de manière à ce que, un tile plus loin, on soit monté d'un pixel. On sera alors effectivement "sur" la pente. Bingo.

Il faudra évidemment s'arranger pour que l'angle entre les deux testpoints soit suffisant par rapport à l'angle de la pente la plus forte dans le jeu, mais ça, ça ne devrait pas nous poser de problème particulier.

Voilà. C'était ma cogitation du dimanche un peu en retard ... j'espère que c'était suivable ^_^

Saturday, December 15, 2007

demi-tour, marche!

Et voilà. Le mécanisme des "testpoints" est ajouté. Avant de recommencer chaque étape d'animation, mon petit ver s'assure qu'il ne rentre pas dans un mur et qu'il ne tombe pas. Je ne suis pas certain que ça conviendra pour tout le monde (Bilou, en particulier).

Il faudra aussi que j'automatise le demi-tour (pour l'instant, le ver s'arrête au bord, et je lui fais faire demi-tour "à la main" en programmant la deuxième animation.

edit: yes! le petit ver est maintenant entièrement autonome et toujours avec un code 100% générique. Une petite machine d'état avec des animations de "pause" entre les allées et venues, et ça y est. Et je peux en mettre autant que je veux! il suffit de rajouter une ligne dans test.cmd ;) Etapes suivantes:
  • animer les pommes
  • désactiver/réactiver les OAM quand ils sortent de l'écran
  • passer *réellement* à deux plans pour l'affichage du décor
  • scrolling parallaxe
  • (post-posé: animation composite pour Bilou).
J'ai aussi commencé un nouveau graphisme pour Bilou: là, je le trouve vraiment trop petit par rapport au "reste du monde".

Got the testpoints support added. Before playing the 'walk' animation again, the woodworm will check it wouldn't enter a wall and wouldn't fall down. It might not work for every entity (clearly not for Bilou), but it does the job here. Adding a turn-back ... there we are. Fully autonomous entity patrolling on a platform with completely generic code. First state machine ever, and I can replicate them here and there by just adding one line in test.cmd file.