Friday, May 31, 2024

Unity Pixels

Cyangmou posted again a picture that reminds me of why I wasn't appealed by Unity development (before Unity started acting crazy with its Terms of Use): how to set up unity camera for pixel art

- pick orthographic lens size

- compute lens size = desired pixel resolution height / (2* pixels per unit)

- there are by default 100 PPU, but you can tweak that to another value to support 32x32 or 16x16 blocks rather than blocks of 100x100 pixels.

Oh. Of course that was before Unity lost everyone's confidence in the indie game scene by announcing developers would have to pay a fee per deployment of their game while many indie developers built their business model on the fact that unity license was free and thus game themselves could be cheap enough so that people would actually try them. There's not a chance I get into Unity today, not even when I know that some studios would have an easier time publishing my game on the Switch if it was made with Unity.

Wednesday, May 29, 2024

SMBW: flying hazards

J'avoue que quand Mario Wonder a été annoncé, ça ne me gênait pas trop de devoir attendre 3-4 mois que mon frangin me l'offre. Quand je l'ai essayé chez lui, il était déjà au monde 2 ... la prise en main était plutôt agréable.

Mais en voyant les pikdors j'ai su que j'avais là une pépite de game design dont je blogguerais encore des années plus tard. Et en particulier que ce jeu allait peut-être apporter les références complémentaires dont j'ai besoin pour ma "peak zone".

I wasn't very hyped by Mario Wonder until I played the level with the condarts on my brother's switch. Then I knew behind dubious decisions, I had a pure gameplay gem in hands.

Granted, it's not the first time you see some ennemy breaking things in a (new) Super Mario Bros game, but the diversity of the design patterns they used with the condor-darts was brilliant. By that time, I was somehow in a dead-end with Peaks Zone design, especially monster design, and that Super Mario Bros Wonder might be the inspiration I (deseperately?) need to reboot my creativity.

Il faut dire que des ennemis volants, j'en ai déjà envisagés quelques uns, mais rien qui ne soit suffisamment convaincant. Bon, en soi les picdors n'ont pas un comportement si différent des mini-necky de Donkey Kong Country, juste qu'ils sont dans un Mario et viennent s'ajouter à la liste de plus en plus longue de briseurs de briques du dernier jeu SMB*.

Pourtant, le fait qu'il soient aussi utilisé comme une variation des thwomps m'a finalement donné envie d'avoir ... bin juste des plumes qui attaquent Bilou. Un clin d'oeil aux "esprits volants" du tout premier univers parallèle de mon frangin ;)

Et ça reste dans le thème de la planète bizarre. Vu l'effet que les "corbeaux" d'Ori m'avaient fait, ce sera précieux.

One of those patterns was thwomp-like behaviour but where the bird moved sideways horizontally so it wouldn't necessarily always hit the same place. That also means you may try to lure them and have them hit a target you're interested in.

And so I came up with that little sketch of a feather-eye ennemy that could hover out of jump reach and attack when the player is spotted. Nice, although, I don't think it would work with the "use as lift when it moves back" or "use as extra cliff while it is stuck into the wall" patterns.

And then I came back to the old menacing bird design of 2021, the one that has Bilou-compatible shape (I don't want the peaks zone baddies to be "just birds", they also have to fit the planet's theme) and found a fun way to make it fly. That dude could attack and move quite freely in the sky, be attracted away of its resting position, get stuck in walls and all.

Ah, et oui, pendant que je préparais tout ça et que je suis retombé sur mon espèce de vautour à tête de ptérodactyle (c'était l'idée), j'ai fini par trouver une manière rigolote de lui faire prendre les airs qui ne demande pas d'en faire un modèle 3D.


Monday, May 27, 2024

Compile-Time Regular Expressions

Well, guess what: there's a C++ header/library out there that allows regular expressions compiled at build-time. And I've got state machine parsing code that could use a little performance boost, especially if I envision GBA support rather than NDS for some future release.

#include <ctre.hpp>
#include <optional>

std::optional<std::pair<std::string_view, std::string_view>> 
match(std::string_view sv) noexcept {
    if (auto re = ctre::match<"state([0-9]++) *-> *state([0-9]++) *on (hit|found|event|fail)">(sv)) {
        return std::pair{
            std::string_view(re.get<1>()),
            std::string_view(re.get<2>())
        };
    }
    return std::nullopt;
}

Is what the siscanf(ln, "state%d -> state%d on %[hitfoundeventfail]%n") test for state transitions would look like, for instance. Some things will be cheaper, like testing explicitly for one of the 4 transition types rather than getting any garbage word you could craft with their letters and testing if (strcmp(reason, "event")) afterwards. 

Other things will require more code, like converting re.get<1>() into a digit as a post-processing step, since regexp do solely text matching and extraction, no text-to-number conversions. 

