mardi 10 avril 2012

Comment faire une caméra orbitale en Unity?

Aujourd’hui, nous allons apprendre à faire une caméra orbitale en Unity. Une caméra orbitale est une caméra qui orbite un objet choisi comme cible. La caméra est tournée au moyen de la souris en appuyant sur le bouton gauche. Les mouvements de la souris sont transformés en rotations de la caméra. La caméra tourne en fixant constamment la cible choisie. De plus, je veux que la caméra zoom et dé zoom à volonté. Comme le dicton dit: une image vaut mille mots et un vidéo vaut au moins 24 images par secondes (pour 24 000 mots/s!!!). Voici le résultat final de ce que je veux implémenter:
Unity Web Player | WebPlayer -->
Principe de bases Lorsque le bouton droit de la souris est pressé, je convertis le mouvement du curseur en degrés selon les coordonnées x et y. J’utilise ces angles pour indiquer la rotation de la caméra. La rotation de la caméra autour du château implique deux mouvements: la rotation de la caméra sur elle-même et son déplacement dans l’espace.


Rotation de la caméra sur elle-même
Commençons par réglé le cas de la rotation de la caméra sur elle-même. Bougeons la caméra d’un angle a:

Sur le diagramme, la caméra du bas est la caméra dans sa position initiale. La caméra du haut est la caméra qui a tourné d’un angle a. La caméra ne pointe plus sur la cible. Pour corriger, la caméra doit être bougé d’un angle b. Les angles a et b sont alternes-internes; par conséquent, a=b. Vive la géométrie!

Unity utilise les quaternions pour représenter les rotations. Les quaternions sont des nombres hyper complexes (c’est-à-dire une extension des nombres complexes). Pour ceux intéresser à approfondir leur connaissance en math, je recommande chaudement Khan Academy. Il n’est pas essentiel de connaître les quaternions pour les utiliser. Je le mentionne pour que vous pensiez de convertir vos angles en quaternion. Les rotations en degrés sont converties en quaternion comme suit (aide Unity pour Quaternion.Euler):
 
Quaternion rotation = Quaternion.Euler( a,b,0 );
où a et b sont les angles de rotation par rapport à l’axe des x et y respectivement. L’axe des x est l’horizontale, l’axe y la verticale et l’axe z est la profondeur. Pourquoi le nom Euler et non pas Angle comme nom de fonction? Il y a une fonction Angle dans Unity. Elle retourne l’angle minimal entre deux rotations. Ici, il faut entrer 3 angles. C’est Leonhard Euler qui a introduit l’utilisation des 3 angles pour décrire l’orientation d’un corps solide. Le nom est resté. La caméra est tournée en modifiant la propriété rotation du composant transform:
 
transform.rotation = rotation;
Deux commandes, et la caméra est tournée!

Déplacement de la caméra
La caméra est à une distance d la cible et doit maintenir cette distance lors de la rotation. La caméra tourne comme attacher à une corde fixée à la cible.

La position de la caméra est égale à la somme des vecteurs \( \vec{p} \) et \( \vec{r} \). Le vecteur \( \vec{p} \) est connu, c’est la position de la cible. La longueur du vecteur \( \vec{r} \) est d, la distance de la caméra à la cible. Le changement de direction du vecteur \( \vec{r} \) est donné par la rotation de la caméra. Posons \( Fr \) comme une fonction de la rotation. La position de la caméra est: \( Fr(\vec{r}) + \vec{p} \). Ok, mais c’est quoi \( Fr \)? Vous vous rappelez les quaternions? Et bien, une de leurs fantastiques propriétés est que lorsqu’ils sont multipliés avec un vecteur, ils retournent un vecteur ayant subit la bonne rotation. Posons \(  Q \) comme étant le quaternion. La position de la caméra est fourni par l’équation: \[  Q \vec{r} + \vec{p} \] Une multiplication suivi d’une addition. Le code C# pour la position de la caméra est:
 
transform.position = rotation * r + p;
La combinaison du code pour la rotation de la caméra sur elle-même et le code pour modifier la position est:
 
void Rotate( float x, float y )
 {
  Quaternion rotation = Quaternion.Euler(y,x,0.0f);

  Vector3 position = 
   rotation * _distanceVector + _target.position;

  transform.rotation = rotation;
  transform.position = position;
 }

