Showing posts with label math. Show all posts
Showing posts with label math. Show all posts

Saturday, January 04, 2025

PicoDriller fait des vagues.

A côté de ses productions principales (lunark, castaway), Johan Vinet nous a aussi proposé un petit fangame sur pico8 d'un jeu où il faut creuser plus vite que son ombre, sorte de croisement entre Tetris et Boulder Dash.

Je dit "petit", mais la qualité du gameplay et de l'habillage est tout à fait remarquable. C'eut été un homebrew sur NintendoDS, vous pouvez être certains que je serais resté scotché dessus!

Mais ce qui m'a frappé au point de le reprendre dans ma récapitulation de 2023, c'est la vague géante de l'écran titre. Monochrome, avec au plus un creux à l'écran, elle est tout à fait dans les contraintes techniques de la SNES: on coderait alors les vagues en reprogrammant les "window" à grand coup de HDMA, et ça débloquerait le principal frein à une adaptation de SchoolRush sur la reine des 16 bit ... 

PicoDriller is a fun little Pico8 game, somehow a crossing over Tetris and Boulder Dash, and it would definitely deserve a complete review of its art and gameplay mechanics. And unfortunately, I'm not going to do that today. Sorry Johan (he's one of the Pico Driller author, and I've been playing some of his other games). No, today, I'll just discuss the title screen of PicoDriller and its very convincing big waves.  

Alors autant vous dire qu'à plusieurs reprises au cours de 2024, je me suis posé la question "mais comment savoir quelles valeurs donner, ligne par ligne, à Wstart et Wend pour qu'ils définissent la zone sous une sinusoïde ? et comment coder ça efficacement ? Et est-ce que ça tiendrait dans le budget CPU d'une frame SNES ?"

Et pour bien commencer 2025, je me suis dit "eh, mais si on partait du sommet de la vague et qu'on suivait la pente pour élargir la zone noire masquant l'image ?  Après tout, la dérivée de sinus, c'est le cosinus et le cosinus, c'est le sinus déphasé d'un quart de tour !

The core reason is School Rush, of course, and how the Super NES couldn't render enough sprites on a single scanline to depict the raising ink in the game. But if you use demomaker tricks with the 'window' feature of the 16-bit queen (using it as 2 horizontal lines whose position and size can be updated on every scanline), you might get a wave moving as smoothly as that in PicoDriller intro, and that might be stunning enough to excuse the lack of additional colors. 

The idea isn't new: it has been teasing me for the whole 2024 year. So with the winter holidays coming again I noted that "well, I could just start with the top-of-sine, at a given position, and widen it according to the slope of the sine at that point, right ?" and then taking my noteboox and starting to scribble trigonometric functions, taking into account that slope of sine is cos, that cos at some point is sine later on the curve and things alike until I thought I had enough to write down a little loop that *should* give all the items needed to render the sine with "just" a pre-computed look up table for cos values.

I had the surprise to see game developer Jayenkai (Blockman and countless other titles) objecting that it might still be too much maths for the SNES hardware, and pointing me towards an online BASIC interpreter with fair documentation of their graphics commands ... He proposed an alternative where quadratic curves are used to emulate sine, and to be honest, the difference isn't that strong unless you're interested by the slope of your sine when it crosses the "horizon line".

Jayenkai (platdude, blockman et tant d'autres) trouvait que ça faisait trop de calculs pour une snes et m'a aiguillé vers un basic-en-ligne pour m'illustrer une approximation quadratique très 6502 ... 

C'était juste ce qu'il me fallait pour tester rapidement le bidule, donc j'ai recodé la génération de la table de lookup (grandement simplifiée par la présence de cos() et arcsin() dans le basic) puis la 'simple' accélération/décélaration de la descente par accumulation, un accès à la lookup table par ligne

Bin je dois dire que ça donne plutôt bien ^_^

But that evaluator (gotojse) was a breath of fresh air, allowing me to toy around and eventually build up a way to render what *I* had in mind as well, proving the concept. Okay, I don't have it animated like in pico driller, but that might come later on ^_^ 

Bon, ce qui fait tout le sel de l'effet de pico driller, c'est avant tout la variation d'amplitude appliquée sur la sinusoïde, qui va jusqu'à en inverser la polarité. Le fait d'avoir doublé l'effet avec une "ombre" est très stylé, mais incompatible avec le hardware SNES que j'envisage de piloter. 

Il me semble aussi que le "niveau 0" monte et descend légèrement au fil du temps suivant une autre sinusoïde. Ce qui serait stylé, ce serait qu'il suive en réalité une sinusoïde de très basse fréquence (moins de 45° par écran), mais ça nécessiterait pas mal de calculs en plus ... pas sûr que je puisse me le permettre ici ...

 


Saturday, August 12, 2023

Swinging animations

Plus le design de Bilou: Dreamland avance, plus la possibilité de s'accrocher et se balancer prend de l'importance dans le gameplay envisagé. Et c'est tant mieux sauf pour une chose: mon moteur d'animations est actuellement très mal adapté pour ce genre de chose. Comprenez, on risquerait plus de se retrouver avec quelque-chose de raide comme Mickey Magical Quest plutôt que les mouvements souples de Fury of the Furries. Ce serait d'autant plus dommage que Spongebop ne souffre pas de ce genre de limitation et que ça rend furieusement bien. Mais voilà, l'image de Spongebop est indépendante de l'angle de sa corde.

The more I sketch some design ideas for Bilou: Dreamland, the more swinging with a rope-like object occurs. I won't claim it will be a primary game mechanic, but it will certainly be an important one. There's just one drawback : so far my game engine offers no support for that kind of animation. Oh, sure, I could go the Mickey Magical Quest way of hard-coding a few frames and the positions Bilou should take to swing, but given that I have a freely-swinging Spongebop in the previous game, I'd prefer go for something like what I've enjoyed in Fury of the Furries: game physics that allows 360° swinging, with free positioning anywhere on the circle defined by the current rope length and almost free extension / reduction of the rope length (Mickey can only climb).

One picture every 10° to swing a tiny
Pour Fury on a pas moins de 36 images différentes et la possibilité de faire un tour complet de son point d'accroche si la physique le permet, soit 10° entre chaque image. Mickey n'offre que 7 images différentes pour un angle de balancier de 90°, soit 15° entre chaque image. La différence devrait être légère (on passe quand-même de 60 fps à 40 fps avec un rapport pareil), mais là où ça change tout, c'est que les angles de la corde elle-même sont discrets à 15° près avec Mickey, dû au fait du hardware 'par tiles' de la super NES. Du coup, les positions de Mickey deviennent elle-même discrètes (bien qu'il puisse remonter sa corde). 