The code generated was quite convincing on x86_64, I'm a bit more suspicious about its ability to improve performance on 32-bit ARM processor, given how it requires 9 instructions to match every single character of the "state" constant string, for instance. If it helps for speed, it might have a significant impact on code size...

It seems to build even for GCC 10.2.0, the latest I installed from devkitpro, but not for the one used to build SchoolRush ...

Friday, May 17, 2024

Goodboy Galaxy

I owe you a presentation of "Goodboy Galaxy", an indie exploration platformer for the GameBoy Advance released for the 20-years anniversary of the device. I heard of it while following developer hot_pengu on twitter (since 2015!) 

The game has high-quality pixel art graphics that perfectly fit the theme of "stranded on an alien planet" and super-smooth animation. To be honest, the animation is so rich that it feels like the artist did not have to worry about technical limits at all.

I don't own the game yet. I haven't decided whether I'd rather run it on a true gameboy or wait for a Nintendo Switch port ... having my GBA power supply lost somewhere in house mess does not help deciding ^^". But I played the free demo a few times, the first being in September 2021 during the kickstarter campaign (which was completed in a mere 8 hours).

Original jam (Goodboy Advance) was in Dec '18, then we (very slowly) worked on a demo of what a full game could look like (Chapter Zero) which we released I think in May '21, then we spent a few months working on a Kickstarter campaign before finally starting the full game.

precised hot_pengu when I asked. I admit that the name's pun on Commander Keen's title "Goodbye Galaxy" created some expectation, but it's not that kind of exploration.

Voilà deux fois qu'il est question de "Goodboy Galaxy" sur ce blog. Je voulais que la 3eme en soit une présentation officielle. Idéalement, je l'aurais faite après avoir passé un certain temps sur la démo (au minimum), voir après avoir joué au jeu complet. Mais voilà: ce monde n'est pas idéal ...

Goodboy Galaxy est sorti en 2023 sur Gameboy Advance après 4 ans et demi de développement en indé. La campagne Kickstarter de 2021, c'était une folie et le budget espéré était disponible en 8 heures à peine. Je suivais déjà le développeur depuis un moment, mais plus occasionnellement que ce que je n'ai fait avec le dévelopement de CyberShadow ... Vu le jeu de mot sur "Goodbye Galaxy", la 2eme aventure de Commander Keen, je m'attendais à un gameplay qui serait à Keen 4 ce que Duck Tales est à Megaman ... Mais non. Le jeu qui s'en rapproche le plus dans ma mémoire, c'est Pharaohs Curse.

Low jumps (1 block), respawning one-way elevators, klap trap monsters, wrapping map ... I had a reminiscence of Pharaohs Curse during my first attempt playing the demo, and the author confirmed it was one of his references.

There are some sections where you have a bit more freedom of movement (2+ blocks, and double jump)  ... that is when your jetpack hasn't been stolen by a weird portal. Your (auto-regenerating) shield and blaster may also be stolen by other portals. If there is a way to tell what the portal will do before taking it, I haven't figured it out.

Cette impression, c'est principalement aux phases d'exploration sans le jetpack que je l'attribue, mais il y a aussi quelques rencontres qui y contribuent. Les générateurs d'assenceur unidirectionnels, par exemple, ou ces machins qui sortent du sol pour vous gober avec un léger retard (heureusement!). C'est que hot_pengu a adopté une approche plutôt inattendue où on commence l'aventure avec tout son équipement (blaster pour tirer, bouclier auto-régénérant et jetpack), mais on rencontre ça et là des portails à sens unique qui nous privent d'un de ces éléments pour un portion du niveau. Ajoutez à ça un level design où vous vous retrouvez tout à gauche du planétoïde si vous allez tout le temps à droite, et vous comprendrez qu'on finit par être un peu déboussolé... 

I must admit this approach of taking your equipment away and then giving it back to you in almost every level is quite unusual, if not unique. The closest I can think of is the "give/take color" doors of Fury of the Furries, although it doesn't affect that much the core gameplay. There have been things like that in Rayman, too, but that was part of the game's ultimate challenge, not part of every level.

Saturday, May 04, 2024