Les contrôles
La rotation de la caméra est activée en pressant le bouton gauche de la souris. La méthode GetButton de la classe statique Input  est ce qu’il nous faut. Cette fonction accepte une chaîne de caractère en entrée qui détermine le type de bouton et retourne un booléen qui indique si le bouton a été pressée. La chaîne de caractère pour le bouton gauche de la souris est  Fire1 dans Unity (pour plus de détails voir ici). Le code pour détecter que le bouton gauche a été pressée est:

void RotateControls()
 {
  if ( Input.GetButton("Fire1") )
  {
  
  }
 }
Maintenant, la position du curseur détermine l’angle de rotation. Pour déterminer la position du curseur, la méthode GetAxis de la classe Input est utilisée. Cette fonction retourne le mouvement de la souris suivant un axe en pixel. L’axe doit être spécifié. Pour la souris, les axes sont nommés Mouse X et Mouse Y. Le code est inséré à l’intérieur du If comme suit:
 
void RotateControls()
 {
  if ( Input.GetButton("Fire1") )
  {
   _x += Input.GetAxis("Mouse X") * _xSpeed;
   _y += -Input.GetAxis("Mouse Y") * _ySpeed;

   this.Rotate(_x,_y);
  }
 }
L’appel à la fonction Rotate, vu précédemment, complète le code pour la rotation. 

Zoom
Pour le zoom, j’utilise la roulette de la souris. Pour déterminer si la roulette a été tournée, la méthode GetAxis est appelée en utilisant la chaîne de caractères: Mouse ScrollWheel. Selon la direction de rotation, j’incrémente ou je décrémente la distance de la caméra à la cible. Ensuite, j’appelle la fonction Rotate pour repositionner la caméra au bon endroit. 

Le code complet est fourni ci-dessous.

OrbitCamera.cs

   
using UnityEngine;
using System.Collections;

/**
 * Change the camera into an orbital camera. An orbital is a camera
 * that can be rotated and that will automatically reorient itself to
 * always point to the target.
 * 
 * The orbit camera allow zooming and dezooming with the mouse wheel.
 * 
 * By clicking the mouse and dragging on the screen, the camera is moved. 
 * The angle of rotation  correspond to the distance the cursor travelled. 
 *  
 * The camera will keep the angular position when the button is pressed. To
 * rotate more, simply repress the mouse button et move the cursor.
 *
 * This script must be added on a camera object.
 *
 * @author Mentalogicus
 * @date 11-2011
 */
public class OrbitCamera : MonoBehaviour
{
 
 //The target of the camera. The camera will always point to this object.
 public Transform _target;
 
 //The default distance of the camera from the target.
 public float _distance = 20.0f;
 
 //Control the speed of zooming and dezooming.
 public float _zoomStep = 1.0f;
 
 //The speed of the camera. Control how fast the camera will rotate.
 public float _xSpeed = 1f;
 public float _ySpeed = 1f;
 
 //The position of the cursor on the screen. Used to rotate the camera.
 private float _x = 0.0f;
 private float _y = 0.0f;
 
 //Distance vector. 
 private Vector3 _distanceVector;
 
 /**
  * Move the camera to its initial position.
  */
 void Start ()
 {
  _distanceVector = new Vector3(0.0f,0.0f,-_distance);
  
  Vector2 angles = this.transform.localEulerAngles;
  _x = angles.x;
  _y = angles.y;
    
  this.Rotate(_x, _y);
  
 }

 /**
  * Rotate the camera or zoom depending on the input of the player.
  */
 void LateUpdate()
 {
  if ( _target )
  {
   this.RotateControls();
   this.Zoom();
  }
 }
 
 /**
  * Rotate the camera when the first button of the mouse is pressed.
  * 
  */
 void RotateControls()
 {
  if ( Input.GetButton("Fire1") )
  {
   _x += Input.GetAxis("Mouse X") * _xSpeed;
   _y += -Input.GetAxis("Mouse Y")* _ySpeed;
     
   this.Rotate(_x,_y);
  }
 
 }
 
