[Résolu] [DB-AL] Comment structurer la création de ses scripts

Questions à propos du scripting. Hors Shader, GUI, Audio et Mobile.
Krafter
Messages : 65
Inscription : 11 Fév 2017 17:03

[Résolu] [DB-AL] Comment structurer la création de ses scripts

Message par Krafter » 20 Mars 2017 14:12

Bonjour à tous!

Aujourd'hui je vous sollicite car j'ai une question bien typique de débutant inexpérimenté : comment structurer / organiser ses scripts? Je ne veux pas parler de la façon de les ranger dans des dossiers, ni même de comment coder proprement. Mais plutôt comment choisir si le comportement d'un gameObject sera régi par 1 script ou 10, si ces scripts seront indépendants ou non... C'est un peu flou j'imagine, donc je vais essayer d'expliquer concrètement ce que je veux dire :


Quand je faisais mumuse sur Unity afin d'apprendre des choses, je créais des scripts en fonction de mes besoin, sans trop réfléchir. Par exemple j'ai un lapin que je veux faire bouger : un script IAlapin.cs. Il doit avoir de la vie? LapinHealth.cs. Et ainsi de suite.

Or j'ai récemment suivi un tuto pour coder son propre Third Person Controller. Dans ce tuto, les personnes créaient un script "TP_Controller" qui allait servir de "cerveau" à ce gameObject. A partir de ce script plusieurs autres scripts étaient appelés. Le personnage doit aller à gauche? "TP_Controller" va appeler une fonction dans "TP_Move" pour le déplacement, une fonction dans "TP_animator" pour l'animation, si on veut un son on va chercher une fonction dans "TP_sounds" par exemple.. Cela afin d'avoir des scripts bien organisés, je trouvais ça pas mal. Voilà pour ce qui est de mon expérience avec les scripts.

Maintenant, je souhaite m'amuser un peu à placer des personnages, des ennemies avec ce que j'ai appris. Or je me pose la question suivante : comment est-ce que je structure ma création de scripts. Par exemple je veux ajouter un lapin, est-ce que je met son déplacement + sa vie + sa mort + ses attaques dans un seul script? Ou bien dans plusieurs scripts indépendants? Ou encore comme je l'ai fais pour le TP Controller en faisant un script "cerveau" qui appelle d'autres scripts? Voilà déjà je voudrais votre avis sur cette organisation du TP Controller, comme le tuto datait beaucoup cette façon de faire n'est peut-être plus bonne? Et ensuite comment faire dans d'autres cas? Pour reprendre l'exemple du lapin, faire le même "schéma" de scripts que le TPC je m'y retrouverai, mais est-ce nécessaire si le lapin est bien plus basique?


J'imagine qu'avec l'expérience on apprend à structurer tout ça, mais si je pouvais commencer à "penser correctement" quand je réfléchis aux scripts à faire et à comment les organiser... C'est pourquoi je serais intéresser d'avoir vos avis et si possible discuter avec vous de vos façons de faire, de "penser" vos scripts :)

J'espère que j'ai été clair dans mon explication,
Merci !
Dernière édition par Krafter le 22 Mars 2017 18:26, édité 1 fois.

Avatar de l’utilisateur
ZJP
Messages : 5748
Inscription : 15 Déc 2009 06:00

Re: [DB-AL] Comment structurer la création de ses scripts

Message par ZJP » 20 Mars 2017 15:36

Pour faire simple, oui, il faut rester sur le "schéma" du tutoriel suivi, que l'on soit débutant ou non. C'est la grande force d'Unity : cela permet de (mieux) se concentrer sur un "aspect" précis du projet sans avoir des scripts à rallonge. Un, dix scripts n'est pas le problème : la compilation règle la question. ;-)

GetComponent, voilà la clef. :mrgreen:

PS :
En parlant de compilation, elle est plus rapide en multipliant le nombre de scripts lors des modifications de ceux-ci. Seuls ceux édités sont recompilés.

