Unity et les APIs Windows / Windows Phone

Imaginons que vous souhaitiez utiliser des APIs spécifiques à la plateforme WinRT (Windows et Windows Phone) qui ne sont pas disponibles de base dans le framework Unity. Par exemple votre jeu pourrait proposer à l’utilisateur de commenter et noter votre application simplement en le redirigeant vers le store Windows. Vous pourriez également proposer à l’utilisateur de choisir son avatar à partir  de sa galerie photo de son téléphone. Cet article vous présente deux méthodes pour utiliser des APIs WinRT depuis votre application unity :

  • L’utilisation de directives de compilation
  • La création de plugins

 

Pré-requis

  • Unity 4.6 ou supérieure
  • PC sous Windows 8.1
  • Visual Studio 2013 Community Edition (gratuit)
  • Visual Studio 2013 Tool for Unity (optionnel)
  • Un téléphone sous Windows Phone 8.1 (optionnel car on peut utiliser l’émulateur WP8.1)

Dans la suite de cet article, nous exporterons notre application Unity vers la plateforme “Universal 8.1”

 

Directives de compilation

La force d’Unity est de pouvoir cibler différentes plateformes avec un code unique bâti sur un framework unifié. Néanmoins il existe des situations où le code doit être adapté aux spécificités des plateformes. Pour palier ce problème, Unity fournit aux développeurs des directives de compilation prédéfinies :

 

compilation_directives

Parmi ces directives, voici celles que nous allons utiliser :

  • NETFX_CORE : à utiliser dès lors qu’on souhaite appeler une API Windows Runtime depuis un script Unity. A noter que le code inclus dans un tel bloc est ignoré par l’éditeur Unity : en plus de l’utilisation des APIs WinRT, c’est un moyen pratique pour utiliser des spécificités du framework .NET 4.5
  • UNITY_METRO : à utiliser dès lors qu’on souhaite utiliser une API Windows Runtime commune à Windows 8.1 et Windows Phone 8.1
  • UNITY_METRO_8_1 : à utiliser dès lors qu’on souhaite utiliser une API Windows Runtime spécifique à Windows 8.1 (et non supportée par Windows Phone 8.1) ou implémenter un comportement spécifique à Windows comme par exemple l’accès à la Charm barre.
  • UNITY_WP_8_1 : à utiliser dès lors qu’on souhaite utiliser une API Windows Runtime spécifique à Windows Phone 8.1 (et non supportée par Windows 8.1) ou implémenter un comportement spécifique à Windows Phone comme par exemple la gestion du bouton “Retour”

Remarque : contrairement à NETFX_CORE, les directives UNITY_METRO, UNITY_METRO_8_1 et UNITY_WP_8_1 sont actives dans l’éditeur Unity dès que vous sélectionner “Windows store” comme plateforme cible dans la boite de dialogue d’exportation.

A titre d’illustration, je vous propose donc d’implémenter un code d’exemple ultra simple : mon application Unity va afficher un Canvas avec un bouton ; lorsqu’on cliquera sur le bouton, l’application ouvrira une boite de dialogue native de la plateforme Windows avec un message texte. Bien que très basique d’un point de vue fonctionnelle, cet exemple va couvrir un certain nombre d’aspects clés pour comprendre la manière dont un script Unity peut interagir avec l’OS.

La première étape consiste donc à créer un projet Unity (en y important le plugin Visual Studio Tools for Unity) :

  • une scène principale dénommée “Main”
  • Un script c# dénommé MessageBox.cs et attaché à la caméra principale. J’y ai défini une méthode dénommée OnMessageBoxClick pour gérer le clic bouton
  • Un canvas contenant un bouton dont le gestionnaire de clics est initialisé avec la méthode OnMessageBoxClick

 

Unity_MessageBox

Parlons maintenant du script C# associé :

1. Configuré sous Visual Studio avant exportation :

