FAQ Unity 3D
FAQ Unity 3DConsultez toutes les FAQ
Nombre d'auteurs : 3, nombre de questions : 89, dernière mise à jour : 16 juin 2021
- Comment ajouter un nouveau script sur un GameObject ?
- Que contient un script par défaut lors de sa création ?
- Pourquoi le nom de ma classe est-il systématiquement identique à celui du fichier créé ?
- Que sont les fonctions Start() et Update() présentes dans les scripts lors de leur création ?
- Comment définir son propre modèle (template) de script ?
- Comment masquer une variable publique de l'inspector ?
- Comment accéder à des données privées dans l'inspector ?
- Comment contrôler les limites d'une donnée dans l'inspector ?
- Comment préciser ou arranger l'affichage de données dans l'inspector ?
- Comment exécuter les scripts dans un ordre bien précis ?
- Comment mettre une application en pause par le biais d'un script ?
- Comment connaitre l'ordre d'exécution des événements MonoBehaviour ?
- Comment assurer un déplacement constant, quelle que soit la puissance de la machine ?
- Comment accéder à un composant d'un GameObject à partir d'un script ?
- Comment accéder à des données d'un autre script ?
- Comment récupérer les enfants d'un GameObject ?
- Comment créer des objets à partir d'un script ?
- Comment conserver des données d'une scène à l'autre ?
- Comment imposer la présence d'un composant sur un GameObject avant de pouvoir affecter un script ?
- Comment faire cohabiter des scripts en C# et en UnityScript (JS) ?
- Mon script étant désactivé dans l'inspector, pourquoi une partie de ce script s'exécute-t-il quand même ?
- Comment créer et positionner un cube dans ma scène ?
- Comment orienter un objet vers un autre ?
- Comment changer la texture principale de mon GameObject ?
- Comment ajouter de la physique à mon objet ?
- Comment changer la couleur d'un GameObject ?
- Comment accéder aux sommets du mesh de mon GameObject ?
- Comment créer un mesh dans un script ?
- Comment regrouper certaines données publiques dans l'inspector ?
- Comment trouver tous les GameObject possédant un Collider dans ma scène ?
- Comment récupérer tous les éléments ayant un tag précis ?
- Quelles sont les pratiques à éviter dans l'écriture de script ?
- Comment créer un script permettant un contrôle de l'ensemble de mon jeu ?
- Comment obtenir l'angle en degrés entre deux objets ?
Il existe plusieurs possibilités. Une des plus simples est de glisser le script (à partir de la fenêtre « Project ») directement sur le GameObject (fenêtre « Scene »). De la même manière, on peut le glisser vers l'inspector lié au GameObject sélectionné. Il est aussi possible dans la partie inspector de faire une sélection par le bouton « Add Component », sélection « Scripts ».
Le script, une fois créé, se présentera comme un squelette d'une classe dérivée de MonoBehaviour et portant le nom du fichier que vous venez de créer. Elle comprend deux méthodes, Start et Update.
Pour qu'un script puisse être considéré comme un composant attachable à un GameObject, le nom de la classe et du fichier doivent être identique.
Les fonctions Start() et Update() font partie des méthodes héritées de la classe MonoBehaviour. Elles sont incluses par défaut lors de la création d'un nouveau script.
- La fonction Start() sera exécutée lorsque l'objet est instancié (et si le script est activé), et juste avant les fonctions de type Update(). Elle est exécutée une seule fois.
- La fonction Update() sera exécutée (si le script est activé) à chaque frame. Si vous avez un rafraichissement de 60 images/seconde, alors la fonction Update() sera exécutée 60 fois/seconde.
Vous pouvez changer le modèle de base des fichiers de script. Pour cela, rendez-vous dans le répertoire Unity → Editor → Data → Resources → ScriptTemplates.
Rajoutez [HideInInspector]
en début de ligne déclarant votre variable.
Employez [SerializeField]. Exemple :
[SerializeField]
private
bool
hasHealthPotion =
true
;
Utilisez l'attribut [Range(min,max)]
, qui permet de définir une valeur minimum et maximum pour la valeur à saisir.
Exemple:
[Range(-
100
,
100
)]
public
int
speed =
0
;
Par exemple, pour donner un titre à une zone de données, employez [HeaderAttribute]. Vous pouvez afficher un court texte explicatif grâce à [Tooltip("")].
Il est aussi possible d'imposer un espace plus conséquent entre certaines parties grâce à [Space(xx)].
En vous rendant dans le menu Edit → ProjectSettings → ScriptExecutionOrder, vous pourrez ajouter une liste de scripts dont l'ordre d'exécution sera alors contrôlé.
Même s'il est possible de choisir l'ordre, cela reste déconseillé. En effet, il y a de grandes possibilités que cette configuration soit oubliée avec le temps et qu'avec des modifications dans les scripts, cela cause des erreurs. De plus, un tel besoin indique certainement que vous avez une trop forte dépendance entre vos scripts et donc, que vous devez changer votre façon de concevoir l'application.
En mode éditeur, en utilisant la méthode Break() de la classe Debug :
Debug.
Break
(
);
En mode exécution, une solution consiste à passer la valeur timeScale de la classe Time à zéro.
L'ensemble des méthodes événements dérivées de la classe MonoBehaviour sont exécutées dans un ordre bien précis. Connaitre leur ordre d'appel ainsi que leur rôle peut s'avérer très utile. Le diagramme est consultable dans la documention.
Lorsque l'on programme des déplacements d'éléments, il est important d'assurer une vitesse apparente identique, quelle que soit la puissance de la machine sur laquelle s'exécute l'application. Pour garder un affichage constant, il faudra avoir recours à la fonction Time.DeltaTime, représentant le temps écoulé entre chaque frame, exemple :
void
Update
(
) {
float
translation =
Time.
deltaTime *
10
;
transform.
Translate
(
0
,
0
,
translation);
}
Au sein d'un script que l'on assigne à un GameObject, la méthode GetComponent() va permettre d'accéder aux différents composants de ce GameObject (animation, mesh, material, etc.). Exemple :
public
HingeJoint hinge;
void
Example
(
) {
hinge =
gameObject.
GetComponent<
HingeJoint>(
);
hinge.
useSpring =
false
;
}
La méthode GetComponent() va permettre d'accéder à des données d'un script (pour peu qu'elles soient publiques) d'un autre GameObject (un script représentant un composant d'un GameObject), exemple :
MyScriptA sc =
otherGameObject.
GetComponent<
MyScriptA>(
);
sc.
value1 =
this
.
refvalue;
foreach
(
Transform t in
transform)
{
Debug.
Log
(
"Child: "
+
t.
name);
}
La fonction Instanciate() permet de créer facilement tous les types d'objets. Par exemple, pour créer un autre GameObject, basé sur un modèle déjà présent dans la scène, on pourra écrire :
public
GameObject enemy;
...
void
Start
(
) {
Instantiate
(
enemy);
}
Il existe plusieurs possibilités pour arriver à partager des données entre les différentes scènes :
- avec un GameObjet (comme celui du joueur par exemple) qui, une fois créé dans la première scène, ne sera pas détruit lors du chargement des scènes suivantes. La fonction DontDestroyOnLoad permettra une persistance du GameObjet entre les différentes scènes ;
- avec la classe PlayerPrefs, qui permet de sauvegarder facilement certaines données de base et de les réimporter après le chargement de scènes suivantes.
Le fonctionnement de certains scripts nécessite la présence de composants bien particuliers au niveau du GameObject. Pour éviter d'affecter à un GameObject un script alors que le(s) composant(s) nécessaire(s) à son fonctionnement ne sont pas présents, on peut conditionner son attribution. Exemple :
[RequireComponent(
typeof
(RigidBody))]
public
class
SomeBehaviorScript :
MonoBehaviour
{
}
Tout est basé sur l'ordre de compilation des scripts. En particulier, si un script en C# souhaite accéder à un script en UnityScript, alors il faudra que celui-ci soit placé dans un des répertoires dont la compilation se fera en premier, notamment les répertoires Standard Assets ou Plugins (les créer s'ils n'existent pas).
Désactiver un script dans l'inspector ne signifie pas qu'il soit complètement hors service pour autant. La fonction MonoBehaviour.Awake sera exécutée, même avec un script désactivé.
Le code suivant permet d'ajouter des primitives dans les scènes :
GameObject cube =
GameObject.
CreatePrimitive
(
PrimitiveType.
Cube);
cube.
transform.
position =
new
Vector3
(
0
,
1
.
0f
,
0
);
L'API permet d'orienter un objet vers un autre. Si target est le Transform d'un objet cible, on écrira pour le GameObject à orienter :
transform.
LookAt
(
target);
Si mytexture est une Texture, et que mon GameObject possède un material, alors :
Renderer renderer =
GetComponent<
Renderer>(
);
renderer.
material.
mainTexture =
mytexture;
L'ajout d'un RigidBody pourra se faire comme suit :
gameObject.
AddComponent<
Rigidbody>(
);
Si mon GameObject possède un material, alors :
Renderer renderer =
GetComponent<
Renderer>(
);
renderer.
material.
color =
new
Color
(
0f
,
1
.
0f
,
0
);
// couleur verte
La classe Mesh permet d'accéder à la géométrie d'un GameObject. Par exemple, pour changer les valeurs sur l'axe des Y de chaque sommet du mesh, on pourra écrire :
void
Update
(
) {
Mesh mesh =
GetComponent<
MeshFilter>(
).
mesh;
Vector3[]
vertices =
mesh.
vertices;
int
i =
0
;
while
(
i <
vertices.
Length) {
vertices[
i]
+=
Vector3.
up *
Time.
deltaTime;
i++;
}
mesh.
vertices =
vertices;
mesh.
RecalculateBounds
(
);
}
À partir d'un GameObject vide, on pourra lui assigner un script permettant une génération procédurale, par exemple :
Vector3[]
newVertices =
new
Vector3[]
{
new
Vector3
(
1
,
0
,
1
),
new
Vector3
(
1
,
0
,
-
1
),
new
Vector3
(-
1
,
0
,
1
),
new
Vector3
(-
1
,
0
,
-
1
) };
Vector2[]
newUV =
new
Vector2[]
{
new
Vector2
(
1
,
1
),
new
Vector2
(
1
,
0
),
new
Vector2
(
0
,
1
),
new
Vector2
(
0
,
0
) };
int
[]
newTriangles =
new
int
[]
{
0
,
1
,
2
,
2
,
1
,
3
};
void
Start
(
) {
gameObject.
AddComponent<
MeshFilter>(
);
gameObject.
AddComponent<
MeshRenderer>(
);
Mesh mesh =
new
Mesh
(
);
GetComponent<
MeshFilter>(
).
mesh =
mesh;
mesh.
vertices =
newVertices;
mesh.
uv =
newUV;
mesh.
triangles =
newTriangles;
mesh.
RecalculateBounds
(
);
}
Une des solutions est de créer des classes de données. Exemple :
using
UnityEngine;
using
System.
Collections;
[System.Serializable]
public
class
PlayerData {
public
int
lifePoints;
public
float
altitude;
public
GameObject go;
public
Transform weapoon;
}
public
class
ThePlayer :
MonoBehaviour {
public
PlayerData myPlayer;
void
Start
(
) {
}
void
Update
(
) {
}
}
Pour récupérer l'ensemble des colliders (et donc aussi les GameObject associés) présents dans une scène, on écrira :
Collider[]
cols =
Object.
FindObjectsOfType<
Collider>(
);
Pour récupérer l'ensemble des GameObjects ayant le même tag, on pourra utiliser la fonction FindGameObjectsWithTag:
GameObject[]
obets =
GameObject.
FindGameObjectsWithTag
(
"NomTag"
);
Au-delà des bonnes pratiques habituellement conseillées au niveau de l'écriture du code, il y a un certain nombre de choses à éviter en ce qui concerne la mise en œuvre de l'API d'Unity. En voici quelques-unes :
- ne pas utiliser de fonctions Find en dehors des fonctions d'initialisation (comme dans Start() par exemple) ;
- limiter les appels aux fonctions GetComponent (plus particulièrement au sein de la boucle de jeu), et ne pas hésiter à mémoriser les références des composants utiles ;
- ne pas nommer ses classes avec des noms de classes ou de composants existant dans l'API d'Unity ;
- éviter les instanciations « massives » dans la boucle de jeu, comme dans le cas de création de projectiles. Cela provoque un appel fréquent au ramasse-miettes (Garbage Collector) pouvant être pénalisant. Il faut privilégier l'emploi d'une technique alternative, comme le « pooling » ;
- éviter l'utilisation abusive de MeshCollider, privilégier les primitives, quitte à les assembler pour obtenir grossièrement la forme souhaitée ;
- ne pas effectuer de changement d'attitude (par l'emploi de fonctions de transformation) sur des objets gérés dynamiquement par la physique ;
- éviter l'emploi des fonctions de gestion de la physique en dehors de la fonction FixedUpdate().
Une des solutions les plus communément employées est celle qui consiste à créer un script de gestion, que l'on pourra par exemple nommer « GameManager ». Elle s'appuie sur la technique du Singleton, et sera créée de telle sorte qu'elle puisse survivre aux différents changements de scène. Voici un exemple de base, à assigner sur un GameObject vide :
using
UnityEngine;
using
System.
Collections;
public
class
GameManager :
MonoBehaviour {
public
static
GameManager instance =
null
;
// vos données globales de jeux:
// ...
//
void
Awake
(
)
{
// Vérifie si l'instance existe déjà.
// Si c'est le cas, détruit l'objet.
if
(
instance)
{
Destroy
(
gameObject);
return
;
}
// définit l'instance.
instance =
this
;
// Définit l'objet comme non destructible entre les scènes
DontDestroyOnLoad
(
gameObject);
// Appelle une fonction d'initialisation du jeu (exemple)
InitGame
(
);
}
// Update est appelée à chaque image.
void
Update
(
)
{
//...
}
// Initialisations diverses pour le jeu.
void
InitGame
(
)
{
//...
}
}
Nous supposons que les deux objets sont sur le même plan (par exemple : la coordonnées y est à 0 pour les deux objets). Sinon, vous pouvez toujours forcer la coordonnée en question à 0 dans votre fonction. La première étape est d'obtenir un vecteur (normalisé) partant de l'objet A et allant vers l'objet B. Ensuite, grâce à la fonction Mathf.Atan2 vous pouvez obtenir l'angle en utilisant comme paramètre les coordonnées z et x. Sachant que Atan2 retourne un angle en radians, il suffit de multiplier le résultat avec Mathf.Rad2Deg et de lui ajouter 180. Voici la fonction :
float
getAngle
(
Transform A,
Transform B)
{
var
posA =
A.
position;
var
posB =
B.
position;
// Forçons les deux objets sur le même plan
posA.
y =
posB.
y =
0
;
Vector3 direction =
(
posA -
posB).
normalized;
return
Mathf.
Atan2
(
direction.
z,
direction.
x) *
Mathf.
Rad2Deg +
180
;
}