PS2:
Une des règles d'optimisation est de ne pas multiplier les "Update()", pas le nombre de scripts. Tu peux (par exemple) avoir 100 scripts collés à un GO, mais un seul "Update()" (celui du script principal) pour les...gouverner tous. :mrgreen:

Avatar de l’utilisateur
evereal
Messages : 109
Inscription : 06 Nov 2015 18:46

Re: [DB-AL] Comment structurer la création de ses scripts

Message par evereal » 20 Mars 2017 16:05

Il n'y a pas vraiment de règle toute faite pour la structure de tes scripts. Déjà car il n'existe pas qu'une seule bonne solution et en second chaque programme aura ses particularité.

Il existe quand même des grands principe de programmation. Pour le paradigme de l'orienté objet sur lequel Unity se base, il faut essayer de dégager un script pour chaque entité/concept de ton programme.
Pour reprendre l'exemple de ton lapin qui bouge, demande toi d'abord comment définir ton lapin par rapport a ton projet. est il le seul a pouvoir se deplacer ? y a t'il d'autre lapin ? d'autre animaux ?
chaque réponse va influencer ta facon de programmer ton lapin :
il est le seul a se deplacer -> aucune raison de ne pas coder le deplacement directement dans ta classe lapin
il n'est pas le seul a se deplacer -> très interessant de coder une classe a part afin de pouvoir la reutiliser sur d'autre entité.

d'autre animaux -> il sera surement interessant de faire une interface IAnimal. Du coup retour a la question du déplacement : à définir dans l'interface ou dans une classe a part ?
la définition dans une interface permet la polymorphie (concept très puissant), la classe a part permet encore une fois d'utiliser le deplacement sur autre chose que des animaux.

Comme tu peux le voir ca peut très vite donner mal au crâne. En règle de base je dirais que les gens ont tendance a faire bcq trop peu de classes différentes. Du coup dans le doute, scinde tes scripts.
La maitrise des diagrammes UML peuvent aussi aider a y voir plus clair dans son projet, ou au moins un schéma fonctionnel.
“La théorie, c'est quand on sait tout et que rien ne fonctionne. La pratique, c'est quand tout fonctionne et que personne ne sait pourquoi. Ici, nous avons réuni théorie et pratique : Rien ne fonctionne... et personne ne sait pourquoi !”

Krafter
Messages : 65
Inscription : 11 Fév 2017 17:03

Re: [DB-AL] Comment structurer la création de ses scripts

Message par Krafter » 21 Mars 2017 11:25

Wow deux réponses top en si peu de temps, merci à vous 2 :) ça éclaircit déjà pas mal de choses pour moi.


ZJP ce que tu dis résonne un peu avec ce dont parlait la personne du tuto, que cela permettait par la suite de modifier / travailler sur les scripts d'animations ou de son sans toucher au reste. Je ne savais pas comment marchait la compilation, je te remercie! Par contre au risque de faire un léger hors sujet : avoir un seul Update dans un script allège la programmation, donc je peux avoir des scripts où je déclare seulement des fonctions hors Update et les appelle dans le script principal? Parce qu'il y a une explication que je n'ai pas trouvé dans les tutos, c'est que j'ai du mal à voir quelles fonctions vont être appelées dans un script. Si je fais une fonction "faite maison" dans un script hors "update" ou autre, elle ne sera pas "lue" par le script si je ne l'appelle jamais c'est bien ça? Sauf si je lui mets des arguments d'entrée je crois?


evereal j'ai peu de notions sur ce qu'est une classe ou une interface mais je sens que ce sont des outils intéressants ^^. J'ai peu de vocabulaire Unity peut-être en ai-je déjà utilisé sans connaitre l’appellation. Si j'ai plusieurs lapins par exemple je pensais faire un prefab avec mon lapin et de cette façon pouvoir le réutiliser. Qu'entends-tu par "coder une classe à part"? Par classe tu entends un script? Faire un script "déplacement lapin" que j'appliquerai à chaque lapin? Si c'est bien ça pourquoi ne pas passer par un prefab? J'ai pris sur l'asset store un pack de lapins de différentes couleurs et je pensais leur coder un comportement similaire avec quelques variations en fonction de la couleur (certains pacifiques, d'autres qui détectent et attaquent le joueur...). Ça serait pour ce type de choses que je pourrais avoir une classe "lapin" qui serait la même mais par exemple les lapins agressifs appelleraient des scripts en plus? (Encore une fois je ne sais pas trop ce qu'est une classe, pardon si je dis n'importe quoi ^^)

