Go to Top

Dans ce tutoriel

Introduction

Un problème récurrent quand on commence avec Unity est de savoir comment accéder aux membres d'un script à partir d'un autre script. Nombreux sont ceux qui pensent que le simple déréférencement du nom du script serait suffisant, ils réalisent rapidement qu'il ne l'est pas.

Durant l'élaboration d'un programme, les variables sont stockées dans la mémoire à des emplacements différents. Si un objet pouvait voir les membres d'autres objets, il y aurait alors le risque de les modifier, même si cela n'était pas voulu. Si,  deux instances d'objets contiennent le même script avec les mêmes noms de variables, le compilateur ne serait pas en mesure de dissocier les deux.

Pour éviter ce problème, chaque objet dans la mémoire ne peut pas voir les autres objets. Il est alors nécessaire de dire à un objet où la variable dont elle a besoin se trouve en mémoire. Le principe du pointeur a été abandonné avec C# et maintenant, nous utilisons à la place des références (qui utilisent en fait un pointeur).

Il ya en fait différentes façons d'accéder à des variables, certaines sont meilleurs que d'autres et certaines sont tout simplement à utiliser dans des situations particulières.

Pour résumer, il existe trois façons:

  1. GetComponent, la plus commune, aussi celle qui crée le plus de confusion au premier abord
  2. SendMessage, qui peut sembler plus facile à saisir, mais aussi beaucoup moins efficace.
  3. Les variable statiques, la plus facile à utiliser, mais aussi la plus compliquée à comprendre pleinement.

Créez une nouvelle scène et ajoutez un Empty GameObject et nommez-le "ObjectA". Créez deux scripts appelés «Scripta» et «ScriptB". Ajouter les deux scripts sur ObjectA.

Ouvrez les deux scripts dans l'éditeur et ajoutez ces lignes:

ScriptA.cs

using UnityEngine;
using System.Collections;
public class ScriptA : MonoBehaviour{
	public int varBInA;
	public int otherBInA;

	void Update(){
		if (Input.GetKeyDown(KeyCode.Space)){
			varBInA = ScriptB.GetVarB();
			otherBInA = ScriptB.varB;
			if (varBInA > 0)
				ScriptB.AddVarB(10);
		}
	}
}



ScriptB.cs

using UnityEngine;
using System.Collections;

public class ScriptB : MonoBehaviour{
	private int varB;
	void Start(){
		varB = 20;
	}

	void Update(){
		if (Input.GetKeyDown(KeyCode.P))
			print("varB in ScriptB"+varB);
	}

        public int GetVarB(){
		return varB;
	}

        public void AddVarB(int num){
		varB += num;
	}
}



ScriptA.js

var varBInA:int;
var otherBInA:int;

function Update(){
	if (Input.GetKeyDown(KeyCode.Space)){
	       varBInA = ScriptB.GetVarB();
		otherBInA = ScriptB.varB;
		if (varBInA > 0)
			ScriptB.AddVarB(10);
	}
}



ScriptB.js

var varB:int;
functionStart(){
	varB = 20;
}

function Update(){
	if (Input.GetKeyDown(KeyCode.P))
		print("varB in ScriptB"+varB);
}

function GetVarB(){
	return varB;
}

function AddVarB(){
varB += num;
}
}



ScriptA.boo

import UnityEngine
import System.Collections
class ScriptA (MonoBehaviour): 
   public varBinA as int
   public otherBinA as int

   def Update ():
      if Input.GetKeyDown (KeyCode.Space):
         varBinA = ScriptB.GetVarB()
         otherBinA = ScriptB.varB
         if varBinA > 0:
            ScriptB.AddVarB(10)



ScriptB.boo

import UnityEngine
class ScriptB (MonoBehaviour):
   public varB as int
   def Start ():
      varB = 20

   def Update ():
      if Input.GetKeyDown(KeyCode.P):
         Debug.Log ("varB in ScriptB $varB")

   def GetVarB () as int:
      return varB

   def AddVarB (num as int):
      varB += num


Lancer ce code retourne simplement l'erreur "An object reference is required to access non-static members".

Nous devons dire au Scripta où est ScriptB afin qu'il puisse accéder à ses membres. Puisque nos scripts sont sur le même objet nous avons simplement besoin de déclarer une variable de type du script que nous souhaitons  et utiliser GetComponent pour trouver le script.

Acceder à un script sur le même objet

Modifier ScriptA ainsi:

ScriptA.cs

using UnityEngine;
using System.Collections;

