Le composant Delphi X

Voilà une suite de composants dont on parle assez dans le monde de la programmation et c'est donc pour cela que je m'y suis mis.

Cette suite sert avant tout à faire des jeux, mais personne ne vous y oblige, on peut aussi créer d'autres types de programmes comme des animations graphiques ou bien autre chose.

Ces composants sont assez simples d'utilisation, ils ne réclament pas un grand niveau Delphi. Même s'il faut avoir une connaissance du système de classe qui est assez utilisé.

Nous allons donc supposer que vous savez manier cette technique, mais si ce n'est pas le cas voilà une brève explication.

Attention ce composant ne fonctionne qu'avec les versions 3,4 et 5 de Delphi. Pour faire fonctionner ce composant sur des versions plus récentes, voici un lien.

Le but de ce tutoriel sera de faire un jeu. Vous le verrez naître à partir de rien si ce n'est une des plus belles suites de composant Delphi (pour moi bien sûr).

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. L'installation

Vous pouvez télécharger DelphiX ici.

Pour l'installer, il vous suffit d'exécuter dans le répertoire..\Bin\.. le fichier qui correspond à votre version de Delphi.

Et voilà c'est fait !!

II. La préparation

Nous allons déjà placer nos composants DelphiX et les décrire 1 à 1, je vous conseille de placer vos composants en lisant ce didacticiel.

Notre fiche va devoir contrôler des composants Delphi X, va devoir passer en plein écran, et d'autres actions graphiques qu'elle n'aurait pas l'habitude de faire. Pour que ceci se fasse plus facilement nous allons devoir changer le type de notre form, allez au début de votre unité et sous la rubrique type vous avez ceci :

 
Sélectionnez
type
TForm1 = class(TForm)

Changez-le en :

 
Sélectionnez
type
TForm1 = class(TDXForm)//La classe DXFORM est similaire à la class form si ce n'est qu'elle enlève des problèmes entre les composants delphiX et la fiche, elle requiert aussi l'unité DXclass que vous devrez ajouter dans le groupe uses.

Ceci n'est pas obligatoire, mais recommandé.

Maintenant nous allons pouvoir poser nos composants :

  • DXdraw : le seul composant visuel de la suite, c'est là que passera tout ce que vous afficherez à l'aide des autres composants DelphiX ;
  • DXimagelist : c'est une bibliothèque où vous mettrez toutes les images que vous utiliserez ;
  • DXtimer : c'est un sosie de notre timer habituel sauf qu'il est spécialement dédié à notre suite DelphiX. Il contient plusieurs options en plus et par exemple une option lag count qui permet d'éviter les retards si votre affichage est ralenti pour une raison inconnue, car il est tout de même beaucoup plus rapide (100 fois +) que notre timer d'origine ;
  • DXsprite : c'est lui qui générera tous les sprites de votre programme, les fera bouger, les détruira ;
  • DXinput : indique les touches utilisées.

Bon nous allons nous arrêter là et nous décrirons les autres plus tard, car ce ne sont pas les seuls, vous l'avez remarqué et en plus j'ai beaucoup de choses à vous faire faire avec ceux-là.

Nous allons voir la préparation de tous ces composants pour que votre programme soit prêt à fonctionner.

II-A. DXdraw

Dans l'inspecteur d'objets, changez ceci :

 
Sélectionnez
align = alclient