Code Snippet

  1. using UnityEngine;
  2. using System.Collections;
  3.  
  4. #if NETFX_CORE
  5. using System;
  6. using Windows.UI.Popups;
  7. using Windows.UI.Core;
  8. using System.Threading.Tasks;
  9. using Windows.ApplicationModel.Core;
  10. #endif
  11.  
  12. public class MessageBox : MonoBehaviour {
  13.  
  14.     #if NETFX_CORE
  15.         public void OnMessageBoxClick()
  16.         {        
  17.             showMessageDialogAsync("Hello from WinRT !");        
  18.         }
  19.  
  20.         async void showMessageDialogAsync(string message)
  21.         {
  22.             await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
  23.             {
  24.                 MessageDialog messageBox = new MessageDialog(message);
  25.                 await messageBox.ShowAsync();
  26.             });
  27.         }
  28.  
  29.     #else
  30.         public void OnMessageBoxClick()
  31.         {
  32.             Debug.Log("Hello from Unity Player");
  33.         }
  34.     #endif

Comme vous pouvez le constater, tout ce qui est encadré par la directive NETFX_CORE est ignoré par l’éditeur Unity (code grisé). Le gestionnaire de clic ne fait qu’afficher une trace dans la console Unity.

2. Configuré sous Visual Studio après exportation “Universal 8.1” :

Code Snippet

  1. using UnityEngine;
  2. using System.Collections;
  3.  
  4. #if NETFX_CORE
  5. using System;
  6. using Windows.UI.Popups;
  7. using Windows.UI.Core;
  8. using System.Threading.Tasks;
  9. using Windows.ApplicationModel.Core;
  10. #endif
  11.  
  12. public class MessageBox : MonoBehaviour {
  13.  
  14.     #if NETFX_CORE
  15.         public void OnMessageBoxClick()
  16.         {        
  17.             showMessageDialogAsync("Hello from WinRT !");        
  18.         }
  19.  
  20.         async void showMessageDialogAsync(string message)
  21.         {
  22.             await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
  23.             {
  24.                 MessageDialog messageBox = new MessageDialog(message);
  25.                 await messageBox.ShowAsync();
  26.             });
  27.         }
  28.  
  29.     #else
  30.         public void OnMessageBoxClick()
  31.         {
  32.             Debug.Log("Hello from Unity Player");
  33.         }
  34.     #endif
  35. }

Cette fois le code encadré par la directive NETFX_CORE est bien actif et pour cause : dans votre projet exporté sous Visual Studio, lorsque vous sélectionnez dans l’explorateur de solutions le projet “MessageBox.Windows” ou “MessageBox.Windows Phone” et affichez les propriétés du projet (onglet Build), vous verrez parmi les directives de compilation actives la directive NETFX_CORE :

NETFX_CORE

Il est temps de disséquer le code : la classe WinRT pour afficher une boite de dialogue native est MessageDialog. Un de ces constructeurs est ici appelé avec en paramètre le message texte passé sous forme de chaine de caractères. Puis la méthode ShowAsync est appelée selon le pattern Await/Async. Ce qui est notable (et qui ne vous aura sans doute pas échappé) c’est le fait que ce bloc de code est inclus dans une lambda expression asynchrone, elle-même passée en paramètre d’un appel à la méthode CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync : cela signifie que l’affichage de la boite de dialogue est délégué au thread graphique (UI) de la fenêtre principale de l’application ; c’est une obligation imposée par le modèle de threading des applications WinRT dès lors qu’on interagit avec l’interface graphique. Logiquement vous pourriez croire que le script Unity s’exécute déjà dans ce thread graphique et que cette délégation est inutile … et bien non, détrompez vous : le script Unity s’exécute dans un background thread (encore appelé worker thread). D’ailleurs Unity a défini des méthodes spécifiques Windows pour gérer ce changement de contexte dans la classe UnityEngine.WSA.Application :

WSA.Thread

Je pourrais donc réécrire mon code ainsi :