public class ScriptA : MonoBehaviour{
	public ScriptB scriptB;
	public int varBInA;
	public int otherBInA;

	void Start(){
		scriptB = GetComponent<ScriptB>();
	}

	void Update(){
		if (Input.GetKeyDown(KeyCode.Space)){
			varBInA = scriptB.GetVarB();
			otherBInA = scriptB.varB;

			if (varBInA > 0){
				scriptB.AddVarB(10);
				print("In ScriptA otherBInA is " + otherBInA);
			}
		}
	}
}



ScriptA.js

	var scriptB: ScriptB ;
	var varBInA:int;
	varotherBInA:int;

	function Start(){
		scriptB = GetComponent(ScriptB);
	}

	function Update(){
		if (Input.GetKeyDown(KeyCode.Space)){
			varBInA = scriptB.GetVarB();
			otherBInA = scriptB.varB;

			if (varBInA > 0){
				scriptB.AddVarB(10);
				print("In ScriptA otherBInA is " + otherBInA);
			}
		}
	}
}



ScriptA.boo

import UnityEngine
class ScriptA (MonoBehaviour):
   public scriptB as ScriptB 
   public varBinA as int
   public otherBinA as int

   def Start ():
      scriptB = GetComponent[of ScriptB]()

   def Update ():
      if Input.GetKeyDown (KeyCode.Space):
         varBinA = scriptB.GetVarB()
         otherBinA = scriptB.varB
         if varBinA > 0:
            scriptB.AddVarB(10)

Avec les modifications, lancez le jeu, appuyez sur P, il devrait s'afficher 20 dans la console, appuyez sur Espace, puis à nouveau sur P, la valeur est maintenant de 30. Il était également possible d'accéder directement à varB sans passer par la fonction puisque varB est public.Notez qu'accéder à une méthode  se fait de la même manière que pour les variables.

Si les membres étaient privée (private), il ne serait pas possible d'y accéder directement. Il faudrait alors untiliser une fonction publique (encapsulation).

Comment GetComponent fonctionne?

Nous devons d'abord déclarer un objet du type du script que nous voulons atteindre.

// C# 
ScriptB script;

// UnityScript
var script:ScriptB;

// Boo
ScriptB script;

Puis on utilise la fonction GetComponent comme ceci:

// C#
script = GetComponent<ScriptB>();

// UnityScript
script = GetComponent(ScriptB);

// Boo
script = GetComponent[of ScriptB]()

La fonction GetComponent va chercher à l'intérieur de l'objet  un composant correspondant au type que nous passons. Si aucun n'est trouvé une référence null est retourné et notre variable  script ne fera rien, elle pointe dans le vide. Si un composant de type ScriptB  est trouvé, alors l'adresse de ce composant est passé à la script variable, la variable pointe désormais vers le composant ScriptB stocké dans la mémoire. Afin d'accéder aux membres publics de ScriptB, nous avons juste besoin de déréférencer la variable  script à l'aide de l'opérateur point.

script.PublicMembers;

Les membres privés sont toujours inaccessibles.

Reference

Donc je peux cacher avec GetComponent?

Oui, et cela est recommandé pour les variables fréquement utilisées comme la Transform.

Quand vous accédez une variable d'un composant "Built-in" (d'usine) comme Rigidbody, Renderer, Transform...etc, Unity permet un accès direct comme ceci:

void Update(){
    transform.position.x += 10;
}

Mais ce que Unity fait derrière:

void Update(){
    GetComponent<Transform>().position.x +=10;
}

GetComponent devrait être éviter dans l'update donc il est préférable de cacher le composant:

C#

Transform _transform;

void Start(){
     _transform = GetComponent<Transform>();
}

void Update(){
     _transform.position.x = new Vector3(_transform.position.x+10,0,0);
}



UnityScript

_transform:Transform;

function Start(){
     trans = GetComponent(Transform);
}

function Update(){
     _transform.position.x +=10;
}



Boo

_transform as Transform

def Start():
   _transform = GetComponent[of Transform]()

def Update():
   _transform.position.x +=10

This will save some computation.

Obtenir plusieurs composants

Que faire si notre ObjectA a plusieurs  ScriptB sur lui-même. Dupliquez le ScriptB deux fois, ObjectA en a désormais trois.

ScriptA devient:

C#

using UnityEngine;
using System.Collections;

public class ScriptA : MonoBehaviour{
	private ScriptB [] scriptB;

	void Start(){
		scriptB = GetComponents<ScriptB>();
	}

