IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Utilisation de DirectX sous Delphi : Asphyre 4.1


précédentsommairesuivant

III. La 2D

III-A. Introduction

La 2D est l'aspect le plus accessible dans le développement de jeu. En revanche, cela ne veut pas dire que c'est facile !
Certains d'entre vous se souviendront d'une époque où le jeu de plateforme et le shoot-them-up étaient rois. Malgré leur âge, ces jeux utilisaient des effets parfois saisissants pour immerger encore plus le joueur. Afficher un personnage reste facile, l'intégrer dans un décor réactif et le faire bouger selon les actions du joueur est bien plus difficile ! Même si la méthode d'affichage reste la même.
C'est pour cette raison que nous verrons dans cette partie les différentes façons d'afficher des textes, des formes géométriques et des images (dit « sprites ») grâce à Asphyre.

Avant de commencer, il faut savoir que la version 4.1 (à la différence de la version Casual) nécessite une DLL pour fonctionner. Celle-ci est fournie dans le répertoire DLL et s'appelle « d3dx9_31.dll ».

Cette DLL est normalement fournie avec DirectX 9.0c (version 2006 donc incluse dans la dernière mise à jour mars 2008). Certains postes qui n'ont pas été mis à jour peuvent ne pas avoir cette DLL.

Comme pour chaque DLL indispensable à un programme, plusieurs choix s'offrent à vous :

- déplacer cette DLL une bonne fois pour toutes dans le répertoire System32 de Windows. Cela permettra à tous vos programmes de fonctionner sans vous en soucier, mais vous risquez de l'oublier lors du déploiement sur d'autres machines qui n'en sont pas forcément équipées ;

- placer cette DLL dans le répertoire de chacun de vos programmes. De cette façon, vous ne risquez pas de l'oublier. En revanche, il faut la placer dans le répertoire à chaque nouveau programme.

Les deux méthodes fonctionnent, choisissez celle qui vous convient le mieux. De toute façon, sans cette DLL, Asphyre refusera de démarrer dès l'initialisation.

III-B. Asphyre en 2D : Comment ça marche ?

Asphyre propose des méthodes à appeler pour initialiser, modifier toutes sortes de paramètres. En y regardant bien, le système n'est pas très différent d'Asphyre eXtreme sauf qu'aucun objet n'est visible, ce qui peut paraitre un peu déroutant au début.
En appelant la méthode Initialize, on initialise (oui le nom est bien choisi !) le moteur d'affichage et Asphyre se charge de créer tout ce dont on a besoin.
En fin de programme, il suffit d'appeler Finalize pour détruire tous les objets créés et libérer la surface utilisée.

III-B-1. Initialiser

Cette initialisation est indispensable pour tout projet utilisant Asphyre et DirectX en général. Il ne faut pas l'oublier !

L'initialisation se fait de la plus simple des façons. Il suffit en effet d'appeler :

 
Sélectionnez
Devices.Initialize(CfgEvent: TScreenConfigEvent; Tag: TObject): Boolean;

Cette méthode Initialize accepte deux paramètres :

  • CfgEvent : permet de spécifier une méthode qui sera exécutée au début de l'initialisation ;
  • Tag : permet de conserver un lien vers le composant qui affiche (en général, la Form principale).

Le booléen renvoyé permet de vérifier que l'initialisation s'est correctement effectuée. Si ce n'est pas le cas, on peut refermer l'application puisque Asphyre n'affichera rien.

La méthode de type TScreenConfigEvent s'écrit de cette manière et devrait être déclarée en private puisque, théoriquement, seule la fiche d'affichage devrait y avoir accès :

 
Sélectionnez
type
  TMainFrm = class(TForm)
    ...
  private
    procedure ConfigureDevice(Sender: TAsphyreDevice; Tag: TObject;
     var Config: TScreenConfig);
  end;

...

procedure TMainFrm.ConfigureDevice(Sender: TAsphyreDevice; Tag: TObject;
 var Config: TScreenConfig);
begin
end;

Cette méthode ConfigureDevice aura pour but principal de paramétrer le composant qui affichera, son type d'affichage, sa taille, s'il est en mode fenêtré, etc.

Pour paramétrer votre affichage, il suffit, dans cette procédure, de modifier le paramètre Config passé en variable.

Typiquement, voici un exemple de ce que l'on peut trouver dans l'un des exemples fournis avec Asphyre (Extrait de BasicShdr) :

 
Sélectionnez
procedure TMainFrm.ConfigureDevice(Sender: TAsphyreDevice; Tag: TObject;
 var Config: TScreenConfig);
begin
  Config.WindowHandle:= Self.Handle; //Spécifie le handle de la Form pour l'affichage
  Config.HardwareTL  := True; //Active le Transform and Lightning matériel

  Config.Width   := ClientWidth; //Taille de la largeur de l'affichage
  Config.Height  := ClientHeight; //Taille de la hauteur de l'affichage
  Config.Windowed:= False; //Définit le mode fenêtré. Ici à faux
  Config.BitDepth:= bd24bit; //Définit la profondeur des couleurs. ici 24 bits
end;

De cette façon, ces données seront utilisées lors de l'initialisation.

Il est également possible d'ajouter des méthodes lorsque certains événements inaccessibles autrement doivent être utilisés :

  • EventDeviceCreate : se déclenche après que l'initialisation ait été effectuée.
    Utilisé pour charger les ressources statiques ;
  • EventDeviceDestroy : se déclenche juste avant la finalisation
    De la même façon, les ressources statiques doivent être libérées ici ;
  • EventDeviceReset : se déclenche quand le dispositif (Device) est placé en état fonctionnel ou qu'il est récupéré lors d'une perte.
    Toutes les ressources non persistantes doivent être créées ici ;
  • EventDeviceLost : se déclenche lorsque le dispositif perd son état fonctionnel ou bien après qu'il ait été perdu.
    Toutes les ressources créées dans DeviceReset doivent être libérées ici ;
  • EventDeviceFault : se déclenche lorsqu'une erreur fatale se produit et que l'application ne peut pas continuer. On peut l'utiliser par exemple, pour afficher un message d'erreur et clore l'application ;
  • EventBeginScene : se déclenche après l'appel réussi de IDirect3DDevice9.BeginScene ;
  • EventEndScene : se déclenche avant l'appel à IDirect3DDevice9.EndScene ;
  • EventSymbolResolve : se déclenche avant quand une ressource (image par exemple) est en train d'être créée et chargée ;
  • EventResolveFailed : se déclenche lorsque la création d'une ressource n'a pas fonctionné pour diverses raisons ;

Pour utiliser ces événements, il faut créer une procédure de type TAsphyreEventCallback dont voici sa syntaxe :

 
Sélectionnez
TAsphyreEventCallback = procedure(Sender: TObject; EventParam: Pointer;
  var Success: Boolean) of object;

Une fois les procédures écrites, il faut appeler les méthodes Subscribe de chacun des événements que vous voulez utiliser.
Par exemple, si vous voulez modifier le Caption de la fiche principale lorsque le moteur est chargé, il faut faire comme ceci :

 
Sélectionnez
procedure TMainFrm.OnDeviceResetCallback(Sender: TObject;
  EventParam: Pointer; var Success: Boolean);
begin
  Caption := 'OK';
end;

Écrivez ensuite, dans la procédure ConfigureDevice :

 
Sélectionnez
procedure TMainFrm.ConfigureDevice(Sender: TAsphyreDevice; Tag: TObject;
  var Config: TScreenConfig);
begin
  Config.WindowHandle:= Self.Handle; //Cette spécification est nécessaire sinon, l'initialisation ne réussit pas!

  EventDeviceReset.Subscribe(OnDeviceResetCallback, Sender);
end;

Ainsi, lorsque l'état du dispositif passera à l'état « En cours de fonctionnement » (fonctionnel), 'OK' sera affiché dans la barre de titre de la fiche.

