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
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) :

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.
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.
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 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 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.

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.

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 :
#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
;
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 :
interface ViewController : UIViewController <AVAudioPlayerDelegate>
{
NSTimer *timer1;
}
Puis insérez la ligne suivante dans la méthode ViewDidLoad
:
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 :
@interface ViewController : UIViewController <AVAudioPlayerDelegate>
{
NSTimer *timer1;
int etat; // Etat du jeu
}
Voici la structure de la méthode boucleJeu
:
(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
:
(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 :
@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
:
(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
:
(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 :
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 :
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 :
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 :
@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 :
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
:
posY = (arc4random() % (hauteur - 60)) + 30;
La ligne 5 affiche le ver sur l'écran aux coordonnées posX
, posY
calculées précédemment :
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 :
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.
leTemps = (arc4random() % 3) + 1;
La ligne 8 désactive le timer actuel :
[timer1 invalidate];
Quant à la ligne 9, elle définit le nouveau timer basé sur la durée aléatoire calculée ligne 7 :
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 :
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 :
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 :
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 :
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 :
rate++;
Puis le Label
est mis à jour en conséquence :
leMessage.text = [NSString stringWithFormat: @"Vers attrapés %i ratés %i",reussi, rate];
Et enfin, un son est émis :
AudioServicesPlaySystemSound(rire);
La ligne 10 fait disparaître le ver de l'écran :
leVer.alpha = 0.0f;
La ligne 11 fixe le temps de la disparition à 1/2 seconde :
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 :
[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 :
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 :
(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 :
la différence entre l'abscisse du toucher et l'abscisse du centre du sprite est inférieure à
30
((abs(location.x - posX)<30)
) ;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
:
#import <AVFoundation/AVFoundation.h>
#import <AudioToolbox/AudioToolbox.h>
Toujours dans le fichier d'en-têtes, implémentez le protocole AVAudioPlayerDelegate
:
@interface ViewController : UIViewController <AVAudioPlayerDelegate>
Enfin, définissez les objets suivants :
audioPlayer
de classeAVAudioPlayer
;bruit
de classeSystemSoundID
;rire
de classeSystemSoundID
.
@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
:
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 !
L'application se trouve dans le dossier ver
. Voici le code de ses deux principaux fichiers.
ViewController.h
#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
#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 !