	void Update(){
		if (Input.GetKeyDown(KeyCode.Space)){
			for (int i = 0; i<scriptB.Length; i++)
				scriptB[i].AddVarB(i);
		}
	}
}



UnityScript

private var scriptB:ScriptB [];

functionStart(){
	scriptB = GetComponents(ScriptB);
}

function Update(){
if (Input.GetKeyDown(KeyCode.Space)){
	for (int i = 0; i<scriptB.Length; i++)
		scriptB[i].AddVarB(i);
	}
}



Boo

import UnityEngine
class ScriptA (MonoBehaviour):
   public scriptB as (ScriptB)

   def Start ():
      scriptB = GetComponents[of ScriptB]()

   def Update ():
      if Input.GetKeyDown (KeyCode.Space):
         for i in range(scriptB.Length):
            scriptB[i].AddVarB(i)

Lancez le jeu, appuyez sur P, la console imprime trois 20. Appuyez sur Espace et P encore, la console devrait ressembler à ceci:

Console

Parce que j'ai utilisé la valeur de i dans la boucle comme paramètre de la fonction, chaque script a reçu une valeur différente. A noter que pusique la boucle commence à 0, un script ne change pas.

Interaction entre objets

Nous pouvons également faire interagir deux objets d'une manière similaire. Créez un nouvel objet nommé "ObjectB". Retirez le composant ScriptB de ObjectA et'ajoutez le sur ObjetB.

Lancez le jeu, vous pouvez imprimer mais appuyer sur Espace lèvera une exception de référence null. Cela est dû au fait que le ScriptB n'est plus sur ObjectA et GetComponent n'a tout simplement pas pu le trouver. Nous devons d'abord trouver l' ObjectB avec GameObject.Find ("ObjectB").

Il vous suffit d'ajouter la ligne suivante dans le scriptA:

// C#
void Start () {
     scriptB = GameObject.Find ("ObjectB").GetComponent<ScriptB>();
}

// UnityScript
function Start(){
    scriptB = GameObject.Find ("ObjectB").GetComponent(ScriptB);
}

// Boo
def Start ():
   scriptB =GameObject.Find ("ObjectB").GetComponent[of ScriptB]()

GameObject.Find () trouve d'abord l'objet passé en paramètre, puis cherche le composant requis à l'intérieur de celui-ci. Il est recommandé d'effectuer ces actions dans le fonction Start ou à un moment du jeu où les retards n'affecte pas le gameplay. GameObject.Find et GetComponent sont des fonctions coûteuses car elles doivent parcourir toute la hiérarchie. Si votre scène ne comprend que 10 objets, ça passera, mais si elle contient des milliers d'objets vous verrez sûrement un mauvais effet.

Interaction entre objets dans la hierarchie

Faites glisser ObjectB dans ObjectA.

Lancez le jeu et vous verrez que cela fonctionne très bien. Pourtant, nous pouvons y arriver de meilleure manière à l'aide de commandes qui indiquent au compilateur que ObjectB est un enfant de ObjectA. Par conséquent, aucune nécessité de chercher tous les objets de la scène.

// C#
void Start () {
    scriptB =_transform.Find ("ObjectB").GetComponent<ScriptB>();
}
// UnityScript
function Start(){
   scriptB =_transform.Find ("ObjectB").GetComponent(ScriptB);
}

// Boo
def Start ():
    scriptB =transform.Find ("ObjectB").GetComponent[of ScriptB]()

Gameobject est échangé pour _transform. En effet, un objet enfant appartient à la transform deu parent donc nous limitons la recherche aux objets se trouvant dans la transform de ObjectA.

Encore mieux:

void Start () {
     script =GetComponentInChildren<ScriptB>();
}
function Start(){
   script =GetComponentInChildren(ScriptB);
}
def Start():
   script = GetComponentInChildren[of ScriptB]()

Note that it also works if you have children of children holding the script. Create an “ObjectC”, add ScriptB to it and add the object to ObjectB. Now A has B which has C.

Notez que cela fonctionne aussi si vous avez des enfants d'enfants qui contiennent le script. Créer un "ObjectC", ajoutez ScriptB dessus et ajoutez ObjectC à ObjectB. Maintenant A contient B qui contient C.

Changez ScriptB pour:

void Update(){
    if(Input.GetKeyDown (KeyCode.P))print (gameObject.name+" "+varB);
}

Lnacez le jeu, vous verrez que tous les objets sont trouvés.

GetComponentsInChildren fonctionnera de la même manière si chaque enfant contient plusieurs fois le script .