Je me suis donc repenché là-dessus pendant mes p'tites vacances, à l'abri dans mon épave de bateau pirate et j'ai peut-être bien une solution. L'idée serait de compléter les deux modes d'animation actuels (l'un qui suit une ligne du temps, l'autre qui colle à une série de déplacements) pour faire un système dans lequel on va choisir d'avancer ou non dans l'animation selon le vecteur-déplacement actuel. En gros, je fais une animation dans laquelle Bilou se balance autour de sa main et le moteur passera à l'image suivante quand Bilou va dans la même direction que celle suivie par cette image dans l'animation.

The summer idea about it was to handle that with a new way of playing back an animation: rather than enforcing a delay between two frames or a specific motion, we would compare the in-game speed vector (xg, yg) against inter-frame motion vectors (xa, ya). You'd then step to the next frame in the animation only if (xg, yg) is "beyond" the expected motion to the next frame. A bit of maths done in a sandbox (literally) shown that we can tell that just by two integer multiplications per frame. It will need separate animation for swing-to-the-left and swing-to-the-right, but I'm perfectly fine with that.

Mais comment savoir si un vecteur effectif (xg, yg) est plus proche de l'ancien vecteur de l'animation (xa, ya) ou de celui de l'étape suivante (xa', ya') ? Je suis parti de mon vieux cours d'analyse math avec "l'extrémité du nouveau vecteur (xg, yg) doit être au-dessus de la droite définie par k*xa' = k*ya' (que l'on peut réécrire y = (ya' / xa') * x). Mathématiquement parlant, on sera au-dessus si yg > ya' / xa' * xg. Programmatiquement parlant, c'est moins drôle, à cause des divisions par zéro dans les verticales et du manque de précision des divisions / multiplications quand on se contente de travailler avec des nombres entiers. J'ai donc voulu ruser et faire un peu d'algèbre pour passer à yg * xa' > ya' * xg, qui ne fait que des nombres plus grands que 1. En fait, je venais de retomber sur l'expression de l'aire signée d'un triangle, bon vieux truc du cours d'algo II à l'unif (merci, feu PAdM). ça tombe bien: elle sert justement à calculer si un point est à gauche ou à droite d'un vecteur donné.

Il faudra vérifier expérimentalement si je dois modifier le sens de l'inégalité pour le balancier-retour, trouver un moyen d'intégrer ça au scripts (probablement avec un 'speedmove' comme on a déjà un 'selfmove'). La bonne nouvelle, c'est que le même système pourrait probablement marcher aussi pour une voiture vue du haut, des segments de lianes qui se balancent et segments de ponts qui s'inclinent :-P

extra bonus with the anim.x * gob.y > anim.y * gob.x expression, is that a faster motion (e.g. longer rope) or a slower motion (e.g. shorter rope) will not affect things, because it would affect both gob.x and gob.y by an identical factor k and thus their effects will compensate in the comparison.

Tuesday, April 09, 2013

Circularity

Spongebop monster design was a way to convert the (imho) funny and interesting spider monsters of prehistorik 2 into Bilou's world. As such, they won't simply move up or down, or track Bilou while sliding (as suggested in the 20-year-old proposal), but also rock and roll, hanging by a thread. Another conversion of dangerous-but-useful NPC rather than an evil ennemy.