 /**
  * Transform the cursor mouvement in rotation and in a new position
  * for the camera.
  */
 void Rotate( float x, float y )
 {
  //Transform angle in degree in quaternion form used by Unity for rotation.
  Quaternion rotation = Quaternion.Euler(y,x,0.0f);
  
  //The new position is the target position + the distance vector of the camera
  //rotated at the specified angle.
  Vector3 position = rotation * _distanceVector + _target.position;
    
  //Update the rotation and position of the camera.
  transform.rotation = rotation;
  transform.position = position;
 }
 
 /**
  * Zoom or dezoom depending on the input of the mouse wheel.
  */
 void Zoom()
 {
  if ( Input.GetAxis("Mouse ScrollWheel") < 0.0f )
  {
   this.ZoomOut();
  }
  else if ( Input.GetAxis("Mouse ScrollWheel") > 0.0f )
  {
   this.ZoomIn();
  }

 }
 
 /**
  * Reduce the distance from the camera to the target and
  * update the position of the camera (with the Rotate function).
  */
 void ZoomIn()
 {
  _distance -= _zoomStep;
  _distanceVector = new Vector3(0.0f,0.0f,-_distance);
  this.Rotate(_x,_y);
 }
 
 /**
  * Increase the distance from the camera to the target and
  * update the position of the camera (with the Rotate function).
  */
 void ZoomOut()
 {
  _distance += _zoomStep;
  _distanceVector = new Vector3(0.0f,0.0f,-_distance);
  this.Rotate(_x,_y);
 }
 
} //End class

OrbitCamera.cs by Mentalogicus is licensed under a Creative Commons Attribution 3.0 Unported License.

12 commentaires:

  1. merci c tres interessent mais a chaque fois que j'integre le script sur la main camera il me sort "can't add script"

    RépondreSupprimer
    Réponses
    1. Ok, j'ai vérifier mon script dans Unity et il compile correctement. J'utilise Unity 3.5. J'ai essayer de reproduire ton erreur et la seule façon dont j'y suis parvenu est en nommant le script différemment qu'OrbitCamera.cs. Le nom du fichier doit correspondre au nom de la classe. Les espaces comptes. "OrbitCamera .cs" ou "Orbit Camera.cs" ne marchera pas et tu auras un message d'erreur. Si cela ne règle pas le problème, envoie moi le message complet du message d'erreur.

      Supprimer
  2. Mon message va peut etre faire tache, mais où devons-nous insérer le code que tu as écrit ?
    Merci je suis vraiment intéresser par ce genre de mouvement de caméra.

    RépondreSupprimer
    Réponses
    1. Le script doit être ajouter sur la caméra principale dans l'éditeur d'Unity. Bonne remarque! Je n'ai indiquer nulle part où placer ce script. Je vais ajouter une remarque dans le code.

      Supprimer
    2. Est ce que ce code marche sur la version 4.0 ?
      Il est obligatoire d'avoir un objet sur la scène pour que le code fonctionne car j'ai des erreurs de script lorsque je met le code sur la caméra ...

      Supprimer
  3. Très bon script, je peux juste pas modifier la vitesse .. J'ai beau mettre des milliers à la place de "1f", ca ne change absolument .. Help ?

    RépondreSupprimer
    Réponses
    1. Réponse un peu tardive mais c'est parce que les variables ne se mettent pas à jour lorsqu'on les modifie dans le code, il faut aller sur ton objet, cliquer droit sur ton script (là où y'a GUI Layer, Camera, Audio Listener etc.) et cliquer sur reset. ça remettra les valeurs inscrites dans le code :)

      Supprimer
  4. Thanks !! =) Great script !!

    RépondreSupprimer
  5. Merci beaucoup ! ça fait gagner du temps :)

    RépondreSupprimer
  6. Salut! Je fait un jeu mais je suis graphiste et je n'ai actuellement personne pour coder. Ducoup, je cherche des Scripts et le tien est PARFAIT! Le problème est que quand je l’insère, il ne marche pas! Je te remercie d'avance!

    RépondreSupprimer
  7. Ton script est parfait et marche très bien sur Unity5.5.0f3
    Est-il possible de limiter la taille du zoom car ça me fait rentrer à l'intérieur des objets.

    RépondreSupprimer
  8. Yop merci pour ton script.

    J'ai dû inverser les composantes dans cette ligne (pour suivre l'ordre naturel d'ailleurs) pourquoi utilises tu y, x et pas x, y ?
    Quaternion rotation = Quaternion.Euler(y,x,0.0f)

    RépondreSupprimer