Données cartographiques vectorisées, extrudées et spatialisées.
Ou le pipeline GeoJSON, D3.js vers Three.js. Cela fait un moment que nous n'avions pas fait de projet Three.js ! Nous jouons surtout avec Unity, mais c'est toujours agréable de revenir…
Edit : ce script est génial, et si vous le combinez avec un ScriptableObject, vous êtes prêt ! Récupérer les noms de vos scènes à l'exécution avec Unity est un problème courant. L'…
Edit : ce script est génial, et si vous le combinez avec un ScriptableObject, vous êtes prêt !
Récupérer les noms de vos scènes à l’exécution avec Unity est un problème courant. L’API de Unity ne fournit pas de moyen simple de le faire. Avec le SceneManager relativement récent, je pensais qu’Unity avait enfin fourni quelque chose pour obtenir des informations sur les scènes ajoutées dans les Build Settings. Et bien non !
J’ai rencontré ce problème en implémentant une petite interface de configuration, où l’utilisateur a la possibilité d’indiquer l’ordre dans lequel les scènes seront jouées. Oui, j’aurais pu coder cela en dur, mais ce n’est pas la manière élégante de faire, à mon avis. J’ai donc mené quelques investigations pour obtenir une bonne fois pour toutes les noms de mes scènes.
Tout d’abord, j’avais besoin des noms de mes scènes, et le seul endroit où je les ai trouvés était dans Editor Build Settings, qui n’est pas documenté par Unity. Oh non, ça commence mal. Cette classe fournit une propriété statique permettant d’obtenir des informations sur les scènes disponibles dans vos Build Settings (activées ET désactivées). Il suffit ensuite de parcourir le tableau en boucle pour récupérer le nom de vos scènes, dans le même ordre que celui dans lequel elles ont été ajoutées aux Build Settings !
EditorBuildSettingsScene[] scenes = EditorBuildSettings.scenes ;
for( int i = 0 ; i < scenes.Length ; ++i)
{
if( scenes[i].enabled )
Debug.Log( "Scene #" + i + " : " + scenes[i].path + " is enabled" );
else
Debug.LogWarning( "Scene #" + i + " : " + scenes[i].path + " is not enabled" );
}
Oh, vous pensiez pouvoir copier-coller ce code directement dans votre script ? Désolé, ce n’est pas aussi simple, je n’aurais pas écrit cet article pour seulement 6 lignes de code ! ;-)
Comme vous l’avez peut-être remarqué, la classe s’appelle EditorBuildSettingsScene, elle appartient donc au namespace UnityEditor, et, comme vous le savez sûrement, les classes de ce namespace ne peuvent pas être utilisées dans un build autonome.
Dans l’éditeur, nous pouvons facilement afficher cette liste en utilisant l’attribut MenuItem comme ceci :
using UnityEngine;
using UnityEditor;
public class GetScenesNamesFromEditor
{
[MenuItem( "Scenes Names/Save Scenes Names" )]
private static void GetScenesNames()
{
EditorBuildSettingsScene[] scenes = EditorBuildSettings.scenes;
for ( int i = 0 ; i < scenes.Length ; ++i )
{
if( scenes[i].enabled )
Debug.Log( "Scene #" + i + " : " + scenes[i].path + " is enabled" );
else
Debug.LogWarning( "Scene #" + i + " : " + scenes[i].path + " is not enabled" );
}
}
}
Maintenant, vous allez me dire “Cela ne résout pas le problème de la récupération des noms de mes scènes dans mon jeu, je ne peux appeler cette fonction que dans mon éditeur !”, et oui, en effet ! Puisque nous ne pouvons pas utiliser directement cette fonction dans notre jeu, utilisons-la indirectement ! Comment ? Un seul mot : Sérialisation !
Commençons par définir ce qu’est la sérialisation :
La sérialisation est le processus consistant à convertir un objet dans un format donné afin de stocker cet objet (dans un fichier, dans une base de données, …) ou de le transmettre via une connexion réseau. Son but principal est de sauvegarder l’état d’un objet afin de pouvoir le recréer plus tard si besoin, éventuellement dans un autre contexte de programme. Le processus inverse s’appelle la désérialisation.
Vous avez sûrement deviné ce que je veux faire ici :
Il y a tellement de façons de faire ça, pourquoi pas utiliser une base de données ? Non, je plaisante, ici, une dizaine de chaînes de caractères seulement doivent être sauvegardées. Je pourrais utiliser un fichier XML, JSON, YAML ou même un simple fichier texte avec un nom de scène par ligne, mais je voulais jeter un œil aux scriptable objects d’Unity.
ScriptableObject est une classe qui vous permet de stocker de grandes quantités de données partagées, indépendamment des instances de script.
Source : Unity Manual
Le plus souvent, les scriptable objects sont utilisés comme assets destinés uniquement à stocker des données. Parfait, c’est justement notre cas ici ! Définissons donc notre structure de données, contenant simplement un tableau de chaînes de caractères. N’hésitez pas à ajouter un autre tableau si vous voulez savoir si les scènes sont activées ou non.
using UnityEngine;
public class ScenesList : ScriptableObject
{
public string[] scenesNames;
}
Nous allons maintenant pouvoir créer un nouveau ScriptableObject en utilisant ScriptableObject.CreateInstance, le seul moyen d’instancier un tel objet (l’opérateur new ne fonctionne pas). Ensuite, nous remplirons le tableau du scriptable object et le stockerons à un endroit où nous pourrons le récupérer plus tard pour le désérialiser.
Les scriptable objects ont été conçus pour être facilement sérialisés et désérialisés sous forme d’assets. Nous allons donc utiliser les fonctions statiques fournies par la classe AssetDatabase.
[MenuItem( "Scenes Names/Save Scenes Names" )]
private static void SaveScenesNames()
{
EditorBuildSettingsScene[] scenes = EditorBuildSettings.scenes;
// First, try to load the list if already exists
ScenesList list = (ScenesList) AssetDatabase.LoadAssetAtPath("Assets/Resources/ScenesList.asset", typeof(ScenesList)) ;
// If doesn't exist, create it !
if( list == null )
{
list = ScriptableObject.CreateInstance<ScenesList>() ;
AssetDatabase.CreateAsset( list, "Assets/Resources/ScenesList.asset" );
}
// Fill the array
list.scenesNames = new string[scenes.Length];
for ( int i = 0 ; i < scenes.Length ; ++i )
{
list.scenesNames[i] = scenes[i].path ;
}
// Writes all unsaved asset changes to disk
AssetDatabase.SaveAssets();
}
Si vous voulez sauvegarder uniquement les scènes activées, utilisez une List temporaire (dans le namespace System.Collections.Generic) :
[MenuItem( "Scenes Names/Save Scenes Names" )]
private static void SaveScenesNames()
{
EditorBuildSettingsScene[] scenes = EditorBuildSettings.scenes;
List<string> scenesNames = new List<string>();
// First, try to load the list if already exists
ScenesList list = (ScenesList) AssetDatabase.LoadAssetAtPath("Assets/Resources/ScenesList.asset", typeof(ScenesList)) ;
// If doesn't exist, create it !
if( list == null )
{
list = ScriptableObject.CreateInstance<ScenesList>() ;
AssetDatabase.CreateAsset( list, "Assets/Resources/ScenesList.asset" );
}
// Fill the array
for ( int i = 0 ; i < scenes.Length ; ++i )
{
if( scenes[i].enabled )
scenesNames.Add( scenes[i].path );
}
asset.scenesNames = scenesNames.ToArray();
// Writes all unsaved asset changes to disk
AssetDatabase.SaveAssets();
}
Dans le dernier script, j’ai décidé de stocker la liste dans mon dossier Resources afin de pouvoir la récupérer facilement avec une seule ligne de code dans n’importe lequel de mes scripts :
ScenesList list = Resources.Load<ScenesList>( "ScenesList" );
Cependant, vous auriez pu stocker l’asset dans un autre dossier, puis déclarer une ScenesList publique dans le script ayant besoin de la liste des noms de scènes, et glisser-déposer l’asset créé dans l’inspecteur.
public class RetrieveScenesNames : MonoBehaviour
{
public ScenesList list ;
private void Start()
{
// Here I suppose you have saved the List in your Resources folder
if( list == null )
list = Resources.Load<ScenesList>( "ScenesList" );
for( int i = 0 ; i < list.scenesNames.Length ; ++ i )
{
Debug.Log( list.scenesNames[i] );
}
}
}
Et… voilà ! Vous avez les noms de vos scènes à l’exécution ! Ou plus précisément, le chemin de vos scènes. Si vous voulez uniquement le nom, je vous suggère d’utiliser la magie des expressions régulières (dans le namespace System.Text.RegularExpressions). Utilisez-les au moment où vous stockez les données ou au moment où vous les utilisez :
private void Start()
{
Regex regex = new Regex( @"([^/]*/)*([\w\d\-]*)\.unity" ) ;
for( int i = 0 ; i < list.scenesNames.Length ; ++i )
{
// Will replace the full path by the second capturing group
// corresponding to the name of the scene
Debug.Log( regex.Replace( list.scenesNames[i], "$2" ) );
}
}
Avec le dernier script utilisé pour stocker les données dans le scriptable object, gardez à l’esprit que les informations ne seront pas mises à jour dynamiquement si vous modifiez vos scènes dans les Build Settings ou si vous les renommez. Vous devrez appeler SaveScenesNames via l’élément de menu.
Si vous ne voulez pas faire cela à la main, vous pourriez écouter l’événement EditorApplication.playmodeStateChanged et sauvegarder les noms des scènes lorsque vous cliquez sur le bouton Play de l’éditeur. Jetez un œil ici pour un exemple d’utilisation.