Utiliser correctement le Yocto-3D-V2 dans Unity

Utiliser correctement le Yocto-3D-V2 dans Unity

De temps en temps, nous avons des clients qui se plaignent du manque de réactivité du Yocto-3D-V2. A chaque fois, il s'agit simplement d'une mauvaise compréhension de notre API. En effet, pour utiliser efficacement un Yocto-3D-V2, il faut impérativement utiliser des callbacks. Pour illustrer de manière ludique ce problème, nous avons décidé de vous montrer comment utiliser correctement unYocto-3D-V2 dans le moteur de jeux Unity 5.



Le but de cet article est de modifier le tutoriel "Roll-a-ball" d'Unity pour contrôler le joueur à l'aide d'un Yocto-3D-V2 au lieu du clavier. Le tutoriel est disponible sur le site d'Unity. Le joueur contrôle une boule et doit attraper 12 objets. Cet exemple est très très basique, mais permet de démontrer comment utiliser le Yocto-3D-V2 à l’intérieur du moteur Unity 5


Le tutoriel Roll-a-Ball founi par Unity
Le tutoriel Roll-a-Ball founi par Unity



Avant de commencer, il faut de suivre le tutoriel pour avoir un exemple qui fonctionne au clavier. On va ensuite modifier le code du jeu pour utiliser l’inclinomètre du Yocto-3D-V2 pour contrôler la balle. Nous allons utiliser les fonctions tilt1 et tilt2 du module pour contrôler le déplacement de la balle sur l'axe X et l'axe Z. Ainsi, lorsqu'on penchera le Yocto-3D-V2 vers la gauche, la balle se déplacera vers la gauche, si on penche le module Yoctopuce en avant la balle avancera, etc..

Le but est de contrôler la balle avec un Yocto-3D-V2
Le but est de contrôler la balle avec un Yocto-3D-V2



Ajouter la librairie Yoctopuce


Avant de pouvoir utiliser les modules Yoctopuce dans Unity, il faut télécharger et ajouter librairie C# au projet Unity. Pour se faire, il faut copier les fichiers sources C# dans le répertoire Assets du projet Unity. Il faut ensuite copier le contenu du répertoire unity de la librairie dans le répertoire Assets/Plugins du projet Unity. Au final, vous devriez avoir arborescence suivante:

/Assets /_Scene /Materials /Plugings /x86 /yapi.dll /libyapi.so /x86 /yapi.dll /libyapi.so /yapi.bundle /Prefabs /Scripts /Sources /yocto_accelerometers.cs /yocto_api.cs ...



Attention, Unity 5 ne détecte pas toujours correctement l'architecture des dll yapi.dll. La version qui se trouve dans le répertoire /Assets/Plugings/x86 est la version 32bits et la version qui se trouve dans le répertoire /Assets/Plugings/x86_64 est la version 64 bits. Vérifiez bien dans l'inspecteur que l'architecture est correcte.

Vérifiez bien qu'Unity utilise la bonne dll
Vérifiez bien qu'Unity utilise la bonne dll



Détecter le Yocto-3D


Maintenant que la librairie Yoctopuce est intégrée au projet, il reste à modifier le script du GameObject Player pour qu'il utilise le Yocto-3D et non le clavier. La première étape est de vérifier la présence d'un Yocto-3D lors du chargement du niveau, le plus simple est d'ajouter le code dans la fonction Start() de l'objet Player. Ce code est très simple, on énumère tous les modules Yoctopuce connectés sur USB et on utilise le premier Yocto-3D ou Yocto-3D-V2 présent. Si aucun module n'est présent ou si l'on ne peut pas accéder au port USB, on affiche un message d'erreur et on quitte la fonction.

Ensuite, il faut initialiser les attributs tilt_x et tilt_z pour qu'ils utilisent les fonctions tilt1 et tilt2 du Yocto-3D-V2 que l'on a décidé d'utiliser.


...

private YTilt tilt_x;
private YTilt tilt_y;

void Start ()
{
    rb = GetComponent<Rigidbody> ();
    count = 0;
    setCountText ();
    winText.text = "";
    errorText.text = "";

    Debug.Log ("Use Yoctopuce Lib " + YAPI.GetAPIVersion ());
    string errmsg = "";
    int res = YAPI.RegisterHub ("usb", ref errmsg);
    if (res != YAPI.SUCCESS) {
        Debug.Log ("error with RegisterHub:" + errmsg);
        errorText.text = errmsg;
        return;
    }
    YModule module = YModule.FirstModule ();
    while (module != null) {
        string product = module.get_productName ();
        if (product == "Yocto-3D" || product == "Yocto-3D-V2") {
            Debug.Log ("Use " + product + " " + module.get_serialNumber ());
            break;
        }
        module = module.nextModule ();
    }
    if (module == null) {
        errorText.text = "No Yocto-3D or Yocto-3D-V2 found";
        return;
    }
    string serial = module.get_serialNumber ();
    tilt_x = YTilt.FindTilt (serial + ".tilt1");
    tilt_y = YTilt.FindTilt (serial + ".tilt2");

}

...



Il nous reste maintenant à modifier la méthode FixedUpdate() pour qu'elle déplace la balle en fonction de l'orientation du Yocto-3D-V2.

