PARTIE V: Physique

Nous voici quasiment arrivés à la fin de ce long tutoriel. Dans cette ultime partie, nous allons aborder deux façons différentes d’introduire de la physique dans notre scène.

  • Vent: Jusqu’à présent, une fois une plante complètement développée, elle ne bouge plus, et reste totalement figée. Pour briser cette ennuyeuse monotonie, nous allons animer la plante d’un léger mouvement, pour simuler la présence de vent.
  • Gravité: Nous verrons ensuite comment utiliser le moteur physique, pour faire chuter les blocs lors de la destruction de la plante.

Simuler du Vent

Comme d’habitude, nous commençons par déclarer deux variables dans notre classe: refEulerAngle pour conserver l’orientation de référence relative au parent, et initShift pour désynchroniser le mouvement des différents éléments de notre plante:

public class MorphoScript : MorphoModule
{
    // Wind
    public Vector3 refEulerAngle = Vector3.up;
    float initShift = 0.0f;
    // .. 

Comme toujours, il ne faut pas oublier d’initialiser et propager la variable regEulerAngle dans la méthode addOrgan():

GameObject addOrgan(string orgpref, Vector3 rot)
{
    // ...
    MorphoScript cp = g.GetComponent("MorphoScript") as MorphoScript;
    if (cp)
    {
        cp.refEulerAngle = rot;
        cp.root = root;
        cp.level = level + 1;
        // ...
}

Il est temps de mettre en place l’animation à la fin de la méthode Update():

 void Update()
{
    // ...
    
    if (gameObject.transform.parent != null)
    {
        Vector3 wind = new Vector3((4f * level) / ((float)maxLevel) * Mathf.Sin(initShift + 0.3f * lifeCounter), 
                                   (4f * level) / ((float)maxLevel) * Mathf.Sin(initShift + 0.2f * lifeCounter),0.0f);
        gameObject.transform.localEulerAngles = refEulerAngle + wind;
    
    }
}

Pour éviter l’homogénéité des mouvements (aussi appelé “Effet Claudette” ), on pourra initialiser initshift avec une valeur aléatoire dans le constructeur ou dans la méthode Start():

initShift = Random.Range(0, 3.14f);

Moteur 3D

Dans la classe MorphoModule, on ajoutera une méthode pour détacher en organe de sa hiérarchie, en désactivant éventuellement le mode cinématique

public virtual void Detach (bool kin = false, bool grav = true)
{
    if (gameObject.transform.parent != null) {
        gameObject.transform.parent = null;
    	
        Rigidbody rgbd = gameObject.GetComponent ("Rigidbody") as Rigidbody;
        if (rgbd) {
            rgbd.isKinematic = kin;
            rgbd.useGravity = grav;
            if (!kin)			
                rgbd.velocity = (Vector3.up * -1.0f);
        }
    }
}

Modifions ensuite la méthode récursive MorphoScript::detachR() de façon a ce qu’elle appelle la méthode précédente. Son prototype est changé de façon à accepter deux booléens, kin pour contrôler l’activation du moteur physique et grav pour appliquer une gravité .

public void detachR(bool kin = false, bool grav = true)
{
    root.numOrgans--;

    while (gameObject.transform.childCount>0)
    {
        GameObject g = gameObject.transform.GetChild(0).gameObject;
        MorphoScript cp = g.GetComponent("MorphoScript") as MorphoScript;
        cp.detachR(kin, grav);
    }
    Detach(kin, grav);
    growthEnabled = false;

    if ((maxlife < 0) || (maxlife > lifeCounter + 10))
        maxlife = lifeCounter + 10;
}

Et… c’est tout! Vous pouvez essayer, le moteur physique prendra la main des que l’on détache un morceau de la structure, en cliquant dessus. En cas de détachement, le temps avant destruction est fixé à 10 secondes, ce qui permet aux organes de commencer leur mouvement de chute avant d’être définitivement détruits de la scène.

A titre indicatif, le code complet de la méthode Update est donné ici, avec quelques changements mineurs.

void Update()
{
    lifeCounter += Time.deltaTime;
    // Destruction automatique des organes
    if (lifeCounter > absmaxlife)
    {
        if (gameObject.transform.parent != null)
        {
            growthEnabled = false;
            if (gameObject.transform.childCount == 0)
            {
                detachR(false, true);
            }
        }
    }

    if ((maxlife > 0) && (lifeCounter >= maxlife))
        GameObject.Destroy(gameObject);
    else
    {
        if ((maxlife > 0) && (lifeCounter > maxlife - 2))
        {
            float size = maxsize * (maxlife - lifeCounter) / 2f;
            if (gameObject.transform.localScale.x > size)
                gameObject.transform.localScale = Vector3.one * size;
        } else
        {
            if (growthEnabled)
            {
                float scoef = lifeCounter * growthSpeed;
                if (scoef > 1.0f)
                    scoef = 1.0f;
                if (scoef < 0.1f)
                    scoef = 0.1f;
                gameObject.transform.localScale = Vector3.one * scoef * maxsize;
            }
        }
    }
    
    if (growthEnabled)
    {
        if ((maxlife < 0) && (lifeCounter > delayBeforeChild) && (level < maxLevel) && (gameObject.transform.childCount == 0))
        {
            if (level < 2)
            {
                addOrgan(childPrefabName, Vector3.zero);
            } else
            {
                if (level == maxLevel - 1)
                    addOrgan(fruitPrefabName, new Vector3(0.0f, phyllo, 0.0f));
                else
                {
                    if ((inclin > 30) && (level < 3))
                        addOrgan(childPrefabName, new Vector3(0.0f, phyllo, 0.0f));
                    if (Random.Range(0, 100) < 99)
                        addOrgan(childPrefabName, new Vector3(inclin, phyllo, 0.0f));
                    if (Random.Range(0, 100) < 99)
                        addOrgan(childPrefabName, new Vector3(inclin, phyllo + 180f, 0.0f));
                }
            }
        }
    }

    if (gameObject.transform.parent != null) // or if (growthEnabled)
    {
        // Wind
        Vector3 wind = new Vector3((4f * level) / ((float)maxLevel) * Mathf.Sin(initShift + 0.3f * lifeCounter),
                                   (4f * level) / ((float)maxLevel) * Mathf.Sin(initShift + 0.2f * lifeCounter), 0.0f);
        gameObject.transform.localEulerAngles = refEulerAngle + wind;
    }
}

Un sol pour rebondir

Ajouter un sol, pour voir rebondir les éléments, est très simple: il suffit de sélection ‘Plane’ dans le menu GameObject/3D Objects, pour ajouter un plan que l’on pourra placer sous la graine.

Matériaux

Si vous n’êtes pas  familier avec le moteur physique, c’est l’occasion d’expérimenter, en jouant en premier lieu sur les matériaux: ceux des prefabs, ceux du sol, etc.

Pour voir l’effet du flag ‘gravité’, on pourra différencier le clic gauche du clic droit de la souris: le flag est mis à vrai  pour un clic gauche, et à faux pour un clic droit:

void OnMouseOver()
    {
    // If mouse button down (left or right)
    if (Input.GetMouseButton(0) || Input.GetMouseButton(1))
    {
        if (gameObject.transform.parent != null)
        {
            GameObject g = gameObject.transform.parent.gameObject;
            MorphoScript cp = g.GetComponent("MorphoScript") as MorphoScript;
            cp.mutation();
            detachR(false, Input.GetMouseButton(0));
        }
    }
}

Conclusion

Ce tour d’horizon de la morphogenèse artificielle avec Unity3D est maintenant terminé. J’espère qu’il vous a plu! N’hésitez pas a faire part de vos commentaires, afin d’améliorer et clarifier le texte, et partager vos propres créations et mutations.

Comme dans les parties précédentes, le code source se trouve dans cette archive.

Leave a Reply

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

software design & creative process