• 28 Mars 2024, 21:13:33


Auteur Sujet: Quelques règles de base pour mieux scripter  (Lu 13464 fois)

0 Membres et 1 Invité sur ce sujet

Hors ligne Syg

  • Expert programmeur C/C++/PAWN
  • *
  • The GTAOnline Jesus
  • Messages: 3908
    • Voir le profil
Quelques règles de base pour mieux scripter
« le: 06 Janvier 2009, 17:22:59 »
Voici un topic qui énumère quelques règles simples que tout bon scripteur peut (doit ?) appliquer.
Si je fais ce topic, c'est simplement parce que beaucoup de problèmes sont liés à la non application de certaines de ces règles (qui peuvent être adaptées selon le cas et les humeurs de chacun, je ne veux aucunement m'imposer).

L'INDENTATION

L'indentation d'un script n'est pas là que pour faire joli, elle augmente le lisibilité du code et permet de détecter certaines erreurs facheuses très difficilement détectable dans un script non indenté.
L'indentation doit devenir un réflexe au moment de l'écriture du script, pas après coup car ça peut être compliqué et source d'erreurs.

Voici un exemple pris dans un post de ce forum :
if (strcmp("/tunning", cmdtext, true, 10) == 0)
{
if(IsPlayerInAnyVehicle(playerid)) // Si le joueur est dans un véhicule....
{
new Car = GetPlayerVehicleID(playerid), Model = GetVehicleModel(Car);
switch(Model) { case 448,461,462,463,468,471,509,510,521,522,523,581,586: return SendClientMessage(playerid,0xFF0000AA,"> Vous ne pouvez pas ajouter de composants dans des motos."); } //Si on ajoute des composants dans des motos ou vélos notre jeu crash...
ShowMenuForPlayer(tunning,playerid); // On active le menu "tunning"
TogglePlayerControllable(playerid,0); // On freeze le joueur
}
else{ // Sinon....
SendClientMessage(playerid,COLOR_GREEN,"> Vous devez être dans un véhicule");
}
if(strcmp(cmdtext, "/dm") == 0)
{
SetPlayerPos(playerid, -1452.3439, -531.1021, 13.5776);
SendClientMessage(playerid, COLOR_ORANGE, "Tu est au dm de la gare de SF");
return 1;
}
return 1;
}

Ce bout de script compile et s'exécute.
Mais si l'on teste ce bout de script, on se rendra compte que la commande /dm ne fonctionne pas.