Pour finir, il suffit de lancer la méthode Initalize de cette façon :

 
Sélectionnez
procedure MainFrm.Create(Sender: TObject);
begin
 if (not Devices.Initialize(ConfigureDevice, Self)) then
  begin
   MessageDlg('Asphyre n''a pas pu s''initialiser!', mtError, [mbOk], 0);
   Close();
   Exit;
  end;
end;

Ainsi, si Asphyre n'a pas réussi son activation pour une raison quelconque, la fiche principale se ferme.

III-B-2. Le Timer

Le timer est une notion importante en programmation DirectX.
En effet, lorsque vous développez une application qui doit dessiner par exemple quelque chose sur la fiche à l'aide de son Canvas (surface qui permet de dessiner du texte et des formes géométriques de type TCanvas), il convient d'écrire le code du dessin dans le OnPaint de la fiche. Lorsque la fiche aura besoin de se redessiner, elle appellera cette méthode. Il est néanmoins parfois nécessaire de forcer le dessin avec un appel à Invalidate, Repaint, etc.
Ici, rien de tout ça : un timer déclenche à chaque cycle la procédure de dessin. L'impact direct est que chacune de vos modifications (par exemple, la position d'une image) sont visibles quasiment en temps réel (Parler de temps réel est un abus de langage : en informatique, rien ne se fait en temps réel. En revanche, le temps entre la modification et la visualisation est tellement court que l'on parle de temps réel).

Une fois l'initialisation effectuée et réussie, il faut déclencher le timer :

 
Sélectionnez
Timer.OnTimer  := TimerEvent;
 Timer.OnProcess:= ProcessEvent;
 Timer.MaxFPS   := 100;
 Timer.Enabled  := True;

Comme vous pouvez le constater, ce Timer est déjà défini dans l'unité AsphyreTimer. Ceux qui ont déjà utilisé la version eXtreme d'Asphyre auront sans doute reconnu les événements OnTimer et OnProcess indispensables au fonctionnement d'Asphyre. En voici un descriptif :

  • Enabled : permet exactement la même chose que le Enabled du TTimer de la VCL, c'est-à-dire, démarrer le Timer ;
  • OnTimer : se déclenche à chaque cycle.
    En règle générale, cet événement doit déclencher le rendu de la scène (2D ou 3D) et le Process du Timer ;
  • OnProcess : est directement déclenché dans l'événement OnTimer. Regroupe toutes les opérations effectuées sur les objets de la scène (déplacement, rotation, etc.) ;
  • MaxFPS : permet de spécifier le nombre maximal de FPS (Frames Per Second : images par seconde). Si vous ne voulez pas le limiter, spécifiez un grand nombre.

Ainsi, il est nécessaire d'écrire deux méthodes réagissant à OnTimer et à OnProcess.
Vous me direz, pourquoi découper ce OnTimer en OnTimer et en OnProcess ? La raison principale est purement pratique : vous verrez qu'à la longue, le code permettant de gérer les différents sprites enfle rapidement. De cette façon, le code est rangé de façon à le retrouver plus facilement. Dans OnTimer, on déclenche le rendu et OnProcess. Dans OnProcess, on a tous les traitements à effectuer.
Ces méthodes sont de type TNotifyEvent et, donc leur écriture se fait comme ceci :

 
Sélectionnez
procedure TimerEvent(Sender: TObject);
procedure ProcessEvent(Sender: TObject);

On les mettra encore une fois dans la section private de la fiche pour les mêmes raisons que la méthode ConfigureDevice.

III-B-2-a. OnTimer

Cet événement se produit automatiquement à chaque cycle du Timer. Comme nous l'avons vu plus haut, c'est ici que l'on appelle le rendu (dessin) de la scène et qu'on appelle la méthode OnProcess à l'aide de la méthode Process de Timer.

 
Sélectionnez
procedure TMainFrm.TimerEvent(Sender: TObject);
begin
  DefDevice.Render(DrawView, Self, $000000);
  Timer.Process;
end;

Analysons plus en détail cette procédure.

 
Sélectionnez
DefDevice.Render(DrawView, Self, $000000);
  • DrawView : méthode appelée pour dessiner des données complémentaires. on peut comparer cette procédure à la procédure OnPaint de TForm : c'est là-dedans qu'on dessine les formes, les textes, etc.
  • Self : objet sur lequel dessiner. Ici, la fiche principale.
  • $000000 : couleur de fond. Ici, du noir.

La méthode DrawView s'écrit de la façon suivante :

 
Sélectionnez
procedure TMainFrm.DrawView(Sender: TAsphyreDevice; Tag: TObject);
begin
...
end;

Ensuite, nous avons :

 
Sélectionnez
Timer.Process();

Cette ligne a pour unique fonction de déclencher OnProcess du Timer.

III-B-2-b. OnProcess

Cet événement regroupe toutes les opérations à effectuer sur les différents objets affichés (ou non). Nous n'en sommes qu'au début du tutoriel, nous verrons son utilisation plus loin. Néanmoins, nous le déclarerons même s'il est vide pour acquérir le bon réflexe.

III-B-3. Finaliser

Une fois le programme terminé, il faut libérer toutes les ressources allouées lors de l'initialisation.
Pour cela, rien de plus simple :

 
Sélectionnez
procedure TMainFrm.FormDestroy(Sender: TObject);
begin
 Devices.Finalize;
end;

Et c'est tout ! Évidemment, si vous avez des ressources que vous avez créées au démarrage, il faut les libérer en choisissant l'événement adéquat comme vu dans la section III.B.1Initialiser.

III-C. Premier programme: « Hello World! »

Passons maintenant à la création d'un petit programme !

Pour respecter la tradition de la programmation, nous allons faire un simple programme qui affiche « Hello World » en blanc sur un fond noir, le tout en mode fenêtré.

Tout d'abord, créez une nouvelle application avec fiche et sauvegardez-la.
Appelez la fiche principale MainFrm.

Première chose à faire: initialiser Asphyre. Comme nous l'avons vu, voici ce dont nous avons besoin :

  • OnCreate de la fiche afin d'initialiser Asphyre ;
  • une procédure ConfigureDevice qui sera appelée lors de l'initialisation pour paramétrer l'affichage ;
  • une procédure TimerEvent pour le OnTimer du Timer ;
  • une procédure ProcessEvent pour le OnProcess du Timer ;
  • une procédure RenderCallback pour dessiner le texte lors de l'appel à la méthode Render ;
  • OnDestroy de la fiche afin de finaliser Asphyre.
 
Sélectionnez
procedure TMainFrm.ConfigureDevice(Sender: TAsphyreDevice; Tag: TObject;
  var Config: TScreenConfig);
begin
  Config.Width   := ClientWidth;
  Config.Height  := ClientHeight;
  Config.Windowed:= True;

  Config.WindowHandle:= Self.Handle;
  Config.HardwareTL  := true;
end;

Voilà pour le ConfigureDevice.
Pour le OnCreate, on aura donc l'initialisation d'Asphyre et du Timer, ainsi que la création de la police de caractères :

 
Sélectionnez
procedure TMainFrm.FormCreate(Sender: TObject);
begin
  if (not Devices.Initialize(ConfigureDevice, Self)) then
  begin
    MessageDlg('Asphyre n''a pas pu s''initialiser!', mtError, [mbOk], 0);
    Close();
    Exit;
  end;

  DefDevice.SysFonts.CreateFont('sys:tahoma', 'tahoma', 9, False, fwtHeavy,
    fqtClearType, fctAnsi);

  Timer.OnTimer  := TimerEvent;
  Timer.OnProcess:= ProcessEvent;
  Timer.MaxFPS   := 100;
  Timer.Enabled  := True;
end;

Nous verrons plus en détail la façon d'écrire un texte plus loin dans le tutoriel.

Passons ensuite aux deux méthodes du Timer :

 
Sélectionnez
procedure TMainFrm.ProcessEvent(Sender: TObject);
begin
  //Rien
end;

procedure TMainFrm.TimerEvent(Sender: TObject);
begin
  DefDevice.Render(RenderCallback, self, $000000);
end;

Dans cette procédure RenderCallback, le texte sera écrit. Voici cette procédure :

 
Sélectionnez
procedure TMainFrm.RenderCallback(Sender: TAsphyreDevice; Tag: TObject);
begin
  Sender.SysFonts.Font['sys:tahoma'].TextOut('Hello world!',
    4, 10, $FFFFFFFF);
end;

Comme dit plus haut, la méthode d'écriture sera détaillée plus loin.

Évidemment, il ne faut surtout pas oublier de libérer toute la mémoire que l'on a allouée :

 
Sélectionnez
procedure TMainFrm.FormDestroy(Sender: TObject);
begin
  Devices.Finalize;
end;

Compilez et exécutez. Vous devriez voir ceci :

Image non disponible
Votre premier programme avec Asphyre

Voici le code complet :

 
Sélectionnez
interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, AsphyreDevices;

type
  TMainFrm = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Déclarations privées }
    procedure ConfigureDevice(Sender: TAsphyreDevice; Tag: TObject;
      var Config: TScreenConfig);
    procedure TimerEvent(Sender: TObject);
    procedure ProcessEvent(Sender: TObject);
    procedure RenderCallback(Sender: TAsphyreDevice; Tag: TObject);
  public
    { Déclarations publiques }
  end;