Notre DXdraw aura la même taille que notre fiche (c'est le seul composant visuel que nous ayons).

 
Sélectionnez
Option -- fullscreen = true

Pour afficher en plein écran : il faudra aussi enlever les bordures de notre fiche pour qu’elles n'apparaissent pas en plein écran (borderstyle = bsnone).

Pour cacher la souris et la remplacer plus tard par une image nous allons utiliser :

 
Sélectionnez
dxdraw1.cursor := crnone;//Signifie que le curseur sera invisible

Que nous ajoutons dans l'événement create de notre fiche.

Pour ne pas avoir un flash de l'écran à l'ouverture modifier l'autoinitialize en false et dans votre form1create ajoutez :

 
Sélectionnez
Dxdraw1.initialize;//procédure qui prépare l'écran

II-B. DXsprite

Pour fonctionner ce composant a besoin d'une chose très importante le nom du composant DXdraw, indiquer-le lui avec la propriété dxdraw.

II-C. DXtimer

La vitesse : là mettez ce que vous voulez, mais 33 me semble bien, car de toute manière il est dur d'avoir un rafraîchissement de l'image supérieur à celui-ci.

Votre DXtimer va servir à réinitialiser votre écran et vos sprites ainsi que les touches donc dans sa propriété timer ajoutez :

 
Sélectionnez
DXdraw1.Surface.Fill(0); // Supprime tout ce qui composait l'écran.
dxspriteengine1.Move(lagcount);//execute la procédure onmove de chaque sprite.
dxspriteengine1.dead; /Détruit les sprites dont la fonction dead a été activée.
dxspriteengine1.Draw; //Dessine les sprites
dxdraw1.Surface.Canvas.Release;//remet à jour le canvas
dxinput1.Update;//remet à jour les touches tapées, car ce composant ne le fait pas seul.
dxdraw1.Flip;//Remet à jour l'écran

II-D. DXimagelist

Bibliothèque d'images, vous pourrez y mettre toutes les images que vous voulez.

Vous pourrez rendre ces images en partie invisibles en précisant la couleur des parties invisibles.

Et précisez dxdraw1 dans la propriété dxdraw.

Pour les images, installez plutôt ce fichier image.dxg qui contient les images utilisées par ce tutoriel. (En double cliquant sur le composant et sélectionnant ce fichier vous les aurez.)

II-E. DXinput

Rien à signaler si ce n'est que si vous voulez vous servir de touches spécifiques ajoutez-les en double cliquant sur le composant.

II-F. Conclusion

Bon si vous exécutez votre programme à ce moment-là vous aurez un écran vide et pour fermer alt + F4 sera la seule solution donc nous allons ajouter une possibilité de fermer avec la touche echap.

Dans le dxinput mettez button1 en echap (double cliquez sur le composant pour le faire) et dans votre timer ajouter :

 
Sélectionnez
if isbutton1 in dxinput1.States then
//dxinput.state comporte la liste des touches qui ont été pressées depuis la dernière mise à jour de celle-ci(avec dxinput.update) close; //fermeture de l'application si echap est pressée.

Et voilà c'est tout.

III. Le jeu

Oui nous allons passer au jeu en lui-même.

Alors quel jeu va-t-on faire ?

Euh je sais pas moi ?

Tiens on va animer des petits animaux, bonne idée, non je déconne !

Ah, je sais ! Disons un jeu ou l'on tire sur des bêtes qui sortent de terre.

C'est mieux ça ? Bon alors elles ne sortent par de terre elles voleront.

De toute manière vous n'avez pas le choix c'est moi qui décide et en plus ce n'est qu'un exemple pour qu'à la fin vous puissiez faire votre propre jeu.

IV. Les sprites

Bien donc commençons.

Les sprites c'est quoi exactement ?

Pour moi, des imaged animées ou non qui se déplacent ou non et qui réagissent ensemble ou non. Par exemple c'est la fumée de la cheminée dans ZELDA, les combattants dans STREET FIGHTER et aussi la balle et les barres dans PONG.

Donc dans notre jeu quels seront les sprites ?

Eh bien il y aura un viseur avec lequel on tirera, un sprite de bête le même pour toutes et il y aura aussi notre fond qui sera un backroundsprite (il sera affiché en mosaïque).

Tout notre jeu sera composé de sprites.

Alors voilà pour créer un sprite il y a plusieurs méthodes, on peut le créer sans rien déclarer, mais nous ne pourrons rien en faire, il sera à l'écran et c'est tout. Aucune collision, aucun mouvement, rien. On peut aussi le déclarer là, deux autres possibilités entrent en compte.

On peut le créer sans garder de référence, mais utiliser une variable pointeur permet d'accéder facilement à ses propriétés.

Dans les deux cas, il pourra bouger et réagir aux collisions.

Notre fond sera créé puis plus jamais nous n'y toucherons donc il sera créé sans avoir été déclaré.

Les ennemis seront créés puis eux non plus ne seront plus jamais utilisés, mais ils se déplaceront tout de même donc déclarons-les sans leur attribuer de variable.

Le viseur lui bouge et il faudra pouvoir y accéder lorsque la souris bougera donc déclarons-le ainsi qu'une variable de son nom.

V. Le fond

Rien de plus simple pour le créer allons dans l'événement create de notre fiche et ajoutons ces quelques lignes :

 
Sélectionnez
with TBackgroundSprite.Create(DXSpriteEngine1.Engine) do
//nous créons un fond et comme une fois qu'il sera créé nous ne pourrons plus y accéder(puisque nous n’avons aucun pointeur ou variable pour connaître sa position) nous allons utiliser with pour préciser ses propriétés
begin
SetMapSize(1, 1);//marque la taille du sprite, ici 1 image sur 1.
Image:=Form1.DXImageList1.Items.Find('fond');//l'image du fond sera celle qui se trouve dans DXImageList1 et qui porte le nom fond
Width := Image.Width;
Height := Image.Height;//la hauteur et la largeur du sprite seront égales à celles de l'image.
Z := 0;
{Z correspond à la couche sur laquelle est notre image si l'on fait un sprite sur la couche 1 il sera visible, mais si ont fait un sprite sur la couche -1 il sera invisible.}
Tile := true //notre image sera répétée sur tout l'écran.
end;

Voilà vous n'aurez plus rien à y faire le composant spriteengine s'occupe de l'afficher à l'écran si vous avez bien pensé à mettre spritengine.draw dans le timer (ce que nous avons fait au début).

Si vous exécutez, vous verrez un joli fond.

Petite précision sur setmapsize si nous avions mis tile := false alors nous n'aurions eu qu'un exemplaire de notre image et si avec ceci il avait eu setmapsize(2,2) on aurait eu un rectangle de 2 images sur 2 qui composera notre fond.

VI. Le viseur

Donc notre viseur devra lui être déclaré, car il a certaines propriétés qui devront être déclarées sous forme de procédure. Alors dans type ajoutons :

 
Sélectionnez
Tviseur = class(TImageSprite);

Et dans var une variable

 
Sélectionnez
viseur : Tviseur;

Ensuite pour le créer dans l'événement create de notre fiche de la même manière que notre fond nous allons ajouter :

 
Sélectionnez
viseur := Tviseur.Create(DXSpriteEngine1.Engine);
with viseur do
begin
Image:=Form1.DXImageList1.Items.Find('viseur');//indique l'image
Width := Image.Width; //reprend la largeur de l'image
Height := Image.Height; // et sa hauteur ce qui fait correspondre la taille du sprite avec celle de l'image.
Z := 2;
end;

Vous l'avez vu notre Z est 2, car il faut bien que le viseur soit supérieur au fond, sinon on ne le verrait pas.

Pour le reste pas de problème.

Ensuite dans notre dxdraw dans l'événement onmousemove ajoutons :

 
Sélectionnez
viseur.X:= X;
viseur.Y := Y;

Et voilà.

Mais pourquoi avoir déclaré une classe intermédiaire nommée Tviseur ?

Quelle est cette procédure si importante ?

Vous comprendrez plus tard.

VII. Nos petites bêtes

Elles seront mouvantes et au départ elles ne seront pas à l'écran.

On les créera donc à l'extérieur, hihi pas sur votre table de salon, mais toujours dans le canvas de l'écran seulement comme c'est une table de dessin virtuelle on peut dessiner à côté chouette non ?

Alors voilà nos Tenemi (nos bébêtes quoi).

 
Sélectionnez
Tenemi = class(TImageSprite)
public
sens : boolean; //variable qui indiquera plus tard le sens de direction de nos bêtes
procédures Domove (movecount : integer); override; //procédure propre au timagesprite donc utilisons override pour remplacer celle d'origine
end;

Alors vous en dites quoi ? Elles ont un sens de direction et une procédure de déplacement qui sera exécuté à chaque dxspriteengine1.Move(lagcount), donc tous les temps de millisecondes.

Bon comme nos ennemis seront créés régulièrement nous allons créer une procédure de création d'ennemis.

 
Sélectionnez
procedure tform1.creaenemi;//pensez à déclarer procédure creaenemi; dans notre tform
begin
//---------------------UN ENEMI-----------------
with Tenemi.Create(form1.DXSpriteEngine1.Engine) do
begin
Image:=Form1.DXImageList1.Items.Find('enemi');//déclare l'image
Width := Image.Width;
Height := Image.Height;
sens := random(2) = 1 ;//choisit aléatoirement le sens de notre sprite
if sens then X:=screen.width+image.width // choisit sa position en fonction du sens pour être sûr qu'il soit caché au début
else
X := -(image.width);//en dehors de l'écran
Y:=random(screen.height);
//positionnement aléatoire
Z:=2;
pixelcheck := true; //cette variable spécifie si les collisions doivent être prises à l'image près (donc s'il y a des transparences elles seront ignorées) ou si cela doit être au pixel près.
end;
//--------------------------FIN--------------------
end;

Ensuite il faut s'occuper du déplacement de nos ennemis.

 
Sélectionnez
procedure Tenemi.Domove (movecount : integer);
begin
inherited Domove(movecount);
 
{les sprites peuvent être animés avec les propriétés anim et Pattern.
dans la source de DelphiX on remarque que le changement d'image (l'animation) se fait dans le domove de timagesprite.
Mais pour que cette action d'animation se fasse, il faut appeler la fonction de l'ancêtre de notre Tenemi, Timagesprite.
Car pour ceux qui ne le savent pas inherited appelle les routines de l'ancêtre.
Ici on pourrait juste mettre inherited; car cela appelle la procédure du même nom}
//ici il n'y aura pas d'animation de sprite je vous expliquerai plus loin pourquoi.
 
if sens then X:= X-3 else X := X+3;//déplacement du sprite en fonction du sens choisi
if (X =screen.Width) or (X <= 0) then sens := not sens;// si le sprite est arriver en dehors de l'écran il change de sens
end;

Bien maintenant comment vont être créés nos ennemis ?

Nous allons créer une variable reste : integer.

Elle indiquera le nombre d'ennemis à créer. Au début dans l'événement create de notre fiche donnons-lui la valeur 5. Puis dans notre Ttimer ajoutons ceci :

 
Sélectionnez
if (reste 0) then
begin
dec(reste);
form1.creaenemi;
end;

Lancez l'application et vous aurez droit à un superbe jeu.

VIII. ?

Bah aH AH je vous ai eu, il ne marche pas le jeu, mais pourquoi à votre avis ?

Oh je vais vous le dire, car vous avez bien dû trouver (en plus si cela se trouve vous avez corrigé le problème).

Bien sûr vous n'avez jamais dit quand il faut détruire les ennemis.

Donc nous allons regarder quand cela serait utile.

Eh bien le mieux c'est le viseur, car c'est lorsque l'on clique qu'on vérifiera le problème et si chaque bête devait le faire ce serait trop long.

Donc ajoutons à notre viseur une option de vérification des collisions.

Avant :

 
Sélectionnez
Tviseur = class(TImageSprite);

Et maintenant :

 
Sélectionnez
Tviseur = class(TImageSprite)
public
procédure DoCollision (Sprite : TSprite; var Done : boolean); override;
//vérifie la collision entre notre viseur et nos ennemis
end;

Et voilà pourquoi une classe Tviseur.

C'est mieux, mais que faut-il faire en cas de collision et bien continuons.

 
Sélectionnez
procédure Tviseur.Docollision(Sprite : TSprite; var Done : boolean);
begin
inherited Docollision(sprite,done);
if (sprite is tenemi) then
begin
tenemi(sprite).dead;//on détruit l'ennemi
inc(reste)//reste indique le nombre d'ennemis à créer
 
end;
 
end;

Puis dans l'événement onclick de notre dxdraw on ajoute :

 
Sélectionnez
viseur.Collision;

Et voilà c'est bon là vous pourrez jouer à votre petit jeu.

IX. C'est triste un jeu de ce genre

Bah oui c'est triste, pas de score, d'explosions, d'animations, de sons. Alors je vais donc vous passer en revue quelques améliorations.

IX-A. Le sens

Il nous faudrait deux images d'ennemi, car s'il vient de la droite ou de la gauche, il n'a pas la même tête donc ajoutons une image enemi2 (dans notre dximagelist) et dans notre enemy.domove on fait ceci :

IX-A-1. Avant

 
Sélectionnez
procedure Tenemi.Domove (movecount : integer);
begin
inherited Domove(movecount);
if sens then X:= X-3 else X := X+3;
if X >=screen.Width then sens := true;
if X <= 0 then sens := false;
end;

IX-A-2. Après

 
Sélectionnez
procedure Tenemi.Domove (movecount : integer);
begin
inherited Domove(movecount);
if sens then X:= X-3 else X := X+3;
if X =screen.Width then
begin
sens := true;
Image:=Form1.DXImageList1.Items.Find('enemi');//en changeant de sens on change l'image
end;
if X <= 0 then
begin
sens := false;
Image:=Form1.DXImageList1.Items.Find('enemi2');
end;
end;

C'est tout.

IX-B. Le son

Pourquoi pas !!

À chaque clic de souris faire retentir un bruit (coup de feu…).

Ajoutons un DXWaveList (base de données de sons) et un DXSound (lecteur).

Le dxwavelist est similaire à un dximagelist sauf que c'est une banque de données qui comporte des sons et non pas des images.

Le dxsound lui se compare plus à un dxdraw.

Dans notre DXWaveList, on met dxsound1 dans la propriété dxsound et ont met notre son que l'on a nommé bang ici (en double cliquant sur le composant et sélectionnant ce fichier vous l'aurez tout prêt).

