mardi 23 juillet 2019

Unity - Instance dans le monde - partie 2

J'aimerais procéder en mentionnant que j'ai aisément corrigé mon problème d'orientation dans le script d'instanciation. Pour se faire, il m'a simplement fallu supprimer l'information de la collision précédente qui restait enregistrée dans la variable. J'ai intié le script en rendant la rotation locale :
       
             var _maisonRot = _maison.transform.localRotation.eulerAngles;

Et après:

            //changer la rotation du même objet
            var _maisonRot = _maison.transform.rotation;
            _maison.transform.rotation = Quaternion.Euler(-90.0f, Random.Range(-90.0f, 90.0f), 0.0f);
       
J'ai simplement rajouté :

            //repositionner la collision
            _collision = _maison.GetComponent<BoxCollider> ();

qui agit comme un reset.

La suite m'a permis de peaufiner la grosseur des collisions lorsqu'elles varient. J'utilise la propriété .OverlapBox qui, selon ma référence vient générer la collision à l'intérieur du BoxCollider .

OverlapBox agit sur la section encadrée rouge


Dans un premier temps, je crée une variable pour séparer les bâtiments de la surface, ce qui éliminera les analyse superflues dans le cas présent:

  [...]
          
void Start()
            [...]
                  _coucheDeMasqueBatiments = LayerMask.GetMask("Buildings");
            [...]

----

  Dans un second temps, je construis une variable récupérant les coordonnées pour le box collider. Il utilise l'original (Il peut être de n'importe quelle taille, il sera modifié selon la "Bounding Box" ou limites de l'objet visuel.)

            [...]
          
void FixedUpdate()
    {
       
        //création de la boite de collision (nécessite une COLLISION BOX)
        //Démarrer en confirmant les collisions

        var _verifCollision = Physics.OverlapBox ( (_collision.center + _maisonPos), (_collision.size)*1.06f, (_maison.transform.rotation),(_coucheDeMasqueBatiments)); 

 
            [...]


   Ensuite, il est temps d'appliquer ceci dans notre calcul, pour faire en sorte que les maisons ne se touchent pas.

Le résultat nous sort 80% des bâtiments qui évitent de se toucher
Il me reste à investiguer plus en profondeur pour les bâtiments qui s'interpénètrent.
---

Le deuxième élément modifié sur le script me permet de progresser sur l'étape 2 de mon plan : Sur une surface.

Pour l'instant, la surface sera plane; la première étape étant de rendre l'opération modulaire. Si j'étire les X de la surface, les bâtiments pourront se rendre jusqu'à la nouvelle limite. Même situation pour les Z et même situation lorsque la surface est compressée. Mon carré d'origine est de 10 x 10. Voici un résultat avec un rectangle de 5 x 20 :

Les objets en soi ont la même configuration que sur la surface 10 x 10


Pour se faire, j'ai ajouté ces 4 variables au script :

    private BoxCollider _surfaceBox;
    private Vector3 _surfaceMatrix;
    private float _surfaceX;
    private float _surfaceZ;

 
Au démarrage :

         _surfaceBox = _surface.GetComponent<BoxCollider> ();
        _surfaceMatrix = _surfaceBox.bounds.size;
        _surfaceX = _surfaceMatrix.x;
        _surfaceZ = _surfaceMatrix.z;



au début de la mise à jour (rappel) :

        _surfaceX = _surfaceBox.bounds.size.x;
        _surfaceZ = _surfaceBox.bounds.size.z;


et durant la mise à jour, je rajoute ce bout de script :


            //Si la OverlapBox possède un plus grand X que Z; réduit la surface de X (meme chose pour s'il s'agit du Z)
            if (_collision.size.x > _collision.size.z)
            {
                _surfaceX -= (_collision.size.x/2.0f);
                _surfaceZ -= (_collision.size.x/2.0f);
            }
            else
            {
                _surfaceX -= (_collision.size.z/2.0f);
                _surfaceZ -= (_collision.size.z/2.0f);
            }


Ce dernier bout de script empêche l'objet de dépasser de la surface (en prenant en considération que le centre de l'objet se trouve au centre), utilisant soit la largeur soit la longueur dépendamment de quelle mesure est la plus grande. Ainsi, on utilise un seul paramètre pour les deux axes : X et Z.

J'aimerais vous revenir très bientôt avec plus de détail sur l'exercice. D'ici là, je vous dis à bientôt,

David


lundi 25 février 2019

Unity - Instance et position dans le monde

Partie 1

Bonjour à tous,

