PARTIE II : Building Blocks

Dans cette seconde partie, nous allons créer les blocs élémentaires qui vont constituer nos plantes. Il s’agira essentiellement de sélectionner quelques meshes, d’en dériver des prefabs, et de voir comment les combiner dans une structure hiérarchique. Comme nous allons maintenant utiliser Unity, je ne saurai que vous inciter a vous familiariser avec son interface[1]

Dans un premier temps, il ne sera pas nécessaire de se ruer sur le code. Au contraire, et particulièrement pour cette partie, je vous invite à la parcourir une première fois, et ensuite de la réaliser pas à pas, surtout si vous n’êtes pas familier avec Unity3D.

Creation de Prefabs

Formes et Matériaux

Nous nous contenterons d’utiliser 3 formes élémentaires, disponibles comme primitives standards dans Unity,  pour représenter les différents organes: cylindre, cube, sphère:

  •  Les portions de tige seront modélisés par un cylindre.
  • Les organes comme les fruits, les feuilles, ou fleurs par des sphères et des cubes.

En ce qui concerne les matériaux, et afin de garder la scène la plus lisible possible,  nous resterons simples: pas de texture, du vert uni pour les tiges, du rouge pour le reste.

Moteur Physique / Rigid Body

Unity3D vient avec un moteur physique simple et puissant, et il serait dommage de ne pas s’en servir! Cependant, dans un premier temps, nous animerons la structure par nous même, sans faire usage du moteur physique. Ce n’est que plus tard que nous aurons l’occasion de lui rendre le contrôle.

Le composant Rigidbody permet de donner des propriétés physiques a un GameObject. Nous n’avons pas besoin de RigidBody dans nos prefabs, mais dans le cas où il y en a un, nous allons temporairement le désactiver, en le configurant en mode ‘cinématique’.

Création des Prefabs

Ainsi nous allons creer 3 prefabs, que l’on nommera respectivement MorphoCylPrefab, MorphoCubePrefab et MorphoSpherePrefab. Une façon rapide pour faire cela, consiste tout d’abord à ajouter les 3 éléments dans une scène, à l’aide du menu “GameObject/Create Other”

On créera ensuite les matériaux, que l’on associera à nos 3 éléments aux différents éléments ajoutés dans la scène. Par exemple, on créera le matériau ‘Fruits’, que l’on glissera sur les objets sphère et cube :

On fera de même avec un matériau vert, que l’on associera aux cylindres. Ensuite il suffit de faire glisser les 3 éléments dans la fenêtre des assets, ce qui aura pour effet de créer automatiquement 3  prefabs, qu’il ne faudra pas oublier de renommer correctement. Les objets ajoutés dans la scène ne nous sont plus utiles, ils peuvent être retirés.

Un mot, concernant l’organisation du projet unity. lui même.  Sur la capture d’écran précédente, nous avons sur la gauche l’arborescence utilisée pour le projet. Le répertoire ‘Morpho’ en particulier contient 3 sous répertoires:

  • Blocks/Resources/: Il contient les prefabs que l’on vient de définir,  ainsi que les 2 matériaux rouge et vert, et un répertoire script encore actuellement vide, mais qui va contenir un script spécifique à ‘MorphoCylPrefab’, mais nous verrons cela un peu plus loin. Le nom ‘Resources’ est important, sinon les prefabs ne peuvent pas être trouvés.
  • Scripts: Ce sont les scripts que nous  allons créer dans la prochaine partie, et qui seront responsable de la croissance des plantes.
  • Seeds: Contiendra les ‘graines’ de nos plantes, a savoir des prefabs auquel on associera un script de croissance. La encore nous verrons cela dans la pratique dans une prochaine partie.

C’est une proposition d’organisation, vous pouvez ranger vos fichiers comme bon vous semble, mais il existe quelques conventions[3].  L’une d’entre elle c’est que les prefabs sont “accessibles” par leur nom dans les scripts si ils sont rangés dans un répertoire qui s’appelle ‘Resources‘, quel que soit son emplacement. Pour créer un répertoire, utilisez le menu contextuel sur la fenêtre des assets.

