Aller au menu - Aller au contenu

Icône TP - Capturez les vers

Mise à jour : 01/02/2012
Difficulté : Facile Facile Creative Commons BY-NC-SA
19 893 visites depuis 7 jours, dont 257 sur ce chapitre classé 18/786
Que diriez-vous d'écrire votre propre jeu ?
Mais si, vous en êtes capables !

Dans ce TP, je vous propose de mettre en oeuvre les techniques étudiées dans cette partie (et aussi un peu dans les parties précédentes).
Le but de votre mission, si vous l'acceptez, va consister à éradiquer une colonie de vers qui menace la santé d'un végétal sur lequel elle a élu domicile.

Je vous sens enthousiastes, mais également soucieux des difficultés à venir ...

N'ayez crainte : si vous suivez les conseils que je vais donner, tout se passera à merveille ;)
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Principe du TP

Un aperçu de ce qui vous attend



Dans ce TP, vous allez devoir :

  • afficher une image en arrière-plan du device ;
  • mettre en place un timer ;
  • afficher/faire disparaître de petits objets graphiques (aussi appelés « sprites »);
  • tester les éventuelles collisions ;
  • ajouter une musique d'arrière-plan ;
  • ajouter un bruitage sur les actions du joueur et sur la perte d'un point.


Votre mission va consister à afficher des sprites sur l'écran pendant une durée aléatoire comprise entre 0,5 et 1,5 seconde, puis à les faire disparaître. Si le joueur touche un sprite avant qu'il ne disparaisse, il gagne un point. Dans le cas contraire, un son est joué pour lui signifier qu'il vient d'échouer.

