Monday, January 21, 2013

More slopes

J'ai envie d'avoir plus de souplesse dans les pentes que juste "45° dans quel sens?". Si ça n'apporte pas grand-chose au niveau du gameplay en tant que tel (un ennemi-marcheur en haut d'une pente garde un avantage stratégique même pour d'autres formes de pentes), ça permet de construire des niveaux plus "organiques", ce qui n'est déjà pas si mal.

I deliberately picked a side item that should somewhat be less braintensive to work on as Lil'son is now released. In clear, something that can take place in a FridayAfternoon branch. So let's see how we could introduce more slope angles (and shapes) in my game engine. I assume that most of these "extra slopes" will be designed in larger chunks of 64 pixel wide, like a staircase, a curved hill top or a pair of 22.5° slopes. 

En revanche, j'ai déjà saturé le nombre de "type de blocs" dont je dispose vu ma technique d'encodage. L'idée cette fois serait de combiner le "type" (encodé par élément 8x8) avec la position du tile au sein d'un bloc de 64x16. Si ça reste jouable au niveau du moteur de jeu, ça demande un support spécifique dans l'éditeur de sprite pour "préparer" ces blocs de 64x16 contenant 16 tiles alloués de manière contigüe dans la SpriteRam (alors qu'ils sont normalement alloués par bloc de 4) puis de les disposer conformément à ce qui est prévu pour un des type d'obstacles souhaités.
You may remember that I have 16 major tiles types, which can be freely assigned to any tile. This "chunking" is thus actually required so that we can use the offset of tiles within a chunk as an additional clue of "which sub-type of slope" we're walking on (and eventually convert that into the appropriate height array in the engine's data. No real difficulty is expected from the game engine code, but it requires that we can alter tiles arrangement in the sprite editors so that the "curved hill" uses appropriate tile numbers. Some UML/C++ joy expected ahead. One that really insists on implementing a SMW clone could rather use "gradual" (11.25) slopes rather than those curves.

En comparaison, le moteur de SMW (selon Lunar Magic) offre 3 angles de pente: 'normal' (22.5), 'gradual' (11.) et 'steep' (45). Au niveau du gameplay, les pentes 'steep' étaient les seules à pousser d'office le joueur vers le bas (si ma mémoire est bonne).

Bon, je sais, ce n'est sans doute pas ultra-prioritaire pour faire avancer Bilou, mais mon petit J.l.n est né vendredi dernier, ce qui réduit un peu ma liberté d'action. Un peu de bidouille dans les éditeurs devrait donc être plus aisé que d'aller créer du code pour de nouvelles interactions avec Inkjet.

Thursday, January 10, 2013

Setting the bar

It's a secret to everybody: I follow the blog of Frogatto & Friends. I think the project has a high potential and is technically well-done. Their editors are truly impressive, for instance. Yet, I'd love to drive my own theses on game engine design to their conclusion, so I keep working on my own approach.

When I stumbled upon a post entitled "Platformer Enemy Design" in the newsfeed, right after I had crafted the "monster design book" out of this blog, I felt like a kid getting his Christmas present. Yet, the content was pretty far away from what I expected! The post is actually more like a call for rationale development applied to people who code monsters behaviour for games.


Juste comme je finissais le post sur les versions papier de "level design" et "monster design", je tombe sur un article d'un des auteurs de "frogatto & friends" (excellent petit jeu indie et open-source) intitulé "design des ennemis dans un jeu de plate-forme"... qui s'avère être très différent de ce à quoi je m'attendais, mais très intéressant malgré tout. Jetrel y présente une série de "pièges" dans lesquels un développeur de jeu peut tomber (liés à la conception des ennemis)  et qui pourrait retarder indéfiniment sa "sortie".


Consider making a flying enemy with basic behavior that makes it fly from one X position, to another X position. In a generous, open space with no terrain in the way, writing this is trivial; [...] It seems reasonable, from a level-designer’s standpoint, that it should always work regardless of the layout; [...] So if you put it in a twisting corridor, it’ll naturally duck under the outcrops, and rise over ridges to find a path back and forth. Except actually it won’t, because [...] there is no pathfinding. There’s no AI; there’s just one, single line of code with two conditionals in it.
What Jetrel calls for, is for people like me to admit that there's little value for a "magical monster that will always behave as a (hypothetical) third-party level designer could want it to behave". Instead, if you have a monster that works well in some type of terrain/environment - which is *the* type of terrain you intend for the level you want to place the monster in, it's perfectly fine. Noone would expect a paratroopa to work correctly in an underwater level, anyway.

Le premier conseil est une variation sur le thème (célèbre) "Keep it Simplest, but not Simpler": faire le plus simple possible, mais pas plus simple que ça. En particulier, s'évertuer à créer des monstres qui se comportent *toujours* correctement, quelque soit l'environnement, est inutile. Ce qui est important, c'est qu'il se comporte correctement *dans le type de niveau pour lequel il a été conçu*. Si la BerryBat est prévu pour poursuivre Bilou dans des cavernes, il se peut très bien qu'il ne fonctionne pas à l'intérieur de la pyramide (plus labyrinthique) parce qu'elle se cogne systématiquement au murs. Inutile aussi de prévoir que Funky Funghi doive "mourir" s'il tombe dans l'encre de la SchoolZone: il n'y a pas d'encre dans son environnement. Dans le même ordre d'idées, les Applemen qui se croisent ne posent pas de problèmes dans un niveau "à la Mario", même s'ils provoquent des problèmes de clarté dans Apple Assault.

