Utiliser un trigger avec des Gizmos

gizmo

Dans le jeu vidéo, une très grande partie des mécaniques de gameplay sont basées sur la détection. Détection du joueur, détection d’un projectile, détection d’un objet… La détection de ces éléments (ou leur non-détection) vont entraîner différentes réactions du jeu.

Nous allons parler ici d’un type de détection particulier : les Triggers. Pour faire simple, ce sont des zones de déclenchement. Si le joueur passe dans cette zone, le Trigger est déclenché et envoie un message. Cela permet de lancer des routines d’animation ou d’autres traitement tout en séparant la détection de la réalisation des actions.

Disclaimer

Cet article a été originalement écrit le 03/10/2014 sur mon précédent blog. Certaines informations présentées ici peuvent donc ne plus fonctionner telles quelles.

Introduction

Lorsque vous avez besoin de détecter quelque chose, le premier réflexe est de se tourner vers les Colliders, que ce soit un BoxCollider, un SphereCollider ou même un MeshCollider. Ces éléments pourront détecter une collision. Pour les Colliders de type Trigger, vous n’aurez aucun élément visuel associé il est invisible.

Faites le test, créez un GameObject vide et affectez lui un BoxCollider. Un joli cube vert apparaît sur la scène. Dans Unity, les Colliders sont représentés en vert, il sont donc facilement repérables. Maintenant, désélectionnez le GameObject… Visuellement, le cube disparaît pour ne pas encombrer la scène. Si dans la plupart des cas, c’est justifié, ça peut devenir un problème dans certains.

Il est impossible de sélectionner quelque chose d’invisible sur la scène autrement que par la hiérarchie. Dans notre exemple, nous avons une hiérarchie simple mais sur une scène beaucoup plus complexe, je vous laisse imaginer comment savoir où sont vos Triggers. Il m’est arrivé plusieurs fois de « perdre » des Triggers sur une scène… Ne riez pas !

Introduction Bis

Dans un soucis de productivité, il est toujours intéressant de se créer sa boite à outil (ou à minima de se la constituer). Gérer proprement et de manière transparente les Triggers me semble un bon élément à ajouter à votre toolbox vous ne croyez pas ?

Commençons donc avec ce qui nous préoccupe le plus : voir les Triggers même lorsqu’ils ne sont pas sélectionnés. Lorsque vous sélectionnez un GameObject avec un Collider attaché, ce dernier est immédiatement dessiné en vert. Ceci, c’est un Gizmo. Via ces Gizmos, des informations supplémentaires peuvent être dessinées sur la scène autour du GameObject sélectionné. Note importante : les Gizmos ne sont dessinés que dans l’éditeur.

Les flèches de couleur et cercles qui vous permettent de déplacer et tourner les GameObjects sur la scène… sont des Gizmos ! Vous pouvez d’ailleurs créer des Gizmos personnalisés pour permettre une modification de vos composants directement depuis la scène.

Pour notre Trigger, nous n’allons pas aller aussi loin, nous allons simplement dessiner la zone de détection. Ca a l’air simple comme ça mais… ça ne l’est pas tant que ça.

Les Flags

Drapeaux_de_Régiments

Le dessin des Gizmos se fait dans une méthode statique de votre composant. Oui, statique, ce qui va poser quelques soucis d’accessibilité. Le nom n’a pas d’importance car comme la plupart des surcharges de l’éditeur, ça se passe au niveau des Attributs. Il faut ajouter un Attribut à votre méthode pour la définir comme « dessinatrice » de Gizmos : DrawGizmo. Jusque là, c’est relativement cohérent. Cet attribut prend un paramère, un GizmoType.

Ce GizmoType correspond à l’état du GameObject. En effet, les Gizmos ne sont pas dessinés tout le temps. Pour reprendre l’exemple de celui des Colliders : ils ne sont dessinés que lorsque le GameObject est sélectionné.

Le code basique est le suivant :

[DrawGizmo(GizmoType.Selected)]
public static void RenderGizmo(Trigger trigger, GizmoType type)
{
}

La méthode doit obligatoirement prendre deux paramètres : une instance du composant dont il faut dessiner les Gizmos, et le type. Là vous devez vous demander pourquoi on passe le type en paramètre alors qu’on le définit déjà dans l’Attribut. Attention, on va plonger dans les tréfonds…

En réalité, GizmoType est un flag et pas une simple énumération. Chacune de ses valeurs est une puissance de 2, ce qui permet de les combiner entre elles. Au lieu d’avoir soit « Selected » soit « Active », vous pouvez avoir les deux, en même temps… le tout stocké dans une seule variable. Le tout rendu possible grâce au décalage de bits. Je ne vais pas plus loin dans les explications, le décalage de bits et les utilisations possible du procédé mériterait un article à lui tout seul ! Pour plus d’infos, voilà un article en anglais sur le sujet et il y a toujours Wikipedia bien sûr.