Pour la question de l'interface, ça m'a l'air très intéressant aussi. Peux-tu m'en dire plus sur ce qu'est "la définition dans une interface"? (Et ne t'inquiète pas si j'ai mal au crâne, c'est une étape obligée j'ai l'impression ^^). Ce concept de polymorphie peut m'être utile si par la suite je veux rajouter d'autres animaux.

Et pour ce qui est de comment définir mon lapin dans mon projet ou les diagrammes UML, je laisse cet aspect de coté pour l'instant. Je n'ai pas commencé mon projet je m'amuse seulement à bricoler des choses pour m'entrainer sur Unity, me faire la main pour apprendre à coder. C'est pour ça que j'ajoute des choses de façon un peu aléatoire et que ça n'a pas vraiment de sens, je me suis juste fais un terrain "test" pour découvrir un peu tout ce qui je pense pourrais me servir par la suite (character controller, caméra, IA,... voilà pour l'instant ^^)

Avatar de l’utilisateur
evereal
Messages : 109
Inscription : 06 Nov 2015 18:46

Re: [DB-AL] Comment structurer la création de ses scripts

Message par evereal » 21 Mars 2017 13:46

classe, interface, héritage, polymorphisme sont en fait tous des concepts de base de la programmation orienté objet (c#, java, ... et à peu prêt tt les langage actuel....). Je t'invite à découvrir tout ca sur le tutoriel d'openclassroom : https://openclassrooms.com/courses/prog ... jet-avec-c

Je vais quand même essayer de répondre à tes questions sans te faire vider le tube d'aspirine.

La classe est par exemple ton fichier Lapin.cs, le script l'algorithme dans ce fichier.

C'est très bien de faire des prefab, ca te permet de placer rapidement les lapins sur ta scène mais ca ne résous pas la question de savoir quels scripts doivent être placé sur ton préfab lapin.

Pour revenir un peu plus haut et répondre a ta question
Parce qu'il y a une explication que je n'ai pas trouvé dans les tutos, c'est que j'ai du mal à voir quelles fonctions vont être appelées dans un script
Les 2 grand point d'entrée pour tes scripts vont se trouver dans la classe Monobehaviour et toutes les classes l'implémentant, les méthodes Start() et Update().
La méthode Start() va s'executer une seule fois dès que ton GameObject associé sera chargé.
La méthode Update() va s'executer a peu prêt 60fois par secondes tant que ton GameObject est présent.

Une des facon la plus propre de coder différents comportement est d'abord d'avoir une classe contenant les mouvements.

Code : Tout sélectionner

public class Lapin {
     // une méthode pour se deplacer à gauche
	public void moveLeft() {
	 transform.Translate(Vector3.right * Time.deltaTime * -1)
	 }
	 // une méthode pour se déplacer à droite
	public void moveRight() {
	 transform.Translate(Vector3.right * Time.deltaTime)
	}
	// une méthode pour se déplacer en avant
	public void moveForward() {
	 transform.Translate(Vector3.forward * Time.deltaTime)
	}
	// une méthode pour se déplacer en arrière
	public void moveBackward() {
	 transform.Translate(Vector3.forward * Time.deltaTime * -1)
	}	
}
Et de l'autre une classe IA qui utilise ces méthodes

Code : Tout sélectionner

public class IALapinPassif : Monobehaviour {
      // on récupère la référence du lapin
      Lapin myLapin = gameObject.GetComponent<Lapin>();
      // on prépare notre objet de génération de nombre aléatoire
      Random rnd;
	void Start() {
	 rnd = new Random();
	}
	
	void Update() {
	 // on génére aléatoirement un nombre qui définira une direction
	 // on ne génère le nombre qu'une fois toute les 200 frames pour éviter d'avoir un lapin sous extasie	 
	 if (frames % 200 == 0) {	  
	     int move = rnd.Next(5);
	   }
	   if (move ==0) { myLapin.moveLeft() }
	   if (move ==1) { myLapin.moveRight() }
	   if (move ==2) { myLapin.moveForward() }
	   if (move ==3) { myLapin.moveBackward() }
	   if (move ==4) {//le lapin reste sur place) }
	}
}
Tu px ensuite faire d'autre IA en suivant la meme structure.

Code : Tout sélectionner

public class IALapinSuicidaire : Monobehaviour {
      // on récupère la référence du lapin
       Lapin myLapin = gameObject.GetComponent<Lapin>();
      
	void Start() {
	}
	
	void Update() {
	Debug.log("This world is too rude for little Rabbit");
	Destroy(this.gameObject);
	}
}
Et la je me dis "Mince, je voulais aussi des canards avec le même comportement mais j'ai la flemme de réécrire tout le code et de l'adapter pour mes canards !"
Du coup je rajoute une interface IAnimal

Code : Tout sélectionner

public interface Animal {
 // quel que soit l'animal, il devra pouvoir aller en avant, en arrière, a gauche et a droite
  // une méthode pour se deplacer à gauche
	public void moveLeft();
	 // une méthode pour se déplacer à droite
	public void moveRight();
	// une méthode pour se déplacer en avant
	public void moveForward();
	// une méthode pour se déplacer en arrière
	public void moveBackward();
}
On ne fait que définir les déplacements, il faudra plus tard, pour chaque animal, coder la façon de se déplacer.

Code : Tout sélectionner

public class Lapin : Animal {
  // une méthode pour se deplacer à gauche
	public void moveLeft() {
	 transform.Translate(Vector3.right * Time.deltaTime * -1)
	 }
	 // une méthode pour se déplacer à droite
	public void moveRight() {
	 transform.Translate(Vector3.right * Time.deltaTime)
	}
	// une méthode pour se déplacer en avant
	public void moveForward() {
	 transform.Translate(Vector3.forward * Time.deltaTime)
	}
	// une méthode pour se déplacer en arrière
	public void moveBackward() {
	 transform.Translate(Vector3.forward * Time.deltaTime * -1)
	}
}

Code : Tout sélectionner

public class Canard: Animal {
// mon canard sera 2 fois moins rapide que le lapin, j'ajoute un paramètre de 0.5 pour chacun des déplacements.
  // une méthode pour se deplacer à gauche
	public void moveLeft() {
	 transform.Translate(Vector3.right * Time.deltaTime * -0.5)
	 }
	 // une méthode pour se déplacer à droite
	public void moveRight() {
	 transform.Translate(Vector3.right * Time.deltaTime * 0.5)
	}
	// une méthode pour se déplacer en avant
	public void moveForward() {
	 transform.Translate(Vector3.forward * Time.deltaTime * 0.5)
	}
	// une méthode pour se déplacer en arrière
	public void moveBackward() {
	 transform.Translate(Vector3.forward * Time.deltaTime * -0.5)
	}
Et mnt on modifie un chouillat les classes IA pour qu'elles acceptent n'importe quel animal

Code : Tout sélectionner

public class IAPassif : Monobehaviour {
      // on récupère la référence de l'animal
      IAnimal myAnimal= gameObject.GetComponent<IAnimal>();
      
	void Start() {
	}
	
	void Update() {
	 // on génére aléatoirement un nombre qui définira une direction
	 // on ne génère le nombre qu'une fois toute les 200 frames pour éviter d'avoir un lapin sous extasie
	 Random rnd = new Random();
	 if (frames % 200 == 0) {	  
	     int move = rnd.Next(5);
	   }
	   if (move ==0) { myAnimal.moveLeft() }
	   if (move ==1) { myAnimal.moveRight() }
	   if (move ==2) { myAnimal.moveForward() }
	   if (move ==3) { myAnimal.moveBackward() }
	   if (move ==4) {//l'animal reste sur place) }
	}
}
Ma classe IA peut a présent contrôler des canards, des lapins, des poulets, des chats, n'importe quoi qui implémente l'interface animal. C'est la polymorphie ;)
“La théorie, c'est quand on sait tout et que rien ne fonctionne. La pratique, c'est quand tout fonctionne et que personne ne sait pourquoi. Ici, nous avons réuni théorie et pratique : Rien ne fonctionne... et personne ne sait pourquoi !”

Krafter
Messages : 65
Inscription : 11 Fév 2017 17:03

Re: [DB-AL] Comment structurer la création de ses scripts

Message par Krafter » 22 Mars 2017 18:25

Très bien, je te remercie pour ta réponse qui est super complète! (Tellement que ça me soulève pleins d'autres questions, mais un peu hors sujet je ferai d'autres posts ^^).

Les classes d'accord j'ai lu un peu le tuto d'OpenClassrooms, je fais la distinction avec le scritp à proprement parler maintenant :)

L'interface il va falloir que je pratique j'ai encore un peu de mal. Si je comprends bien dans ton exemple je met la classe IA passif sur n'importe quel animal et il va avoir un déplacement, qui sera plus ou moins lent fonction de si c'est un canard ou un lapin? (Il faut aussi mettre la classe lapin ou canard sur le GO?).

En fait ce que je ne comprends pas bien, c'est quand tu fais cette ligne de code :

Code : Tout sélectionner

public class IAPassif : Monobehaviour {
      // on récupère la référence de l'animal
      IAnimal myAnimal= gameObject.GetComponent<IAnimal>();
Tu met cette classe où et tu récupère quoi exactement? Si je comprends bien chaque GO à une classe "Lapin" ou "Canard" sur lui qui détermine sa vitesse de déplacement, une interface Animal qui permet de donner les mouvements autorisés et une classe "IA" qui va par exemple leur donner un mouvement aléatoire? Et si je comprends bien cela permet par exemple, si l'on veut changer la vitesse des lapins ou encore le comportement de tous nos animaux, de ne modifier qu'une seule classe? (Ce qui me laisse supposé que lorsque l'on modifie une classe dans notre dossier script, si 60 animaux de notre scène l'utilise il se verra instantanément modifier sur les 60?).


Je crois avoir compris mais je demande pour m'en assurer :) Je mets en résolu en tout cas merci beaucoup de ces formidables réponses. Quand on débute et qu'on galère, avoir ce genre d'aide ça motive :)


PS : ça m'a fait sourire le coup des lapins sous extasie ^^

Avatar de l’utilisateur
evereal
Messages : 109
Inscription : 06 Nov 2015 18:46

Re: [Résolu] [DB-AL] Comment structurer la création de ses scripts

Message par evereal » 26 Mars 2017 21:56

Oui tu as bien compris.
Pour pousser encore un peu la reflexion, mon code n'est pas correct

Code : Tout sélectionner

public class IAPassif : Monobehaviour {
      // on récupère la référence de l'animal
      IAnimal myAnimal= gameObject.GetComponent<IAnimal>();
Il n'est en fait pas possible de passer une interface comme parametre pour la méthode GetComponent.
Il faut soit passer par une classe abstract qui implemente ton interface IAnimal et hérite de la classe Monobehaviour.
http://www.cjonesdev.com/blog/unity-get ... interfaces

Soit avoir l'objet Canard ou Lapin stocké dans une autre classe et récupérer le dis objet dans la classe IA via qqchose du genre

Code : Tout sélectionner

 IAnimal myAnimal= gameObject.GetComponent<AutreClasseImplementantMonobehaviour>().getAnimal();
C'est un peu un détour obligatoire a faire pour l'utilisation d'interface sous unity, mais bon ça reste du détail, du moment que le principe est bien compris tu pourras bien structurer tes classes
“La théorie, c'est quand on sait tout et que rien ne fonctionne. La pratique, c'est quand tout fonctionne et que personne ne sait pourquoi. Ici, nous avons réuni théorie et pratique : Rien ne fonctionne... et personne ne sait pourquoi !”

Répondre

Revenir vers « Scripting »