La figure suivante représente ce que vous devez obtenir (libre à vous de choisir un autre fond d'écran et un autre sprite) :

L'application « Capturez les vers  »



Les ressources du projet



Le projet utilisera les ressources suivantes :

  • une image JPG ou PNG de 320x480 pixels pour l'arrière-plan ;
  • une image PNG transparente de 60x60 pixels pour le sprite ;
  • un morceau MP3 pour l'arrière-plan sonore ;
  • un son CAF qui sera émis lorsque le joueur pointe un ver avec son doigt ;
  • un autre son CAF qui sera émis lors de la disparition d'un ver ou lorsque le joueur pointe l'arrière-plan et non le ver.


Vous pouvez télécharger les ressources que j'ai utilisées pour vous faciliter la tâche, mais rien ne vous empêche de créer les vôtres.

Télécharger les ressources

Mise en place d'un timer



Le timer du jeu de casse-briques est une bonne base de départ. Mais attention, il ne s'agit que d'une base de départ. N'oubliez pas que le sprite devra s'afficher pendant une durée variable comprise entre 0,5 et 1,5 seconde. D'autre part, un message de démarrage du type « Attention … 3 … 2 … 1 … C'est à vous » devra s'afficher au début de la partie. Le délai entre les différentes parties du message sera fixé à 1 seconde, pour que le joueur ait le temps de lire ce qui est écrit. Étant donné qu'un seul timer sera utilisé pour rythmer ces différentes phases de jeu, il sera nécessaire de modifier son paramétrage pendant l'exécution du jeu.

Affichage/disparition d'un sprite



Le sprite sera affiché dans un contrôle Image View. Pour le faire disparaître/apparaître, vous utiliserez sa propriété alpha. Affectez la valeur 0.0f à cette propriété pour faire disparaître le ver, et la valeur 1.0f pour le faire apparaître.

Je ne sais pas si vous vous rappelez du chapitre précédent, dans lequel les briques étaient cachées en agissant sur leur propriété hidden. Ici, je vous propose d'utiliser une variante qui produira le même effet. N'oubliez pas que la documentation de Xcode est une alliée de choix lorsque vous expérimentez une nouvelle technique.


Tests de collisions



Il vous suffit d'utiliser la méthode touchesBegan pour obtenir les coordonnées du toucher (une technique similaire, basée sur la méthode touchesMoved a déjà été étudiée dans le jeu de casse-briques). Comparez les coordonnées du toucher avec celles du sprite et vous saurez si le joueur a bien visé ou s'il a besoin de nouvelles lunettes !

Je ne reviendrai pas sur les techniques à utiliser pour afficher une image en arrière-plan, jouer une musique de fond ou émettre des bruits lors des actions du joueur. Ces techniques doivent maintenant être acquises. Dans le cas contraire, revenez en arrière pour savoir comment les mettre en œuvre.


Je crois que je vous ai bien aidés et que vous êtes fin prêts à réaliser votre propre jeu.
Amusez-vous bien !

Solution

Que votre application fonctionne à la perfection ou que vous ayez buté sur un ou plusieurs points de détail, je vais vous montrer ma façon de voir les choses.

Comme toujours en programmation, il n'y a pas une, mais plusieurs solutions. C'est pourquoi je parle de « ma façon de voir les choses ». Il se peut donc que vous ayez écrit un tout autre code pour parvenir au même résultat.


Comme je vous l'ai suggéré dans la section « Principe du TP », le projet a été scindé en plusieurs étapes consécutives. Nous allons maintenant examiner chacune de ces étapes.

Création du projet



Définissez un nouveau projet de type Single View Application et donnez-lui le nom « ver ».
Munissez-vous des ressources suivantes :

  • une image JPG ou PNG de 320x480 pixels pour l'arrière-plan ;
  • une image PNG transparente de 60x60 pixels pour le sprite ;
  • un morceau MP3 pour l'arrière-plan sonore ;
  • un son CAF qui sera émis lorsque le joueur pointe un ver avec son doigt ;
  • un autre son CAF qui sera émis lors de la disparition d'un ver ou lorsque le joueur pointe l'arrière-plan et non le ver.


Pour stocker ces fichiers dans l'application, vous allez créer le dossier Resources. Cliquez du bouton droit sur le dossier ver dans le volet de navigation et sélectionnez New Group dans le menu. Donnez le nom « Resources » à ce dossier et déplacez les cinq fichiers de ressource depuis le Finder dans le dossier Resources de l'application. La figure suivante représente le volet de navigation que vous devriez avoir.

Les ressources apparaissent dans l'arborescence du projet


Ici, fondver.jpg est l'image d'arrière-plan du jeu, ver.png le sprite, morceau.mp3 la musique d'arrière-plan, pong.caf le bruit émis lorsque le joueur appuie sur un sprite et rire.caf le bruit qui est émis lorsqu'un sprite disparaît sans que le joueur ait cliqué dessus.

Comme vous pouvez le voir, le sprite est un fichier PNG. Ce format n'a pas été choisi au hasard. En effet, les images PNG peuvent contenir des pixels transparents, ainsi le sprite se superposera parfaitement sur l'image d'arrière-plan (figure suivante)


À gauche, le sprite contient des pixels transparents ; à droite, non


Définition de l'écran de jeu



L'écran de jeu est vraiment simple, il contiendra deux contrôles :

  • un Image View, pour afficher le ver ;
  • un Label pour afficher le score.


Cliquez sur MainStoryboard.storyboard dans le volet de navigation et ajoutez un contrôle UIImageView ainsi qu'un contrôle Label au canevas. Définissez l'outlet leVer pour l'Image View et l'outlet leMessage pour le Label.

Le fichier d'en-têtes doit maintenant ressembler à ceci :

Code : Objective-C
1
2
3
4
5
6
7
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property (weak, nonatomic) IBOutlet UIImageView *leVer;
@property (weak, nonatomic) IBOutlet UILabel *leMessage;
@end


Affichage d'une image en arrière-plan



Cliquez sur ViewController.m dans le volet de navigation et insérez la ligne suivante dans la méthode viewDidLoad ;

Code : Objective-C
1
self.view.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:@"fondver.jpg"]];


Cette technique a été utilisée et expliquée à maintes reprises dans les chapitres précédents, nous n'y reviendrons pas.

Mise en place du timer



Commencez par définir la variable d'instance timer1 de classe NSTimer dans le fichier d'en-têtes :

Code : Objective-C
1
2
3
4
interface ViewController : UIViewController <AVAudioPlayerDelegate>
{
  NSTimer *timer1;
}


Puis insérez la ligne suivante dans la méthode ViewDidLoad :

Code : Objective-C
1
timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(boucleJeu) userInfo:nil repeats:YES];


scheduledTimerWithTimeInterval:1.0f met en place un timer exécuté toutes les secondes. La méthode appelée a pour nom BoucleJeu (selector:@selector(boucleJeu)). Elle se répète indéfiniment, jusqu'à ce que l'application soit arrêtée par le joueur (repeats:YES).

Vous vous doutez certainement de ce que sera l'étape suivante : la définition de la méthode boucleJeu.

J'ai distingué trois étapes de jeu :

  • une étape initiale, pendant laquelle un message indique au joueur que la partie va commencer ;
  • une deuxième étape pendant laquelle le sprite est affiché sur l'écran ;
  • une troisième étape pendant laquelle le sprite est dissimulé.


Pour définir ces trois étapes, j'ai choisi d'utiliser une variable d'instance de classe int que j'ai appelée etat. Cette variable est définie dans le fichier d'en-têtes :

Code : Objective-C
1
2
3
4
5
@interface ViewController : UIViewController <AVAudioPlayerDelegate>
{
  NSTimer *timer1;
  int etat;       // Etat du jeu
}


