Réaliser un effet graphique avec DirectX sous DELPHI par :
Plouf
(plouf@win.be ou theplouf@yahoo.com)

 

PARTIE I (1/3)

Chapitre 1 : la boucle principale
.
a) On nettoie
Quand vous ouvrez Delphi, un nouveau projet vous attend, même qu'il s'appelle Project1. Devant vous apparaît la bête fenêtre grise par défaut, qui se contente de se fermer quand on le luit dit, ce qui d'ailleurs provoque l'arrêt du programme. En fait de bête fenêtre, il s'agit déjà de tout un travail fait par notre pote Delphi, car ouvrir une fenêtre ne se fait pas tout seul (bon, ça n'est pas dur non plus, ne me faites pas dire ce que je n'ai pas dit).
Or donc Delphi nous offre un travail déjà tout fait, et pour l'en remercier, la première chose que nous faisons, à ce projet, c'est d'y supprimer Form1, qui malgré sa haute technologie ne nous interesse pas du tout. On ne va pas afficher dans une fenêtre, mais à l'écran...
Alors pour supprimer cette fenêtre du projet, c'est facile, vous allez dans le menu (bon, ma version est en Français, mais c'est pas dur à traduire savez-vous?) Projet/Retirer du projet, vous y sélectionnez Unit1/Form1, et PAF vous faites Ok.
Patatra, ya plus rien! Ou presque rien, remarquez tout de même que le nom Prject1 apparaît toujours dans la barre de titre de Delphi. C'est que, malgré les apparences, vous n'avez pas du tout fermé le projet en cours, et qu'il reste donc "quelque chose".
Bon, avant toutes choses, commençons par lui donner un nom et sauvons-le quelque part sur notre beau disque dur. Nous allons l'appeler Effect (je suis à court d'imagination ce soir et puis j'ai mal dormi). On fait donc "Tout enregister" dans le menu Fichier, ce qui provoque l'apparition de la boite de dialogue de sauvegarde vous demandant où vous désirez (et sous quel nom) enregistrer votre oeuvre. Bon, donc vous l'appelez Effect.Dpr (dpr pour Delphi Project je pense, et pis si c'est autre chose on s'en fiche un peu), vous sauvez et vous constatez que la barre de titre de Delphi vient de prendre le nom Effect.
Nous progressons...
Bien, je vous disais donc que malgré les apparences, il restait quelque chose dans ce projet. Les personnes curieuses auront déjà essayé de lancer le programme "pour voir", et elles ne verront rien sauf que le programme se terminera immédiatement.
Forcément, que voulez-vous qu'il fasse?
Bien, donc je vais vous apprendre un truc (ou bien pas du tout parce que vous le savez déjà) : en Delphi, c'est comme en Pascal, tout programme commence par le mot clef Program. Si vous êtes attentif, le code qui accompagne une fenêtre commence toujours par Unit. Ca n'est donc jamais le programme principal. Mais où est donc ce fichu programme principal? Ben c'est facile, le fichier .dpr, c'est un fichier texte, et c'est précisément lui, le programme.
Bon, forcément, Delphi vous propose de l'éditer, il vous suffit d'aller dans Projet/Voir le source.
C'est pas grand...
Mais on s'y retrouve. Cette fois notre fichier commence par Program et se termine par End. (Admirez Delphi qui a automatiquement changé le nom en notant program Effect)
Analysons un peu ce que l'on a sous les yeux. Tout d'abord, une clause Uses, bien connue des habitués de TurboPascal. Pour ceux qui débarquent du C ou du C++, c'est une sorte de #include, mais en mieux. (La directive $include existant en Delphi comme en TP7).
Vous voyez qu'on utilise Forms, c'est l'unité de gestion des fenêtres. Késako? Pourtant, yen a pas, de fenêtres. Mais vous devez utiliser Forms si vous voulez utiliser cette espèce de pseudo variable objet qu'est Application. Application c'est donc une sorte de bidule bien pratique quand il s'agit de l'interaction entre votre programme et Windows. Vous savez peut-être que pour quitter votre programme depuis une fenêtre, vous appelez Application.Terminate. L'utilité précise de tout ceci sera précisé plus loin.
Ensuite, on voit une directive de compilation, qui demande à Delphi d'inclure tous les fichier RES du répertoire dans le programme. Le fichier RES contient les informations sur les fenêtres que vous créez avec la souris dans l'éditeur de Delphi. Comme vous avez supprimé l'unique exemplaire, il n'y a aucun .res dans le répertoire et vous pouvez supprimer cette ligne.
Suivent ensuite deux lignes qui sont Application.Initialize et Application.Run. Initialize permet de faire des trucs marrants quand on utilise des Units qui le sont également, mais ici on ne va pas jouer avec les subtilités (dont j'avoue ne pas toutes les maitriser, loin de là) de notre ami Delphi, ce qui en fait également une ligne inutile qu'on efface, et hop!
Et puis on a Application.Run. A l'époque je me demandais vraiment ce que ça faisait, ce drole de truc. Dans la situation qui nous occupe, si vous supprimiez cette ligne, vous ne verriez aucune différence, car de toutes façons ce programme ne fait rien. En fait Run est une boucle dont on ne sortira qu'à la fermeture de la fenêtre principale du programme. Comme ici il n'y a aucune fenêtre... on sort tout de suite et le programme se termine.
.
b) La boucle
Ici, nous désirons en fait reprogrammer cette boucle, car non seulement nous désirons faire quelque chose d'interessant malgré que nous n'avons (pour le moment) aucune fenêtre, mais de plus nous voulons pouvoir appeler nos procédures à chaque tour de cette boucle, comme tout programme fait sous le bon vieux DOS.
Alors pas de remords, virons cette dernière ligne. (Il ne reste vraiment plus grand chose, là...)
Bon, alors si vous avez déjà programmé un effet graphique sous Dos, habituellement (une fois les initialisations de circonstances faites), cela commence par un Repeat et termine par un Until IsTerminated (ou un boolean du même genre).
Bien, alors faisons comme sous Dos, créons une variable IsTerminated de type boolean, mettons là à false, et tapons donc ce petit programme :
.
Var IsTerminated:Boolean;
Begin
IsTerminated:=False;
Repeat
???
Until IsTerminated;
End.
.
La question est à présent la suivante : que fait-on dans la boucle et quand décide-t-on qu'il est temps de quitter?
C'est maintenant que je vais me faire taper dessus : après avoir bousillé notre fenêtre, je vais vous dire de la recréer. Mais pas de la même manière. Nous allons créer une fenêtre sans bords (sans barre de titre ni rien), et entièrement maximisée de manière à recouvrir tout l'écran. La raison en est bien simple : une fois que nous aurons un contrôle absolu sur l'écran grâce au directx, il faudra se souvenir que nous restons sous Windows, et que par conséquent une application pourrait décider d'afficher sa fenêtre. Même si on sera en droit d'immédiatement réécraser ce qu'elle vient de faire, ça sera vite très très très moche. C'est pourquoi nous ouvrons une fenêtre recouvrant tout l'écran, et comme nous sommes maîtres chez nous, nous sommes sûr que rien ni personne n'ira jouer avec notre image.
Et puis c'est facile, car grâce à cette fenêtre, on peut recevoir les événements clavier pour savoir quand quitter.
.
c) La fenêtre
Créer sois-même une fenêtre en Delphi n'est pas compliqué. Une fenêtre, c'est après tout une variable comme une autre. Nous allons donc déclarer une variable de type TForm (c'est le type fenêtre). TForm n'est pas un Type mais une classe, nous ne pourrons donc pas directement l'utiliser, il faudra l'initialiser en appelant son constructeur (si vous ne voyez pas de quoi je parle, ça n'est pas grave, il vous suffira de me croire sur parole). Donc on commencera dans le code par MyForm:=TForm.Create(Nil); (en supposant que MyForm soit la variable de type TForm)
Une fois cet objet créé et initialisé, il faut lui expliquer ce qu'on attend de lui. Ici c'est simple, il faut lui dire "pas de bordures et taille maximale", ce qui se traduit par :
 
MyForm.BorderStyle:=bsNone;
MyForm.WindowState:=wsMaximized;
.
Il faut à présent configurer l'événement OnKeyPress : créez la procédure suivante :
.
Procedure Key(Sender: TObject; Var Key: Char);
Begin
IsTerminated:=True;
End;
.
Et associez l'événement à cette procédure :
@MyForm.OnKeyPress:=@Key;
.
Ca n'est pas, loin s'en faut, la manière la plus élégante de procéder mais cela suffira...
Ensuite on peut afficher cette fenêtre :
MyForm.Show;
.
A la sortie de la boucle, il faudra se souvenir de supprimer la fenêtre :
Form.Hide;
Form.Release;
.
Si vous lanciez le programme sans rien mettre dans la boucle principale, vous auriez bien la fenêtre qui s'afficherait sur tout l'écran, mais rien ne se passerait quand vous presseriez une touche : vous seriez bel et bien bloqués. Car si on a dit à Delphi ce qu'il devait se passer quand il recevait un message de pression d'une touche, on ne lui a jamais dit de LIRE ces messages! Il faut donc mettre dans la boucle principale l'instruction Application.ProcessMessages.
Notre application est à présent celle-ci :
.
program Effect;
uses Forms;
var
IsTerminated:Boolean;
MyForm:TForm;
..
Procedure Key(Sender: TObject; Var Key: Char);
Begin
IsTerminated:=True;
End;
.
begin
IsTerminated:=False;
MyForm:=TForm.Create(Nil);
MyForm.BorderStyle:=bsNone;
MyForm.WindowState:=wsMaximized;
@MyForm.OnKeyPress:=@Key;
MyForm.Show;
Repeat Application.ProcessMessages;
Until IsTerminated;
MyForm.Hide;
MyForm.Release;
end.
.
Il ne nous reste qu'à savoir comment afficher quelque chose sur cet écran vierge, ce que nous verrons dans le chapitre 2.
.
d) Approfondissements
Le ProcessMessages peut parraître curieux pour ceux qui n'ont pas lu le tutoriel sur le fonctionnement du multitâche Windows, et si cela est le cas, je vous presse vivement à en prendre connaissance (tain je fais des tournures de phrases intelligentes ce soir). On ferra bien la différence entre ProcessMessages et HandleMessage, qui malgré leurs noms proches ne font pas exactement la même chose :
ProcessMessages va lire UN message et l'envoyer à qui de droit, puis retourne à l'appelant. Si aucun message n'est en attente, il retournera de suite, ce qui fait que la boucle s'excécute sans interruption. Autrement dit, notre application ne se trouve jamais en état de Idle.
HandleMessage lui, va excécuter tous les messages se trouvants dans la queue de l'application, puis retournera à l'appelant. Si aucun message n'était disponible, il ne REVIENDRAIT PAS a l'appelant et partirait en idle jusqu'à l'arrivée d'un message. Autrement dit, si l'utilisateur ne faisait rien (et c'est bien ce que l'on attend de lui dans un effet graphique), notre application se bloquerait tout de suite, et n'afficherai des nouvelles images qu'au moment où, par exemple, la souris aurait été déplacée sur la fenêtre. Sympa...
Pour ceux qui arrivent tout droit du VC++, ou pour ceux qui sont curieux, on peut remplacer ProcessMessages par :
uses (...),Windows;
(...)
var Msg:TMsg;
(...)
Repeat
PeekMessage(Msg,0,0,0,PM_REMOVE);
TranslateMessage(Msg);
DispatchMessage(Msg);
Until IsTerminated;
...
.
Et HandleMessage par :
uses (...),Windows;
(...)
var Msg:TMsg;
(...)
Repeat
GetMessage(Msg,0,0,0);
TranslateMessage(Msg);
DispatchMessage(Msg);
Until IsTerminated;
...
.
Cela peut vous servir si vous décidez également d'afficher votre fenêtre par un autre moyen que TForm (CreateWindowEx), de manière à éviter l'utilisation de l'unit Forms car celle-ci (A la différence de Windows) est toujours inclue en entier dans le .exe et lui donne tout de suite une taille importante (plus de 300k pour pas grand chose).

Retour | PARTIE II

Hit-Parade