Code Snippet

  1. async void showMessageDialogAsync(string message)
  2. {
  3.     UnityEngine.WSA.Application.InvokeOnUIThread( async () =>
  4.     {
  5.  
  6.         MessageDialog messageBox = new MessageDialog(message);
  7.         await messageBox.ShowAsync();
  8.     },
  9.     false);
  10. }

Cela fonctionnerait tout aussi bien ; c’est à vous de choisir en fonction de vos préférences. L’avantage de la méthode InvokeOnUIThread est qu’elle peut être appelée directement depuis un script Unity en dehors de tout bloc NETFX_CORE. Vous aurez également remarqué que l’exemple ci-dessous n’utilise qu’une seule directive NETFX_CORE. Dans quel cas de figure utiliser UNITY_METRO_8_1 ou UNITY_WP_8_1 ? La réponse est double :

 

Réponse 1 : 90% des APIs pour Windows et Windows Phone sont communes.

convergenceControls01

Pour utiliser les 10% restants (parties bleue et rose), il faudra donc utiliser ces directives. Je peux vous donner un exemple concret : imaginez que vous souhaitiez rediriger votre utilisateur vers l’application Store native afin de l’inciter à noter votre application. Bien que l’API soit identique pour Windows et Windows Phone, le format de l’URI utilisé en paramètre d’entrée est différent entre les deux store (“package family name” pour windows et “application ID” pour windows phone) :

 

Code Snippet

  1. #if NETFX_CORE
  2.         public void OnStoreReviewClick()
  3.         {
  4.             #if UNITY_METRO_8_1
  5.                 showStoreReviewPageAsync("ms-windows-store:Review?PFN={0}", "com.microsoft.app.contoso");
  6.             #elif UNITY_WP_8_1
  7.                 showStoreReviewPageAsync("ms-windows-store:reviewapp?appid={0}", "ad543082-80ec-45bb-aa02-ffe7f4182ba8");
  8.             #endif
  9.         }
  10.  
  11.         private async void showStoreReviewPageAsync(string prefix, string data)
  12.         {
  13.             await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
  14.             {
  15.                 var uri = new Uri(string.Format(prefix, data));
  16.                 await Windows.System.Launcher.LaunchUriAsync(uri);
  17.             });
  18.         }
  19. #endif

Bien sûr avec l’arrivée de Windows 10, ce code deviendra obsolète. En attendant, cela reste une implémentation efficace.

Réponse 2 : pour gérer le bouton “Retour” présent sur Windows Phone mais absent sur Windows. En général, lorsque l’utilisateur active la touche “Retour”, soit il le fait lorsqu’il navigue dans des menus (dans ce cas il faut afficher le menu précédent ou quitter le jeu), soit il l’actionne en plein jeu (dans ce cas, il faut mettre le jeu en pause). Sous Unity, le bouton “Retour” est mappé sur la touche “ESC”. Par conséquent voici un exemple de code basique gérant la touche retour uniquement pour Windows Phone :

Code Snippet

  1. void Update()
  2.     {
  3.         #if UNITY_WP_8_1
  4.             if (Input.GetKeyDown(KeyCode.Escape))
  5.             {
  6.                 Application.Quit();
  7.             }
  8.         #endif
  9.     }

 

Les Plugins

Utiliser des directives de compilations c’est bien mais cela peut rendre le code difficilement maintenable ou lisible. Une alternative possible est de définir une librairie (DLL) contenant tous les appels dépendants de la plateforme. Dans cette seconde partie de l’article, je vous propose donc de définir une telle librairie ou plutôt deux librairies :

  • Une librairie dénommée “MyPlugin.dll” dans laquelle je vais définir la méthode permettant d’afficher une boite de dialogue native (au sens natif à la plateforme Windows)
  • Une seconde librairie ayant le même nom “MyPlugin.dll” et contenant la même interface mais avec une implémentation vide (on parle alors de “Stub” ou coquille vide). Pourquoi ? Tout simplement pour permettre à votre jeu de s’exécuter dans le player de l’éditeur Unity. Du coup l’appel aux APIs plateformes ne se fera que dans un contexte d’exécution plateforme.