Voici la structure de la méthode boucleJeu :

Code : Objective-C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
-(void) boucleJeu
{
  switch(etat)
  {
    case 0:
      // Décompte de départ
      break;
    case 1:
      // Affichage du ver
      break;
    case 2:
      // Disparition du ver
      break;
  }
}


Il ne reste plus qu'à insérer des instructions à la suite des trois case pour donner vie à la boucle de jeu

Case 0



Au lancement de l'application, le jeu doit afficher un décompte. L'étape initiale correspond donc à etat = 0. Définissez cette valeur dans la méthode viewDidLoad :

Code : Objective-C
1
2
3
4
5
6
- (void)viewDidLoad
{
  [super viewDidLoad];
  etat = 0;
  timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(boucleJeu) userInfo:nil repeats:YES];
}


Au début de la partie, un message du type « Attention … 3 … 2 … 1 … C'est à vous » doit s'afficher dans le Label. Chacune des parties de ce message, c'est-à-dire « Attention », « 3 », « 2 », « 1 » et « C'est à vous » est affichée pendant une seconde. Pour savoir où l'on se trouve dans le décompte, vous allez définir la variable d'instance compteur de classe int dans le fichier d'en-têtes :

Code : Objective-C
1
2
3
4
5
6
@interface ViewController : UIViewController
{
  NSTimer *timer1;
  int etat;       // État du jeu
  int compteur;   // Compteur de départ de jeu
}


Puis vous allez initialiser cette variable à 4 dans la méthode viewDidLoad :

Code : Objective-C
1
2
3
4
5
6
7
- (void)viewDidLoad
{
  [super viewDidLoad];
  etat = 0;
  compteur = 4;   // Compteur au départ du jeu
  timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(boucleJeu) userInfo:nil repeats:YES];
}


Maintenant, vous allez compléter la méthode boucleJeu pour afficher les messages dans le Label :

Code : Objective-C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
-(void) boucleJeu
{
  switch(etat)
  {
    case 2:
      // Disparition du ver
      break;

    case 1:
      // Affichage du ver
      break;
    
    case 0:
      // Décompte de départ
      if (compteur == 4) 
      {
        [leMessage setText:@"Attention"];
        compteur--;
      }
      else if ((compteur >=1) && (compteur <=3))
      {
        leMessage.text = [NSString stringWithFormat: @"%i ...",compteur];
        compteur--;
      }
      else if (compteur == 0)
      {
        [leMessage setText:@"C'est à vous !"];
        compteur = 4;
        etat = 1; //Affichage d'un ver
      }
      break;
  }
}


Examinons ces instructions.
Au lancement du jeu, compteur vaut 4. Le test if (compteur == 4) étant vérifié, les instructions exécutées sont donc les suivantes :

Code : Objective-C
1
2
3
4
5
if (compteur == 4) 
{
  [leMessage setText:@"Attention"];
  compteur--;
}


L'instruction de la ligne 3 affiche le message « Attention » dans le Label et l'instruction de la ligne 4 décrémente la variable compteur.
La méthode boucleJeu se termine. Elle sera à nouveau exécutée dans une seconde puisque le timer mis en place a une période de 1 seconde.

À la deuxième exécution de la méthode boucleJeu, compteur vaut 3. C'est donc cette portion du code qui est exécutée :

Code : Objective-C
1
2
3
4
5
else if ((compteur >=1) && (compteur <=3))
{
  leMessage.text = [NSString stringWithFormat: @"%i ...",compteur];
  compteur--;
}


L'instruction de la ligne 3 affiche la valeur du compteur dans le Label : 3. La propriété text du Label étant de type NSString, il est nécessaire d'effectuer une conversion int -> NSString. C'est la raison d'être du message de la ligne 3.

La ligne 4 décrémente la variable compteur qui vaudra 2 lors du prochain passage dans la méthode boucleJeu.

Vous l'aurez compris, la même portion de code est exécutée lorsque compteur vaut 2, puis vaut 1. Une fois les messages « 2 » puis « 1 » affichés dans le Label, compteur vaut 0. C'est donc la dernière partie du code qui est exécutée :

Code : Objective-C
1
2
3
4
5
6
else if (compteur == 0)
{
  [leMessage setText:@"C'est à vous !"];
  compteur = 4;
  etat = 1; //Affichage d'un ver
}


Dans ce cas, la ligne 3 affiche « C'est à vous » dans le Label, la ligne 4 initialise le compteur à 4 et la ligne 5 affecte la valeur 1 à la variable etat, ce qui signifie que la portion de code qui suit le case 1 sera exécutée lors du prochain passage dans la méthode boucleJeu.

Case 1