Si on indente correctement ce bout de script, l'erreur saute au yeux :
if (strcmp("/tunning", cmdtext, true, 10) == 0)
{
   if(IsPlayerInAnyVehicle(playerid)) // Si le joueur est dans un véhicule....
   {
      new Car = GetPlayerVehicleID(playerid), Model = GetVehicleModel(Car);
      switch(Model) { case 448,461,462,463,468,471,509,510,521,522,523,581,586: return SendClientMessage(playerid,0xFF0000AA,"> Vous ne pouvez pas ajouter de composants dans des motos.");
      ShowMenuForPlayer(tunning,playerid); // On active le menu "tunning"
      TogglePlayerControllable(playerid,0); // On freeze le joueur
   }
   else
   {
      SendClientMessage(playerid,COLOR_GREEN,"> Vous devez être dans un véhicule");
   }
   if(strcmp(cmdtext, "/dm") == 0)
   {
      SetPlayerPos(playerid, -1452.3439, -531.1021, 13.5776);
      SendClientMessage(playerid, COLOR_ORANGE, "Tu est au dm de la gare de SF");
      return 1;
   }
   return 1;
}
La commande /dm est incluse dans la commande /tunning.

LES ACCOLADES

Les accolades servent à délimiter des bloc d'instructions dans un script.
On peut en mettre autant qu'on veut du moment qu'une accolade ouvrante correspond à une accolade fermante de même niveau.
Même si elles ne paraissent pas nécessaire, il faut prendre l'habitude de toujours mettre les accolades.
Prenons un bout de script :
if (a==10)
   b=52;
et imaginons, que dans le cas où a égale 10, je veuille aussi changer la valeur de c. Je rajoute donc une ligne à la suite :
if (a==10)
   b=52;
   c=49;
Mais ce bout de code ne fait pas ce qu'on attend de lui, il affecte systématiquement la valeur 49 à c, quelque soit la valeur de a.
Le code correct est :
if (a==10)
{
   b=52;
   c=49;
}
Et si dès le départ on avait écrit :
if (a==10)
{
   b=52;
}
l'erreur n'aurai pas pu être faîte.
L'exemple en question est simple et beaucoup d'entre vous n'aurait pas fait l'erreur mais je peux vous assurer que dans un script plus compliqué, c'est une erreur que l'on peut faire facilement et qui se révèle très difficile à trouver.

LA FACTORISATION

Quand je développe (en C/C++ ou en PAWN), j'applique toujours la règle la plus importante à mes yeux : la factorisation.
Ce que j'appelle factorisation, c'est mettre en "facteur" la partie commune à plusieurs bouts de code, comme en math lorsque que l'on factorise une expression.
Exemple en math : (x+1)(x+2)+(x+1)(2x+4) = (x+1)(3x+6) = 3(x+1)(x+2)

Dans le cas d'un script, on cherchera à créer une fonction à partir d'un bout de script que l'on retrouve plus d'une fois à l'identique dans le script.
Exemple :
if (strcmp ("/mls", cmdtext, true) == 0)
{
   new vehicleid;

   if (IsPlayerInAnyVehicle (playerid))
   {
      vehicleid = GetPlayerVehicleId (playerid);
      SetPlayerPos (playerid, 1.0, 2.0, 3.0);
      SetVehiclePos (vehicleid, 1.0, 2.0, 3.0);
      SetVehicleZAngle (vehicleid, 4.0);
      PutPlayerInVehicle (playerid, vehicleid, 0);
      SetCameraBehindPlayer (playerid);
   }
   else
   {
      SetPlayerPos (playerid, 1.0, 2.0, 3.0);
      SetCameraBehindPlayer (playerid);
   }
}
if (strcmp ("/msf", cmdtext, true) == 0)
{
   new vehicleid;
   if (IsPlayerInAnyVehicle (playerid))
   {
      vehicleid = GetPlayerVehicleId (playerid);
      SetPlayerPos (playerid, 5.0, 6.0, 7.0);
      SetVehiclePos (vehicleid, 5.0, 6.0, 7.0);
      SetVehicleZAngle (vehicleid, 8.0);
      PutPlayerInVehicle (playerid, vehicleid, 0);
      SetCameraBehindPlayer (playerid);
   }
   else
   {
      SetPlayerPos (playerid, 5.0, 6.0, 7.0);
      SetCameraBehindPlayer (playerid);
   }
}

Dans le code ci dessus, on trouve deux parties de code identique (aux valeurs des paramètres près).
Si je veux par exemple positionner l'angle du joueur quand il est à pied, il faut que je corrige à deux endroits (plus si j'ai plus de commandes de téléport).
Je vais donc factoriser pour faire une fonction que j'appellerais TeleporterJoueur :
TeleporterJoueur (playerid, Float:X, Float:Y, Float:Z, Float:Angle)
{
   new vehicleid;
   if (IsPlayerInAnyVehicle (playerid)
   {
      vehicleid = GetPlayerVehicleId (playerid);
      SetPlayerPos (playerid, X, Y, Z);
      SetVehiclePos (vehicleid, X, Y, Z);
      SetVehicleZAngle (vehicleid, Angle);
      PutPlayerInVehicle (playerid, vehicleid, 0);
      SetCameraBehindPlayer (playerid);
   }
   else
   {
      SetPlayerPos (playerid, X, Y, Z);
      SetCameraBehindPlayer (playerid);
   }
}

Le code du dessus peut donc maintenant s'écrire :
if (strcmp ("/mls", cmdtext, true) == 0)
{
   TeleporterJoueur (playerid, 1.0, 2.0, 3.0, 4.0);
}
if (strcmp ("/msf", cmdtext, true) == 0)
{
   TeleporterJoueur (playerid, 5.0, 6.0, 7.0, 8.0);
}

On voit tout de suite les avantages d'un telle factorisation :
   - Si un téléport fonctionne alors tous les autres fonctionneront (sauf si les coordonnées sont fausses),
   - L'ajout d'un nouveau téléport est simple, rapide et sans risque,
   - La correction d'un bug ne conduit qu'à la modification de la fonction TeleporterJoueur, pas des 10 ou 15 commandes de téléport,
   - L'ajout d'une nouvelle fonctionnalité dans le téléport est très simple à mettre en œuvre et à tester,
   - Le code est plus concis et lisible donc plus facilement corrigible et modifiable.

Je le répète, la factorisation est la règle la plus importante de tout bon développeur.
Et elle peut s'appliquer dans de très nombreux cas, pas seulement avec les téléports.

LES VARIABLES

Il existe deux type de variables en PAWN :
- Les variables locales (déclarées dans les fonctions, callback ou bloc d'instructions)
- Les variables globales (déclarées en dehors de toute fonction ou callback)

Les variables locales ne peuvent être utilisées que dans le bloc d'instructions où elles sont déclarées.
Les variables globales peuvent être utilisées dans toutes les fonctions ou callback du script.

Les variables locales ne posent pas de problème particulier car leur portée est limitée au bloc d'instructions dans lequel elles sont declarées.
Par contre, il faut faire attention avec les variables globales qui peuvent être modifiées partout dans le script.
Cette particularité peut être source d'erreurs quand une fonction ou callback modifie la valeur d'un variable globale pendant qu'un autre fonction ou callback est en train de la lire.
Il faut donc être très rigoureux dans l'utilisation que l'on fait de ce type de variables.

Chaque fois que c'est possible, on utilisera des variables locales.

Une bonne habitude consiste à déclarer toutes ses variables locale (même celle utilisées dans les boucles) en début de fonction ou de callback pour éviter les surprises à la compilation (voire à l'exécution).

Exemple qui ne compile pas :
UneFonction ()
{
   new a=8;
   if (a==8)
   {
      new b;
      b=a+12;
   }
   a=b+5; // Le compilateur produira l'erreur "b n'est pas défini"
}

En PAWN, il n'existe qu'un seul type de variable (contrairement au C/C++). Ce type par défaut est entier.
Mais il est possible d'utiliser un tag (étiquette) pour transformer cette variable en un autre type.
Le seul tag disponible en standard en PAWN est Float:. Il permet de transformer une variable de type entier en variable de type décimal (ou nombre à virgule flottante).
Exemple :
new Float:b = 12.84;L'utilisation de ce type de variable consomme plus de ressource que pour le type par défaut, il ne faut donc les utiliser que quand c'est nécessaire.

LES CONDITIONS

Les conditions sont la clé d'un code non linéraire. Elles permettent d'orienter le script en fonction de ce qui se passe sur le serveur.
Les conditions s'utilisent avec les mots clés (ou instructions) suivants : if, while, do/while et for.
Les conditions peuvent très vite devenir très compliquées à formuler et il ne faut pas hésiter à les éclater en plusieurs conditions.
Par exemple, on veut tester pour chaque joueur qui est connecté si il est passager d'un véhicule ou si il est à pied.
On pourrait écrire :
for (i=0 ; i<MAX_PLAYERS ; i++)
{
   if (IsPlayerConnected (i) && (IsPlayerInAnyVehicle (i) && GetPlayerState (i) == PLAYER_PASSENGER) || GetPlayerState (i) == PLAYER_ON_FOOT))
   {
      /* On fait quelque chose */
   }
}
On voit très vite dans ce bout de code qu'il est très facile de se tromper dans la condition (parenthèses, opérateurs).

Il vaut donc mieux écrire :
for (i=0 ; i<MAX_PLAYERS ; i++)
{
   if (IsPlayerConnected (i))
   {
      if (IsPlayerInAnyVehicle (i))
      {
         if (GetPlayerState (i) == PLAYER_PASSENGER)
         {
            /* On fait quelque chose */
         }
      }
      else if (GetPlayerState (i) == PLAYER_ON_FOOT)
      {
         /* On fait quelque chose */
      }
   }
}
NB : Ici, on tombe dans un autre travers où l'on est obligé d'utiliser deux fois le même code (/* On fait quelque chose */) mais ce n'est qu'un exemple

Dans un test, les termes (séparés par && ou ||) sont évalués un par un et comparer à 1 (vrai).
Dans le cas d'un condition ne comportant que des &&, l'évaluation de cette condition s'arrête dès qu'un des termes est faux. Ceci peut avoir des conséquences inattendues.
Exemple :
if (a==2 && ++b==5)
{
}
Dans ce bout de script, on test la valeur de a puis la valeur incrémentée de b.
Si a=2 et b=4 alors les test est vérifié et en sortie, a=2 et b=5 (puisqu'il sera incrémenté)
Si a<>2 et b=4 alors le test n'est pas vérifié mais en sortie, b est toujours égal à 4 car comme a n'est pas égal à 2, l'évaluation de la condition s'arrête et le code se poursuit après le bloc d'instructions du if.

++
Syg
Courtesy of GtaManiac

Hors ligne R@f

  • *
  • GTAOnline Addict
  • Messages: 4655
    • Voir le profil
Re : Quelques règles de base pour mieux scripter
« Réponse #1 le: 06 Janvier 2009, 17:26:11 »
Bon boulot Syg, je suis sûr que ça va en aider certains ;)

++
R@f

Hors ligne cristab

  • *
  • Messages: 8379
    • Voir le profil
Re : Quelques règles de base pour mieux scripter
« Réponse #2 le: 06 Janvier 2009, 17:36:21 »
gg syg la au moin c'est clairement expliquer, dit donc l'année 2009 fleurit avec de super tuto si seulement ces tuto aurais pus être la quand j'ai commencer au mois d'Aout j'aurais perdu moins de cheveux   :P
pas d'aide en PM, vous êtes sur un forum est il me semble que vous êtes la pour avoir de l'aide donc pourquoi MP une seul personne qui ne vous répondra pas alors qu'il y a plein de membre ici

Hors ligne NatiVe™

  • *
  • Messages: 7977
    • Voir le profil
Re : Quelques règles de base pour mieux scripter
« Réponse #3 le: 06 Janvier 2009, 17:51:05 »
Beau taff Syg :D

NV. 8)



Tu vois avant? Bah j'étais là encore avant!

Hors ligne Azz45

  • Good Player
  • *
  • Mafioso
  • RolePlay Player
  • Messages: 2809
    • Voir le profil
Re : Quelques règles de base pour mieux scripter
« Réponse #4 le: 06 Janvier 2009, 17:55:23 »
Gg Syg :D

En parlant de tuning en haut , avec /vmenu le tuning se sauvegarde pas une idée ?

Cris t'es cheveux t'en as beaucoup encore  :lmfao ( Je parle de ton Avatar Of Course )

~•~  ~•~  ~•~  ~•~  ~•~  ~•~  ~•~  ~•~  ~•~  ~•~  ~•~


~•~  ~•~  ~•~  ~•~  ~•~  ~•~  ~•~  ~•~  ~•~  ~•~  ~•~~•~  ~•~  ~•~  ~•~
Avancement du GM : 3%
~•~  ~•~  ~•~  ~•~  ~•~  ~•~  ~•~  ~•~  ~•~  ~•~  ~•~
~•Merci Kloken, pour la vidéo lors de mon anniversaire•~

Hors ligne Nikko™

  • *
  • Mafioso
  • Messages: 2393
    • Voir le profil
Re : Quelques règles de base pour mieux scripter
« Réponse #5 le: 07 Janvier 2009, 11:25:59 »
Beau travail comme a chaque fois  ::)
Efectivement, pas mal de tuto fleurissent ^^