Bien entendu, dans un projet Unity, il faut aussi définir au moins une scène, les ressources relatives à cette dernière seront rangées dans le répertoire ‘SceneSetup’, et la scène elle même à la racine des assets.  Lorsqu’un projet est créé une scène est ajoutée par défaut, et peut être sauvegardée sous forme d’asset avec le menu File.  Il est nécessaire d’ajouter au moins une caméra dans une scène. Et bien sur, il ne faut pas hésiter à ajouter de la lumière (une lumière directionnelle par exemple) pour embellir le rendu.

Maintenant que nous avons défini nos blocs élémentaires,  voyons comment leur associer des scripts.

Scripts

L’une des facilités qu’offre Unity3D est la possibilité d’associer des scripts à n’importe quel GameObject d’une scène, et ce, de façon dynamique. Nous allons user (abuser?) de cette fonctionnalité en associant un script  à chaque GameObject de notre plante.  Certains scripts seront eux même capable de créer de nouveaux GameObjects, et de les doter à leur tour d’un script…et ainsi de suite. C’est ainsi que l’on va construire nos plantes de façon récursive.

Pour créer un script,  on pourra utiliser le menu contextuel, par un clic droit dans la fenêtre des assets. Dans ce tutoriel, nous utiliserons C# comme langage. Un double click sur le script ouvre un éditeur. Le bouton droit sur le script permet de changer son nom. Pour resynchroniser l’éditeur de code et Unity3d, on pourra utiliser la encore le menu contextuel de la fenêtre des assets “Sync MonoDevelop Project”.

Pour instancier un prefab dans un script, on peut directement utiliser son nom:

GameObject g;
g = Instantiate (Resources.Load (MorphoCylPrefab)) as GameObject;

Apres instanciation, il est possible d’ajouter un script, la encore à partir de son nom (ou plutôt de la classe qu’il implémente mais en générale on confond les deux)

g.AddComponent (scriptName);

Nous verrons en particulier le cas où tous les GameObjects partagent un même et unique script.  Même si il s’agit du même script, chaque copie se comporte comme une instance de classe, et possède sa propre zone mémoire pour stocker les variables locales, donc chaque script se comporte de façon indépendante, avec son propre jeu de paramètres. En architecture des systèmes, cette organisation s’apparente a du calcul réparti de type SIMD (=Simple Instruction, Multiple Data). Mais avant d’aller plus loin, voyons comment nous allons organiser notre code.

Organisation du code

Nous allons répartir le code dans deux classes, MorphoBlockScript qui hérite de MonoBehaviour, et MorphoPlantScript, qui hérite de MorphoBlockScript. Pour la seconde, nous allons mettre tout ce qui est spécifique a notre variété de plante, tandis que la première va regrouper des méthodes qui peuvent servir à différentes variétés, notre boite a outil en quelque sorte. Chaque classe est définie dans un fichier à part. Les scripts seront rangés dans le répertoire Morpho/Blocks/Scripts, dans l’arborescence des assets.

Ainsi dans la classe MorphoBlockScript, on pourra se doter de la méthode suivante, qui créée un nouveau GameObject à partir d’un prefab, le dote d’un script, et si le RigibBody existe, active le mode cinématique:

public GameObject CreateGameObject (string prefabname, string script)
{
	GameObject g = Instantiate (Resources.Load (prefabname)) as GameObject;	
	if (g != null) {
		// Adds script	
		if (script != null)
			g.AddComponent (script);
		// Enable RigidBody's kinematic mode (if it exists)
		Rigidbody rgbd = g.GetComponent ("Rigidbody") as Rigidbody;
		if (rgbd != null) {						
			rgbd.isKinematic = true;
		}
	}
	return g;
}

Ce n’est qu’un exemple, qui sera repris dans la prochaine partie. pour le moment il n’est pas encore nécessaire de créer ces scripts.

Empiler les blocs 

La plante étant une structure arborescente par excellence, il est naturel d’organiser notre plante de façon hiérarchique: chaque organe pourra ainsi avoir un ou plusieurs sous organes – il sera leur parent. Le premier élément de la plante, qui ne possède pas de parent, sera nommé la racine.