Création du plugin

Nous allons donc créer un plugin sous forme de DLL universelle ciblant Windows 8.1 et Windows Phone 8.1.

Pour commencer je vais créer un nouveau projet sous VS2013  : dénommé “MyPlugin” de type Visual C# –> Store App –> Universal App –> Class Library (Portable for Universal Apps) :

image

  1. Je choisis la catégorie de template Visual C# –> Store App –> Universal App
  2. Je veille à ce que la version de mon plugin soit basée sur le framework .NET 4.5.1 afin de pouvoir utiliser dans mon plugin la dernière version .NET compatible avec les plateformes Windows 8.1 / Windows Phone 8.1
  3. Puis je sélectionne Class Library (Portable for Universal Apps) ce qui me permettra de créer une librairie sous forme de DLL utilisable à la fois sur Windows et sur Windows Phone
  4. Je définis un nom de projet pour ma librairie : UniversalPlugin
  5. Enfin je nomme ma solution MyPlugins et je valide la création en cliquant sur le bouton “OK”

 

Visual Studio va alors créer la structure de fichiers suivante (capture sous de l’explorateur de fichiers sous Windows ) :

image

Et la structure de projet logique suivante (capture de l’explorateur de solution sous Visual Studio) :

image

Par défaut VS2013 a crée une classe unique dénommée Class1 définie dans le fichier correspondant Class1.cs. Je vous propose d’effacer ce fichier (sélectionnez le projet “UniversalPlugin” depuis l’explorateur de solution Visual Studio et ouvrez le menu contextuel en cliquant sur le bouton droit de la souris, puis sélectionnez “Effacer”). Nous allons créer une interface qui va définir les méthodes publiques du plugins qui seront accessibles depuis vos scripts Unity : pour cela je vous invite à ré-affichez le menu contextuel du projet “UniversalPlugin” sous Visual Studio et sélectionnez “Ajouter  / Nouvel élément” :

image

  1. Je sélectionne une interface
  2. … que je nomme IUniversalPlugin.cs (par convention il est intéressant de commencer tout nom d’interface par un “i” majuscule : cela permet en un clin d’œil d’identifier les interfaces de leur implémentation.
  3. Cliquer sur le bouton “Ajout / Add”

Nous allons ensuite modifier le fichier IUniversalPlugin.cs pour définir deux méthodes permettant d’afficher une boite de dialogue native :

Code Snippet

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5.  
  6. namespace MyPlugins
  7. {
  8.     /*
  9.      *   Define unity plugin interfaces for accessing APIs common to both Windows 8.1 and Windows Phone 8.1
  10.      */
  11.     public interface IUniversalPlugin
  12.     {
  13.         // Prompt a native dialog box with a message
  14.         void ShowMessageDialog(string message);
  15.  
  16.         // Prompt a native dialog box with a message and a title
  17.         void ShowMessageDialog(string message, string title);
  18.     }
  19. }

Pensez à bien rendre cette interface publique. Reste à implémenter ces deux méthodes. Pour ce faire je vais créer une nouvelle classe dénommée UniversalPlugin dans un nouveau fichier UniversalPlugin.cs. Comment ? Très simple : dans la fenêtre d’explorateur de solution Visual Studio, sélectionnez le projet UniversalPlugin et cliquez sur le bouton droit de la souris. Dans le menu contextuel, sélectionnez “Ajouter / Classe” :

image

  1. Par défaut, Classe est sélectionnée … ne changez rien
  2. Puis nommez le fichier UniversalPlugin.cs : le fichier contiendra alors la définition (vide) de la classe UniversalPlugin

Ensuit nous allons modifier ce fichier UniversalPlugin.cs ainsi :

Code Snippet

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6.  
  7. using Windows.ApplicationModel.Core;
  8. using Windows.UI.Core;
  9. using Windows.UI.Popups;
  10.  
  11. namespace MyPlugins
  12. {
  13.     public class UniversalPlugin : IUniversalPlugin
  14.     {
  15.         // Singleton pattern
  16.         private static readonly IUniversalPlugin iinstance = new UniversalPlugin();
  17.         public static IUniversalPlugin IInstance
  18.         {
  19.             get
  20.             {
  21.                 return iinstance;
  22.             }
  23.         }
  24.  
  25.         // Implementation of the first public method of IUniversalPlugin
  26.         public void ShowMessageDialog(string message)
  27.         {
  28.             showMessageDialogAsyncPriv(message, null);
  29.         }
  30.  
  31.         // Implementation of the second public method of IUniversalPlugin
  32.         public void ShowMessageDialog(string message, string title)
  33.         {
  34.             showMessageDialogAsyncPriv(message, title);
  35.         }
  36.  
  37.         // Both public implementation of IUniversalPlugin rely on the following common private method
  38.         private async void showMessageDialogAsyncPriv(string message, string title)
  39.         {
  40.             // Request the execution of the lambda expression argument to be done by the UI thread from the current worker thread
  41.             await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
  42.             {
  43.                 MessageDialog messageBox;
  44.  
  45.                 if (title != null) messageBox = new MessageDialog(message, title);
  46.                 else
  47.                     messageBox = new MessageDialog(message);
  48.  
  49.                 // Display the message box
  50.                 await messageBox.ShowAsync();
  51.             });
  52.         }
  53.     }
  54. }