++
nikko

Hors ligne Kuroba

  • *
  • Bandit
  • Messages: 365
    • Voir le profil
Re : Quelques règles de base pour mieux scripter
« Réponse #6 le: 07 Janvier 2009, 11:52:25 »
Beau boulot Syg  :)


 Userbar by DarSon.

Hors ligne [viruz]rider_77

  • *
  • Mafioso
  • C/C++ programming
  • Messages: 2154
    • Voir le profil
Re : Quelques règles de base pour mieux scripter
« Réponse #7 le: 08 Janvier 2009, 00:11:53 »
Cool, un autre bon tuto pour les débutants de scripting.
GG Syg:D

vincentdu90

  • Invité
Re : Quelques règles de base pour mieux scripter
« Réponse #8 le: 31 Mars 2009, 17:46:31 »
Je comprend pas très bien la factorisation , car moi la je comprend que le joueur sera teleporter au même endroit mais sous un angle différent .

Hors ligne S!m

  • *
  • Messages: 2341
    • Voir le profil
Re : Re : Quelques règles de base pour mieux scripter
« Réponse #9 le: 01 Avril 2009, 01:53:25 »
Salut,

Je comprend pas très bien la factorisation , car moi la je comprend que le joueur sera teleporter au même endroit mais sous un angle différent .