aujourd'hui, j'inspecte l'instanciation dans Unity afin de reproduire un projet que j'ai conçu manuellement sur 3DSMax. Souhaitant reproduire dans Unity, il s'agira de produire une ville procédurale qui sera façonnée automatiquement. Ceci permettra ainsi de se concentrer sur des effets de lumière. Je souhaite, au moment de la conception, avoir un environnement qui fait du sensé.


Mon plan pour la première partie :











Pour se repérer au départ j'ai installé un script dans mon Objet de GameConfig.
Il est nommé Building Spawn:


-Maison : placer l'objet avec collision qui sera Instancié. Dans ce cas-ci j'ai mis l'objet ci-haut.
- Surface : Terrain sur lequel les objets se trouveront.
Pour le moment, j'ai placé un Plane sur le monde et je l'ai mis dans ce paramètre.
- Maison Pos, Nbre Batiments, Nbre Batiments Actuel : Pourraient être privés, mais je les ai mis publics pour que vous ayez une idée de ce qui est analysé. Ces chiffres changeront à chaque update.
- Nbre Batiments Min, Nbre BatimentsMax : Comme les noms indiquent, ce sont les variations du nombres d'instances de l'objet dans Maison qui se retrouveront dans la scène.
- Collision : Automatiquement, à l'ouverture du jeu, le Box Collider* de l'objet dans le paramètre Maison sera attaché.

*Pas besoin d'un Mesh Collider puisque ce sont des objets très cubiques et l'analyse que Unity fait peut devenir un peu trop compliquée sur des collisions à géométrie complexe.

 1- Générer 1 objet ...

J'ai rapidement mis en place des bâtiments à la forme allongée qui doit avoir une boîte de collision (Box Collision) pour la suite.



C'est une étape qui se termine avec deux lignes de script :
    // Paramètre avec l'objet - Variable
    public GameObject _maison;

Pour l'instant, on peut mettre cette ligne dans la fonction On Start :
    //L'instanciation - FixedUpdate
    Instantiate(_maison);


2 et 3- ...sur la surface ET au hasard ...

Le produire SUR la surface s'avérera un étape supplémentaire qui n'a pas encore été abordée dans ce projet. Bien s'assurerque le centre de l'objet s'appuie au sol dans le logiciel de modélisation (au bas du BoxCollider). Pour tout le reste, il me faudra un article complet sur le sujet.

Autrement; nous passons à l'étape 3 : je désire générer l'objet au hasard, ailleurs qu'au centre du monde.

Vient l'utilité de Maison Pos :
         // créer les coordonnées - Variable
         public Vector3 _maisonPos;
         // dire que ce sont les mêmes coordonnées que l'objet dans Maison - On Start             
         _maison.transform.position = _maisonPos;


Pour l'instant, on peut mettre cette ligne dans la fonction On Start :
         // changer la position de l'objet maitre - Update
          _maisonPos.x = Random.Range(-5.0f, 5.0f);
          _maisonPos.z = Random.Range(-5.0f, 5.0f);
          _maison.transform.position = _maisonPos;

4-  ... et le multiplier...

Cette fois, nous auront toutes nos Variables - je les ai toutes mises publiques mais elle peuvent être privées (private) :

    public GameObject _maison;
    public GameObject _surface;
    public Vector3 _maisonPos;
    public int _nbreBatiments;
    public int _nbreBatimentsActuel = 0;
    public int _nbreBatimentsMin = 1;
    public int _nbreBatimentsMax = 10;
    public BoxCollider _collision;


   void Start()
    {
        //ajustés
        _nbreBatiments = Random.Range(_nbreBatimentsMin, _nbreBatimentsMax);
        _maison.transform.position = _maisonPos;
        _collision = _maison.GetComponent<BoxCollider> ();
       
        //créés
        var _maisonRot = _maison.transform.localRotation.eulerAngles;
       
    }

    void Update()
    {
       
        // Layer 10 et layer 9 ne se voient pas (collisions)
        _maison.layer = 10;
        _surface.layer = 10;
       
    }
    void FixedUpdate()
    {
        //Pour suivre le nombre de bâtiments (PEUT ÊTRE REMPLACÉ PAR UNE FOR LOOP)
        if ( _nbreBatimentsActuel < _nbreBatiments )
        {

                //isole et isole les collisions - layer 10 ne voit pas layer 9 - avant l'instanciation
                _maison.layer = 9;

                //L'instanciation
                Instantiate(_maison);
                

                //remet la layer - layer 9 voit la layer 9 - après l'instanciation
                _maison.layer = 10;
               
                //Ajout d'un token - Nombre de bâtiment (peut se faire en for loop)
                _nbreBatimentsActuel += 1;
               
               //changer la position de l'objet maitre
            _maisonPos.x = Random.Range(-5.0f, 5.0f);
            _maisonPos.z = Random.Range(-5.0f, 5.0f);
            _maison.transform.position = _maisonPos;
               
            //changer la rotation du même objet
            var _maisonRot = _maison.transform.rotation;
            _maison.transform.rotation = Quaternion.Euler(-90.0f, Random.Range(-90.0f, 90.0f), 0.0f);
           
        }
        else
        {
            print("Il y a " + _nbreBatiments + " bâtiments dont " + _nbreBatimentsActuel + " sont    apparus.");
        }
           
        //Éviter de laisser la maison à (0, 0, 0)   
        if (_maison.transform.position.x == 0.0f  & _maison.transform.position.z == 0.0f )
        {
            _maison.transform.position = _maisonPos;
        }
    }