Première remarque : j’ai renommé l’espace de nom initial “UniversalPlugin” en “MyPlugins” de manière à distinguer l’espace de nom des classes qu’il contient. Pour cela j’ai sélectionné avec le curseur de souris l’espace de nom initial, UniversalPlugin et j’ai cliqué sur le bouton droit et choisi le menu “Refactor –> Rename” : cela permet de modifier le nom d’espace dans la globalité du projet (au niveau de la DLL et de tous les fichiers de code).

Ensuite, afin d’optimiser l’utilisation de la classe UniversalPlugin, nous utilisons le pattern classique de “Singleton” ce qui permet de ré-utiliser la même instance pour différents appels. Un appel à la méthode d’affichage d’une boite de dialogue natif se fera donc ainsi :

UniversalPlugin.IInstance.ShowMessageDialog(“Hello from WinRT”);

 

La méthode showMessageDialogAsyncPriv mérite quelques explications : l’affichage d’une boite de dialogue sous Windows/Windows Phone requiert l’exécution dans le contexte du thread graphique (encore appelé thread UI). Or par défaut, une application Unity s’exécute dans un thread applicatif (encore appelé worker thread). Par conséquent la méthode CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync permet de demander l’exécution du code asynchrone inclus dans la lambda expression passée en second paramètre. Si vous appelez directement la méthode messageBox.ShowAsync, votre application se terminera brutalement suite à une levée d’exception système. Lorsque nous compilons le projet “UniversalPlugin”, une DLL dénommée UniversalPlugin.dll sera générée dans le répertoire MyPlugins\UniversalPlugin\bin\Debug (ou MyPlugins\UniversalPlugin\bin\Release). Comme je le mentionnais en début de paragraphe sur les plugins : la création de cette librairie est nécessaire mais pas suffisante. En effet si vous utilisez directement cette librairie depuis vos scripts Unity, ces derniers ne compileront pas du fait de l’utilisation d’API .NET WinRT (à moins d’utiliser une directive de type NETFX_CORE … mais dans ce cas on perd tout l’intérêt d’utiliser un plugin). Il faut donc définir une seconde librairie reprenant les mêmes méthodes de la première librairie mais en y implémentant des méthodes vides (classiquement appelées Stub). C’est donc ce que nous allons voir maintenant.

Depuis l’explorateur de solutions sous Visual Studio, sélectionnez la solution “MyPlugins” puis afficher le menu contextuel (souris bouton droit) et choisissez le menu “Ajouter / Nouveau Projet” :

