Aller au contenu

Organiser son code.

  • Fishou 
Note: L’article est principalement axé sur C2, mais des notions similaires peuvent être appliquées dans d’autres langages de programmation.

Lors de vos débuts dans le monde de la création vidéo-ludique, vous tâtonnez, testez des trucs, afin d’obtenir un résultat. Mais au fur et à mesure que vous avancez, vous faites des choses de plus en plus complexes, de plus en plus complètes, et aussi qui demandent plus de ressources. Parvenir à debugger tout ça devient de plus en plus difficile, et une simple modification doit être effectuée un millier de fois partout où le code l’utilise.

Sur le long terme, ce n’est pas gérable, la solution? S’organiser afin de rendre le code facile à relire, à classer, à editer, en clair, moins vous avez à retoucher au code, mieux c’est.

L’organisation et les performances vont-elles de paires?

Il m’est arrivé d’entendre dire que il fallait mieux avoir le moins d’events possible pour un exécution rapide du code, en théorie, cela est peut-être vrai, en pratique, cela a bien trop d’inconvénients pour le peu d’avantage que ça apporte, je m’explique:

  • Avoir un code très peu détaillé, très peu ouvert, cela implique de modifier des éléments sensibles du code assez fréquemment
  • Le nombre de modifications à apporter est très grand, donc plus de risques de faire des erreurs
  • Le code est très difficile à partager, car peu de détails sur son fonctionnement
  • Optimisation plus complexe
  • liste incomplète

Personnellement, je préfère avoir un code qui ressemble plus à un plan d’assemblage d’une machine, il y a des pièces que l’on va réutiliser, autant ne pas repréciser le nom complet de la pièce à chaque fois, mais utiliser une nomenclature pour s’en sortir.

La « beauté d’un code »

Un terme que j’aime utiliser est la « beauté d’un code », je défini un code beau comme étant un code:

  • Qui utilise des groupes, et les active/désactive afin de consommer moins de ressources (je détaillerai plus tard pourquoi)
  • Qui utilise des fonctions pour chaque calcul complexe et répétitif (via Function.Call() )
  • Qui utilise des fonctions pour des actions répétées (je détaillerai le bon usage des fonctions plus tard)
  • Qui utilise une event sheet principale pour les layout utilisant un style de gameplay
  • Qui utilise des includes pour bien classer les events
  • Qui possède une event sheet séparée pour les variables globales, et qui utilise des constantes globales
  • Qui utilise des constantes et variables locales pour une édition plus rapide
  • Qui utilise les familles à bon escient, ainsi que les containers
  • Qui est facile à lire pour une personne expérimentée avec C2
  • Qui possède les bonnes conditions, et qui les applique comme il faut (par exemple ne pas check les collisions de tout les objets du layout en entier si seule l’action à l’écran compte et que les collision cells ne sont pas assez performantes)
  • Qui utilise les sous-dossiers comme il faut, et qui à des noms suffisamment clairs (appeler tous les ennemis « Foe_Shooter », « Foe_Blaster » etc.. n’est pas nécessaire si ils sont référencés comme ils faut dans des sous dossiers, mais vous pouvez le faire si vous aimez l’auto complétion)
  • Liste incomplète

Bien sûr je ne dis pas que vous devez appliquer ça du jour au lendemain, ni que vous devez suivre ça à la lettre, faut juste orienter sa manière de programmer vers ce type de résultat, afin que ça devienne un réflexe avec le temps.

Avantages des groupes

Utiliser des groupes dans construct 2 (raccourci clavier : G) permet de classer les events, et de les activer/désactiver à volonté, un groupe désactivé est complètement ignoré, cela est très bon niveau performances (on notera que si le debuggeur est utilisé, les groupes désactivés ne sont pas complètement ignorés, et donc cela entraîne un mesure de la consommation des groupes qui ne sera pas présente dans le jeu final)

Il ne sert à rien d’avoir des vérifications faites dans certains cas, et donc utiliser des groupes et les désactiver est pratique:

  • Par exemple, vous avez un groupe qui contrôle un ennemi, les collisions associées à cet ennemi, le nombre d’ennemi en question, et d’autres, vous pouvez désactiver ce groupe lorsqu’il est actif et que l’ennemi n’existe pas en jeu.
  • Vous avez un groupe qui définit les contrôles du joueur, vous pouvez le désactiver lors des cinématiques, ou lors des dialogues si vous le voulez.
  • Si votre jeu se concentre sur les ennemis présents à l’écran uniquement, vous pouvez n’activer le groupe définissant l’IA des ennemis que lorsque l’un d’entre eux est à l’écran, et vérifier dedans que seuls les ennemis à l’écran soient influencés.

En faisant cela, vous devriez soulager votre jeu un peu et alléger votre code, de plus, vous pouvez définir des variables et constantes locales dans un groupe, vous permettant d’éditer plus facilement vos équations et autres formules.

Les fonctions

L’objet function dans C2 permet de regrouper des actions, et de les appeler facilement, il existe deux moyens d’appeler une fonction:

  • Via l’action « Call Function »
  • En utilisant l’expression Function.call(FunctionName); où FunctionName est le nom de la fonction

Le premier cas est utile pour regrouper et exécuter des actions de manière répétée, ou pour isoler certaines actions « sensibles » ou qui vont être modifiées souvent (personnellement, je préfère isoler les actions en rapport avec les APIs des sites web si possible)