Goodboy('s geyser) was here

J'ai quand-même commencé à tenter de dessiner un jet d'eau. J'avais lancé un appel sur twitter, pour essayer de trouver des références. Il faut dire que les cascades de l'époque 16-bit étaient encore plus convainquantes que les jets d'eau de la même période.

J'ai eu une réponse en or, du genre de celles que j'ai eues pour les arbres l'an dernier. Le temps de faire le point, et je vous raconte tout ça ;)

"I can't draw convincing water jet from below" did not sound like a very convincing reason for not using a water jet if it is the proper game mechanic to use. I finally have a good one to pixel study and started doing my own, as you can see on the photo, but allow me to rewind and start where it started.

Un pas en arrière pour revenir à l'époque 16-bit. On préférait souvent éviter d'avoir de trop grosses animations à gérer sur ce genre de machine, et animer de l'eau se faisait généralement avec un dégradé et une modification cyclique sur la palette de couleur (palette cycling). Le foncé devient blanc pendant que le clair devient foncé, le très clair devient clair et le blanc devient très clair...

Back in the 16-bit era, it was frequent to do color cycling to animate a waterfall. Sometimes it worked nicely, sometimes it was so-so. It works best if the raster is long enough and if the animation speed remains movie-quality. At cartoon-12fps, you start seeing as flashing more than falling down water.

But you'll note the bottom of the waterfall is often missing in those scenes. And when you look at the few game art that tried having upwards water jet, you understand why: it no longer works. We expect water to become darker with density increase. In a waterfall, vertical density variation are interpreted as downwards waves, but when water eventually widens up in a fountain-like mushroom cap, it is always more dense at the center and less dense on the "edges". If you do palette-cycling here, you break that.

ça donne une illusion potable de cascade, pourvu qu'elle ne soit pas trop grande et que la vitesse d'animation soit réglée au millipoil. Et si possible, utilisez plus que 4 couleurs pour le cycle, parce que sinon on se retrouve avec quelque-chose comme le décor de Yoshi's Island qui tient plus du clignotement que de l'écoulement d'eau. 

Et malheureusement, la même stratégie est appliquée dans les (nettement plus) rares jets d'eau de l'époque. Je dis "malheureusement" parce que pour un jet d'eau, on va forcément devoir aussi animer le "chapeau de champignon" qui va avec, pour lequel la surface animée est encore plus grande. Et l'effet clignotement encore amplifié. On l'a déjà dans Bubsy, où les pixels isolés clignotants ne parviendront pas à faire oublier le fait qu'ils sont statiques. On l'a dans le final de Link's Awakening et dans le jet d'eau d'un jeu obscur avec la mascotte du Mac Do.

In Bubsy, for instance, the artist sprayed out the water pixels as the water starts falling down. This is coherent with the style, but animating them will just give you blinking static pixels of water. And the stylized 'mushroom cap' used in Link's Awakening and Mac Do game flashes even more aggressively.

Là où ça coince tout particulièrement avec le "champignon", même quand on évite les pixels statiques, c'est qu'avec ce type d'image, le clair et le foncé ne sont plus interchangeables. On voudrait que le bord soit plus clair parce que l'eau y est plus éparpillée. Faites-y du palette-cycling et vous aurez des images qui donnent l'impression d'être en négatif.

J'avoue que je trouve un peu dommage qu'avec les resources graphiques de la SNES, on en soit réduit à ça pour animer l'eau. Mais il faut reconnaître qu'en misant tout sur de la RAM vidéo, la console n'a plus la possibilité de reprogrammer les plages d'adresses (bank switching) pour faire des "animations gratuites" comme la génération 8-bit. Toute animation va impliquer un transfert DMA vers cette VRAM et le budget pour ces transferts est limité (comme toujours).

Truly, the "cap" of the fountain/geyser needs dedicated animated tiles, but we're unlikely to see that on 16-bit engines. 8-bit machines could have done that with more ROM and evolved mapper chip, but 16-bit consoles no longer try to pull pixels directly from the ROM. They put them in dedicated video RAM, and rely on DMA channels to bring animation frames in due time. But the amount of pixels you can transfer per frame is limited. Animating Bubsy's geyser that way at 60fps would consume 25% of your animation power:

On NTSC with overscan mode turned off, there are 262 - 224 = 38 scanlines in vblank. Subtract one scanline for prerender time, and you may end up with 165.5 * 37 = a smidge under 6 KiB per vblank.

6K, sur SNES, c'est 46 blocs-question de Super Mario World. Animer quelque-chose de la taille de la fontaine de Bubsy à 60fps, ça demanderait donc 1/4 de la puissance dans la partie critique du moteur de jeu.

Bon, et après l'époque 16-bit, alors ? Du côté de Super Princess Peach, par exemple, qui est plutôt réussi côté pixel art ? Un jeu ou pleurer est une mécanique de jeu, il doit bien y avoir des jets d'eau dedans non ? 

So, well, my game is not for a 16-bit system anyway, so could there be any water jet pixel art for 32+ game that I could study instead ? Say, in Super Princess Peach ? A game where you cry waterjets sure should also have some geyser-like elements, right ?

Well, it does indeed, in Wavy Beach 2. It uses a "cone" of water that might be animated through color cycling plus a "flower" top that follows the rule "keep the center dark and the edges light". But even then, I don't find it appealing, and I don't see how I could make it match anything but the super-stylized environment of SPP.

Eh bien oui, en effet. Dans le niveau 2 de la plage. Mais je dois bien avouer que je ne suis que moyennement emballé par le style. On a un premier élément (le cône) qui utilise un effet de palette qui ne fonctionne pas trop mal, les traîts latéraux restant sombres en permanence. Puis on a cette "fleur" qui grandit et rétrécit, gardant toujours le sombre au centre et se permettant des éclaboussures au bord des "pétales" sur la dernière frame.

Mais ... bof. Même en corrigeant le truc pour que la princesse apparaisse par-devant la fleur, ça ne me convainc pas. Oh, ça marche plutôt bien avec le reste de l'esthétique stylisée de SPP, mais ce type d'animation dans Bilou ? Pas convaincu.

Et depuis ? Parce que bon, le modèle pour la cascade de Bilou, il ne date pas de 2005. Mais le truc, c'est que j'ai surveillé les cascades en pixel art pendant pas mal d'année, sachant que j'en aurais besoin tôt ou tard. Alors que des geysers, c'est plutôt un truc de dernière minute.

Et c'est là que hot_pengu, l'auteur de Goodboy Galaxy, m'a pointé vers la vidéo de son jeu sur GBA

And so I finally asked hints to people on twitter who might have seen something I could use as a reference, or ever proper keywords to search for one, and to my surprise, I received an answer from indie game developer hot_pengu:

We call it a 'geyser' internally for goodboy, and this (timestamped vid) is how we represent it.

Je jette un oeil, je prends un petit screenshot pas fou mais qui pourrait donner un point de départ, et là,

Here's a better look, if it's useful! (there's two versions, one comes out of a monster)

He added as I posted a quick snapshot for future pixel study, handing the Goodboy Galaxy spritesheet with 2 sizes of exactly-what-I-needed material that you see printed on the top photo. 6-frame stunning animations, with tileable base and stylish top. Even the style isn't that different from the one I have for my waterfall!

Une animation pixel-art moderne, tout en fluidité et utilisant bien les 6 frames, qui peut être étirée en hauteur comme on veut ! C'est celle que vous avez vu tout en haut de cet article, imprimée et que je suis occupé à étudier. Parce que là, j'ai bien mieux qu'une référence pour donner une seconde chance au cas du bouchon: j'ai une idée.

Voyez, ce geyser, je peux le placer au fond du trou, directement, sans avoir besoin de bouchon. Il bouge, il attire l'attention. Pas moyen que le joueur ignore sa présence. Il a un look quelque-part entre la plate-forme et le bumper ... on pourrait toujours sauter dessus, on ne sait jamais. 

I like how it simply requires the player to hop into the proper spot to trigger. Much cleaner gameplay than the "pull the cover" I had thought about, but it remains interactive. Plus, by being already flowing before we interact, there's no more questions about "where does this water comes from, where does it goes afterwards", etc.

Deuxième bon point, une fois que Bilou a sauté dessus, je peux réutiliser le type de comportement que le joueur rencontrera plus tard avec Inkjet: Bilou reste "coincé", la pression s'accumule et wouf! on est projeté vers le haut.

And that would match the way 'inkjet' monsters will lately be used as delayed bumpers in the School Zone ... Since this game will no longer feature a welcome screen with the inkjet, it's a good thing the player can be shown early what happens after the "caught in a boiling pot" animation.

Et si il a raté son premier saut, il peut retomber sur le "chapeau" du geyser et re-sauter de là.

Mieux encore: si le joueur n'est pas resté dans le geyser jusqu'à être projeté, on peut le pousser vers le haut s'il entre en contact avec le "pied" du geyser. Et si rien de tout ça ne se produit, on peut directement réessayer la même manipulation. Pas de risque d'aller se coincer en nageant, de faire redescendre l'eau trop tôt ou quoi que ce soit de ce genre.

Bref, j'avais pensé vous redessiner *mon* geyser sur DS pendant la petite semaine de vacances, mais au final, j'ai juste eu le temps de faire une petite feuille de notes pour illustrer ce que j'imagine comme mécanique avec mon geyser ... parce que les vacances d'une famille 11 + 15, ça ne ressemble pas vraiment à la dynamique 8 + 12 et ses plaines de jeu à surveiller :-P

I expect that the platform-look of the geyser top will invite even the younger players to jump on. I expect that its animation will catch their attention much more than a purple block or handle. Should they fail to use the bump effect to reach the key, they can be caught by the platform-top and jump again. If they jumped out before the geyser happened, they can jump into the flowing geyser and be pushed up to the platform-top. It's flawless ^_^

Now I just need to find enough time in the upcoming evenings to complete it ^^"

PS: if you want to animate something like that, consider animating it without the vertical motion first: just the wobbles and the sparkles, and only then apply the vertical shift to each frame. Unsure I will follow that advice myself this time.