image

    1. Je choisis la catégorie de template Visual C# –> Windows Desktop
    2. Je veille à ce que la version de mon plugin soit basée sur le framework .NET 3.5 (et non .NET 4.5.1) car Unity s’appuie sur Mono (dont implémentation Open Source de .NET est équivalent à la version 3.5)
    3. Puis je sélectionne Class Library (Portable for Universal Apps) ce qui me permettra de créer une librairie sous forme de DLL utilisable à la fois sur Windows et sur Windows Phone
    4. Je définis un nom de projet pour ma librairie Stub : UniversalPlugin.Stub
    5. Enfin je conserve le chemin de destination par défaut (qui correspond à mon répertoire de travail) et je valide la création en cliquant sur le bouton “OK”

Visual Studio va alors créer la structure de fichiers suivante (capture sous de l’explorateur de fichiers sous Windows ) :

image

Et la structure de projet logique suivante (capture de l’explorateur de solution sous Visual Studio) :

image

Dorénavant nous avons deux projets dans notre solution MyPlugins :

  • UniversalPlugin
  • UniversalPlugin.Stub

Si vous ouvrez le fichier Class1.cs créé par défaut, vous verrez que la classe appartient à l’espace de nom (namespace en anglais) UniversalPlugin.Stub : c’est un problème car l’objectif du stub est de rendre les choses transparentes pour Unity lorsque vous exécuterez votre code depuis l’éditeur. Par conséquent il faut modifier cet espace de nom pour qu’il soit identique à l’espace de nom de la première librairie à savoir MyPlugins. Pour cela, sélectionnez le projet “UniversalPlugin.Stub” depuis Visual Studio, cliquez sur le bouton droit de souris et cliquez sur le menu “Propriétés” :

image

  • Renommer le nom de l’assembly en UniversalPlugin afin que le fichier DLL du stub soit identique au nom de fichier DLL de la première librairie
  • Renommer le nom d’espace par défaut en MyPlugins
  • Sauvegarder les propriétés du projet (combinaison de touches CTRL + S)

Je vous propose ensuite d’effacer le fichier Class1.cs (sélectionnez le projet “UniversalPlugin.Stub” depuis l’explorateur de solution Visual Studio et ouvrez le menu contextuel en cliquant sur le bouton droit de la souris, puis sélectionnez “Effacer”). Maintenant nous allons ajouter une classe (ou plutôt un lien symbolique)  du projet UniversalPlugin (donc déjà existante) dans le projet UniversalPlugin.Stub : il s’agit de l’interface IUniversalPlugin ; pour cela sélectionnez le projet UniversalPlugin.Stub, puis afficher le menu contextuel et choisissez “Ajouter / Elément existant” :

image

  1. Modifiez le répertoire sélectionné par défaut (UniversalPlugin.Stub) afin de parcourir le répertoire UniversalPlugin
  2. Sélectionnez le fichier d’interface IUniversalPlugin.cs
  3. Cliquez sur le bouton “Ajouter comme lien” de manière à créer une référence à ce fichier dans votre projet courant UniversalPlugin.Stub

Notez la petite flèche sur l’icone du fichier IUniversalPlugin.cs de votre projet UniversalPlugin.Stub :

image

 

Nous avons créé un lien symbolique qui permet de simplifier la maintenance de l’interface du plugin : si nous voulons ajouter une nouvelle méthode, inutile de modifier deux fichiers avec le risque d’erreur que cela peut induire. Reste maintenant à définir la partie Stub, ce qui est très simple : sélectionnez le projet UniversalPlugin.Stub, puis afficher le menu contextuel et choisissez “Ajouter / Elément existant” :

image

    1. Modifiez le répertoire sélectionné par défaut (UniversalPlugin.Stub) afin de parcourir le répertoire UniversalPlugin
    2. Sélectionnez le fichier d’interface UniversalPlugin.cs (sans le “i” majuscule cette fois)
    3. Cliquez sur le bouton “Ajouter” de manière à créer une copie de ce fichier dans votre projet courant UniversalPlugin.Stub

