Affichage des articles dont le libellé est C#. Afficher tous les articles
Affichage des articles dont le libellé est C#. Afficher tous les articles

lundi 7 mai 2012

Truc pour organiser le code C#

Le C# (et aussi le C++) offre une instruction de précompilation fort utile pour organiser le code: #region. Cette commande spécifie un bloc de code que vous pouvez réduire ou développer en cliquant sur le - ou + à la gauche de la page d'édition. Une région se termine par la commande #endregion. Voici un exemple dans MonoDevelop:
Cliquer sur l'image pour l'agrandir.
Une région Logical Operators a été créée avec les commandes #region et #endregion. Un petit symbole - entouré d'un carré est à la gauche de la commande #region et permet de réduire ce bloc de code. Une fois réduit, le code ressemble à ceci:
Cliquer sur l'image pour l'agrandir.
Les fonctions à l'intérieur de la région ont disparu et sont remplacées par une étiquette indiquant Logical Operators. MonoDevelop ajoute automatiquement dans le haut à droite une liste des régions définies par l'utilisateur.
La liste des régions.
En cliquant sur un élément de la liste, MonoDevelop place le curseur au début de la région. Très utile pour naviguer rapidement dans le code. De plus, MonoDevelop ajoute les régions dans le Document Outline et regroupe les fonctions de chaque région.

Il est possible d'imbriqués les régions. Par exemple, ajoutons une région Bidon qui entoure la région Logical Operators:
Cliquer sur l'image pour l'agrandir.
Deux icônes d'offuscation apparaissent comme attendu. Par contre, voici deux comportements inattendus et désagréables:
  1.  La liste des régions n'est pas imbriquée. La région Bidon apparaît après la région Logical Operators!
  2. Dans le Document Outline, la région Logical Operators a été remplacée par la région Bidon
MonoDevelop ne semble pas supporter complètement les régions imbriquées. Je ne sais pas quand le problème sera réglé. Si quelqu'un est au courant du développement de MonoDevelop et connaît la réponse, faites-le-moi savoir.

vendredi 4 mai 2012

Comment documenter les scripts dans Unity?

Les commentaires dans les scripts sont importants pour expliquer leur fonctionnement. L'utilisation des commentaires varie d'une école de pensée à une autre. Certains prônent l'utilisation massive de commentaires pour expliquer les intentions du code. D'autres croient que le code devrait être auto explicatif et libre de commentaires. Ma position sur le sujet est mitoyenne. J'utilise des noms de fonctions et de variables évocatrices, claires et indiquant leur fonction le plus souvent possible. Malgré tout, si je ne commente pas régulièrement, j'ai de la difficulté à saisir mon propre code après quelques mois. Alors, je documente.

L'ajout d'un commentaire se fait en ajoutant deux barres obliques "//":
//Un commentaire vraiment inutile.
string action = "Commente moi!"
ou pour un bloc de commentaires, avec "/*" et "*/":
/*
Il était une fois, dans l'ouest, une princesse prisonnière
dans une taverne miteuse gardée par des truands à la peau
tannée par le soleil et au regard sauvage, etc., etc., etc.
*/
string action = "Commente, mais commente égal!"
et aussi avec "///":
///Parce que // fait tellement 1990.
string action = "Commente moi!"

Dans MonoDevelop, si vous tapez "///" avant une entité (une variable, une fonction, une classe, etc.) vous obtenez:
/// 
/// Du XML???
/// 
private string action = "Commente moi!" 
MonoDevelop crée un commentaire avec un tag XML. Ce code XML est utilisé par un programme externe pour générer automatiquement la documentation. Les générateurs de documentation sont des programmes qui lisent le code source et les commentaires, l'analysent et retournent une documentation en format texte, HTML, XML, Latex, etc. qu'il est possible de consulter et d'effectuer des recherches. C'est très pratique pour se retrouver dans un gros projet avec des centaines de scripts. Mon générateur de documentation favori est Doxygen. Je vais parler de Doxygen dans un prochain billet.