SendMessage et BroadcastMessage

Nous venons de voir différentes manières de faire interagir deux objets ou deux scripts. SendMessage et BroadcastMeassage sont deux autres possibiltés qui peuvent s'avérer plus simples mais aussi plus coûteuses. Le principe reste le même, on a une référence à un objet et on souhaite affecter un script qu'il contient.

Avec SendMessage, on peut appeler une fonction sur un objet sans avoir à trouver une référence au script. Vous allez me dire: "C'est quoi tout ce remue-manège si on peut faire simple?"

SendMessage est une fonction extrêmement lente!

Et cela devrait vous paraitre logique, avec GetComponent on indique exactement où se trouve le script en mémoire. Avec SendMessage, le compilateur connait l'objet mais il devra passer par tous les scripts et toutes les fonctions jusqu'à ce qu'il trouve l'heureuse élue. Cela peut altérer votre jeu si vous l'utilisez souvent sur plusieurs objets.

SendMessage ne cherche que sur l'objet auquel l'appel est fait, BroadcastMeassage lance une recherche sur l'objet visé mais aussi sur tous ces enfants. La recherche sera donc encore plus longue si l'objet contient une grande hierarchie.

Voici un exemple tout simple en C#:


SendMessage

// ObjetA  Script.cs

int argent = 100;
void FonctionA(){
    print("FonctionA dans l'objet A");
}
void RetireArgent(int montant){
    argent -=montant;
}

// ObjetB

ObjetA obj;

void Start(){
    obj = GameObject.Find("ObjetA");
}
void Update(){
    if(Input.GetKeyDown(KeyCode.Space)){
        obj.SendMessage("FonctionA");
        obj.SendMessage("RetireArgent",50);
    }
}

Dans l'exemple ci-dessus, le premier appel va simplement chercher la fonction et lancer une impression sur la console. Le deuxième appel pass aussi un paramètre requit par la fonction.

BroadcastMessage fonctionne de la même manière.

Fonctions Interactives

Unity utilise de nombreuses fonctions qui font interagir deux objets ou plus. L'avantage de ces fonctions réside dans le fait qu'elles stockent de nombreuses informations sur l'autre objet.

Il existe deux principaux groupes:

  1. Fonctions de collision : OnCollisionXXXX/OnTriggerXXXX
  2. Fonctions de la class Physics

Voici une simple déclaration de collision :

// C#
void OnCollisionEnter(Collision other){}

//UnityScript
function OnCollisionEnter(other:Collision){}

//Boo
def OnCollisionEnter(collision as Collision):

Chaque appel crée une référence à l'autre objet qui est désormais accessible dans la fonction. other dans notre example est un référence à l'autre objet. En déréférençant cette variable nous pouvons désormais accéder aux composants de l'objet avec GetComponent.

void OnCollisionEnter(Collision other){
   if(other.gameObject.tag == "ObjetB"){
       ScriptB script = other.GetComponent<ScriptB>();
       script.variable = 20;
   }
}

Dans cet exemple en C#, j'accède aux données de other et vérifie si other a pour tag "ObjetB". Je sais alors que je suis en collision avec l'ObjetB et j epeux faire une requête pour le ScriptB avec GetComponent.

L'avantage est que vous n'avez pas à chercher l'objet avec GameObject.Find();, ilvous est donné par la fonction.

À noter que la classe COllider est un peu plus complète que la class Collsion, mais vous serez en mesure de faire les mêmes actions avec les deux classes. Il se peut que Collision nécessite une déréférenciation de plus pour accéder la même variable.

Les fonctions de la classe Physics ont un principe similaire. Elles utilisent une variable ou un tableau de variable pour stocker des information sur l'objet qvec lequel nous interagissons.

Voici par exmple un raycast:

using UnityEngine;
using System.Collections;

public class example : MonoBehaviour {
    void Update() {
        RaycastHit hit;
        if (Physics.Raycast(_transform.position, _transform.forward, out hit))
            print(hit.gameObject.name);

    }
}

Une variable de type RaycastHit est déclarée et passé comme paramètre à la fonction, le mot-clé out en C# permet de modifier une structure passée comme paramètre. En utilisant hit, nous pouvons retrouver toutes sortes d''informations concernant l'objet. Dans mon exemple, j'imprime simplement le nom de l'objet.

Interaction en action

Afin de donner une représentation visuelle et pratique, la vidéo ci-dessous résume quelques-unes des utilisations de GetComponent (en anglais pour le moment).

, , , , , , , ,

Leave a Reply