Modifier votre fichier UniversalPlugin.cs fraichement dupliqué :

Code Snippet

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5.  
  6.  
  7. namespace MyPlugins
  8. {
  9.     public class UniversalPlugin : IUniversalPlugin
  10.     {
  11.         // Singleton pattern
  12.         private static readonly IUniversalPlugin iinstance = new UniversalPlugin();
  13.         public static IUniversalPlugin IInstance
  14.         {
  15.             get
  16.             {
  17.                 return iinstance;
  18.             }
  19.         }
  20.  
  21.         // Implementation of the first public method of IUniversalPlugin
  22.         public void ShowMessageDialog(string message)
  23.         {
  24.             
  25.         }
  26.  
  27.         // Implementation of the second public method of IUniversalPlugin
  28.         public void ShowMessageDialog(string message, string title)
  29.         {
  30.             
  31.         }        
  32.     }
  33. }

Comme vous le constatez, j’ai vidé le contenu des méthodes ShowMessageDialog et j’ai effacé la méthode showMessageDialogAsyncPriv.Compilez maintenant votre projet UniversalPlugin.Stub et vous obtiendrez la fameuse seconde librairie portant le même nom que la première “UniversalPlugin.dll”

Utilisation du plugin

Maintenant je vous propose de reprendre l’application Unity utilisée dans la première partie de cet article. Avant d’ouvrir le projet sous Unity il est important de déployer les fichiers DLL (plugin et stub). Dans un premier nous allons les déployer à la main via l’explorateur de fichier. Ultérieurement je vous montrerai comment créer un fichier de déploiement (fichier d’extension .unitypackage) ou package qu’il sera alors possible d’importer depuis Unity. Voici la structure classique de fichier de notre projet Unity initial dénommé MessageBox :

image

 

Dans le répertoire “Assets” je vais créer un sous-répertoire dénommé “Plugins” : ATTENTION l’orthographe de ce répertoire est importante ; si vous oubliez le “s’” à la fin, l’éditeur ne saura pas localisez vos DLLs et vous ne pourrez pas exporter votre jeu. Une fois ce répertoire Plugins créé, vous allez :

  1. y copier la librairie Stub de votre plugin
  2. y créer un répertoire dénommé “Metro” et copier dans ce nouveau répertoire la librairie de votre plugin

Maintenant nous pouvons ouvrir le projet Unity et éditer le fichier script Unity dénommé MessageBox.cs ainsi :

Code Snippet

  1. using UnityEngine;
  2. using System.Collections;
  3.  
  4. using MyPlugins;
  5.  
  6. public class MessageBox : MonoBehaviour
  7. {
  8.     void Update()
  9.     {
  10.         #if UNITY_WP_8_1
  11.             if (Input.GetKeyDown(KeyCode.Escape))
  12.             {
  13.                 Application.Quit();
  14.             }
  15.         #endif
  16.     }
  17.  
  18.     public void OnMessageBoxClick()
  19.     {
  20.         UniversalPlugin.IInstance.ShowMessageDialog("Hello from WinRT platform", "Native Dialog");    
  21.     }
  22.  
  23. }

Notez deux choses :

  1. L’inclusion de l’espace de nom “MyPlugins”
  2. La modification du gestionnaire de clic OnMessageBoxClick avec l’appel à notre plugin.

 

Si maintenant vous exécutez votre application depuis l’éditeur Unity et que vous cliquez sur le bouton MessageBox, rien ne se passe et c’est normal puisque la méthode ShowMessageDialog appelée est celle du stub dont l’implémentation est vide. Pour finir je vous invite à exporter votre jeu en application universelle 8.1. Si l’exportation réussie, Unity va ouvrir l’explorateur de fichier contenant votre projet Visual Studio :

image

Ouvrez le fichier MessageBox.sln avec Visual Studio 2013 puis sélectionnez le projet MessageBox.Windows (Windows 8.1) et cliquer sur le bouton “Local Machine’'” après vous êtes assurés que l’architecture processeur soit bien “x86” :