Lorsque etat vaut 1, l'application doit afficher un ver sur l'écran pendant une durée aléatoire comprise entre 1 et 3 secondes.
Pour accomplir cette étape, vous aurez besoin de nouvelles variables. Complétez le fichier d'en-têtes comme suit :

Code : Objective-C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@interface ViewController : UIViewController
{
  NSTimer *timer1;
  int etat;       // État du jeu
  int compteur;   // Compteur de départ de jeu
  float leTemps;  // Durée de l'état actuel 
  int largeur;    // Largeur de l'écran
  int hauteur;    // Hauteur de l'écran
  float posX;     // Position en X du ver
  float posY;     // Position en Y du ver
}


La variable leTemps sera utilisée pour stocker la durée de l'affichage du sprite.

Les variables largeur et hauteur contiendront la largeur et la hauteur en pixels du device. Ces valeurs sont importantes, car tous les devices n'ont pas la même définition.

Enfin, les variables posX et posY seront utilisées pour mémoriser la position du sprite sur l'écran.

Complétez le code situé entre les instructions case 1: et break; comme suit :

Code : Objective-C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
case 1:
  // Affichage du ver
  posX = (arc4random() % (largeur - 60)) + 30;
  posY = (arc4random() % (hauteur - 60)) + 30;
  leVer.center = CGPointMake(posX, posY);
  leVer.alpha = 1.0f;
  leTemps = (arc4random() % 3) + 1; //Nombre aléatoire entre 1 et 3
  [timer1 invalidate];
  timer1 = [NSTimer scheduledTimerWithTimeInterval:leTemps target:self selector:@selector(boucleJeu) userInfo:nil repeats:YES];
  etat = 2;
  break;


La ligne 3 calcule la position en X du sprite. Pour cela, nous utilisons la fonction Objective-C arc4random, qui renvoie un nombre entier compris entre 0 et 4 294 967 295. La valeur renvoyée étant bien supérieure à la taille des écrans des iPhones (tant horizontalement que verticalement), il est nécessaire de la réduire.

La variable largeur représente la largeur en pixels de l'écran. Étant donné que le sprite est un carré de 60x60 pixels et que la fonction qui l'affiche sur l'écran se base sur son centre, il est nécessaire de restreindre les nombres tirés aléatoirement entre 30 et largeur - 30.
La première partie du calcul (arc4random() % (largeur - 60)) renvoie un nombre compris entre 0 et largeur - 60. En lui ajoutant la valeur 30, on obtient la plage souhaitée : 30 à largeur - 30.

La ligne 4 utilise un calcul similaire pour obtenir un nombre aléatoire compris entre 30 et hauteur - 30 :

Code : Objective-C
1
posY = (arc4random() % (hauteur - 60)) + 30;


La ligne 5 affiche le ver sur l'écran aux coordonnées posX, posY calculées précédemment :

Code : Objective-C
1
leVer.center = CGPointMake(posX, posY);


La ligne 6 affecte la valeur 1.0f à la propriété alpha du sprite pour le faire apparaître sur l'écran :

Code : Objective-C
1
leVer.alpha = 1.0f;


La ligne 7 mémorise dans la variable leTemps un nombre entier aléatoire compris entre 1 et 3. Ce nombre va correspondre au temps d'affichage du sprite. Libre à vous de le modifier comme bon vous semble.

Code : Objective-C
1
leTemps = (arc4random() % 3) + 1;


La ligne 8 désactive le timer actuel :

Code : Objective-C
1
[timer1 invalidate];


Quant à la ligne 9, elle définit le nouveau timer basé sur la durée aléatoire calculée ligne 7 :

Code : Objective-C
1
timer1 = [NSTimer scheduledTimerWithTimeInterval:leTemps target:self selector:@selector(boucleJeu) userInfo:nil repeats:YES];


Enfin, la ligne 10 indique que le jeu a changé d'état. Lors de la prochaine exécution de la méthode boucleJeu, ce sont les instructions qui suivent le case 2: qui seront exécutées :

Code : Objective-C
1
etat = 2;


Ah oui, j'allais oublier. Les variables largeur et hauteur n'ont pas été initialisées. Ajoutez les deux lignes suivantes dans la méthode viewDidLoad pour réparer cette lacune :

Code : Objective-C
1
2
largeur = self.view.bounds.size.width;  // Largeur de l'écran
hauteur = self.view.bounds.size.height; // Hauteur de l'écran


Case 2