var
  MainFrm: TMainFrm;

implémentation

uses
  AsphyreEvents, AsphyreTimer, AsphyreSystemFonts;

{$R *.dfm}

procedure TMainFrm.ConfigureDevice(Sender: TAsphyreDevice; Tag: TObject;
  var Config: TScreenConfig);
begin
  Config.Width   := ClientWidth;
  Config.Height  := ClientHeight;
  Config.Windowed:= True;

  Config.WindowHandle:= Self.Handle;
  Config.HardwareTL  := true;
end;

procedure TMainFrm.FormCreate(Sender: TObject);
begin
  if (not Devices.Initialize(ConfigureDevice, Self)) then
  begin
    MessageDlg('Asphyre n''a pas pu s''initialiser!', mtError, [mbOk], 0);
    Close();
    Exit;
  end;

  DefDevice.SysFonts.CreateFont('sys:tahoma', 'tahoma', 9, False, fwtHeavy,
    fqtClearType, fctAnsi);

  Timer.Enabled  := True;
  Timer.OnTimer  := TimerEvent;
  Timer.OnProcess:= ProcessEvent;
  Timer.MaxFPS   := 100;
end;

procedure TMainFrm.FormDestroy(Sender: TObject);
begin
  Devices.Finalize;
end;

procedure TMainFrm.ProcessEvent(Sender: TObject);
begin
  //Rien
end;

procedure TMainFrm.RenderCallback(Sender: TAsphyreDevice; Tag: TObject);
begin
  Sender.SysFonts.Font['sys:tahoma'].TextOut('Hello world!',
    4, 10, $FFFFFFFF);
end;

procedure TMainFrm.TimerEvent(Sender: TObject);
begin
  DefDevice.Render(RenderCallback, self, $000000);
  Timer.Process;
end;

end.

Comme vous pouvez le voir, aucun composant n'est nécessaire. Toutefois, il faut tout de même déclarer les unités qui contiennent les routines que l'on a utilisées. Puisque justement, il n'y a pas de composants, leur rajout ne se fait pas de façon automatique.
Seul AsphyreDevices nécessite d'être déclarée en interface puisque les procédures que l'on ajoute contiennent des types qui y sont déclarés. Toutes les autres peuvent être déclarées en implémentation.

III-C-1. Bug de librairie d3dx9d_31.dll

Si vous êtes sous Delphi 2007 ou RAD Studio 2007, il se peut que vous ayez une erreur telle que celle-ci :

Image non disponible
Cette application n'a pas pu démarrer, car d3dx9d_31.dll est introuvable. La réinstallation de cette application peut corriger ce problème

C'est un bug connu d'Asphyre. La raison est simple: Yuriy Kotsarenko utilise le SDK de DirectX qui propose cette DLL pour le déboguage. Sa version Release est nommée « d3dx9_31.dll » (sans le dernier d). Delphi 2007, par défaut est en configuration de Debug. Dans le code de DXDX9.pas, si le mode debug est activé, c'est la DLL « d3dx9d_31.dll » qui sera utilisée, sinon, c'est l'autre.

Pour le résoudre, vous avez plusieurs solutions :

  • récupérez cette DLL : allez sur MSDN , téléchargez le SDK et installez-le. Normalement, la librairie devrait s'ajouter dans System32 ;
  • copiez « d3dx9_31.dll » et renommez-la en « d3dx9d_31.dll » ;
  • passez votre projet en configuration Release (Projet\Options… section Compilation puis dans la boite déroulante « Config construction » en haut à droite, choisissez Release) puis reconstruisez (pas recompilez) le tout ;
  • la méthode la plus simple : rendez-vous dans l'unité D3DX9.pas d'Asphyre et allez ligne 82. Vous y trouverez ce code :
 
Sélectionnez
d3dx9MicrosoftDebugDLL = 'd3dx9d_31.dll';

Vous l'avez compris : ce code définit la DLL lorsque le mode Debug est actif. Copiez cette ligne (ou remplacez-la) par ceci :

 
Sélectionnez
// On laisse la ligne commentée au cas où, mais ce n'est pas forcément 
// nécessaire si vous ne comptez pas utiliser cette librairie de débogage
//  d3dx9MicrosoftDebugDLL = 'd3dx9d_31.dll';
  d3dx9MicrosoftDebugDLL = 'd3dx9_31.dll';

Cette DLL ne peut pas être distribuée. Pour pallier ce problème, vous pouvez télécharger les DLL libres de droits.

III-D. Méthodes de dessin

Dans cette section, nous allons voir les principales méthodes de dessin, à savoir écrire du texte, dessiner des formes et afficher une image.

III-D-1. Écrire du texte

Écrire du texte sur une surface DirectX reste assez simple grâce à Asphyre. Nous allons voir ici les deux méthodes qui permettent de dessiner du texte.

III-D-1-a. Écriture « simple » à partir d'une police système

La méthode ressemble à la méthode TextOut du TCanvas de la VCL. À ceci près que l'appel à ce TextOut se fait à un endroit bien précis.

Avant toute chose, il faut créer la police que l'on va utiliser. Pour cela, dans le OnCreate, il suffit d'appeler le constructeur de police. Dans notre exemple, on a cette ligne :

 
Sélectionnez
  DefDevice.SysFonts.CreateFont('sys:tahoma', 'tahoma', 9, False, fwtHeavy,
    fqtClearType, fctAnsi);

Voici la déclaration que l'on trouve dans AsphyreSystemFonts.pas :

 
Sélectionnez
  function CreateFont(const Name, SystemFontName: string; Size: Integer;
   Italic: Boolean = False; Weight: TFontWeightType = fwtNormal;
   Quality: TFontQualityType = fqtClearType;
   Charset: TFontCharsetType = fctAnsi): Integer;
  • Name : le nom de la police. C'est ce nom que l'on va utiliser pour appeler cette police de caractères.
  • SystemFontName : le nom de la police telle qu'elle est définie dans Windows.
  • Size : taille de la police.
  • Italic : police en italique ou non.
  • Weight : épaisseur de la police.
  • Quality : qualité de la police.
  • Charset : charset de la police.

Weight définit l'épaisseur de la police. Il propose neuf différentes valeurs :

 
Sélectionnez
fwtThin, fwtExtraLight, fwtLight, fwtNormal, fwtMedium, fwtSemiBold, fwtBold, fwtExtraBold, fwtHeavy

Voici à quoi ressemblent ces valeurs avec Quality et Charset à leur valeur par défaut (respectivement fqtClearType et fctAnsi) et police Technic :

Image non disponible
Les différentes épaisseurs de police

La différence est subtile, mais assez visible sur cette police à cette taille.

Une fois la police créée, on peut écrire sur la surface. Pour cela, il suffit d'écrire, comme dans notre exemple, la procédure suivante dans le Callback de Rendu :

 
Sélectionnez
  Sender.SysFonts.Font['sys:tahoma'].TextOut('Hello world!',
    4, 10, $FFFFFFFF);