Les codes sont équivalents, du moins au résultat apparent, seulement le code factorisé est beaucoup plus facile à modifier par la suite, si par exemple tu veut ajouter un cout de 250$ à chaque téléportation, il te suffit d'éditer une petite fonction au lieu de modifier chaque commande, comme le signalait Syg

si je ne répond pas à ta question, tente de mieux indiquer ce que tu ne comprend pas

++Sim++





vincentdu90

  • Invité
Re : Quelques règles de base pour mieux scripter
« Réponse #10 le: 01 Avril 2009, 20:02:23 »
Enfaite moi ce que je comprend c'est que on peut crée un nouveau teleporte a partir de la fonction TeleporterJoueur et au lieu de refair a chaque fois le blabla on aurait juste a mettre les nouvelles coordonnées , mais je crois que j'ai mal compris .

Hors ligne S!m

  • *
  • Messages: 2341
    • Voir le profil
Re : Quelques règles de base pour mieux scripter
« Réponse #11 le: 02 Avril 2009, 02:06:46 »
Salut,

si l'on reprend le poste de Syg,

if (strcmp ("/mls", cmdtext, true) == 0)
{
   new vehicleid;

   if (IsPlayerInAnyVehicle (playerid))
   {
      vehicleid = GetPlayerVehicleId (playerid);
      SetPlayerPos (playerid, 1.0, 2.0, 3.0);
      SetVehiclePos (vehicleid, 1.0, 2.0, 3.0);
      SetVehicleZAngle (vehicleid, 4.0);
      PutPlayerInVehicle (playerid, vehicleid, 0);
      SetCameraBehindPlayer (playerid);
   }
   else
   {
      SetPlayerPos (playerid, 1.0, 2.0, 3.0);
      SetCameraBehindPlayer (playerid);
   }
}
if (strcmp ("/msf", cmdtext, true) == 0)
{
   new vehicleid;
   if (IsPlayerInAnyVehicle (playerid))
   {
      vehicleid = GetPlayerVehicleId (playerid);
      SetPlayerPos (playerid, 5.0, 6.0, 7.0);
      SetVehiclePos (vehicleid, 5.0, 6.0, 7.0);
      SetVehicleZAngle (vehicleid, 8.0);
      PutPlayerInVehicle (playerid, vehicleid, 0);
      SetCameraBehindPlayer (playerid);
   }
   else
   {
      SetPlayerPos (playerid, 5.0, 6.0, 7.0);
      SetCameraBehindPlayer (playerid);
   }
}