J'ai déjà mis la variable de "_collision" ici pour préparer l'étape suivante. Ce qui est le plus important ici c'est l'ordre dans lequel nous procédons.

Nous verrons ici des problème de "groupes de maison interpénétrant" l'une dans l'autre.



Également, nous aurons un problème de rotation. En faisant un troubleshoot, j'ai remarqué que le système n'avait gardé que les rotation entre 0.3 et 0.7 dans la traduction en Quaternion, alors je vais rechercher pour une solution plus appropriée. Certains confirment la complexité des Quaternions dans leur réponse comme l'une des pistes ici envoyée par "rutter" :
https://answers.unity.com/questions/790877/random-rotation-in-z-axis.html

5- ... sans qu'ils ne se touchent.

Ici, nous répondons au problème des groupes de maisons avec un système utilisant les physiques "Physics.OverlapBox". Étant donné que la physique procède par moment de façon erratique avec la fonction d'Update, je me vois suggéré d'utiliser la fonction de FixedUpdate, environnement contrôlé pour la physique.

Bref, voici ce dont nous avons besoin de nouveau (et ou le placer entre les commentaires) :

        // [...]créés
        var _enableMeshCollider = _surface.GetComponent<MeshCollider>().enabled ;

        //var _maisonRot = _maison.transform.localRotation.eulerAngles; [...]


 
         //[...]if ( _nbreBatimentsActuel < _nbreBatiments )
        //{


            //mise à jour de la position de la COLLISION BOX
            _collision = _maison.GetComponent<BoxCollider> ();
           
            //création de la boite de collision (nécessite une COLLISION BOX)
            //Démarrer en confirmant les collisions

            var _verifCollision = Physics.OverlapBox ( (_collision.center + _maisonPos), (_collision.size), (_maison.transform.rotation));
           
            //Continuer en vérifiant les contacts des collisions
            if (_verifCollision.Length == 1 || _verifCollision.Length == 0)
            {


                print ("ce qui est écrit " + _verifCollision.Length + "- ne touche rien");

               //troubleshooting coordonnées de la hitbox
                print ("non-hit ET le centre X : " + (_collision.center.x + _maisonPos.x) + " ET la taille X : " + _collision.size.x + " ET le centre Z : " + (_collision.center.z + _maisonPos.z) + " ET la taille Z : " + _collision.size.z + " ET la rotation : " + _maison.transform.rotation);


                //isole et ferme les collisions - layer 10 par rapport à la layer 9 - avant l'instanciation
                //_maison.layer = 9;
[...]




               // [...]Ajout d'un token
               //_nbreBatimentsActuel += 1;
               
           
}
            else
            {
               
                print ("ce qui est écrit " + _verifCollision.Length );
                
//troubleshooting coordonnées de la hitbox
                print("hit build"
ET le centre X : " + (_collision.center.x + _maisonPos.x) + " ET la taille X : " + _collision.size.x + " ET le centre Z : " + (_collision.center.z + _maisonPos.z) + " ET la taille Z : " + _collision.size.z + " ET la rotation : " + _maison.transform.rotation);

               
            }


            //changer la position de l'objet maître
            _maisonPos.x = Random.Range(-5.0f, 5.0f);
[...]





             //[...]_maison.transform.rotation = Quaternion.Euler(-90.0f, Random.Range(-90.0f, 90.0f), 0.0f);
            
            //repositionner la collision
          
 _collision = _maison.GetComponent<BoxCollider> ();

        //}
       //else
[...]


Ce sont des étapes cruciales ou l'objet "Maison", qui sera instancié, parcourt l'environnement à la recherche d'un endroit ou rien n'est placé (collision).  









Les maisons se tiennent plus à distance mais semblent garder relativement le même angle

---

Retour

Utilisation de la loop avec "for" : Je crois que je dois transférer à la "for loop" plutôt que de continuer directement sur l'Update. Cela va me permettre d'isoler mes problèmes suivants.Imprécision de la vérification des Collisions : Notez que j'ai mis " if (_verifCollision.Length == 1 || _verifCollision.Length == 0)" comme condition pour créer une instance, ce qui signifie qu'il peut y avoir 1 collision. _verifCollision est la OverlapBox (Zone qui détecte des superpositions de Collisions). Si je mets uniquement "_verifCollision.Length == 0", ce qui signifie que l'Array des Collisions serait vide, je me retrouve avec une boucle infinie ou la recherche d'un endroit sans collision ne se termine jamais. Je suppose que le layer mask ne s'active pas au bon moment ou pas du tout. Je crois qu'il me faut mentionner à la OverlapBox quelle layer elle doit analyser. Elle semble mettre l'objet maître dans les collisions.

Rotations d'objet et d'OverlapBox différentes : À quelques endroits, les objets s'interpénètrent . Selon mes recherches, il s'agirait encore d'un problème lié aux rotations Quaternion vs Euler. Il est certain que je vais regarder cela de près pour la suite.

Partie 2 à venir...

Je vous souhaite la meilleure journée,
À bientôt. 

jeudi 31 janvier 2019

Unreal - Conception d'un noise procédural pour les normales

Bonjours à tous,

je me lance dans cette année 2019 avec comme initiative de développer mes talents de Unreal 4.

J'ai regardé un peu pour le sujet à aborder et j'ai constaté que si je veux me donner un défi, je peux me créer un modèle sans texture diffuse (ton de gris) et l'éclairer. Avant de commencer, je constate que le shader par défaut ne me convient pas et qu'il mérite des petits rajouts de détails. Habituellement, c'est dans la diffuse que nous rajoutons un noise de façon procédurale. Cette fois-ci j'ai préféré me tenter à l'expérience d'une normal map.
 



































































Noise normalisé dans un environnement non-linéaire

Avec une normalisation du noise, je reçoit ce type d'information:

Évidement le moindrement que vous regardez une map de normal,  vous constatez que celle-ci est faussée par des coordonnées du monde. Je ne peux que la restreindre à des informations qui imitent les tangeantes.

Comment faire? Voici :
- Je sépare mes trois canaux (RVB)
- J'isole mon Z (B) pour qu'il soit pure : n'oubliez pas qu'une normale, de base, possède un canal bleu 100% duquel nous soustrayons. Dans la tangeante, j'ai constaté qu'une valeur décente où cesser est lorsque le bleu est à 66% (deux tiers arrondis).
- Les autres couleurs (RV) seront mises à 50% : le but ici étant de produire un canevas "blanc pour la normale supplémentaire, bref:
- RÉSUMÉ - R:50% V: 50% B:100%

- On additionne le tout ensemble
La normale de base dans un environnement non-linéaire
- On se crée un LERP afin de jouer correctement avec l'intensité du "noise" (canal "Alpha" de la node).
- Je me permets de mettre le multiplicateur de chaque canal (RG) ainsi que l'intensité dans le LERP (alpha) en tant que paramètres pour la suite.

- L'alpha du LERP est primordial car c'est ce qui empêche la normale de présenter des information que j’appellerais World-based (en opposition à Tangent-based). Je le restreint entre 66% et 100% car c'est dans cette région que se situe l'écart qui nous est nécessaire (pour la tangente).






https://upload.wikimedia.org/wikipedia/commons/c/c4/Ripple_disc_norm.jpg
C'est grâce à ce disque ci-contre, trouvé sur le web, que j'ai pu confirmer mes restrictions.












Noise normalisé (tangente) dans un environnement non-linéaire
- Voici ce que le mélange donne à 66% d'alpha :


















- Voici le résultat sur une sphère 
- Une fois paramétré dans une instance, je l'intègre à un mur. Voici le résultat :


Avant
Après


Une image du Graph du "Normnoise"


Dôme avec le "normnoise"

 La prochaine étape sera de l'intégrer avec une autre normale sur l'objet. Cela signifie qu'il me faudra utiliser des canaux UV séparés.

D'ici là, je vous souhaite une bonne journée,

Vôtre,
David



Suite au Game Jam 2019


Salut à tous,

j'ai retardé mes posts puisque je voulais discuter d'un évènement auquel j'ai participé : le Global Game Jam 2019.

C'est mon 4e Game Jam et je suis toujours bien excité à l'idée de créer un jeu de A à Z en une fin de semaine.

Cette fois-ci comme auparavant, je me suis greffé à une équipe comportant des amis (à chaque fois c'est une surprise d'en rencontrer). L'équipe était constituée uniquement de programmeur; mais dans un game jam, on prend rapidement un autre poste.

C'est la première fois que je me retrouve à être l'unique artiste (dans l'officiel). Cela m'a créé de réels défis que même en "scoping" je n'aurais pu concevoir. Design de persos (2D et 3D), création de shaders, "rigging" et "skinning", modélisation, texture et animation. C'était un peu trop malgré qu'on me diminuait constamment la charge, soit en prenant une partie du boulot (merci à l'équipe) soit en coupant sur certaines fonctionnalités qui ne seraient plus visuelles.

Le Jeu

Il s'intitule Haunted(Not) House. Il est fait sur le thème de "Ce que signifie la maison pour vous" que l'équipe a reviré en : "Ce que vous signifiez pour la maison".

Vous jouez maintenant la maison qui se trouve a être "hantée" par vous même. Vous tentez de protéger la personne qui y habite. C'est un vieil homme qui se nomme Herméningilde.

De mon côté, il me fallait le rendre sympatique. Certain membre de l'équipe voulaient une sonorité "Wacky". J'ai alors pensé aux Munny; en me rétractant rapidement puisque nous voulions garder l'aspect amusant uniquement pour les animation. J'ai donc gardé l'idée des grandes têtes de Munny et j'y ai juxtaposé des corps plus élancés, un peu comme dans " Last Day of June".
Sketch
3D




















Inspiration principale : Last Day of June














Shader//Ergonomie

J'ai également tenté de produire un shader avec lequel nous pourrions montrer la progression de la crainte. Je voulais une donnée paramétrable sur les Y qui monte et descend créant une jauge et, visuellement, montrant une crainte grimpante.

Pour ainsi faire j'ai utilisé le Fresnel en modifiant le canal View vers le Monde. Faisant ainsi, il est possible de manipuler les dégradés du Fresnel pour qu'ils puisse partir du bas d'un axe et rejoindre le haut de celui-ci. Les nodes "add" sont ainsi rajoutées afin de créer les déplacements. La contrainte devient celle-ci: les réplacement se font tels des rotations puisque nous utilisons 3 canaux qui sont ajustés sur un objet dans les 3 dimensions!
Section 1 du canal d'émission : De l'oeil à l'objet

Quelques considération ont dû être prises lors de l'application du "Shader". D'un premier temps, l'objet est importé de Max vers Unity, ce qui crée une rotation de l'axe de Y sur les X de 90 degrés, ce qui déplace l'axe des Y et des Z (problème bien connu et dû à la fonction primitive de 3DSmax de travail en l'isométrique).



Comme on parle de normales de l'objet, il faut modifier le canal rouge (des X) car l'axe des Y tombe  tombe sur cet axe X.



Section 2 du canal d'émission : De la normale à l'objet


Afin de terminer la "shader", je paramètre les évènements afin de pouvoir appeler les propriétés sur les personnages. Plus il est stressé plus la démarquation va être élevée.





Animations


Évidement, je ne peux partir sans mentionner l'intégration d'animations ainsi que le rigging et skinning durant cette fin de semaine.

Le rigging est on ne plus simple : le bon vieux biped de 3DSMax, merci bonsoir!

Le skinning m'a simplement demandé de faire quelques loops additionnel sur un mesh très simple, ce qui m'a pris peu de temps.

Pour l'animation, il y avait deux personnages a faire avec 3 animations de base pour chacun (6 animations). Notre vieux monsieur tient une posture fière. Son opposant, Path le malfrat, se tient torse bombé tel que quelqu'un affrontant des peur bravement. Évidement, chacun a son animation de course suite à la crainte! C'est à ce moment là que je me suis amusé à leur faire des mouvement plus clownesque.

Herméningilde appeuré


En bref...


C'est énormément de poids sur les épaules être le seul artiste visuel d'une équipe lorsque tu es entouré de programmeur. J'ai donc dû laisser aller le level art pour que l'on puisse réellement se concentrer sur les 3 C : Characters, Camera, Controls.

J'ai tout de même mis ma touche lumineuse APRÈS que la fin de semaine soit terminée :




Sur ce, je vous laisse la suite pour un prochain post de blogue,

Vôtre,
David