On identifie notre police grâce à son nom (ici 'sys:tahoma') puis on appelle sa méthode TextOut dont voici les déclarations :

 
Sélectionnez
  procedure TextOut(const Text: WideString; const Rect: TRect;
   Format: TFontFormatType; Color: Cardinal); overload;

  procedure TextOut(const Text: WideString; x, y: Integer;
   Color: Cardinal); overload;

La première permet de définir la position du texte dans un cadre (TRect) ce qui permet de spécifier des effets particuliers pour le texte que l'on veut inscrire dedans. Asphyre propose les dix valeurs différentes de TFontFormatType suivantes :

 
Sélectionnez
fftTop, fftBottom, fftLeft, fftRight, fftCenter, fftVCenter, fftNoClip, fftExpandTabs, fftSingleLine, fftWordBreak

Ces valeurs peuvent être combinées afin, par exemple d'obtenir un texte centré verticalement et horizontalement dans une case de cette façon :

 
Sélectionnez
var
  UneCase: TRect;
begin
  //Initialisation de UneCase
  ...
  Sender.SysFonts.Font['MaPolice'].TextOut('Mon texte', UneCase, [fftCenter, fftVCenter], $FFFFFFFF);
end;

La seconde permet de spécifier simplement la position du texte à l'aide de ses coordonnées en pixels sur la surface.

Le dernier paramètre est la couleur du texte. Il se décompose ainsi $AARRVVBB, AA étant la valeur de transparence, RR la valeur de rouge, VV, la valeur de vert et BB la valeur de bleu. On peut donc facilement définir la couleur ainsi que son opacité :

Image non disponible
Différentes valeurs d'opacité
 
Sélectionnez
  Sender.SysFonts.Font['sys:tahoma'].TextOut('Opacité = $FF (255)',
    4, 10, $FFFF0000);
  Sender.SysFonts.Font['sys:tahoma'].TextOut('Opacité = $CC (204)',
    4, 30, $CCFF0000);
  Sender.SysFonts.Font['sys:tahoma'].TextOut('Opacité = $99 (153)',
    4, 50, $99FF0000);
  Sender.SysFonts.Font['sys:tahoma'].TextOut('Opacité = $66 (102)',
    4, 70, $66FF0000);
  Sender.SysFonts.Font['sys:tahoma'].TextOut('Opacité = $33 (51)',
    4, 90, $33FF0000);
III-D-1-a-i. Écrire le nombre d'images par seconde (FPS)

Une des utilités les plus intéressantes est de pouvoir afficher le nombre de FPS actuel. Cela peut nous informer de la qualité d'optimisation de notre programme.
Pour cela, il faut appeler la fonction FrameRate de Timer.

Le résultat de cette fonction est un entier. Il suffit donc de convertir la valeur et d'écrire, par exemple :

 
Sélectionnez
Sender.SysFonts.Font['sys:tahoma'].TextOut('FPS: ' + IntToStr(Timer.FrameRate),
  4, 10, $FFFFFFFF);
III-D-1-b. Écriture à partir d'une police « complexe »

Comme nous l'avons vu, il est très facile de créer des polices simples grâce à quelques lignes de code. Voyons maintenant comment créer des polices un peu plus complexes grâce à l'un des outils fournis avec Asphyre : FontTool.exe.

Cette méthode nécessite l'emploi de cet outil et l'intégration à votre programme est un peu plus complexe.

III-D-1-b-i. Description de l'outil FontTool.exe

Cet outil se trouve à la base du répertoire dans lequel vous avez décompressé Asphyre. Voici de quoi il a l'air :

Image non disponible
Interface de FontTool.exe

Depuis ce programme, on va pouvoir créer une police personnalisée avec toute sorte d'effets (ombrage, antialiasing, etc.).
Chaque police sera représentée par une image (en général PNG) contenant tous les caractères de la police et d'un fichier XML contenant toutes les données relatives à cette police.

Voyons maintenant en détail à quoi servent tous ces panneaux.

Image non disponible
Panneau Windows Font Settings

Dans ce panneau, vous pouvez paramétrer la police, sa taille et ses caractéristiques (gras, italique) comme dans n'importe quel éditeur de texte. Le bouton Details ouvre la boite de dialogue de sélection de police de caractères que tout le monde connait.
Un aperçu de la police brute est affiché en dessous.

Image non disponible
Panneau Font Preview

Ce panneau montre un aperçu de ce que donnera la police définitive. La mise à jour se fait au bout de 2-3 secondes après avoir modifié un paramètre.

Image non disponible
Panneau Rendering Options

Ce panneau vous permet de sélectionner les effets que vous voulez intégrer à votre police. Les effets disponibles sont l'anticrénelage (Antialias), le contour (Outline), l'ombrage (Shadow), l'exagération des contours (Sharpen). Ces effets peuvent être combinés. Pour les activer, il suffit de cliquer sur le bouton correspondant.
Les deux listes déroulantes du dessus permettent de définir l'étendue des caractères qui seront intégrés à la police. En effet, comme nous l'avons vu plus haut, une image de la police est générée. Si vous ne comptez utiliser que les chiffres par exemple, il est inutile de surcharger les ressources pour rien : intégrez uniquement les chiffres dans votre police. Vous pouvez néanmoins intégrer tous les caractères que vous voulez.

Image non disponible
Panneau Individual Letter Text

Ce panneau définit la luminosité (Brightness) et l'opacité (Opacity) des caractères. Si vous jouez sur la valeur de luminosité, vous verrez les couleurs s'assombrir ou s'éclaircir dans l'aperçu de gauche.
Le fond en carreau de cet aperçu permet de contrôler facilement l'opacité. Cette opacité vient en complément de celle que l'on peut définir dans TextOut. Ainsi, si on définit, dans FontTool, une opacité à 50 %, si dans TextOut, on la définit à 100 % ($FF), on aura tout de même une police transparente à 50 %.

Les deux panneaux suivants permettent de paramétrer les effets Border et Shadow.

Image non disponible
Panneau Outer Letter Border

Ce panneau paramètre l'effet de bordure. Vous pouvez spécifier la luminosité, l'opacité et l'épaisseur de cette bordure.

Image non disponible
Panneau Letter Depth Shadow

Ce panneau, quant à lui, permet de paramétrer l'effet d'ombrage. Vous pouvez ainsi modifier les valeurs d'adoucissement (les ombres seront moins marquées), de luminosité, d'opacité, et la distance de l'ombre par rapport à son objet en pixels. Si vous voulez donner l'effet d'un texte juste au-dessus de ce qu'il couvre, laissez une petite valeur. Comme pour tous les paramètres, vous pourrez vérifier le résultat dans l'aperçu de gauche.

Vous l'aurez compris, l'énorme avantage d'utiliser cette méthode pour créer une police – outre les effets disponibles – est que l'on ne dépend plus de la police elle-même : si vous utilisez une police non fournie avec Windows par exemple, vous n'avez plus à vous soucier de son déploiement puisque la police est intégrée à une image.

III-D-1-b-ii. Création d'une police

Voyons maintenant comment créer une police.
Exécutez FontTool.exe et paramétrez-le ainsi :

Panneau Windows Font Settings

Champ

Valeur

Font

Arial

Size

12

Italic

Non

Bold

Oui

Panneau Rendering Options

Champ

Valeur

Etendu de caractères

#032 à #126

Antialias

Oui

Outline

Oui

Shadow

Non

Sharpen

Non

Panneau Individual Letter Text

Champ

Valeur

Brightness

1.000

Opacity

0.600

Panneau Outer Letter Border

Champ

Valeur

Brightness

0.700

Opacity

1.000

Thickness

1

Pour saisir une valeur exacte sur les gauges, on peut cliquer avec le bouton droit de la souris pour la saisir manuellement.

Panneau Letter Depth Shadow
Puisque l'effet Shadow n'est pas utilisé, il n'est pas nécessaire de paramétrer cette zone.