Ensuite il vous reste juste à ajouter cette ligne dans votre l'événement onclick de notre dxdraw1 :

 
Sélectionnez
form1.DXWaveList1.Items.find('bang').Play(false);

La propriété qui accompagne la procédure play signifie qu'il faut attendre la fin du son pour continuer les autres routines.

IX-C. Les Explosions

Ben oui ce n'est pas drôle, la bête elle disparaît sans rien, alors on va la faire exploser (enfin un truc dans ce genre hi hi!).

Alors créons une nouvelle classe :

 
Sélectionnez
TBoom = class(TImageSprite)
public
procedure Domove (movecount : integer); override;
end;

Notre Tboom sera une animation de quelques images donc pour le créer on ajoutera une procédure boom(LX, LY :integer) (les variables LX et LY correspondent à la position de notre explosion, celle de la bête).

On écrit cette procédure :

 
Sélectionnez
procedure Tform1.Boom(LX,LY:integer);
begin
with Tboom.Create(form1.DXSpriteEngine1.Engine) do
begin
X:=LX;
Y:=LY;
Z:=2;// même hauteur que les bêtes.
Image:=Form1.DXImageList1.Items.Find('boom');
Width := Image.Width;
Height := Image.Height;
AnimCount:=Image.PatternCount;//nombre d'images dans l'animation
AnimLooped := false; //animation en boucle
AnimPos := 0;//image de départ pour l'animation
AnimSpeed := 1/10; //vitesse d'animation
end;
end;