Lorsque etat vaut 2, l'application doit faire disparaître le ver qui est affiché. La durée de la disparition (c'est-à-dire le temps pendant lequel aucun ver n'est affiché sur l'écran) est très brève : 1/2 seconde. L'application doit également tester si le joueur a touché le ver avant qu'il ne disparaisse et, dans le cas contraire, émettre un son.
Pour accomplir cette étape, vous aurez besoin d'une nouvelle variable pour savoir si l'écran a été touché par le joueur. J'ai choisi d'appeler cette variable aTouche. Définissez-la dans le fichier d'en-têtes. Tant que vous y êtes, définissez les variables reussi et rate pour tenir le compte des vers capturés et des vers ratés :

Code : Objective-C
1
2
3
int aTouche;  // 1 si le joueur a touché l'écran, 0 sinon
int reussi;   // Nombre de vers capturés
int rate;     // Nombre de vers ratés


Ces trois variables étant définies, retournez dans le code et complétez les instructions entre case 2: et break; comme suit :

Code : Objective-C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
case 2:
  // Disparition du ver
  if (aTouche == 0)
  {    
    rate++; // Sinon, le joueur n'a pas eu le temps de toucher l'écran, donc le ver est raté
    leMessage.text = [NSString stringWithFormat: @"Vers attrapés %i ratés %i",reussi, rate];
    AudioServicesPlaySystemSound(rire);
  }
        
  leVer.alpha = 0.0f;
  leTemps = 0.5f;
  [timer1 invalidate];
  timer1 = [NSTimer scheduledTimerWithTimeInterval:leTemps target:self selector:@selector(boucleJeu) userInfo:nil repeats:YES];
  etat = 1;
  break;


Examinons ces instructions.
Par convention, aTouche vaut 0 si l'écran n'a pas été touché par le joueur. Il vaut 1 dans le cas contraire.
La ligne 3 teste si l'écran n'a pas été touché. Dans ce cas, cela signifie que le joueur n'a pas eu le temps de capturer le ver.
Le nombre de vers ratés est donc incrémenté de 1 :

Code : Objective-C
1
rate++;


Puis le Label est mis à jour en conséquence :

Code : Objective-C
1
leMessage.text = [NSString stringWithFormat: @"Vers attrapés %i ratés %i",reussi, rate];


Et enfin, un son est émis :

Code : Objective-C
1
AudioServicesPlaySystemSound(rire);


La ligne 10 fait disparaître le ver de l'écran :

Code : Objective-C
1
leVer.alpha = 0.0f;


La ligne 11 fixe le temps de la disparition à 1/2 seconde :

Code : Objective-C
1
leTemps = 0.5f;


Rappelez-vous, le ver doit disparaître pendant 1/2 seconde. C'est la raison pour laquelle la ligne 12 désactive le timer actuel et la ligne 13 le remplace par un nouveau, basé sur une période de 1/2 seconde :

Code : Objective-C
1
2
[timer1 invalidate];
timer1 = [NSTimer scheduledTimerWithTimeInterval:leTemps target:self selector:@selector(boucleJeu) userInfo:nil repeats:YES];


Enfin, la ligne 14 fait passer le jeu dans l'étape 1. La prochaine exécution de la méthode boucleJeu provoquera donc l'affichage d'un nouveau sprite :

Code : Objective-C
1
etat = 1;


J'espère que vous n'avez pas trop souffert en décortiquant la méthode boucleJeu. Il faut bien avouer qu'elle était un peu longue. Mais vous serez certainement d'accord avec moi, ces instructions n'avaient rien d'insurmontable.

Pour souffler un peu, n'hésitez pas à lancer l'application. Vous verrez, elle fonctionne à la perfection en ce qui concerne la séquence de départ ainsi que l'affichage et la disparition des sprites.

J'ai une bonne nouvelle pour vous : cette méthode était de loin la plus complexe de l'application.

Tests de collisions



Vous allez maintenant écrire un peu de code pour tester si la position pointée par le joueur correspond à celle du sprite.
Pour cela, il vous suffit d'utiliser la méthode touchesBegan pour obtenir les coordonnées du toucher. Comparez ces coordonnées avec celles du sprite et vous saurez si le joueur a bien ou mal visé.

Voici le code à mettre en place :

Code : Objective-C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
  aTouche = 1; // Le joueur a touché l'écran
  UITouch *touch = [[event allTouches] anyObject];
  CGPoint location = [touch locationInView:touch.view];
  if ((abs(location.x - posX)<30) && (abs(location.y - posY)<30))
  {
    reussi++;
    AudioServicesPlaySystemSound(bruit);
  }
  else 
  {
    AudioServicesPlaySystemSound(rire);
    rate++;
  }
  leMessage.text = [NSString stringWithFormat: @"Vers attrapés %i ratés %i",reussi, rate];

  // Affichage d'un autre ver
  [timer1 invalidate];
  timer1 = [NSTimer scheduledTimerWithTimeInterval:0.0f target:self selector:@selector(boucleJeu) userInfo:nil repeats:YES];
  etat = 2;
}