L'utilisation du système de documentation avec l'XML amène un autre énorme avantage, en plus de la génération automatique de la documentation. Si vous glisser votre souris au-dessus d'une entité du code (une variable, une fonction, un enum, etc.), une info bulle jaune apparaît qui ressemble à ceci:

L'information de cet encadré n'est pas très utile. Nous apprenons que _selected est un booléen. Avec l'ajout d'un commentaire XML, voici le résultat:
Notre commentaire apparaît dans l'info bulle jaune! Cette description va apparaître si le curseur glisse au-dessus de la variable _selected quel que soit sa position dans le code. Si une variable publique est commentée, la description va aussi apparaître si la variable est utilisée dans une autre classe. Ce petit encadré peut sauver du temps. Imaginer que vous êtes dans le code. Vous travaillez sur une fonction, mais vous ne vous souvenez plus de l'intention d'une fonction ou de ce que stocke une variable. Normalement, vous naviguez dans le code, allez à la fonction ou à la variable, lisez la description et revenez à votre fonction. C'est long et inutile. Avec les commentaires XML, glisser le curseur sur la variable ou la fonction mystérieuse et voilà la description (si bien sur vous l'avez documentée).

Les commentaires en XML sont plus longs que les commentaires normaux, mais je recommande de les utiliser systématiquement pour permettre de générer la doc automatiquement et pour les info bulles. La longueur des commentaires n'est pas un problème réel avec les éditeurs modernes. Mono Develop permet de cacher les commentaires en appuyant sur le symbole +:
Avant.
Après.
Peu importe la longueur du commentaire, une fois refermé, il ne prendra qu'une ligne, vous permettant de lire le code sans obstruction.

lundi 16 avril 2012

Comment faire des captures d'images en Unity?

Vous avez ce jeu magnifique ou ce superbe effet et vous voulez le présenter au monde entier. Vous démarrez votre jeu et pressez la touche Print Screen (en mode Windowed, sinon l'image est noire). Vous allez dans Photoshop ou, comme moi dans GIMP et sélectionner l'écran de jeu et vous le copiez dans une nouvelle image. Vous avez votre capture d'écran. Jusqu'à maintenant, c'est la procédure que j'utilisais à défaut de connaître mieux.

Aujourd'hui, j'ai appris une commande magnifique: Application.CaptureScreen. Cette fonction prend une capture d'écran directement du jeu! Fini les manipulations dans GIMP! La fonction requière un argument obligatoire, le chemin d'accès au fichier comprenant le nom du fichier et la taille de l'image. La taille du fichier est un entier qui détermine combien de fois plus grande sera l'image. Si j'entre 3, la capture d'image sera 3 fois la taille de l'écran!

Voici le script que j'ai utilisé:
using UnityEngine;
using System.Collections;

public class ScreenShot : MonoBehaviour 
{

 // Update is called once per frame
 void Update () 
 {
  if ( Input.GetKeyUp( KeyCode.F3 ) )
  {
   Debug.Log("Screen captured.");
    Application.CaptureScreenshot("Screenshot.png"); 
  }
 }
}
et le résultat (j'ai utilisé le Prefab Big Tree dans Standard Asset/Tree Creator pour l'arbre):


J’utilise la touche F3 pour déclencher la capture d'image. La question maintenant: où est enregistrée l'image? Lors de l’exécution dans l'éditeur d'Unity, l'image est enregistrée dans le répertoire du projet. C'est le répertoire qui contient le répertorie Assets. Pour l'exécutable, c'est différent. La compilation du projet crée un .exe et un dossier nommé nom_du_exe_data. Par exemple, si mon .exe se nomme screenshot.exe, le répertoire sera screenshot_data. Ce dossier est le répertoire de base et c'est l'endroit où les captures d'images sont enregistrées. La fonction ne fait rien si déclencher à partir du lecteur Web.

vendredi 13 avril 2012

Comment ajouter un script n'importe où dans le menu Component d'Unity?

Par défaut, Unity ajoute les scripts dans le menu Component->Scripts. Ce sous-menu se remplit rapidement avec l'ajout de scripts et devient difficile à utiliser. Un peu de classification serait le bienvenu. La solution est de créer ses propres sous-menus dans le menu Component. Pour ce faire, l'attribut AddComponentMenu est ajouté avant la déclaration de la classe avec comme argument une chaîne de caractère contenant le nom du sous-menu suivi du nom du script:
[AddComponentMenu("SousMenuName/SousSousMenuName/NomDuScript")]
Reprenons le script RandomObjectsCreator.cs utilisé pour instancier des prefabs lorsque la touche "Espace" est pressée. Je veux que ce script apparaisse dans le menu Component->Mentalogicus->Creator. J'ajoute l'attribut AddComponentMenu de la manière suivante:
using UnityEngine;
using System.Collections;

[AddComponentMenu("Mentalogicus/Creator/RandomObjectsCreator")]
public class RandomObjectsCreator : MonoBehaviour 
{
 public Transform _prefab;
 public float _radius = 10;
 
 // Update is called once per frame
 void Update () 
 {
  if ( Input.GetKey("space") )
  {
   //Create a random position.
   //The insideUnitSphere return a random 
   //position inside a sphere of radius 1.
   Vector3 rndPos = Random.insideUnitSphere * _radius;
   
   //Create a random rotation.
   Quaternion rndRotation = Random.rotation;
   
   //Instantiate a new object at a random 
   //position with a random rotation.
   Transform newGameObj = Instantiate( _prefab, 
    rndPos, rndRotation) as Transform; 
  }
 }
}

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

Unity compile le script et le menu apparaît à l'endroit désiré!

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.

dimanche 4 mars 2012

Les tableaux en C#

En programmation, les tableaux ou Array en anglais sont des outils puissants. Un tableau contient plusieurs valeurs qui sont accessibles par un index.

Pour faire une analogie, prenons une rue avec des maisons. Les maisons sont les espaces mémoire. Il est possible de nommer chaque maison par un nom, disons, celui de son propriétaire:
Le nom assigné à chaque maison est une variable dans un programme. En utilisant le nom de la maison, je peux accéder à la maison et savoir ce qu'il y a dedans. Dans le cas présent, nous allons y mettre des humains. La variable va contenir le nombre d'humains dans chaque maison. Je ne parle pas encore de tableau, j'y viens plus tard. Pour l'instant, je n'ai pas indiqué le nombre de personnes dans chaque maison. Je dois assigner un nombre de personnes pour chaque maison. Le pseudo-code est:
  
 Rambo = 1;
 Lili = 1;
 John = 1;
 Ada = 1;
 Linus = 1;
 Steve = 1;
C'est assez long! Imaginer maintenant qu'il y a une centaine de personnes sur cette rue. Toutes les maisons sont occupées par des célibataires, donc, une personne par maison. Vous allez devoir manuellement indiquer qu'il y a une personne dans chaque maison! Ce n'est pas très pratique. Heureusement, il y a les tableaux.

Reprenons notre exemple. Nommons la rue Tapaga. La rue est une analogie pour un tableau. Une rue contient des maisons. De même, un tableau contient une liste d'espace mémoire. Chaque maison a une adresse qui est l'équivalent de l'index du tableau.
Pour accéder à une maison, je dis 0 Rue Tabaga, 1 rue Tabaga, etc. Pour accéder à un tableau en C#, la syntaxe est:
 Rambo = Tabaga[0];
Je mémorise la valeur de l'index 0 du tableau Tabaga dans la variable Rambo. Remarquer que les caractères [] sont utilisés au lieu des parenthèses habituelles utilisées en mathématique. En mathématique, pour accéder à un élément d'un vecteur, la notation est a(1), a(2), etc. En C# et pour tous les langages de la famille du C, les crochets sont utilisés. En Fortran, la notation mathématique est utilisée.

Le premier élément d'un tableau est à l'index 0 et non 1. Donc, si je veux accéder au dixième élément d'un tableau, l'index à utiliser est 9 et non dix. Le code est:
 Rambo = Tabaga[9];
La question que vous vous posez sûrement est: pourquoi les concepteurs du C (l'ancêtre du C++ et du C#) n'ont-ils pas commencé les tableaux à 1, comme dans le vrai monde (ou comme en Fortran). Les éléments d'un tableau C sont enregistrés dans des espaces mémoire contigus. En sachant l'adresse du premier élément, les autres éléments sont atteints en additionnant l'index du tableau. Ainsi, le tableau n'a pas besoin de mémoriser l'adresse mémoire de tous les éléments, mais seulement du premier, sauvant de la mémoire. Un index en C est un décalage par rapport au premier élément du tableau. Le tableau contient l'adresse du premier élément du tableau. Disons que l'adresse du premier élément est 987. L'adresse du premier élément est: 987 + 0, le second est 987 + 1, le troisième est 987 + 2, le nième est 987 + i où i est l'index du tableau.

L'écrire dans un tableau est l'inverse de la lecture. Il suffit de mettre le tableau à gauche de l'égalité. Le code est:
 
 Tabaga[1] = 1;
Il est désormais possible de dire que chaque maison de la rue Tabaga possède une seule personne. En C#, le code est:
for each maison in Tabaga
{
 maison = 1;
} 
L'instruction for each est une boucle. Une boucle répète l'action entre les {} plusieurs fois. Dans le cas d'une boucle for each, la boucle se répète tant qu'il y a des éléments dans Tabaga. maison est une variable de la boucle for each avec l'adresse d'une maison de la rue Tabaga. En assignant 1 à maison, j'assigne 1 à un élément de Tabaga. La boucle visitant toutes les maisons, le résultat final est que chaque maison à désormais 1 d'assigné. Une boucle est un peu comme le facteur. Le facteur visite chaque maison une à la fois et délivre le courrier en fonction de l'adresse de la maison.

Les boucles démontrent la puissance de la notation par tableau. La boucle initialisera tous les éléments de Tabaga à 1, peu importe la taille de Tabaga. Donc, si le nombre de maisons sur Tabaga change, le code, lui, reste le même! Il n'y a rien à changer!

Bon, maintenant que nous avons expliqué les tableaux, il est temps d'apprendre la notation pour les créer en C#. Pour créer un tableau unidimensionnel d'entier en C#, la notation est:
 int[] Tabaga;
 Tabaga = new int[5];
ou en une seule instruction:
 int[] Tabaga = new int[5];
Ici encore, le C# est contre-intuitif. La notation la plus naturelle serait:
 int Tabaga(5) = new int(5);
Pour les admirateurs de cette notation, sachez que Visual Basic ressemble un peu à cela. De même que Fortran.

Mettons-nous dans la perspective du C#. Une variable est déclarée en donnant son type et le nom de la variable. Le type est ce que la variable contient comme int, double, float ou un objet comme Vector3 ou Person, etc. Maintenant, je veux déclarer un tableau de int. Le type de la variable est donc int[] où [] représente un tableau. Dans ce cas, il est logique que le int[] soit en premier et la notation d'un tableau est compatible avec celle des variables.

L'instruction:
 int[] Tabaga;
crée un pointeur à un tableau de int. Un pointeur est une variable qui contient une adresse mémoire qui, elle, contient des données. Dans le cas présent, le pointeur ne contient pas d'adresse. Pour revenir à notre analogie, Tabaga est une rue vide, sans maison. La rue est vide, mais je peux construire des maisons par la suite.

Analysons l'instruction suivante:
 Tabaga = new int[5];
L'instruction new indique aux programmes de créer un tableau de int contenant 5 éléments. new crée l'espace mémoire nécessaire au tableau et retourne l'adresse mémoire qui est enregistrée dans Tabaga. Pour revenir à notre analogie, la commande new construit des maisons dans notre rue.

Si vous avez d'autres questions sur les tableaux en C#, n'hésitez pas à me le demander.

jeudi 17 novembre 2011

Comment sauver et ouvrir les préférences du joueur dans Unity?

Il existe deux méthodes dont je connais l'existence pour enregistrer des données dans Unity:
  • la classe PlayerPrefs de Unity;
  • le XML.
PlayerPrefs est idéal pour enregistrer une petite quantité de données. La taille maximale permise est de 1 MO. Le site d'Unity pour cette classe est:
http://unity3d.com/support/documentation/ScriptReference/PlayerPrefs.html .
Le XML est plus versatile que PlayerPrefs. Vous pouvez enregistrer la quantité d’informations que vous voulez avec le format que vous voulez. Je vous réfère à ce site si vous voulez plus d'information sur le XML avec Unity:
http://answers.unity3d.com/questions/8480/how-to-scrip-a-saveload-game-option.html .

Dans ce tutoriel, je vais me concentrer uniquement sur PlayerPrefs. Cette classe offre trois fonctions pour mémoriser des données: SetFloat, SetInt et SetString et trois fonctions pour lire: GetFloat, GetInt et GetString. Disons que dans notre jeu, nous voulons sauver le nom du joueur qui est stocké dans la variable playerName, le code C# sera:
 PlayerPrefs.SetString( "PlayerName", playerName );
Le premier paramètre est la clé qui va permettre de retrouver la valeur et le second, la variable que nous voulons enregistrer. PlayerPrefs est un dictionnaire. Nous lui fournissons une valeur et une clé. Pour lire le nom du joueur, le code est simplement:
 playerName  = PlayerPrefs.GetString( "PlayerName");
Il n'y a rien de plus à faire. Unity se charge automatiquement d'enregistrer dans un fichier les valeurs de PlayersPrefs lors de la fermeture du jeu, que ce soit sur Windows ou en mode Web. Par contre, il est possible de forcer l'enregistrement à l'aide de la fonction:
 PlayerPrefs.Save();
Passons à un exemple concret pour voir comment appliquer PlayerPrefs dans une application. Voici un simple cube avec un bouton qui change sa couleur aléatoirement:
Unity Web Player | WebPlayer
-->

Le bouton change la couleur du cube. Si vous rafraîchissez la fenêtre, le cube réapparaît avec la dernière couleur choisie. Voici le script utilisé:
 
using UnityEngine;
using System.Collections;

/**
 * Change the color of a cube and save the data using PlayerPrefs.
 *
 * @author Mentalogicus
 * @date 11-2011
 * 
 * Copyright (C) 2011, Mentalogicus
 * All rights reserved
 * 
 */
public class StatsMenu : MonoBehaviour 
{
 //The current color of the cube.
 Color _current = new Color(1,1,1,1);

 // Use this for initialization
 void Start () 
 { 
  //Load the saved color.
  Load();
  
  //Change the shader to transparent diffuse.
  this.renderer.material.shader = Shader.Find("Transparent/Diffuse");
  
  //Apply the current color to our cube.
  this.renderer.material.color = _current;
 }
 
 /**
  * Show a button to change the color and 4 labels to show
  * the color RGBA component.
  */
 void OnGUI ()
 {
  GUI.BeginGroup( new Rect(50,50, 200,150) );
  GUI.Box( new Rect(0,0, 200,150), "Change Color" );
   
  if ( GUI.Button( new Rect( 10,25,100, 20), "Change Color" ) )
  {
   ChangeColor();
   AutoSave();
  }
  
  ShowColor();
  
  GUI.EndGroup();
 }
 
 /**
  * Show the current color used on your cube.
  */
 void ShowColor()
 {
  GUI.Label( new Rect( 10, 50, 150,20), "Red: " + _current.r );
  GUI.Label( new Rect( 10, 75, 150,20), "Green: " + _current.g );
  GUI.Label( new Rect( 10, 100, 150,20), "Blue: " + _current.b );
  GUI.Label( new Rect( 10, 125, 150,20), "Alpha: " + _current.a );
 }
 
 /**
  * Change the color to a random one.
  */
 void ChangeColor()
 {
  _current = new Color( Random.value, Random.value, Random.value, Random.value );
   
  this.renderer.material.color = _current;
 }
 
 /**
  * Save the color with the PlayerPrefs class.
  */
 void AutoSave()
 {
  PlayerPrefs.SetFloat( "Color_Red", _current.r );
  PlayerPrefs.SetFloat( "Color_Green", _current.g);
  PlayerPrefs.SetFloat( "Color_Blue", _current.b );
  PlayerPrefs.SetFloat( "Color_Alpha", _current.a );
 }
 
 /**
  * Load the color with the PlayerPrefs class.
  */
 void Load()
 {
  if ( PlayerPrefs.HasKey("Color_Red") )
  {
   _current.r = PlayerPrefs.GetFloat( "Color_Red" );
  }
  
  if ( PlayerPrefs.HasKey("Color_Green") )
  {
   _current.g = PlayerPrefs.GetFloat( "Color_Green");
  }
  
  if ( PlayerPrefs.HasKey("Color_Blue") )
  {
   _current.b = PlayerPrefs.GetFloat( "Color_Blue" );
  }
  
  if ( PlayerPrefs.HasKey("Color_Alpha") )
  {
   _current.a = PlayerPrefs.GetFloat( "Color_Alpha" );
  }
 }
 
}

Ce script est appliqué sur le cube. La classe PlayerPrefs est limitée dans ces capacités. Heureusement, il existe une extension nommée PlayerPrefsX qui étend les capacités de PlayerPrefs. Ce script est à l'adresse suivante:
http://www.unifycommunity.com/wiki/index.php?title=ArrayPrefs#C.23_-_PlayerPrefsX.cs .

Bonne programmation!

lundi 14 novembre 2011

Comment instancier des objets dans Unity 3D?

Unity 3D est un engin de jeux complets avec un éditeur. Si Unity vous est totalement inconnu, je vous recommande de visiter leur site: http://unity3d.com/. Ce tutoriel assume que vous avez déjà un peu joué avec Unity 3D et que vous êtes familier avec l'interface. Les scripts sont en C#. Le but du tutoriel est d'apprendre à instancier des objets durant l'exécution du jeu et non dans l'éditeur.
L'éditeur d'Unity.
La première étape consiste à créer un nouveau projet dans Unity. Je l'ai appelé "RandomObjectsCreator". À la création du projet, Unity crée les répertoires Assets, Library et Temp. Le répertoire Assets sert de librairie pour les objets que nous ajoutons aux jeux. C'est le seul des trois répertoires que nous allons utiliser.

L'instanciation d'objet dans Unity se fait à l'aide de prefabs. Prefab est la version raccourcie de Préfabriquée. Le prefab est l'équivalent d'un plan de construction. L'objet a telle forme, telle couleur et va réagir de telle et telle façon dans telles et telles conditions. Une fois le prefab créé, il est possible de créer des milliers de versions du même objet à l'intérieur du jeu et chaque objet aura la forme et le comportement spécifié dans le prefab.

Pour créer un prefab, aller dans l'onglet Projet->click droit->Create->Prefab. Renommer ce prefab "Cube".
Menu pour la création d'un prefab.
L'icône du prefab est une boîte blanche vide. Cette icône indique que le prefab est vide. Pour le remplir, nous allons ajouter un cube à la scène. Dans le menu supérieur: GameObject->Create Other->Cube.
Création d'un cube dans Unity.
Dans le menu "Hierarchy", sélectionnez Cube et glissez le sur le prefab Cube. L'icône du prefab devient une boîte bleue, indiquant que le prefab contient un objet. Vous pouvez maintenant effacer le Cube de la scène. Si vous glissez le prefab Cube sur la scène, un cube apparaît! Magika!
Ajout d'un objet au prefab.
Il est temps de créer notre script. Dans le tab "Project", click droit->Create->C# Script. Nommons-le: "RandomObjectsCreator". En double cliquant sur le script, l'éditeur par défaut de Unity ouvre le script. Dans mon cas, c'est MonoDevelop qui est ouvert. Unity a créé automatiquement une classe nommée RandomObjectsCreator avec la fonction Start et la function Update:

using UnityEngine;
using System.Collections;

public class RandomObjectsCreator : MonoBehaviour 
{

 // Use this for initialization
 void Start () 
 {
 
 }
 
 // Update is called once per frame
 void Update () 
 {
 
 }
}

La classe hérite de la classe générique MonoBehavior. Les scripts attachés sur des objets dans la scène doivent obligatoirement hériter de MonoBehavior pour fonctionner. La classe MonoBehavior permet d'accéder aux propriétés et aux composants de l'objet auquel est attaché le script.

Le code pour instancier un prefab est:
Instantiate( _prefab, position, rotation);
La fonction Instantiate va créer un clone du prefab contenu dans la variable _prefab, l'afficher dans la scène à la position "position" avec la rotation "rotation" et va retourner une référence de l'objet avec le type Object. Le type Object est le type de base en C#. Dans le précédent code, nous n'avons pas mis de variable pour récupérer la référence à l'objet. Pour ce faire, le code sera:
Transform newGameObj = 
     Instantiate( prefab, position, rotation) as Transform; 
Transform est un type générique d'Unity qui permet d'accéder à toutes les propriétés de l'objet de la scène. Le as Transform est un casting. Instantiate retourne une référence de type Object. Or, nous voulons une référence de type Transform. Nous indiquons la transformation de la référence de type Object en une référence de type Transform avec le as Transform. Il est à remarquer que cette notation est identique à la suivante:
Transform newGameObj = 
     (Transform)Instantiate( _prefab, position, rotation); 
Personnellement, je préfère le as, car c'est plus proche de l'anglais que l'opérateur de casting avec parenthèse.

Pour faire apparaître un cube, le joueur doit peser sur la touche espace. Le code est entré dans la fonction Update qui est appelée chaque fois que l'écran est rafraichi. La commande nécessaire est:
Input.GetKey("space");
Cette fonction retourne vrai si la touche espace est enfoncée et faux autrement. La position de notre cube sera choisi aléatoirement dans une sphère d'un rayon déterminé par la variable _radius:
Vector3 rndPos = Random.insideUnitSphere * _radius;
et avec une rotation aléatoire:
Quaternion rndRotation = Random.rotation;
Le script complet est le suivant:
using UnityEngine;
using System.Collections;

public class RandomObjectsCreator : MonoBehaviour 
{
 public Transform _prefab;
 public float _radius = 10;
 
 // Update is called once per frame
 void Update () 
 {
  if ( Input.GetKey("space") )
  {
   //Create a random position.
   //The insideUnitSphere return a random position inside a sphere of radius 1.
   Vector3 rndPos = Random.insideUnitSphere * _radius;
   
   //Create a random rotation.
   Quaternion rndRotation = Random.rotation;
   
   //Instantiate a new object at a random position with a random rotation.
   Transform newGameObj = Instantiate( _prefab, rndPos, rndRotation) as Transform; 
  }
 }
}

Ce script doit être attaché à un GameObject dans la scène pour fonctionner. Nous allons créer un GameObject vide. Dans le menu GameObject->Create Empty.Un objet nommé GameObject est crée. Renommons le ObjectsCreator. Sélectionner ObjectsCreator et glisser le script RandomObjectsCreator dans l'onglet Inspector. Le script contient deux variables prefab, radius. Glisser le prefab Cube sur le none à droite de la variable prefab. Le nom Cube apparaît avec une icône de manipulateur 3D.
Ajout d'un script et assignation de la variable prefab aux prefab Cube.
Appuyer sur Play et ensuite sur la touche "espace". Des cubes vont apparaître aléatoirement à l'écran. Je vous recommande d'ajouter une lampe directionnelle dans la scène pour embellir votre rendu.
-->