Une fois la police paramétrée, il faut la sauvegarder. Pour cela, cliquez sur le bouton en bas à gauche « Save Font As… » puis sélectionnez un chemin et un nom pour vos fichiers. Créez un répertoire « Fonts » dans le répertoire de votre projet puis enregistrez votre police dedans en la nommant « MaPolice.png ».

L'outil enregistrera automatiquement les deux fichiers dans le répertoire sélectionné. Ces deux fichiers auront le même nom.
Vous devez donc avoir deux fichiers :

  • MaPolice.png

    Image non disponible
    Le fichier MaPolice.png
  • MaPolice.xml : ce fichier contient les données nécessaires à l'utilisation de la police.
III-D-1-b-iii. Créer le fichier XML de ressources

Auparavant, dans Asphyre eXtreme, on utilisait un composant, le TAsDb qui se chargeait d'ouvrir un fichier de type .asdb qui contenait toutes les ressources du programme. Ce fichier de ressources était créé à l'aide d'un outil proposé avec cette version d'Asphyre et permettait de rajouter toute sorte de fichiers.

Dans la version 4.1, tout se fait en XML. Le format .asdb n'existe plus (au grand dam de beaucoup de gens et au contraire de Asphyre Casual qui a repris ce système).

Vous devez donc créer un fichier XML qui fera appel au fichier XML de la police. Ce fichier XML peut contenir les données de polices et d'images que nous verrons plus loin.

Créez donc un nouveau fichier XML dans Delphi et appelez-le Ressources.xml.
Voici le squelette de ce fichier XML :

 
Sélectionnez
<unires>

</unires>

Il est inutile de spécifier la version d'encodage des caractères.
Chaque ressource (image, police) sera incluse dans un groupe que l'on peut nommer.

Comme notre police est contenue dans un fichier image, nous devons d'abord la répertorier dans un groupe d'image :

 
Sélectionnez
<image-group name="default group">
</image-group>

Entre ces balises, nous devons spécifier l'image. Les images sous Asphyre sont des textures et il convient de les déclarer comme tels :

 
Sélectionnez
<image uid="mapolicepicture" type="image">
  <format type="a8r8g8b8" miplevels="auto"/>

  <textures count="1">
   <texture num="0" source=".\fonts\mapolice.png" />
  </textures>

 </image>

Regardons plus en détail ces lignes. La première ligne ouvre un nouveau nœud image. L'attribut uid est l'identifiant de l'image. L'attribut type définit lui le type d'image. Ce type peut être image, surface ou dynamic.
Le nœud format définit les caractéristiques de l'image. type définit comment sont encodées les couleurs et miplevels définit le niveau de mip-mapping.
Comme nous l'avons vu, il faut maintenant déclarer les textures qui composent notre image. Dans notre exemple, il n'y a qu'une seule texture : l'image MaPolice.png. Comme vous le voyez, il est tout à fait possible d'utiliser des chemins relatifs.

Une fois l'image créée, on peut déclarer une police dans un groupe de police de cette façon:

 
Sélectionnez
<font-group name="default">
</font-group>

Dans ce groupe, on répertorie une nouvelle police en utilisant l'image répertoriée plus haut et le fichier XML que l'on a créé avec FontTool.exe :

 
Sélectionnez
<font uid="mapolice" type="bitmap" image="mapolicepicture" space="0.5" desc=".\fonts\mapolice.xml" />