À la ligne 3, la variable aTouche est initialisée à 1 pour indiquer que le joueur a touché l'écran.

Les lignes 4 et 5 récupèrent les coordonnées du toucher. La technique utilisée ici a été décrite dans la section « Immersion dans le code » du jeu de casse-briques. Reportez-vous-y si nécessaire.

La ligne 6 teste si le joueur a touché un sprite. Les entités location.x et location.y représentent les coordonnées du toucher. Quant à posX et posY, elles représentent le centre du sprite.

Si :

  1. la différence entre l'abscisse du toucher et l'abscisse du centre du sprite est inférieure à 30 ((abs(location.x - posX)<30)) ;
  2. la différence entre l'ordonnée du toucher et l'ordonnée du centre du sprite est inférieure à 30 ((abs(location.y - post)<30)) ;


cela signifie que le joueur a touché le sprite. Dans ce cas, le nombre de vers capturés est incrémenté de 1 (ligne 8) et un bruit de capture est émis (ligne 9).

Si le joueur a touché l'écran en dehors du sprite, un rire est émis (ligne 13) et le nombre de vers ratés est incrémenté de 1 (ligne 14).

Pour terminer, le timer actuel est désactivé (ligne 19), un nouveau timer à effet immédiat (scheduledTimerWithTimeInterval initialisé à 0.0f) est défini (ligne 20) et l'application bascule dans l'étape 2 pour dissimuler le ver affiché.

Maintenant, il ne reste plus qu'à définir la « couche sonore » de l'application, c'est-à-dire la musique d'arrière-plan et les deux bruitages.

Musique d'arrière-plan



Nous avons déjà vu cette technique dans un chapitre précédent. Elle consiste à mettre en place un objet AVAudioPlayer.
Commencez par cliquer sur la première entrée dans le volet de navigation, basculez sur l'onglet Build Phases, développez l'élément Link Binary with Libraries et ajoutez les frameworks AVFoundation.framework et AudioToolbox.framework.

Cliquez sur ViewController.h dans le volet de navigation et faites référence aux deux frameworks avec des instructions \#import :

Code : Objective-C
1
2
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>


Toujours dans le fichier d'en-têtes, implémentez le protocole AVAudioPlayerDelegate :

Code : Objective-C
1
@interface ViewController : UIViewController <AVAudioPlayerDelegate>


Enfin, définissez les objets suivants :

  • audioPlayer de classe AVAudioPlayer ;
  • bruit de classe SystemSoundID ;
  • rire de classe SystemSoundID.


Code : Objective-C
1
2
3
4
5
6
7
@interface ViewController : UIViewController <AVAudioPlayerDelegate>
{
  ... 
  AVAudioPlayer *audioPlayer;
  SystemSoundID bruit;
  SystemSoundID rire;
}


Pour activer la musique de fond et rendre accessibles les deux effets spéciaux, ajoutez les instructions suivantes dans la méthode viewDidLoad :

Code : Objective-C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
AudioSessionInitialize (NULL, NULL, NULL, (__bridge void *)self);
UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
AudioSessionSetProperty (kAudioSessionProperty_AudioCategory, sizeof (sessionCategory), &sessionCategory);
NSData *soundFileData;
soundFileData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"morceau.mp3" ofType:NULL]]];
audioPlayer = [[AVAudioPlayer alloc] initWithData:soundFileData error:NULL];
audioPlayer.delegate = self;
[audioPlayer setVolume:1.0];
[audioPlayer play];
    
AudioServicesCreateSystemSoundID(CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("pong"), CFSTR("caf"), NULL), &bruit);
AudioServicesCreateSystemSoundID(CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("rire"), CFSTR("caf"), NULL), &rire);


Les lignes 1 à 9 mettent en place la musique de fond, comme nous l'avons vu dans le chapitre consacré au son.
Les lignes 11 et 12 mettent en place les sons bruit et rire.

Et voilà, l'application est entièrement opérationnelle. J'espère que vous avez pris autant de plaisir que moi à la développer !

Si vous testez cette application dans le simulateur iOS 5.0, il se peut que vous obteniez une erreur à rallonge dans la console.
Ne vous en faites pas : cette erreur n'en est pas vraiment une et l'application fonctionne à la perfection sur un iPhone, un iPod Touch ou un iPad tournant sous iOS 5. Un correctif devrait être intégré dans les prochaines mises à jour de Xcode.
Si cette erreur vous dérange vraiment, vous pouvez opter pour une solution alternative.


L'application se trouve dans le dossier ver. Voici le code de ses deux principaux fichiers.

Télécharger le projet

ViewController.h