When I was implementing Dumblador's wandering lone feet, last month, I encountered that very kind of situation. Making the feet move back *to the blador* in all circumstances quickly proved totally un-obvious. It would have required them to move at ludicrous speeds here, making jumps that even Bilou can't deal there, etc. But in practice, I don't necessarily want bladors to recover *at all costs*. What I do want, is that blador recovers with variable time depending on the terrain style ("hill"-like or "hole"-like) when the player takes too much time to grab it. It's fun if it recover even though you thrown it to the next book, but it's in no way mandatory.
There is however one key element that I want to bring into Bilou's gameplay, which is the suspension created by allowing monsters to be thrown away from their original location or have their path blocked by some other items (i.e. drop an inkjet on a pendat's pathway). That both rules out the simple "follow design path" approach used in RSD game-maker and the "level encodes where the path ends" used for some Keen monsters and in "The Game Factory" engine. That doesn't mean I need super-smart monsters that can climb up locations and track Bilou, but they definitely need some sort of state machine and level features detection so that they can have sufficient amount of autonomy.

Ce qui est amusant, c'est que les pieds baladeurs de Dumblador ont justement posé des questions de ce genre il y a un mois. Je les avais codé initialement pour qu'il puissent franchir *n'importe quel* obstacle (quelque soit sa hauteur) en les faisant sauter de plus en plus haut. Au final, ils étaient tout simplement incontrôlables et finissaient par sauter tellement haut qu'ils ne parvenaient de toutes façons pas à franchir l'obstacle en question. Dans la démo de nouvel an, les pieds *escaladent* les obstacles plutôt que de chercher à sauter par-dessus. Ça me convient. Il y aura des situations dans lesquels un Dumblador assomé ne pourra pas récupérer ses pieds (en particulier si Bilou l'emporte avec lui), mais il existera aussi des dispositions de niveau dans lesquelles j'obtiens l'effet désiré: un ennemi qui prend plus ou moins de temps à récupérer selon la situation dans le niveau (autorisant des stratégies du genre "j'attends qu'il soit au bord pour l'assomer) tout en offrant un retour visuel sur le temps qu'il reste pour s'en saisir.

J'admets qu'il faudra que je garde le conseil à l'esprit et que j'en fasse une ligne de conduite, parce que j'ai une tendance naturelle à résoudre des problèmes larges. La façon dont les ennemis "marcheurs" de Bilou décident de faire ou non demi-tour en est un exemple, mais je ne saurais me résoudre à une approche "dessinée" du trajet suivi par les monstres (cf. RSD Game-Maker), car j'ai déjà pu me rendre compte à l'époque que les types d'interactions que je souhaite apparaître au coeur du gameplay (interrompre le chemin de ronde d'un pendat en faisant tomber un inkjet, par exemple)

If you do a boatload of work on an enemy to make it work in different situations, but the basic decisions the player faces are still the same, you’ve wasted your time. The basic gameplay is still the same; you’re just jumping through hoops to provide it.

Which I translate in "no matter how smart that inkjet is when it shoots at you: if you can dodge its balls just by jumping anyway, you have changed nothing". I think this is deeply connected to interplay. I'll have to push more thought into that part.


Il y a d'autres choses intéressantes dans cet article, mais là, ça fait 3 fois que ma fée me demande l'heure en 30 minutes, donc je vais lacher un peu mon clavier ...

Sunday, January 06, 2013

Blog vs Epub, round 1

My fairy suggested a very nice Christmas gift for my (not so lil' anymore) nephews: she owns a thermal books binder and went "oh, but you could print some story of yours and bind it in a book with my machine... My audio processors catalyzed that into "how about stripping some meaningful text out of your blog and print that on 8x8" pages?" and immediately thanked her for that marvelous idea.

Of course, that required first a major upgrade of my "blogpressing" tools. The "images scanner" got complemented with list-post.pl which extracts posts having a certain tag and formats them into an HTML document. From there, I could use Open Office to import the document, export it into ODT format and start adjusting image sizes and other formatting annoyance to fit the documents into two ~50 pages illustrated text. The fight to get that printed out of my fairy's HP all-in-one printer is for another post.

Suffice to say that I decided that I'd avoid printing for my own reading needs and try to take advantage of my cybook instead.

I had no luck with the graphical front-end of Calibre this time, so I dug a bit the web and figured out that I could use the command line approach:

ebook-convert tagtionary.html test.epub --breadth-first --max-levels=8 --margin-left=2 --margin-right=2 --verbose

Even then, calibre gave me a hard time. I guess running Lucid Lynx in 2013 is the root of all my problems, so I'll have to upgrade sooner than wished. Btw:

non-ASCII characters in URLs abort the HTML-to-epub conversion -- leading with mysterious "ascii codec can't decode" exception (and no offending URL/file mentioned), and so did %-escaping in filenames. I had to manually interrupt the conversion after it took about half an hour in conversion attempts, with 3GB resident set and taking up to 7GB of virtual address space.

 It got to the "Creating EPUB Output" stage, and most pages said "No large tree found", then "splitting on page-break". All fine. A few pages have "large tree #0", with a split point defined. (I have no idea why "Split point: {http://www.w3.org/1999/xhtml}h3 /*/*[2]/*[663]" is mentionned there). Even the largest file "english.html" got happily split into 6 parts. Then for some curious reason, "mybrew.html" enters an endless series of "splitting... split tree still too large: 464KB."
From there on, it consumed more and more memory, obviously leaking all the prior attempts.
(edit: after dropping the offending mybrew.html, I managed to get the 54MB epub file. Checking on Odyssey ASAP).
(edit++: Calibre distributed in latest LTS handled mybrew.html out of the (virutal)box =:)