Alors on va décrire un peu le truc.

On a créé un sprite Tboom avec l'image boom. anim count correspond au nombre d'images animlooped indique si oui ou non ce sprite part en boucle.

Animpos l'image sélectionnée. Animspeed la vitesse d'animation.

Patterncount correspond au nombre d'images dans notre image boom (car elle est divisée en plusieurs images). Pour décrire ces images, modifiez la propriété pattern dans votre image (cela correspond aux dimensions d'une image).

Notre sprite sera créé à chaque destruction d'une bête, donc ajoutons dans la collision de notre sprite viseur cette petite ligne :

 
Sélectionnez
Form1.boom(sprite.X,sprite.Y);

bien sûr il faut ajouter une procédure domove pour lui indiquer quand il doit se détruire.

Donc voilà notre domove :

 
Sélectionnez
procedure Tboom.Domove (movecount : integer);
begin
inherited Domove(movecount);
if animpos = animcount-1 then dead;
end;

Ceci fait en sorte que la position de l'animation soit égale à celle de la dernière image du sprite.

IX-D. Le Score

Et oui, il faut un score pour savoir où on en est.

Donc commençons par déclarer une variable :

 
Sélectionnez
Score : integer;

Ensuite dans l'événement create de notre fiche, on met score := 0 et à chaque collision de viseur inc(score).