Une éponge qui se balance au bout de sa corde ... une idée de monstre sympa et qui ouvrait pas mal de perspectives dans la "School Zone", mais techniquement plus délicate à réaliser qu'un "simple" monstre sauteur ou marcheur, en particulier avec les contraintes techniques de la DS. Depuis l'automne dernier je dois bien avoir gribouillé 3 ou 4 techniques succeptibles de rendre la chose possible sans jamais être allé jusqu'au stade de l'expérimentation. Mais maintenant que je peux attacher un objet à un autre au niveau du script, il est temps de monter un prototype.

Now, it's time to turn this "nice idea" into some real code. Physically speaking, spongebop will act as a pendulum bob, oscillating as the result of gravity and counter-force originating from thread tension. We're taught that when 17 at school. There's just two major drawback to a "straight coursebook implementation":
  • it's full of angles, sin(x) and cos(x), which are pretty heavy for our 66MHz processor
  • It assumes a rigid, fixed-length rod between bob and pivot.


Conservation de l'énergie, mouvement circulaire ... ce ne sont pas les approches qui manquent, mais chacune a aussi ses hypothèses de travail (notamment la présence d'un axe fixe que je ne désire pas). J'étais au départ prêt à utiliser une variante des algorithmes à la bresenham pour les cercles, mais la multiplication n'est en fait pas si coûteuse en terme de temps de calcul que je ne l'aurais cru. Je peux donc exploiter assez librement l'équation x²+y²<=r qui décrit tous les points à l'intérieur d'un disque. Dès que les la distance entre SpongeBop et son point d'attache ne valide plus cette contrainte, c'est qu'il tire trop sur la corde.
     Bilou would pretty much love that sort of predictable and massive pendulum platform, but spongebop's thread is instead fairly elastic and landing with excessive velocity triggers a rodeo session, says the comic concept art.
    In other term, I would like something more flexible, that can constraint Spongebop to stay within a certain circular area, but still allow Lissajous-like patterns when drawn away from its equilibrium path, as I explained this lunch-time to Cyril. So fundamentally, Spongebop's behaviour is defined by two controllers:
    • gravity, that takes care of the "free riding" when x²+y²<r²
    • radius detects situations where x²+y²>r². As soon as that occurs, we use a 'unit' vector that points towards the pivot position and iteratively move spongebop back into the allowed area.

    Ce qui est assez amusant, c'est que si je parviens à faire en sorte que SpongeBop ne puisse allonger exagérément sa corde, j'aurai le mouvement de balancier par simple application du même contrôleur gravity que celui qui sert pour les sauts de Bilou. Je rejoins alors le "scénario BD" qui prévoit un comportement plus cahotique lorsqu'on atterit trop violemment sur le dos d'une éponge. J'utilise donc le vecteur "attache-éponge" pour définir une "direction de rappel" et ajouter un déplacement vers le centre tant que Bop est hors de son cercle de repos.

    Ideally, energy preservation should keep the sponge oscillating as long as the level runs, but there's something that looks very much like friction in the digital world: precision loss. Since we're working with fixed-point arithmetic here, we're losing some speed in rounding coordinates and angles, so that after about 3 cycles, there isn't much movement left. It's compensated by a refreshing impulsion generated everytime Spongebop arrives just below the pivot point, as if it had some muscles that helps it control its movement. That part is handled by some gobscript and the controller merely report "you're at (0,ymax)" through an event.

    it's a sponge bob ^_^

    MUL instruction on ARM9 merely takes 2 cycles. As a result, I ended up dropping my initial idea of using (x+1)²=x²+2x+1 and simply re-compute (x+a)*(x+a) whenever needed.
    I have at most one division per frame (per instance), to define the centripedic 'unit' (1/16th of pixel) vector. Yet, division isn't handled by the CPU, but by some extra software (the support library coming along with devkitpro's flavour of GCC). I think I'll live with it unless it turns out that I'm over my cycles-per-frame budget.


    Le résultat est plutôt satisfaisant. Ce n'est pas un arc de cercle parfait et je dois "relancer" le mouvement régulièrement, les erreurs d'arrondi en virgule fixe ayant tendance à l'amortir trop rapidement. Je m'en sors donc avec une division (software) par éponge et par frame au prix d'une progression itérative pour recentrer Bop lors de ses déviations. C'est plus erratique que le pendule du cours de physique, mais ça colle plutôt bien au personnage. Reste à maîtriser la 3D pour tracer l'élastique et le co-processeur mathématique de la DS pour avoir accès aux divisions et aux racines carrées la prochaine fois.

    Agreed, the trajectory followed by SpongeBop on the animation above is not quite a circular arc, but for some dizzy sponge on an elastic string, I think it does the trick. Now it's time I allow Bilou to "land" on SpongeBop, because solely bopping over is pretty tricky.


    Hmm ... tracing a GL_LINE between the spongebop and its pivot (not yet visible) would be a nice use case to introduce 3D support in the mix ...

    PS: all this was the last idea of a long list of dropped approaches. Maybe I'll discuss alternatives later on.
    PPs: of course, although the ARM processor on the DS has no support for division/square roots, the DS itself has extra hardware for such functions.  Since it says "square root takes 13 cycles", it could be worth to use the sqrt(dx²+dy²)/sqrt(R) ratio to properly compute the pullback force rather than relying on iterations here. (that's still an occurence of premature optimisation, imho)