on remarque ici que chacune de ces commandes appellent un certains nombre de fonctions, de surcroit, les mêmes, donc au lieu de réécrire à chaque fois ces fonctions, on les regroupent dans une seule fonction dénommé TeleporterJoueur (dans ce cas-ci) qui les comprend toutes, en y incorporant tous les paramètres nécessaire au plein contrôle de ses téléports....

TeleporterJoueur (playerid, Float:X, Float:Y, Float:Z, Float:Angle)
{
   new vehicleid;
   if (IsPlayerInAnyVehicle (playerid)
   {
      vehicleid = GetPlayerVehicleId (playerid);
      SetPlayerPos (playerid, X, Y, Z);
      SetVehiclePos (vehicleid, X, Y, Z);
      SetVehicleZAngle (vehicleid, Angle);
      PutPlayerInVehicle (playerid, vehicleid, 0);
      SetCameraBehindPlayer (playerid);
   }
   else
   {
      SetPlayerPos (playerid, X, Y, Z);
      SetCameraBehindPlayer (playerid);
   }
}

Ainsi, si l'on appelle cette fonction avec les paramètre indiqué :
TeleporterJoueur (playerid, 1.0, 2.0, 3.0, 4.0);

et que l'on a le code TeleporterJoueur, il s'agit de la même chose (ou presque) que de faire le code suivant (le résultat est le même):
   new vehicleid;

   if (IsPlayerInAnyVehicle (playerid))
   {
      vehicleid = GetPlayerVehicleId (playerid);
      SetPlayerPos (playerid, 1.0, 2.0, 3.0);
      SetVehiclePos (vehicleid, 1.0, 2.0, 3.0);
      SetVehicleZAngle (vehicleid, 4.0);
      PutPlayerInVehicle (playerid, vehicleid, 0);
      SetCameraBehindPlayer (playerid);
   }
   else
   {
      SetPlayerPos (playerid, 1.0, 2.0, 3.0);
      SetCameraBehindPlayer (playerid);
   }


les paramètres entrés dans TeleporterJoueur (playerid, 1.0, 2.0, 3.0, 4.0); sont reliés à ceux des fonctions appelées par cette fonction, ce qui donne le résultat suivant (valeur entre parenthèses):

TeleporterJoueur (playerid, Float:X (1.0), Float:Y(2.0), Float:Z (3.0), Float:Angle (4.0))
{
   new vehicleid;
   if (IsPlayerInAnyVehicle (playerid)
   {
      vehicleid = GetPlayerVehicleId (playerid);
      SetPlayerPos (playerid, X (1.0), Y (2.0), Z (3.0));
      SetVehiclePos (vehicleid, X (1.0), Y (2.0), Z (3.0));
      SetVehicleZAngle (vehicleid, Angle (4.0));
      PutPlayerInVehicle (playerid, vehicleid, 0);
      SetCameraBehindPlayer (playerid);
   }
   else
   {
      SetPlayerPos (playerid, X (1.0), Y (2.0), Z (3.0));
      SetPlayerFacingAngle(playerid, Angle (4.0));//j'ajoute (pour permettre au joueur de regarder au bon endroit)
      SetCameraBehindPlayer (playerid);
   }
}

en espérant avoir été clair (malgré certaines répétitions...)

++Sim++





vincentdu90

  • Invité
Re : Quelques règles de base pour mieux scripter
« Réponse #12 le: 02 Avril 2009, 07:32:38 »
Oui j'ai bien compris enfaite c'est les coordonnées de syg mit au hasard qui me mettait dans l'erreur car je croyais que c'etait des angles . ^^

Sinon bah c'est super praique ce truc , merci syg pour ton cours .