Le second cas est très utile pour des formules mathématiques complexes, et s’utilise de la manière suivante:

  1. On définit une fonction (par exemple « somme »), qui utilisera 2 paramètres (le paramètre 0 et le paramètre 1)
  2. L’action sera « Return value », et la valeur sera Function.param(0)+Function.Param(1)
  3. lorsque vous voulez faire le calcul de la somme de Nombre_De_Points et Nombre_De_Bonus_acquis pour l’afficher dans un texte, faites : Set text to Function.Call(« somme », Nombre_De_Points, Nombre_De_Bonus_acquis)
  4. et voilà

Les fonctions rajoutent des checks à faire en plus par l’engine vous me direz.. bah en fait non, les fonctions sont comme le « on start of layout » par exemple, elles ne sont pas une valeur vraie ou fausse, mais une partie du code appelées, et donc ça ne devrait pas causer de soucis

Les fonctions ont un soucis par contre, elles ne pickent pas d’objet, mais elle peuvent utiliser des paramètres, pour utiliser une fonction sur un ennemi lorsqu’il est crée par exemple:

On created > Call function « name » with parameter 0 = Self.UID
On function « name »
-Pick by UID Function.Param(0) > do stuff

 Les includes

Pour comprendre le fonctionnement des includes, je vous renvoie à ce tutoriel : Includes, par crystal noir

Les includes sont très pratique pour s’organiser, j’aime personnellement avoir une event sheet de fonctions, une event sheet de gestion des sons, une pour l’initialisation du niveau, une pour les variables et constantes globales (qui n’a pas besoin d’être incluse celle-ci), un include pour les API éventuelles, si vous voulez, vous pouvez mettre un include sous une condition/dans un groupe pour pouvoir désactiver sous certaines conditions.

Ne pas penser en terme de code, mais en terme de logique

Une règle qui est selon moi primordiale, et qui doit se lire dans votre code, vous êtes un être humain, et un être humain n’est pas une machine (quoique je crois que il y a des micro-ondes qui me lisent °^° ), sa faculté à pouvoir penser, imaginer, prévoir, est ce qui est important, il est donc conseillé vivement de pouvoir lire le code en y comprenant l’effet derrière.

Je ne dis pas d’avoir un commentaire par ligne de code, mais entre (ViewportLeft(« game »)-ViewportRight(« game »))/(ViewportTop(« game »)-ViewportBottom(« game »)) et Function.Call(« Aspect ratio », « game »), je pense que l’un est plus clair que l’autre, que entre « Sprite Set X to Sprite.X+5*dt » et « Sprite set X to Self.X+Ennemi_speed*dt », la clarté est plus grande dans le second cas.

Au final votre code est une construction à partir d’éléments, et je préfère largement avoir une partie qui nécessite de réfléchir, mais avec le reste parfaitement clair, reprenons notre exemple plus haut:

Commentaire: Adaptation de vitesse par rapport à l’aspect ratio du layer « game »
Sprite Set X to Sprite.X+5*dt*(ViewportLeft(« game »)-ViewportRight(« game »))/(ViewportTop(« game »)-ViewportBottom(« game »))

Commentaire: Adaptation de vitesse par rapport à l’aspect ratio du layer « game »
Sprite set X to Self.X+(Ennemi_speed*dt*Function.Call(« Aspect ratio », « game »))

(Ce n’est que un exemple pour illustrer, pas un code que j’utilise personnellement)
Au final, le premier code n’est pas suffisamment clair pour que je comprenne tout de suite l’idée, le deuxième un peu plus, et si je veux éditer la vitesse, je peux changer la valeur de la variable/constante locale Ennemi_Speed, ou bien la fonction qui calcule l’aspect ratio d’un layer.

Le code doit être lisible par un être humain, un être humain ne parle pas en lignes de codes, mais en concepts, notions, et langage humain (français, anglais, etc..), donc si le code présente de traces de ça, c’est un grand plus.

Petite note

J’entends (trop) souvent parler des mauvaises perfs de C2, oui, C2 fait du HTML5 + JS, oui, c’est plus lent que du natif, mais par contre, les threads sur scirra à propos des « mauvaises perfs » de C2, ça revient tout les 2 mois, et 50% du temps, y’a un truc véridique entouré de pleins de trucs bidon, soyons clair: C2 se passe de connaissances en programmation, mais pas en game design, lorsque j’entends que il est impossible de faire un jeu « pro » sur C2, ma réponse est la suivante: Un jeu « pro » ça se fait de manière « pro », ça doit être bien pensé, et bien géré, si vous faites un jeu ou l’intégralité de tous les ennemis du jeu entier sont actif en même temps, ça va pas marcher, faut faire des concessions, et c’est pas valable que pour C2 ça:

  • les ennemis qui repopent dans megaman
  • les transitions d’écran dans zelda
  • les ennemis qui apparaissent à certains endroits du niveau dans mario
  • Les groupes d’ennemis qui popent dans Fist of the north star:Ken’s rage 2
  • les textures qui n’apparaissent que à une certaine distance dans les jeux en 3D
  • le nombre de tir limité à l’écran dans certains jeux

Le but est toujours d’alléger le travail de la machine qui interprète le jeu, sans pour autant massacrer le jeu lui-même, ça à toujours été fait et sera toujours fait, et c’est une chose dont il faut avoir conscience dans C2 aussi.