Secret (cliquez pour afficher)

Code : Objective-C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>

@interface ViewController : UIViewController <AVAudioPlayerDelegate>
{
  NSTimer *timer1;
  int etat;      // État du jeu
  int compteur;  // Compteur de départ de jeu
  float leTemps; // Durée de l'état actuel 
  int largeur;   // Largeur de l'écran
  int hauteur;   // Hauteur de l'écran
  float posX;    // Position en X du ver
  float posY;    // Position en Y du ver
  int aTouche;   // 1 si le joueur a touché l'écran, 0 sinon
  int reussi;    // Nombre de vers capturés
  int rate;      // Nombre de vers râtés
  AVAudioPlayer *audioPlayer;
  SystemSoundID bruit;
  SystemSoundID rire;
}

@property (weak, nonatomic) IBOutlet UIImageView *leVer;
@property (weak, nonatomic) IBOutlet UILabel *leMessage;
@end



ViewController.m


Secret (cliquez pour afficher)

Code : Objective-C
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#import "ViewController.h"
#include <stdlib.h>

@implementation ViewController
@synthesize leVer;
@synthesize leMessage;

- (void)didReceiveMemoryWarning
{
  [super didReceiveMemoryWarning];
  // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle

-(void) boucleJeu
{
  switch(etat)
  {
    case 0:
    // Décompte de départ
    if (compteur == 4) 
    {
      [leMessage setText:@"Attention"];
      compteur--;
    }
    else if ((compteur >=1) && (compteur <=3))
    {
      leMessage.text = [NSString stringWithFormat: @"%i ...",compteur];
      compteur--;
    }
    else if (compteur == 0)
    {
      [leMessage setText:@"C'est à vous !"];
      compteur = 4;
      etat = 1; //Affichage d'un ver
    }
    break;
    case 1:
      // Affichage du ver
      posX = (arc4random() % (largeur - 60)) + 30;
      posY = (arc4random() % (hauteur - 60)) + 30;
      leVer.center = CGPointMake(posX, posY);
      leVer.alpha = 1.0f;
      leTemps = (arc4random() % 3) + 1; //Nombre aléatoire entre 1 et 3
      [timer1 invalidate];
      timer1 = [NSTimer scheduledTimerWithTimeInterval:leTemps target:self selector:@selector(boucleJeu) userInfo:nil repeats:YES];
      etat = 2;
      aTouche = 0;
      break;
    case 2:
    // Disparition du ver
    if (aTouche == 0)
    {    
      rate++; // Sinon, le joueur n'a pas eu le temps de toucher l'écran, donc le ver est raté
      leMessage.text = [NSString stringWithFormat: @"Vers attrapés %i ratés %i",reussi, rate];
      AudioServicesPlaySystemSound(rire);
    }
    leVer.alpha = 0.0f;
    leTemps = 0.5f;
    [timer1 invalidate];
    timer1 = [NSTimer scheduledTimerWithTimeInterval:leTemps target:self selector:@selector(boucleJeu) userInfo:nil repeats:YES];
    etat = 1;
    break;
  }
}

-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
  aTouche = 1; // Le joueur a touché l'écran
  UITouch *touch = [[event allTouches] anyObject];
  CGPoint location = [touch locationInView:touch.view];
  if ((abs(location.x - posX)<30) && (abs(location.y - posY)<30))
  {
    reussi++;
    AudioServicesPlaySystemSound(bruit);
  }
  else 
  {
    AudioServicesPlaySystemSound(rire);
    rate++;
  }
  leMessage.text = [NSString stringWithFormat: @"Vers attrapés %i ratés %i",reussi, rate];

  // Affichage d'un autre ver
  [timer1 invalidate];
  timer1 = [NSTimer scheduledTimerWithTimeInterval:0.0f target:self selector:@selector(boucleJeu) userInfo:nil repeats:YES];
  etat = 2;
}

- (void)viewDidLoad
{
  [super viewDidLoad];

  // Arrière-plan
  self.view.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:@"fondver.jpg"]];
  
  // Audio
  AudioSessionInitialize (NULL, NULL, NULL, (__bridge void *)self);
  UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
  AudioSessionSetProperty (kAudioSessionProperty_AudioCategory, sizeof (sessionCategory), &sessionCategory);
  NSData *soundFileData;
  soundFileData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"morceau.mp3" ofType:NULL]]];
  audioPlayer = [[AVAudioPlayer alloc] initWithData:soundFileData error:NULL];
  audioPlayer.delegate = self;
  [audioPlayer setVolume:1.0];
  [audioPlayer play];
    
  AudioServicesCreateSystemSoundID(CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("pong"), CFSTR("caf"), NULL), &bruit);
  AudioServicesCreateSystemSoundID(CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("rire"), CFSTR("caf"), NULL), &rire);
  
  // Timer
  etat = 0;      // État 0 au départ du jeu : message de départ
  reussi = 0;    // Aucun ver capturé au départ
  rate = 0;      // Aucun ver raté au départ
  compteur = 4;  // Compteur au départ du jeu
  largeur = self.view.bounds.size.width;  // Largeur de l'écran
  hauteur = self.view.bounds.size.height; // Hauteur de l'écran
  timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(boucleJeu) userInfo:nil repeats:YES];
}