Dans notre cas, si on voulait une seule méthode pour dessiner les Gizmos de notre composant dans les deux états « Selected » et « Active », la notation serait la suivante :


[DrawGizmo(GizmoType.NotSelected | GizmoType.Selected)]
public static void RenderGizmo(Trigger trigger, GizmoType type)
{
}

Notez le pipe simple qui va cumuler les deux états.

Mise en place

Il était long le préambule hein ? Mais on avance on avance… Dans notre cas, on voudrait dessiner la BoundingBox de notre BoxCollider en permanence. On va donc créer le dessinateur de Gizmos en conséquence. Comme expliqué plus haut, la méthode de dessin est statique, il faut donc prévoir des Propriétés à notre composant afin de pouvoir accéder à tous les élément utiles. Dans notre cas, le seul élément intéressant est le BoxCollider. Là encore, comme la méthode de dessin est statique, il faut faire attention à ce que l’on fait…


private BoxCollider _collider;
private BoxCollider Collider
{
    get
    {
        if(_collider == null)
        {
            _collider = GetComponent<BoxCollider<();
        }
        return _collider;
    }
}

Avec cette Propriété, on est sûr de ne pas avoir de NullReferenceException. Passons à la méthode de dessin maintenant. Histoire de compliquer encore un peu les choses, on va rajouter une idée. Voir en permanence un Collider ça peut servir, mais c’est quand même encore mieux si on sait visuellement lorsqu’il est sélectionné ou non. On va gérer ces deux cas, grâce aux flags, dans la même méthode, comme ceci :

private static readonly Color NotSelectedGizmo = new Color(72F / 255F, 3F / 255F, 111F / 255F);
private static readonly Color SelectedGizmo = new Color(159F / 255F, 62F / 255F, 213F / 255F);

[DrawGizmo(GizmoType.NotSelected | GizmoType.Selected)]
public static void RenderGizmo(Trigger trigger, GizmoType type)
{
    if((type & GizmoType.Selected) == GizmoType.Selected)
    {
        Gizmos.color = SelectedGizmo;
    }
    else
    {
        Gizmos.color = NotSelectedGizmo;
    }
    Gizmos.DrawWireCube(trigger.transform.position + trigger.Collider.center, trigger.Collider.size);
}

Par soucis de performance, les couleurs sont stockées de manière statique. La première condition est un peu exotique, mais c’est comme ça qu’il faut gérer les Flags. Ici, on applique un masque avec « & » sur le type pour vérifier s’il est du type qui nous intéresse. Le cas échéant, on active une couleur ou une autre dans les Gizmos. Ensuite, il suffit de dessiner le cube en mode Wireframe. On lui donne une position (la position du composant), le centre du Collider et sa taille. La Propriété prend là toute son importance : on ne fait qu’une seule fois appel à GetComponent.

Compilez et testez… Vous avez une belle boite violette foncée (ou claire si le GameObject est sélectionné). Si le cube apparaît en vert, c’est que le composant BoxCollider est « ouvert » dans le GameObject.

Le Collider est maintenant visuel, mais pas sélectionnable depuis la scène. On va donc finir en modifiant un tout petit truc bien caché mais tellement utile : l’icône du GameObject. En cliquant sur le cube vert bleu et rouge, un menu apparaît et vous avez la possibilité de paramétrer une icône personnalisée ou bien simplement un signet de couleur :

menu

En sélectionnant une icône comme ceci, un nouveau Gizmo automatique est généré comme ceci :

trigger

Le point important est qu’il vous permet de sélectionner votre Collider jusqu’à présent invisible et inaccessible. Cliquez sur le libellé ou l’icône, et votre Collider sera sélectionné. Usez mais n’abusez pas de ces Gizmos. Si votre scène s’encombre d’informations inutiles, celles qui le sont seront perdues dans la masse.

Le Trigger

Et le Trigger à proprement parlé… il est où dans tout ça ? La gestion d’un Trigger en soit… c’est pas sorcier. Ce qu’on vient de voir plus haut en revanche, c’est déjà plus complexe. Mais allons tout de même jusqu’au bout. Il y a 3 types de détection : « entre dans la zone », « reste dans la zone » et « sort de la zone ». On ne va gérer qu’un seul de ces 3 cas : l’entrée dans la zone.

Il nous faut 2 Propriété exposées dans l’inspecteur : le GameObject cible, et la méthode de callback à appeler. Ensuite, la gestion de la collision est simple, on utilise le système d’événements d’Unity. Si vous avez un système de gestion des événements personnalisé, c’est ici qu’il faut le brancher.

public GameObject Target;
public string callback;

void OnTriggerEnter(Collider c)
{
    Target.SendMessage(callback);
}

 

Et voilà, vous avez maintenant un composant de gestion des Triggers. Il est incomplet, mais c’est une bonne base. Profitez-en pour explorer toutes les possibilités que vous offrent les Gizmos, il y a vraiment de quoi s’amuser !

Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s