Mais on a notre score. Maintenant il faut l'afficher donc dans l'événement timer du timer avant de dessiner notre écran, donc après avoir déplacé les sprites (au cas où l'un d'entre eux est mort) et dessiné ceux-ci, on met cette ligne qui écrit notre score en haut à gauche de l'écran.

 
Sélectionnez
t := inttostr(score);
dxdraw1.Surface.Canvas.TextOut(1,1,'SCORE : '+t);

Bien sûr déclarez t en tant que string.

Vous pouvez bien sûr changer la taille ou la police ect (couleur & co…).

Par exemple :

 
Sélectionnez
dxdraw1.Surface.Canvas.Font.Color := clblue;
dxdraw1.Surface.Canvas.Font.Height := 40 ;

Ce qui donne un truc dans ce genre :

SCORE : 0

X. Mais pourquoi ils ne sont pas animés nos ennemis

Eh bien cela aura été avec plaisir, mais malheureusement un bogue dans DelphiX nous en empêche.

Je vais donc vous donner ce que j'ai trouvé au sujet de ce bogue.

Lorsque que l'image n'est pas sur sa position 0 de l'animation et que vous avez choisi pixelcheck (ce qui est fait le plus souvent), eh bien la collision est toujours fausse.

Enfin je ne vais pas vous ennuyer avec cela amusez-vous bien avec votre nouveau joujou euh… pardon composant :-).