image

A l’exécution, cliquez sur l’unique bouton “Display MessageBox” du Canvas Unity :

image

Et voilà ….

Note : vous pouvez également tester l’application en version Windows Phone 8.1 car notre plugin est universel.

Déploiement du plugin

Comme vous l’avez constaté, le déploiement manuel de votre plugin est un peu fastidieux et on a vite fait de s’emmêler les pinceaux entre la DLL du plugin et sa version Stub portant le même nom. Pour palier se problème, nous allons créer un petit script (batch file) tout simple :

Code Snippet

  1. @echo off

  2.  

  3. REM Visual Studio project directory for DLLs

  4. SET UNIVERSAL_PLUGIN_DLL=.\UniversalPlugin

  5. REM Unity directory structure

  6. SET UNITY_PROJECT_DIR=%1

  7. SET PLUGIN_DIR=%UNITY_PROJECT_DIR%\Assets\Plugins\\par SET METRO_DIR=%PLUGIN_DIR%\Metro\\par

  8. IF %1.==. GOTO ERROR1

  9.  

  10. echo  %UNITY_PROJECT_DIR%

  11. echo  %PLUGIN_DIR%

  12. echo  %METRO_DIR%

  13.  

  14. IF not exist %PLUGIN_DIR% (mkdir %PLUGIN_DIR%)

  15. IF not exist %METRO_DIR% (mkdir %METRO_DIR%)

  16.  

  17. xcopy %UNIVERSAL_PLUGIN_DLL%.Stub\bin\Release\*.dll  %PLUGIN_DIR% /Y

  18. xcopy %UNIVERSAL_PLUGIN_DLL%\bin\Release\*.dll  %METRO_DIR% /Y

  19.  

  20. GOTO Success

  21.  

  22. :ERROR1

  23.   ECHO Unity project folder is missing !!

  24.   GOTO End

  25.  

  26. :Success

  27.   ECHO DLL files have been copied with success !

  28.   

  29. :End

  30. pause

 

Pour le tester :

  1. Copiez le fichier deploy.bat ci-dessous dans le répertoire de votre solution Visual Studio utilisée pour créer votre plugin. Dans mon cas il s’agit du répertoire suivant : C:\work\dev\wp\unity\MyPlugins
  2. Ouvrez une invite de commande et placez-vous dans ce même répertoire puis exécutez la commande suivante :

deploy C:\work\dev\unity\MessageBox

Vous obtenez alors la sortie suivante :

image

Notez que mon fichier batch déploie uniquement les fichiers DLLs en mode release.

Distribution du plugin

Sachez que vous pouvez distribuer votre plugin sur l’asset store de Unity en version gratuite ou payante. Pour distribuer votre plugin via l’asset store de Unity ou autre site web, il vous faudra packager votre plugin dans un fichier d’extension “.unitypackage”, ce qui permettra aux utilisateurs de l’importer directement depuis Unity. Pour cela il faut utiliser l’exécutable Unity en mode scripté et créer au moins un projet Unity utilisant votre plugin (dans notre cas il s’agit du projet MessageBox). Voici la commande à saisir :

image

  • l’option –batchmode permet d’utiliser Unity sans ouvrir l’éditeur
  • l’option –projectPatch indique le répertoire du projet Unity (ici notre projet test MessageBox)
  • l’option –exportPackage indique le répertoire contenant notre plugin
  • Et à la fin nous indiquons le nom du package personnalisé, MyPlugins.unitypackage, qui sera généré à la racine du répertoire MessageBox

Une fois le package généré, d’autres développeurs pourront l’utiliser via Unity en sélectionnant le menu Assets / Import Assets / Custom Package. S’ouvrira alors la fenêtre Unity suivante :

image

 

Conclusion

Voilà … l’utilisation des APIs Windows/Windows Phone 8.1 depuis vos scripts Unity n’ont plus de secrets pour vous ou presque. Vous pouvez téléchargez librement les exemples de codes détaillées dans ce article ici