Si on regarde cette ligne, excepté space (Sinon c'est trop facile!), on comprend rapidement à quoi correspondent les attributs :

  • uid : identifiant de la police ;
  • type : le type de ressource d'où elle est tirée ;
  • image : l'uid de l'image qui contient la police créée précédemment ;
  • space : facteur de multiplication pour la largeur du caractère espace (0.0 = pas d'espace ; 1.0 = un espace entier) ;
  • desc : chemin du fichier XML qui définit la police.

Il est tout à fait possible de diviser les groupes en plusieurs fichiers XML. Par exemple, ici, on pourrait tout à fait avoir un fichier Images.xml et Fonts.xml, le premier contenant la déclaration du groupe d'images, le second du groupe de police.

En effet, une fois chargées, les ressources sont identifiées seulement par leur uid. Donc il importe peu qu'elles soient déclarées dans le même fichier !

Voilà le plus dur est fait ! À noter qu'il existe un utilitaire qui permet de faire ceci. Cet utilitaire, créé par le membre Ch@ser, est téléchargeable sur le site d'afterwarp ici. Les seuls inconvénients sont :

  • quelle que soit l'image (texture complexe ou simple comme notre exemple) toutes les données sont ajoutées, même celles qui sont inutiles. Le XML en devient vite illisible ;
  • l'interface pas franchement ergonomique à mon goût.

En revanche, on visualise plus facilement les données grâce à ce programme. L'autre avantage est que les possibilités du XML y sont – normalement – toutes. On peut donc utiliser ce programme pour savoir comment se constituent ces fichiers XML.

III-D-1-b-iv. Charger la police dans le projet

Une fois le fichier XML dûment créé, il faut maintenant charger dans le programme les données que l'on a définies dans ce fichier.
Comme toujours, Asphyre permet de faire assez simplement cette manœuvre. Contrairement à ce que l'on pourrait croire, le chargement de ces fichiers se fait avant l'initialisation et dans le OnCreate !

Le chargement se fait pour chacune des ressources de façon distincte :

 
Sélectionnez
procedure TMainFrm.FormCreate(Sender: TObject);
begin
  ImageGroups.ParseLink('Ressources.xml'); //Chargement des images
  FontGroups.ParseLink('Ressources.xml'); //Chargement des polices

  if (not Devices.Initialize(ConfigureDevice, Self)) then //Initialisation
  begin
    MessageDlg('Asphyre n''a pas pu s''initialiser!', mtError, [mbOk], 0);
    Close();
    Exit;
  end;

  //Démarrage et paramétrage du Timer
  Timer.Enabled  := True;
  Timer.OnTimer  := TimerEvent;
  Timer.OnProcess:= ProcessEvent;
  Timer.MaxFPS   := 100;
end;

Notez bien l'ordre de chargement des ressources. Dans notre cas il est important !

En effet, dans le fichier XML, nous déclarons une image puis une police dépendante de cette image. Si l'on ne charge pas d'abord les images, le gestionnaire de polices ne va pas trouver l'image correspondante.

En général, puisque les images dépendent seulement de fichiers externes, on peut sans se soucier les charger en premier.

N'oubliez pas de déclarer les unités dans la clause uses :

  • ImageGroups est déclaré dans MediaImages ;
  • FontGroups est déclaré dans MediaFonts.

Une fois ces images chargées, on peut initialiser. En outre, il est également possible de vérifier que les ressources se soient chargées correctement.
Comme nous l'avons vu plus haut, il suffit de déclarer une méthode en cas d'erreur de chargement de cette façon :

 
Sélectionnez
procedure TMainFrm.ConfigureDevice(Sender: TAsphyreDevice; Tag: TObject;
  var Config: TScreenConfig);
begin
  Config.Width   := ClientWidth;
  Config.Height  := ClientHeight;
  Config.Windowed:= True;

  Config.WindowHandle:= Self.Handle;
  Config.HardwareTL  := true;

  EventResolveFailed.Subscribe(OnResolveFailed, Sender); //Inscription de la méthode pour l'événement
end;

Il faut alors créer une nouvelle procédure OnResolveFailed de cette façon :

 
Sélectionnez
procedure TMainFrm.OnResolveFailed(Sender: TObject; EventParam: Pointer;
  var Success: Boolean);
begin
  MessageDlg('Erreur de chargement de ressources.', mtError, [mbOk], 0);

  Devices.Finalize();
  Timer.Enabled:= False;
  Application.Terminate();
end;

Ainsi, si une erreur se produit, un message d'erreur apparait puis l'application se ferme.

Il ne faut pas libérer les images et polices créées: tout se fait dans des sections finalization dans leur unité respective.

III-D-1-b-v. Écrire avec la police

Nous voilà enfin parvenus à tout charger correctement! Il ne reste plus qu'à écrire avec cette police amoureusement créée.
De la même façon qu'avec l'écriture « simple », tout se passe encore dans le Callback de rendu. Par contre, cette fois-ci, ce n'est pas Sender.SysFonts, mais Sender.Fonts que l'on utilise :

 
Sélectionnez
procedure TMainFrm.RenderCallback(Sender: TAsphyreDevice; Tag: TObject);
begin
  Sender.Fonts.Font['MaPolice'].TextOut('Ma chaine de caracteres',
    4, 10, $FFFF0000 );
end;

procedure TMainFrm.TimerEvent(Sender: TObject);
begin
  DefDevice.Render(RenderCallback, self, $FFFFFF);
  Timer.Process;
end;

Pour des raisons esthétiques, j'ai simplement modifié la couleur de fond dans TimerEvent afin d'avoir un fond blanc.
Vous devriez avoir un résultat comme celui-ci :

Image non disponible
Résultat d'écriture avec une police complexe

Il existe également une autre procédure TextOut qui, à la place d'un Cardinal pour la couleur demande un type TColor2.
Mais un TColor2, c'est quoi donc ?? C'est évidemment un type d'Asphyre qui est une combinaison de deux couleurs. Mais pourquoi deux couleurs ? Que de questions !! Pour la réponse, je vous laisse découvrir ce que ça donne avec ce code :

 
Sélectionnez
  Sender.Fonts.Font['MaPolice'].TextOut('Ma chaine de caracteres',
      4, 10, cColor2($FF00FF00, $FFFF0000) );
Image non disponible
L'autre utilisation de TextOut

En effet, simplement en spécifiant deux couleurs et en utilisant la fonction cColor2, on obtient un dégradé le plus simplement du monde.

Cette fonction cColor2 est déclarée dans l'unité AsphyreTypes. Cette unité contient une quantité assez importante de constantes et autres routines de conversion.

Un bon conseil: si vous devez écrire une petite routine de conversion (de couleur par exemple) vérifiez d'abord que cette fonction n'existe pas déjà dans cette unité !

Par exemple, la fonction cColor2 utilisée ci-dessus renvoie un type TColor2 à partir de deux couleurs passées en paramètres.

On peut également modifier les options de dessin de la police. Par exemple, on voudrait modifier l'espace entre chaque caractère. Pour cela, la propriété Font propose une propriété Options qui permet ce genre de modifications.
Par exemple, ce code :

 
Sélectionnez
  with Sender.Fonts.Font['MaPolice'] do
  begin
    Options.Kerning := -2;
    TextOut('Ma chaine de caracteres',
      4, 10, cColor2($FF00FF00, $FFFF0000) );
  end;

va diminuer l'espace entre chaque caractère. D'autres paramètres sont disponibles comme la taille (attention: ce n'est qu'un facteur d'échelle : l'image d'origine reste la même !), afficher l'ombrage ou non, etc.

III-D-2. Dessiner des formes

Le dessin de forme, comme on le fait sur un TCanvas à l'aide de MoveTo, LineTo, Rectangle, FillRect, etc. dans la VCL, se fait de manière assez similaire puisque le type TAsphyreDevice propose une propriété Canvas de type TAsphyreCanvas qui permet toute sorte de dessin.

Toutes les fonctions de dessin se font dans la procédure de Callback du rendu que l'on a appelé jusque là RenderCallback. Dans les codes qui suivront, Sender sera donc du type TAsphyreDevice.

À l'instar du TCanvas de la VCL, le TAsphyreCanvas permet de dessiner une grande variété de figures géométriques. Nous ne verrons ici que le point, la ligne, le rectangle et l'ellipse afin de bien s'imprégner de la méthode de dessin d'Asphyre.

III-D-2-a. Paramétrer le dessin

Avant de dessiner quoi que ce soit, on peut paramétrer des valeurs telles que la largeur des lignes, le type d'anticrénelage, etc.
Pour cela, à l'instar du TCanvas de la VCL, le TAsphyreCanvas offre des propriétés. Voyons les plus utiles :

  • LineAntialias : permet de spécifier si on veut utiliser l'anticrénelage ou non ;
  • LineWidth : épaisseur de la ligne.

Nous allons le voir plus loin, mais plusieurs procédures de dessin se déclinent en deux versions : Ex et Hw (par exemple LineEx et LineHw). La fonction type Hw est plus rapide puisqu'elle utilise directement le matériel (la carte graphique). En revanche, Hw n'est pas impacté par LineWidth et l'anticrénelage n'est pas paramétrable.

III-D-2-b. Dessiner un point

La méthode de dessin de base est de dessiner un pixel. En fait, on ne dessine pas un pixel à proprement dit, on lui définit une couleur. Cette opération se fait en utilisant la procédure PutPixel du TAsphyreCanvas. Deux méthodes surchargées sont disponibles et ont uniquement pour différence le type de donnée pour les coordonnées du pixel. L'une demande X et Y, l'autre demande un type TPoint2.

 
Sélectionnez
  procedure PutPixel(const Point: TPoint2; Color: Cardinal;
   DrawFx: Integer); overload;
  procedure PutPixel(x, y: Single; Color: Cardinal; DrawFx: Integer); overload;

Les paramètres suivants sont la couleur du point et sa méthode de dessin que nous détaillerons plus bas.

III-D-2-c. Dessiner une ligne

Il existe plusieurs façons de dessiner une ligne. Nous allons voir les deux plus caractéristiques.

Le première utilise LineEx. Cette fonction accepte un point de départ, un point d'arrivée et une couleur :

 
Sélectionnez
Sender.Canvas.LineEx(4, 30, 200, 30, $FFFF0000);
Sender.Canvas.LineEx(Point(4, 30), Point(200, 30), $FFFF0000);
Sender.Canvas.LineEx(Point2(4, 30), Point2(200, 30), $FFFF0000);

Ces trois méthodes donnent rigoureusement le même résultat. À noter que la fonction Point2 ainsi que le type TPoint2 se trouve dans l'unité Vectors2.

La seconde méthode permet un peu plus de possibilités. On pourra en effet définir un dégradé et une transparence.

 
Sélectionnez
Sender.Canvas.LineHw(4, 35, 200, 35, $FFFF0000, $FF00FF00, fxuBlend);

Cette ligne dessine une ligne partant du rouge opaque ($FFFF0000) vers un vert opaque ($FF00FF00). Le dernier paramètre définit la façon dont va se dessiner la ligne. fxuBlend (unité AsphyreEffects.pas) spécifie d'utiliser les channels alpha (de transparence) des couleurs utilisées.
De la même façon que LineEx, LineHw propose plusieurs versions surchargées qui pourront être utiles suivant le type de données que vous avez au moment de l'appel (TPoint ou TPoint2).
Il est à noter que LineHw est plus rapide que LineEx.

III-D-2-d. Dessiner un rectangle

À l'instar du dessin de ligne, on retrouve deux types de procédures de TAsphyreCanvas: WireQuadEx et WireQuadHw. Les différences sont les mêmes : la première dessine un rectangle simple à l'aide de ses quatre côtés et d'une couleur.
La seconde dessine un rectangle avec les quatre côtés, un type TColor4 (combinaison de quatre couleurs pour définir un dégradé entre chaque « branche » du rectangle) et une méthode de dessin.

Exemple d'utilisation :

 
Sélectionnez
Sender.Canvas.WireQuadEx(Point4(Point2(4, 10), Point2(200, 10), Point2(200, 30), Point2(4, 30)), $FF00FF00);

Sender.Canvas.WireQuadHw(
      Point4(Point2(4, 50), Point2(200, 50), Point2(200, 70), Point2(4, 70)),
      cColor4($FFFFFFFF, $FFFF0000, $FF00FF00, $FF0000FF),
      fxuBlend);

La fonction Point4 est déclarée dans l'unité AsphyreTypes.
Voici ce que donne ce code :

Image non disponible
Rectangles utilisant les deux méthodes

Les quatre points donnés doivent être dans l'ordre afin d'avoir un rectangle. Il est tout à fait possible d'obtenir ceci :

Image non disponible
Rectangle avec les points dans le désordre

avec ce code :

 
Sélectionnez
Sender.Canvas.WireQuadHw(
      Point4(Point2(4, 50), Point2(200, 70), Point2(200, 50), Point2(4, 70)),
      cColor4($FFFFFFFF, $FFFF0000, $FF00FF00, $FF0000FF),
      fxuBlend);

En fait, les procédures WireQuadEx et WireQuadHw permettent de dessiner des quadrilatères, quels qu'ils soient.

À noter qu'il existe également des procédures permettant de dessiner des rectangles pleins. En particulier, FillQuad qui s'approche beaucoup de WireQuad :

 
Sélectionnez
Sender.Canvas.FillQuad(
      Point4(Point2(4, 50), Point2(200, 50), Point2(200, 70), Point2(4, 70)),
      cColor4($FFFFFFFF, $FFFF0000, $FF00FF00, $FF0000FF),
      fxuBlend);

Donne un rectangle rempli de beaux dégradés :

Image non disponible
Résultat de FillQuad
III-D-2-e. Dessiner un cercle et une ellipse

Deux procédures sont fournies pour cette opération : Circle et Ellipse (oui les noms sont bien choisis…). Voici les paramètres demandés par cette procédure :

  • x et y : la position du centre du cercle ;
  • Radius : le rayon du cercle (type single) ;
  • Steps : précision de l'arrondi. Plus la valeur sera grande, mieux l'arrondi sera dessiné. En revanche, le dessin sera plus lourd ;
  • Color : la couleur de type cardinal ($AARRGGBB).

Exemple d'utilisation :

 
Sélectionnez
Sender.Canvas.Circle(50, 50, 40, 20, $FF0000FF);

Comme toujours, voici le résultat :

Image non disponible
La procédure Circle

La procédure Ellipse marche rigoureusement de la même façon sauf qu'évidemment, au lieu du rayon seulement, on définit le grand axe et le petit axe.
À noter que la procédure Circle ne fait rien d'autre qu'appeler la procédure Ellipse avec Radius en petit axe et grand axe !

De la même façon que pour le quadrilatère, il existe des procédures équivalentes pour dessiner un cercle et une ellipse remplis. Ces fonctions sont FillCircle et FillEllipse et s'utilisent de la même façon que FillQuad avec les paramètres de Circle et Ellipse à savoir un type TColor4 (pour faire des dégradés) et une méthode de dessin.
Pour illustrer ceci, voici le résultat que donne ce code :

 
Sélectionnez
  with Sender.Canvas do
  begin
    FillEllipse(50, 50, 40, 20, 20,
      cColor4($FFFFFFFF, $FFFF0000, $FF00FF00, $FF0000FF),
      fxuBlend);
    FillCircle(150, 50, 40, 20,
      cColor4($FFFF00FF, $FFFF00FF, $FFFFFF00, $FF00FFFF),
      fxuBlend);
  end;
Image non disponible
Résultat de FillEllipse et FillCircle

III-D-3. Afficher une image

Passons maintenant à l'affichage des images.
La première chose à prendre en compte est la taille de l'image. En effet, Asphyre n'affichera que des images dont la taille est une puissance de 2 (2, 4, 8, 16, 32, 64, 128, 256, etc.).
Donc si vous avez par exemple une image de 300x100, il vous faudra l'inscrire dans une image dont la taille est une puissance de 2 supérieure. Dans cet exemple, une image de 512x128 convient. Évidemment, il faut choisir correctement la taille de son image originale pour éviter d'avoir une image dont une bonne partie n'affiche rien. Par exemple, si une image a un de ses côtés à 513 pixels, l'image affichée dans Asphyre devra être de 1024 pixels puisque dans 512, cela ne rentre pas.
Si vous utilisez une image dont la taille ne correspond pas à cette règle, votre image sera déformée afin de correspondre à sa taille puissance de 2 minimale.

Heureusement, un outil est fourni pour modifier les images. Il s'agit de TextureMaker.exe. Cet outil va permettre de créer une image qui respecte cette règle depuis une autre image.

Si nous utilisons ce programme, cela est uniquement dû à la taille de l'image non conforme. Il va sans dire que cette manipulation peut être faite depuis n'importe quel logiciel de dessin en copiant l'image dans une autre de taille conforme.

Si l'image est déjà à la bonne taille, il est inutile de faire cette opération.

III-D-3-a. Utilisation de TextureMaker
Image non disponible
Interface de TextureMaker

Ce programme sert à modifier les images de façon à ce qu'elles soient de taille d'une puissance de 2. Il peut également découper une image en plusieurs pour optimiser la place de vos sprites sur l'image.
Vous pouvez aussi extraire des images depuis une image source suivant un schéma défini.

III-D-3-a-i. Qu'est-ce qu'un pattern ?

Dans l'animation, le pattern est une notion très importante. En effet, lorsque vous animez un personnage par exemple, vous ne faites « que » succéder des images donnant l'illusion du mouvement. Donc deux options s'offrent à nous pour arriver à ce résultat :

  • utiliser une image par mouvement ;
  • utiliser une image qui regroupe tous les mouvements.

Il est évident qu'en termes d'utilisation de ressources, la seconde méthode est à privilégier !
Ce genre d'image s'appelle une feuille de sprites.
Voici donc à quoi pourrait ressembler une telle image :

Image non disponible

Cette image est extraite de l'excellent tutoriel de Loka que vous trouverez ici.
Comme vous le voyez, chaque décomposition du mouvement est sur la même image. Chacune de ses décompositions est un pattern ! Par exemple, dans notre exemple la taille du pattern à utiliser est de la taille de l'image puisqu'il n'y a pas d'animation. En revanche, sur une feuille de sprites, la taille du pattern est celle de chacun des éléments.

III-D-3-a-ii. Paramétrage de TextureMaker

Comme d'habitude, analysons chaque partie du programme séparément.

Image non disponible

Première chose à faire : charger l'image. Cela se fait à l'aide du bouton Browse. Une fois l'image chargée, il faut spécifier la taille du pattern.

Donc, si vous avez bien suivi, vous aurez compris que dans l'exemple qui nous intéresse, la taille du pattern est la taille de l'image. Vous remarquerez le « Number of patterns » grisé en dessous qui indique le nombre de patterns d'après la taille que vous avez spécifiée au-dessus.

Image non disponible

Cette partie peut être intéressante si vous n'utilisez pas de format d'image transparent. Vous pouvez en effet choisir de garder les informations de transparence de l'image utilisée (channel Alpha) ou bien spécifier une couleur qui va servir de transparence avec une tolérance.
En règle générale, je vous conseille d'utiliser un format contenant des informations de transparence comme le PNG par exemple.

Image non disponible

Cette partie permet de spécifier la taille de sortie de l'image. Comme nous l'avons vu, la taille doit être une puissance de 2. En cliquant sur le bouton Image non disponible, le programme calculera lui-même la taille la plus adaptée.

Image non disponible

Ce paramètre correspond à l'écart que vous voulez donner entre vos patterns. Si vous voulez qu'ils soient accolés, laissez à 0, sinon, vous pouvez spécifier une autre valeur. Dans notre cas, laissons 0 puisque nous n'avons qu'un seul pattern sur la texture.

Image non disponible

Cette partie permet de visualiser l'organisation finale et les pixels inutilisés d'après les paramètres que l'on a fournis.

III-D-3-b. Déclarer l'image dans le XML

Comme nous avons vu dans le chapitre III.D.1.b.iiiCréer le fichier XML de ressources, il faut déclarer l'image dans le fichier XML de ressources.

 
Sélectionnez
<image uid="MonImage" type="image">
    <format type="a8r8g8b8" miplevels="auto"/>
    <textures count="1">
        <texture num="0" source=".\Images\MonImage.png"/>
    </textures>
</image>

Si l'image utilise des patterns, on peut les spécifier en les déclarant juste après le nœud format :

 
Sélectionnez
<pattern width="300" height="100" count="1" padx="0" pady="0" />

Dans notre cas, c'est évidemment inutile.

III-D-3-c. Charger l'image dans le programme

Nous l'avons déjà vu, le chargement des images depuis le fichier ressources se fait dans le OnCreate de la fiche avant l'initialisation d'Asphyre.

 
Sélectionnez
procedure TMainFrm.FormCreate(Sender: TObject);
begin
  ImageGroups.ParseLink('Ressources.xml');

  if (not Devices.Initialize(ConfigureDevice, Self)) then
  begin
    MessageDlg('Asphyre n''a pas pu s''initialiser!', mtError, [mbOk], 0);
    Close();
    Exit;
  end;

  Timer.Enabled  := True;
  Timer.OnTimer  := TimerEvent;
  Timer.OnProcess:= ProcessEvent;
  Timer.MaxFPS   := 100;
end;
III-D-3-d. Dessiner l'image

Nous voilà enfin à l'étape du dessin. De la même façon que toutes les opérations de dessin sur le TAsphyreCanvas, cela se fait dans la procédure de Callback du rendu.
L'affichage d'une image se fait en deux temps :

  • UseMap : on identifie d'abord l'image à dessiner ;
  • TexMap : on l'affiche sur le TasphyreCanvas.

Voilà ce que donne le code :

 
Sélectionnez
procedure TMainFrm.RenderCallback(Sender: TAsphyreDevice; Tag: TObject);
const
  //Constante pour donner la position de l'image
  X = 10;
  Y = 10;
var ImageIndex: integer;
    p4: TPoint4;
begin
  //On retrouve l'index de l'image que l'on veut utiliser
  ImageIndex := Sender.Images.Image['MonImage'].ImageIndex;

  //On retrouve le rectangle d'affichage de l'image à l'aide des constantes
  //de position et de la taille de la texture.
  //PpBounds4 convertit 4 entiers (gauche, droite, largeur, hauteur) en un type TPoint4
  p4 := pBounds4(X, Y,
    Sender.Images[ImageIndex].Texture[0].Size.X,
    Sender.Images[ImageIndex].Texture[0].Size.Y);

  with Sender.Canvas do
  begin
    //Pour vérifier que l'image est correcte, on peut désactiver l'anticrénelage
    Antialias := atNone;

    //On définit l'image à utiliser et son pattern (0)
    UseImage(Sender.Images[ImageIndex], 0);
    //On dessine l'image sélectionnée
    TexMap(p4, clWhite4, fxuBlend);
  end;
end;

Regardons un peu plus en détail ce code.
UseImage sert donc à sélectionner l'image à utiliser. Il existe quatre versions surchargées de cette procédure suivant les données que vous avez. Celle que j'ai choisie est celle faisant appel au pattern. Il n'y en a qu'un, c'est le premier, son index est donc 0.
Il est aussi possible de définir ce que l'on veut afficher en utilisant des coordonnées de texture :

 
Sélectionnez
  procedure UseImage(Image: TAsphyreCustomImage;
   const Mapping: TPoint4; TexNum: Integer = 0); overload;

Cette version accepte un paramètre de type TPoint4 qui représente les coordonnées de texture. Ici, puisque l'on veut afficher la totalité de l'image, on peut définir des coordonnées de textures qui utilisent cette totalité. Comme je vous le disais, vérifiez d'abord que ce que vous voulez faire n'existe pas déjà dans AsphyreTypes ! On utilisera donc

 
Sélectionnez
TexFull4: TPoint4 = ((x: 0.0; y: 0.0), (x: 1.0; y: 0.0), (x: 1.0; y: 1.0),
  (x: 0.0; y: 1.0));

Le code sera donc :

 
Sélectionnez
UseImage(Sender.Images[ImageIndex], TexFull4);

Passons ensuite à TexMap.

 
Sélectionnez
  procedure TexMap(const Points: TPoint4; const Colors: TColor4;
   DrawFx: Integer);

Le premier paramètre est de type TPoint4 (un tableau de quatre TPoint) qui donne le cadre dans lequel dessiner l'image. Vous l'aurez compris, la fonction pRect4 de l'unité AsphyreTypes (encore elle) convertit un type TRect en type TPoint4.
Le paramètre suivant est un type TColor4 (un tableau de quatre couleurs en cardinal). clWhite4 est encore une constante de l'unité AsphyreTypes qui représente 4 blancs.
Le dernier paramètre de type Integer, nous le connaissons maintenant, il s'agit de la méthode de dessin.

Voici ce que peut donner ce code :

Image non disponible
Dessin du logo de developpez.com avec TexMap

Il est évidemment possible de faire varier le paramètre Colors de façon à obtenir des effets intéressants sur l'image. Par exemple, voyons ce que donne ce code :

 
Sélectionnez
    TexMap(pRect4(aRect), cColor4($66FFFFFF, $99FF0000, $CC00FF00, $FF0000FF), fxuMultiBlend);

La fonction cColor4 construit un type TColor4 à l'aide de une ou de quatre couleurs.
Le paramètre fxuMultiBlend permet de combiner les transparences entre celles de l'image et celles des couleurs définies ainsi que les couleurs elles-mêmes.
Nous obtenons alors cette image :

Image non disponible
Effets de couleur avec fxuMultiBlend et des couleurs personnalisées
III-D-3-e. Les effets de déformations

Comme nous l'avons vu, la procédure TexMap utilise un type TPoint4 qui est simplement un tableau de quatre TPoint2 qui définissent chacun un angle de l'image. Il est donc tout à fait possible de modifier un ou plusieurs angles afin de déformer l'image. Ce code par exemple modifie le premier point :

 
Sélectionnez
procedure TMainFrm.RenderCallback(Sender: TAsphyreDevice; Tag: TObject);
const
  X = 10;
  Y = 10;
var
  t: TAsphyreCustomImage;
  p: TPoint4;
begin
  with Sender.Canvas do
  begin
    t := Sender.Images.Image['ImageDVP'];
    UseImage(t, TexFull4);

    p := pBounds4(X, Y,
      t.Texture[0].Size.X,
      t.Texture[0].Size.Y);

    p[0].y := p[0].y - 50;

    TexMap(p, clWhite4, fxuBlend);
  end;
end;

Voci le résultat d'une telle modification :

Image non disponible
Résultat de la modification d'un des points

On peut également effectuer une rotation. À l'aide des fonctions Rotate de AsphyreTypes : pRotate4, pRotate4se, pRotate4c.
Voici par exemple ce code :

 
Sélectionnez
procedure TMainFrm.RenderCallback(Sender: TAsphyreDevice; Tag: TObject);
const
  X = 100;
  Y = 50;
var
  t: TAsphyreCustomImage;
  p: TPoint4;
begin
  with Sender.Canvas do
  begin
    t := Sender.Images.Image['ImageDVP'];
    UseImage(t, TexFull4);

    p := pBounds4(X, Y,
      t.Texture[0].Size.X,
      t.Texture[0].Size.Y);

    p := pRotate4(
      Point2(X, Y),
      Point2(t.Texture[0].Size.X, t.Texture[0].Size.Y),
      Point2(0, 0),
      45);

    TexMap(p, clWhite4, fxuBlend);
  end;
end;

donne ce résultat :

Image non disponible
Rotation d'une image

Il est aussi possible de modifier l'échelle, de faire un miroir horizontal et vertical (respectivement pMirror4 et pFlip4). Leur utilisation ressemble à celle que l'on vient de voir, je ne reviendrai donc pas dessus.

III-E. Téléchargement du programme d'exemple

Image non disponible

Vous pouvez télécharger le programme d'exemple ici : Exemple2D.zip

III-F. Conclusion

Nous venons donc de voir les principales méthodes de dessin en 2D. Ce sont les bases qui vous permettront de créer votre premier jeu en 2D avec Asphyre. Pourquoi par un shoot-them-up ou un jeu de plateformes ? Voir un casse-briques ! Bref, une fois que vous avez assimilé ce tutoriel, seule votre imagination sera votre limite !

Éclatez-vous et n'hésitez pas à me proposer vos créations !


précédentsommairesuivant

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 © 2008 Pierre RODRIGUEZ. 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.