XI. Petit récapitulatif

Oui, un petit bilan s'impose pour que tout soit bien en ordre.

Pour pouvoir commencer, il faut mettre les composants.

Ensuite il faut préparer la fiche (dxform,borderstyle…) et dans notre timer mettre les actions de réactualisation des sprites et de l'écran(enfin le dxdraw).

Pour afficher des sprites il faut les créer.

Pour cela on peut les créer avec ou sans référence et avec ou sans classe.

Dans delphiX il y a plusieurs types de sprite, mais le plus utilisé est Timagesprite.

Chaque Timagesprite a entre autres une propriété domove et une propriété docolision. Ces propriétés sont importantes, car ce sont elles qui rendront nos sprites mouvants et réactifs aux autres sprites donc retenez bien ces deux propriétés.

Et pour finir on peut toujours utiliser directement le dxdraw et écrire dessus (avec le canvas) comme nous l'avons fait, mais nous pouvons aussi dessiner avec des procédures du dximagelist, draw (dessine) drawadd (dessine avec des effets de transparence(alpha blending)) et quelques autres que vous découvrirez en regardant les exemples fournis avec delphiX vous y découvrirez par exemple un joli shoot'em up (Samples\Sprite\Shoot) ou un exemple dans lequel toutes les manières de dessiner une image (en vague, en alpha, en agrandissement, etc.) sont présentes.

C'est fini il ne vous reste plus qu'à télécharger les exemples de ce tutoriel si vous en avez besoin et si vous avez des problèmes des critiques ou autre chose voici mon mail, .

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2013 Bodman . Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.