A ne pas faire


La mauvaise approche est de récupérer l'orientation du module à l'aide de la méthode get_currentValue() des objets tilt_x et tilt_y:

void FixedUpdate ()
{
    // do not do that!!!
    double roll = tilt_x.get_currentValue ();
    double pitch = -tilt_y.get_currentValue ();
    Vector3 movement = new Vector3 ((float)roll, 0.0f, (float)pitch);
    rb.AddForce (movement * speed);
}



Cette solution n'est pas fausse, mais elle est hautement inefficace. Comme nous l'avons expliqué dans un précédent article, la méthode get_currentValue() travaille par polling, c'est-à-dire qu'à chaque appel la méthode lit par USB la valeur de l'inclinomètre et cette opération peut prend jusqu'à 100 millisecondes!

Pour une application traditionnelle, perdre 100 millisecondes lors d'un clic n'est pas catastrophique, mais pour un jeux vidéo, perdre 100ms à chaque frame est une catastrophe. Le framerate devient tellement bas que le jeu devient injouable.

Heureusement, il existe une solution.

La bonne approche


La bonne approche est de travailler par callback et non pas par polling. Pour chaque fonction des modules Yoctopuce, il est possible d'enregistrer une fonction de callback qui sera appelée lorsque la valeur de cette fonction est modifiée.

Ce mode de fonctionnement est nettement plus efficace pour deux raisons: La première est que le module utilise un protocole de communication beaucoup plus efficace, la seconde est que la communication est gérée en arrière plan par la librairie.

Pour travailler en callback, il faut commencer par écrire les fonctions de callback pour nos objets tilt_x et tilt_z. Les fonctions de callback des objets de type YTilt reçoivent deux paramètres: l'objet YTilt concerné et une chaîne de caractères qui contient la nouvelle valeur. Notez qu'une fonction de callback reçoit la valeur sous forme de chaîne de caractères, c'est à l'utilisateur de convertir cette chaîne de caractères vers le bon type.

Nos deux fonctions de callback doivent simplement parser la chaîne de caractères et stocker la valeur dans un attribut. Attention, il faut faire attention de ne pas appeler de fonction get_XXX() dans les fonctions de callback car l'appel irait lire la valeur directement sur Yocto-3D-V2 et on se retrouverait avec le même problème de latence qu'en polling.

private double _x;
private double _z

void TiltCallbackX (YTilt sensor, string value)
{
    _x = double.Parse (value);
}

void TiltCallbackZ (YTilt sensor, string value)
{
    _z = -double.Parse (value);
}



Il faut ensuite enregistrer ces deux fonctions de callback à l'aide de la fonction registerValueCallback(). Ces appels peuvent être ajoutés à la fin de de la méthode Start() de l'objet Player

void Start ()
{
    ...
    string serial = module.get_serialNumber ();
    tilt_x = YTilt.FindTilt (serial + ".tilt1");
    tilt_y = YTilt.FindTilt (serial + ".tilt2");

    tilt_x.registerValueCallback (TiltCallbackX);
    tilt_y.registerValueCallback (TiltCallbackZ);
}



Pour finir, il faut modifier la fonction FixedUpdate pour appler la méthode YAPI.HandleEvents() et utiliser les attributs _x et _z pour calculer la force à appliquer à la balle. De nouveau, il ne faut pas utiliser de fonction get_XXX() pour ne pas ralentir l'exécution.

void FixedUpdate ()
{
    string errmsg = "";
    int res = YAPI.HandleEvents (ref errmsg);
    if (res != YAPI.SUCCESS) {
        errorText.text = errmsg;
        return;
    }
    Vector3 movement = new Vector3 ((float)_x, 0.0f, (float)_z);
    rb.AddForce (movement * speed);
}



Au lieu de prendre 100 ms, la nouvelle fonction FixedUpdate() ne prend plus que quelques millisecondes. Le gain est donc énorme. C'est d'autant plus important que la méthode FixedUpdate() est appelée à chaque frame, ce qui a un impacte considérable sur le framerate du jeu.

  
En utilisant les callbacks, le framerate est bien plus élevé



Comme d'habitude, vous pouvez trouver le code complet de cet exemple sur GitHub.

Conclusion


Voilà, vous savez maintenant comment utiliser efficacement les modules Yoctopuce. Dans Unity la différence est flagrante, et utiliser des callbacks est la seule solution viable, mais cette technique peut être utilisée dans n'importe quelle autre application. Il est aussi possible de gérer d'autres événements par callback comme nous l'avons expliqué dans ce précédent article. Pour finir, chaque librairie Yoctopuce possède un exemple Prog-Event-Based qui illustre les différentes façons d’utiliser les callbacks.

Commenter 2 commentaires Retour au blog



1 - laupas Dimanche 04 septembre 2016 16H13

Il me semble qu'il y a une petite erreur danss la méthode Start(): tilt_y.registerValueCallack(pitchTiltCallbackZ au lieu de TiltCallbackZ);

2 - seb (Yocto-Team)Lundi 05 septembre 2016 6H37

Merci. C'est corrigé.

Yoctopuce, get your stuff connected.