Dans Unity3D, si l’on a 2 GameObjects a et b, pour que a devienne le parent de b, il suffit utiliser la propriété transform.parent de b, de la façon suivante:

 b.transform.parent = a.transform;

Le placement d’un fils est relatif a son parent, il est défini par les propriétés suivantes de l’objet Transform[4]:

  • localPosition: c’est la position exprimée dans le référentiel du parent: (0,0,0) place l’objet à la même position que le parent. La hauteur des blocs cylindriques étant fixé à 2, on peut par exemple placer un fils à la position  (0 , 2, 0) pour créer un empilement.
  • localEulerAngles: définit l’orientation relative au parent, en procédant à 3 rotations autour de chaque axes X,Y,Z, dans un ordre donné. On ne s’intéressera qu’aux rotations autour de l’axe X (l’inclinaison), et de l’axe Y (la phyllotaxie), ce sera suffisant pour fabriquer des structures arborescentes..
  • Scaling: Facteur d’échelle appliqué sur les 3 axes. Il faudra garder la même valeur pour les 3 axes pour éviter toute distorsion. (1,1,1) correspond à la taille ‘normale’ de l’asset. Il pourra être utilisé pour faire grandir les organes, et ainsi simuler leur croissance. Enfin, il faut noter que le scaling se cumule le long de la hiérarchie.

Empiler des cylindres est à première vue sans difficulté: il suffit de donner au cylindre fils les coordonnées relatives (0,1,0). Mais dés que l’on va vouloir incliner le cylindre fils, on va se rendre compte que le centre de rotation n’est pas au bon endroit, dans le cas qui nous concerne: au lieu d’être au niveau de la base, il est placé au centre du cylindre.

De plus, puisque nous en sommes à modifier le cylindre, il serait plus élégant qu’il soit plus fin.

Correction de la forme du cylindre

Une solution  possible (et probablement la plus efficace) consiste à créer de toute pièce un cylindre avec les bonnes propriétés, par exemple avec un modeleur comme blender[2], et de l’importer dans les assets.

Ici, nous allons directement appliquer une transformation sur le mesh, à chaque fois que l’on instanciera le prefab. Certes cette méthode est moins efficace (la transformation est ré-appliquée à chaque instanciation), mais elle nous permettra de ne pas avoir recours à un outil supplémentaire, et en passant, d’aborder succinctement le contrôle des meshes par script. Nous allons donc transformer la forme du cylindre, en modifiant la position de ses vertices, en appliquant une translation vers le haut (0,1,0), ainsi qu’un changement d’échelle selon les axes X et Z (multiplication des coordonnées par ½). Le script suivant est créé :

using UnityEngine;
using System.Collections;

public class MorphoNormCyl : MonoBehaviour 
{
    void Start () 
    {
        Mesh mesh = gameObject.GetComponent<MeshFilter> ().mesh;
        Vector3[] vertices = mesh.vertices;
        int i = 0;
        while (i < vertices.Length) 
        {
            Vector3 v = vertices [i];
            v.x *= 0.5f;
            v.y += 1.0f;
            v.z *= 0.5f;
            vertices [i] = v;
            i++;
        }

    gameObject.GetComponent<MeshFilter> ().mesh.vertices = vertices;

    // No need to run the script anymore
    enabled = false;
    }
}

Le script est ajouté au prefab MorphoCylPrefab, de façon à être exécuté à chacune de ses instanciations.  Pour cela, il suffit de faire glisser le script sur le prefab. (Ici un apercu de la fenetre inspector pour ce prefab), ou utiliser le bouton ‘Add Component‘ dans l’inspecteur, sur le prefab en question.

Après transformation, le résultat correspond déjà plus à nos attentes. On constatera cependant que selon l’inclinaison, un interstice peut apparaître entre les 2 cylindres. Pour le masquer, on placera le cylindre fils un peu plus bas, par exemple aux coordonnées relatives (0,1.9,0)

Voila! il est maintenant possible d’empiler des blocs mais nous n’allons pas faire cela à la main bien sur! Dans la prochaine partie, nous allons explorer les joies de la récursivité et enfin obtenir nos premières plantes!

Références

 

Leave a Reply

Your email address will not be published. Required fields are marked *

software design & creative process