- (void)viewDidUnload
{
  [self setLeVer:nil];
  [self setLeMessage:nil];
  [self setLeVer:nil];
  [super viewDidUnload];
  // Release any retained subviews of the main view.
  // e.g. self.myOutlet = nil;
}

- (void)viewWillAppear:(BOOL)animated
{
  [super viewWillAppear:animated];
}

- (void)viewDidAppear:(BOOL)animated
{
  [super viewDidAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated
{
  [super viewWillDisappear:animated];
}

- (void)viewDidDisappear:(BOOL)animated
{
  [super viewDidDisappear:animated];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
  // Return YES for supported orientations
  return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}

@end

En résumé


Il est tout à fait normal que vous ayez éprouvé quelques difficultés durant ce développement. Cependant, si vous prenez un peu de recul et si vous relisez calmement tout ce qui a été dit, vous verrez que cette application consiste en un assemblage de "briques logicielles" étudiées dans les chapitres précédents. Par extension, je peux affirmer que le développement d'une quelconque autre application utilisera une démarche similaire.

Arrivés à ce point dans la lecture de ce tutoriel, vous êtes donc à même de créer toutes les applications qui vous passeront par la tête. Bien sûr, toutes les applications sont différentes et ce tutoriel n'a pas passé en revue toutes les facettes de iOS, mais vous en savez assez pour compléter ce qui a été dit par ... tout ce qui n'a pas été dit. La documentation Apple sera une alliée incontournable. A vous de la dompter pour qu'elle vous donne toutes les méthodes dont vous aurez besoin dans votre prochains développements !
Chapitre précédent Sommaire Chapitre suivant

Partager

7 commentaires pour "TP - Capturez les vers"
Note moyenne : 3.41 / 4 (189 votes)
Pseudo Commentaire
Hors ligne sdznovice # Posté le 08/04/2012 à 22:21:34

Bonjour,

l'application marche parfaitement bien, il y a juste un détail que je n'arrive pas à résoudre, le son lors de la réussite du touché d'un vers et celui lorsque l'on ne le touche pas ne fonctionne plus. Ils marchaient il y a quelques temps mais ce n'est plus le cas. Je n'ai pourtant pas fait de modifications qui pourraient avoir perturbé leur fonctionnement.
J'ai essayé beaucoup de chose mais en vain.

Auriez vous une idée d'où vient le problème?

Merci d'avance.
Hors ligne MichelMartin # Posté le 15/04/2012 à 11:35:08

Avis : Très bon Groupe : Auteurs

Bonjour sdznovice,

Il y a forcément eu une modif qui a changé le comportement de l'application. La trouver n'est pas forcément une mince affaire. Il me semble plus simple de réécrire l'application à partir de zéro. Cela ne devrait pas demander plus de 10 minutes...
Hors ligne sdznovice # Posté le 16/04/2012 à 00:28:31

Bonjour,

J'ai suivi vos conseils et recommencé plusieurs fois le TP cependant je n'ai toujours pas trouvé le moyen de faire marcher les bruitages.
J'ai même utilisé vos fichiers .caf pour voir si le problème ne venait pas des miens mais non ça ne marche pas non plus avec les vôtres.

Je n'arrive pas à trouver d'où vient le problème.

Auriez-vous une idée de se que je pourrais mal faire pour que ça ne marche pas ?

Merci d'avance.
Hors ligne ChickN # Posté le 20/04/2012 à 16:03:33
Avatar

Super TP et super TUTO ;)

Merci de suivre ce lien pour m'aider: http://www.siteduzero.com/forum-83-759 [...] les-vers.html
J'ai un petit probleme :)

ChickN
Hors ligne MichelMartin # Posté le 01/05/2012 à 18:28:07

Avis : Très bon Groupe : Auteurs

@sdznovice
Bonjour,
Pour vous aider à résoudre le problème, j'ai quelques petites questions :
- Quelle version de Xcode utilisez-vous ?
- Toutes les appli ont été développées sous Lion/Xcode 4.2
- Avez-vous utilisé le code exact du tuto ?
- Est-ce des erreurs sont signalées ? Si oui, lesquelles ?

Voir tous les commentaires