Friday, April 30, 2010

On se refait un Zelda ?

Ils sont nombreux, sur Internet, spriters, game-makers et même programmeurs, fan de tout poils à se lancer dans l'aventure de "mon propre Zelda"... Mystery of Solarus, 3 Shards of Light, Occarina of Time 2D ... La liste est sans doute encore bien plus grande. Et à bien y regarder, hormis le fait que j'utilisais Bilou comme personnage, mon propre projet de RPG était à 90% calqué sur Zelda: Link's Awakening. C'est inévitable: Zelda -quel qu'il soit- est un jeu qui marque et une fois l'aventure terminée, on en veut toujours plus (constat qui s'étend à tous les univers de Fantasy réussis, d'ailleurs). Dommage, d'une certaine manière que toute cette créativité ne soit pas accompagnée de graphismes originaux, mais tout ceux qui se sont essayés à la programmation de jeux savent à quel point il est difficile de trouver une équipe de graphistes qui vous suive dans votre aventure.

Par curiosité, j'ai regardé d'un peu plus prêt le projet "Solarus", dont le moteur "deluxe" est open-source. Codé en C++ avec du Lua pour le scripting -- une approche assez en vogue dernièrement -- il constitue une bonne base de comparaison vis-à-vis de mon propre moteur de jeu. Par exemple

-- Function called when the player wants to talk to a non-playing character.
-- If the NPC is the monkey, then the monkey sound is played and the dialog starts.
function event_npc_dialog(npc_name)

if string.find(npc_name, "monkey") then

if not savegame_get_boolean(24) then
-- monkey first dialog
play_sound("la_monkey")
start_message("outside_world.village.monkey")
else
play_sound("la_monkey")
start_message("outside_world.dungeon_2_entrance.monkey")
end
end
end
trouvé dans map0003.lua, propose une série de fonctions-scripts qui devront être appelées dans des cas précis. event_npc_dialog, par exemple, est d'office appelée par le code C++ lorsque le joueur tente d'interagir (bouton A?) avec une InteractiveEntity de sous-type NON_PLAYING_CHARACTER. Qui l'eut cru, hein ? Il vaut donc mieux que cette fonction soit définie si vous avez quoi que ce soit dans le fichier .map qui soit interactif. Un fichier complémentaire (map0003.dat), généré par un éditeur graphique, fixe les coordonnées des différents NPC en donnant, ici via la chaîne "monkey", le nom du personnage qui sera passé en argument à event_npc_dialog.


Il y a donc toute une série d'action-clé, véritables "point d'ancrage" dans le code C++ pour définir les interactions qui régissent les donjons et les énigmes d'un zelda. event_hero_on_sensor, event_switch_enabled, event_message_sequence_finished, etc. Dans l'autre sens, le script va essentiellement faire des appels à des fonctions C++, auxquelles on aura assigné un "nom Lua". P.ex.
lua_register(context, "play_sound", l_play_sound);

int MapScript::l_play_sound(lua_State *l) {

MapScript *script;
called_by_script(l, 1, &script);
const SoundId &sound_id = lua_tostring(l, 1);

script->game->play_sound(sound_id);

return 0;
}


Les chaînes de texte constituent un élément essentiel de tout ce système, identifiant systématiquement les entités. Pour un Zelda (où le temps de réaction des NPC n'est pas critique pour le jeu) sur PC (avec 1GHz facilement), ce n'est pas un soucis. Sur console portable, l'issue d'une telle approche est incertaine. Au passage, Lua prend en charge lui-même les constructions telles que if-then, les manipulations de chaîne et les expressions numériques. Ouf.

A noter que la "logique" d'une séquence se retrouve forcément éparpillée entre "parler au NPC", "fin de dialogue", , "fin de mouvement du NPC", "item ramassé", etc. Et également mélangées avec les interactions des autres personnages présents sur la même map. On peut s'en sortir, bien sûr, mais de là à dire que le modèle de programmation est transparent, je ne m'y risquerais pas. C'est une tournure d'esprit à prendre, probablement assez proche de la programmation d'interfaces graphiques sous winwin.
-- Function called when the monkey has finished jumping
function event_npc_movement_finished(npc_name)

if monkey_jumps == 1 then
-- first jump finished: wait a little amount of time before jumping again
start_timer(300, "monkey_timer", false)
elseif monkey_jumps == 2 then
-- second jump finished: start the last jump
play_sound("la_monkey")
npc_jump("monkey", 1, 64, false)
monkey_jumps = 3
else
-- last jump finished: remove the monkey from the map and unfreeze the hero
npc_remove("monkey")
unfreeze()
end
end


Si je n'en suis pas encore au stade où je peux proposer une alternative en "GOBscript Bilou" à un comportement de ce genre, je voudrais le contre-balancer avec le système des "gendoors/killdoors" de rayman designer. Une série d'objet dans le jeu peuvent être reliés à un "compteur" (représenté par le magicien) de sorte que lorsqu'un d'eux (ou tous, c'est selon) entrent en contact avec Rayman, une autre série d'objets (ici, un bonus et un clown ennemi) sont créés ou détruit. Un bon nombre de "salles-à-énigme" dans les donjons de Zelda peuvent se réduire à ce genre d'interaction. Un simple lien, créé graphiquement entre les objets, a donc suffi ici, là où plusieurs fonctions interviennent dans l'approche "solarus-dx".

Suite au prochain épisode ... il est temps que j'aille mettre ma fée au lit ;)

No comments: