Memento Unity

 0
 0
Tps de lecture estimé:7 minutes 50 secondes

Voici une liste des trucs et astuces pour Unity.
Ce fichier est un canevas qui s'étoffera au fur et à mesure de mes expérimentations sur ce moteur de jeu.

Memento Unity


Fondamentaux

Editeur

  • Différencier le "play mode" pour la scene courante
    • Menu > Edit > Préférence > Colors > PlayMode Tint
  • s'assurer d'un rendu correct pour les material PBR
    • Menu > projet settings > Player > Rendering > color space = Linear
  • TOP: faire du snapping sur angles des objet: sélectionner l'objet, maintenir la touche V, cliquer sur l'angle voulu et déplacer le curseur de la souris
  • TIP: il est possible d'afficher le contenu des variables privées dans l'inspecteur. pour cela il faut cliquer sur le menu de l'onglet "inspecteur" (icône à droite de l'icône "lock") et choisir "debug"

Assets

  • import FBX
    • le collider n'est pas générés par défaut lors d'un import de FBX. il faut l'activer dans les options d'importation.

C Sharp

  • TIP:pour masquer une variable publique dans l'inspecteur il suffit d'ajouter le tag "[HideInInspector]"
  • TIP:pour afficher une variable privée dans l'inspecteur il suffit d'ajouter le tag "[SerializedField]"
  • Appeler une méthode répétitivement sans boucle ni update
    {
    void Start () {
      InvokeRepeating("Instantiation", 0, Delay);
    }
    void Instantiation () {
      Instantiate(Prefab, SpawnLocation.position + Offset, Quaternion.identity);
      callCount++;
      if (callCount >= Number) {
          CancelInvoke();
      }
    }
    }

Materials

  • les "Forward Rendering Options" sont très gourmandes en ressource (comme les "Forward Rendering" en général). Il faut les désactiver sur mobile, et sur PC il peut être intéressant de ne les activer (par script)
    qu'à un certains niveau de LOD, ou quand on est proche de l'objet concerné.

Lighting

  • la "directional light" utilisée par défaut dans les réglages de la skybox (windows>lighting>settings) est celle qui à la plus forte intensité (ou la seule)
  • la couleur de la "directional light" utilisée dans les réglages de la skybox influence la couleur du soleil
  • le "culling mask" permet de sélectionner les layers impactés par la lumière
  • les "cookies" sont des textures appliquées à des light et permettant de projeter un masque
  • les "area light" sont uniquement visibles lors du baking et jamais en Realtime (ou dans la scene). Elles sont similaires à des "emissives objects" mais pour du baking uniquement.
  • diminuer les valeurs de "Indirect resolution" et "LightMap résolution" ((windows>lighting>settings) permet de diminuer le temps de baking pendant la phase de développement.
éclairages indirect (global illumination...)
  • seuls les éléments marqués "static" sont pris en compte dans les éclairages indirect (ou global illumination)
  • on utilise les "light probes" pour reproduire les effect de l'éclairage indirect sur les objet qui ne peuvent pas être "static" (le player par exemple)
  • la densité de light probe est fonction de l'intensité l'illumination indirecte de la scene et de l'importance de la zone pour les objets non "static": + de lumière ou + d'importance => + de probes dans la zone
reflexion probes
  • Unity ne reflète que la skybox sur les objets réfléchissants (ex: objet avec un "metallic material"). les "reflexions probes" permettent de réfléchir les autres éléments du décors ("static" uniquement en mode "bake", tous en mode "realtime")
  • les "reflexions probes" en "Realtime" sont extremement gourmandes en resources.
  • les "reflexions probes" ont aussi un effet sur les objet moins ou non réfléchissants en particulier sur les ombres, permettant de mieux les "poser" dans le décor.

Game Design

2D / UI

  • il est possible de créer des sprites "placeholder" via le menu assets>create>sprites
  • l'utilisation de renderTexture permet d'afficher un objet en 3D dans un canvas en 2D (par exemple dans un menu de sélection d'un personnage 3D)
Canvas
  • la taille du canvas est fixée par la résolution choisie dans la game view
  • par défaut, la taille des éléments d'un canvas ne VARIENT PAS AVEC la résolution de la game view. Pour changer cela, il faut modifier la propriété dans l'inspecteur: canvas>canvas scaler>scale with Screen Size
  • en modifiant la valeur du render Mode, on peut soit voir les éléments en 2D superposée (canvas>render Mode>"Screen Space - Overlay"), utile pour les élément de HUD ou bien en 3D intégrée (canvas>render Mode>"World Space"), utile pour les infos du gameplay (les "barres de vie" par exemple)

Animations

  • dans le composant "animator", cocher la case "Apply root motion" permet prendre en compte ou pas un déplacement induit par l'animation (déplacement du "root node" pendant l'animation, par ex lors de la course)
  • on peut ajouter des script de type "StateMachineBehaviour" sur des transitions dans un "Animator" pour déclencher des actions spécifiques (par exemple destruction du gameobject en fin d'animation)
  • pour créer une animation à partir d'une spritesheet, il suffit de glisser tous les sprites la composant sur la scene
  • l'utilisation du composant "animator" (utilisation des états et du blending) ajouté généralement par défaut lors de la création d'une animation peut être remplacé par un composant "animation", plus simple à gérer (pas de blending, pas de d'états)

TextMeshPro

  • ce "package" permet de créer des atlas de sprites à partir d'une police TTF et d'y appliquer des effets. Idéal pour la création de titres.

Game Play

Colliders
  • il est possible de mettre plusieurs colliders dans un gameobjet, par exemple 1 pour la gestion de la collision et un autre pour déclencher un évènement lors de son approche (isTrigger=true, plus large que l'objet)
  • IMPORTANT SleepMode: Par défaut un objet qui ne "bouge plus" (rapport énergie cinétique/masse < la valeur "Sleep Threshold" dans project settings>Physics) entre en sleep mode et ne déclenche plus de d'évènement de collision (OnCollisionXXX ou OnTriggerXXX).
    C'est une optimisation du moteur. Il est déconseille de mettre "Sleep Threshold" à 0. Par contre, on peut activer ou pas le sleep mode d'un objet par script.
RigidBody
  • drag = résistance de l'air au mouvement
  • Angular drag = résistance à la rotation du mouvement
  • isKinematic=OFF permet de suspendre les effets de la physiques, isKinematic=ON permet de les activer. Cela permet d'activer ou pas la physique via un script sans toucher au rigibody.
  • Interpolate permet de faire des interpolations du movement (lissage) en fonction de la frame précédente (Interpolate=Interpolate) ou de la totalité des frames (Interpolate=Extrapolate). Cela rend les déplacement plus réalistes.
  • Collision Detection
    • Discrete: permet de tester les collisions à chaque frame physique: moins gourmand en CPU mais peut manquer des collisions sur les mouvements rapides
    • Continuous: permet de tester les collisions à chaque frame: moins gourmand en CPU mais peut manquer des collisions sur les mouvements très rapides
    • Continuous Dynamic: le plus gourmand en CPU, pas d'erreur sur la détection des collisions sur les mouvements très rapides

Tips

  • les "joints" du moteur physique peuvent être utilisé pour similer des mouvements, plutôt que de le faire via un script: rotation de porte, pont basculant avec rebond, tourniquets...

Quelques exemples

  • un simple "player controler 2D" horizontal

    using System;
    using UnityEngine;
    [RequireComponent(typeof(Rigidbody2D))]
    [RequireComponent(typeof(Collider2D))]
    public class PlayerController : MonoBehaviour {
      public float Speed = 10f;
      public float BoundX = 10f;
      public float ReloadTime = 0.2f;
      public GameObject ProjectilePrefab;
      public GameObject ExplosionPrefab;
      private float lastTimeShot = 0;
      private Rigidbody2D lRigidbody;
      private void Start () {
          lRigidbody = GetComponent<Rigidbody2D>();
          if (ProjectilePrefab == null) {
              Log.Warning("ProjectilePrefab is not defined");
          }
    
          if (ExplosionPrefab == null) {
              Log.Warning("ExplosionPrefab is not defined");
          }
      }
      private void FixedUpdate () {
          // player mouvement
          var speedFactor = Speed * Time.deltaTime;
          var posX = transform.position.x + Input.GetAxis("Horizontal") * speedFactor;
          var posY = transform.position.y;
          // clamp x value amount the BoundX property
          posX = Mathf.Clamp(posX, -BoundX, BoundX);
          lRigidbody.MovePosition(new Vector2(posX, posY));
          // player fire
          if (ProjectilePrefab != null) {
              if (Input.GetButtonDown("Fire1") && (Time.time - lastTimeShot > ReloadTime)) {
                  FireProjectile();
                  lastTimeShot = Time.time;
              }
          }
      }
      public void FireProjectile () {
          print(name + " fires a " + ProjectilePrefab.name);
          Instantiate(ProjectilePrefab, transform.position, transform.rotation);
      }
      public void Hit (Vector3 hitPosition) {
          if (ExplosionPrefab != null) {
              print(name + " explodes with a " + ExplosionPrefab.name);
    
              Instantiate(ExplosionPrefab, hitPosition, Quaternion.identity);
          }
      }
    }
  • un simple "projectile controler 2D" vertical

    using UnityEngine;
    [RequireComponent(typeof(Rigidbody2D))]
    [RequireComponent(typeof(Collider2D))]
    public class ProjectileController : MonoBehaviour {
        public float Speed = 10f;
        public float TimeBeforeDestruction = 10f;
        private Rigidbody2D lRigidbody;
        private void Start () {
            lRigidbody = GetComponent<Rigidbody2D>();
            Destroy(gameObject, TimeBeforeDestruction);
        }
        private void FixedUpdate () {
            var speedFactor = Speed * Time.deltaTime;
            lRigidbody.MovePosition(new Vector2(transform.position.x, transform.position.y + speedFactor));
        }
        private void OnTriggerEnter2D (Collider2D other) {
            if (other.CompareTag("Player") && gameObject.name.Contains("Enemy")) {
                print("Player is Damaged");
                other.GetComponent<PlayerController>().Hit(transform.position);
                Destroy(gameObject);
            }
            else if (other.CompareTag("Enemy") && gameObject.name.Contains("Player")) {
                print("Enemy is Damaged");
                EnemyController controller = other.GetComponent<EnemyController>();
                if (controller == null) {
                    EnemyControllerSmart controller = other.GetComponent<EnemyControllerSmart>();
                    if (controller != null) controller.Hit(transform.position);
                }
                else {
                    controller.Hit(transform.position);
                }
                Destroy(gameObject);
            }
        }
    }
  • utilisation d'un raycast pour détecter un object devant le player
    var ray = new Ray(transform.position + new Vector3(0f, playerCollider.center.y, 0f), transform.forward);
    Debug.DrawRay(ray.origin, ray.direction * rayDistance, Color.red);
    RaycastHit hit;
    if(Physics.Raycast(ray, out hit))  {
      if(hit.distance < rayDistance)      {
          if(hit.collider.gameObject.tag == "Enemy")          {
              print("There is an enemy ahead!");
          }
      }
    }

Article précédent Article suivant