Developpez.com - Rubrique Delphi

Le Club des Développeurs et IT Pro

Création d'un jeu en 3D avec Delphi et FireMonkey

Un tutoriel de Grégory Bersegeay

Le 2016-10-26 21:28:28, par Alcatîz, Responsable Pascal, Lazarus et Assembleur
Apprendre à créer un jeu en 3D avec Delphi et FireMonkey
Un projet compilable pour Windows, Mac OS X, Android et iOS, réalisé avec Delphi 10 Seattle

À l'occasion de sa première approche du développement avec FireMonkey, le framework multiplate-forme de Delphi, Grégory Bersegeay a décidé de reprendre un jeu vidéo mythique : Pong. Cela nous permet de voir des rudiments de 3D et d'intelligence artificielle, le tout dans un projet relativement simple d'environ 420 lignes de code.


Les étapes les plus importantes de la création du projet sont détaillées. Sans toucher au code mais simplement en recompilant, nous pourrons faire tourner l'application sous Windows et OS X. Si l'on dispose du plugin Mobile ou d'une version Enterprise de Delphi, on peut le compiler aussi pour iOS et Android.

http://gbegreg.developpez.com/tutori...eation-jeu-3d/

Et vous ?
Que pensez-vous de ce tutoriel ?
Avez-vous déjà testé Delphi XE ?
Connaissez-vous le framework FireMonkey ?

Retrouvez les meilleurs cours et tutoriels pour apprendre la programmation Delphi
Retrouvez les meilleurs cours et tutoriels pour apprendre la programmation des jeux
  Discussion forum
29 commentaires
  • gbegreg
    Membre expert
    Bonsoir,

    Je vous remercie et je suis flatté de vos retours positifs sur le tutoriel. J'ai uploadé une autre version des sources : la raquette du joueur peut évoluer dans une zone (avant/arrière (axe Y) en plus du droite/gauche (axe X) déjà implémenté). Vous pouvez télécharger cette nouvelle version à cette adresse :
    http://gbegreg.developpez.com/tutoriels/delphi/firemonkey/Pong3D/src/FMXPongBis_src.zip

    A noter : il ne sera toutefois pas possible de donner plus de vitesse au palet ou de faire des amorties

    Techniquement, j'ai ajouté :
      un TPLane pour matérialiser la limite de la zone dans laquelle la raquette du joueur peut évoluer. J'ai associé à ce TPlane un nouveau TLightMaterialSource avec une couleur "Diffuse" Chocolate (pour rester dans le ton de la table de jeu). Le TPlane est cloné (tout comme la raquette) dans la procédure initialiserPlateau afin de matérialiser la zone de jeu de l'adversaire. Attention, l'IA n'est pas modifiée et la raquette gérée par l'ordinateur se déplacera toujours uniquement sur l'axe X).

      Dans l'événement OnMouseDown de la raquette du joueur, lors de la récupération de la position de la raquette, on prend en compte en plus la position sur l'axe Y (Point3D(1,0,0) remplacé par Point3D(1,1,0))

      Dans l'événement OnMouseMove de la raquette du joueur, même chose que précédemment, on prend en compte en plus la position sur l'axe Y (Point3D(1,0,0) remplacé par Point3D(1,1,0)) et on ajoute les contrôles de la nouvelle position par rapport à la limite de zone qui s'étend maintenant aussi sur l'axe Y :
      Code :
      1
      2
      3
      if Point.y > 19 then Point := Point3D(point.x,19,point.z);
      if Point.y < 12 then Point := Point3D(point.x,12,point.z);

      Enfin, dans l'événement OnMouseUp, on repositionnait la raquette au centre sur l'axe X et bien là, en plus, on la remet à sa position initiale sur l'axe Y :
      Code :
      raquetteJoueur.AnimateFloat('Position.Y',19);

    Avec ces petites modifications, le jeu ressemble un peu plus à un jeu auquel j'ai joué enfant sur Atari ST : Shufflepuck Cafe... nostalgie Ça me donne peut être une idée pour un prochain tutoriel : un petit jeu de type "The Light Corridor" (qui était aussi sur ST et Amiga si ça parle à certains...).

    Je n'ai pas eu l'occasion encore d'utiliser les fonctionnalités de "Tethering". Est ce que ces dernières permettraient d'ajouter la possibilité de jouer à deux humains sur deux machines distinctes (PC/PC, PC/Mac, PC/Android...) ? Je pense que ça pourrait être un bon complément au tutoriel...
  • gbegreg
    Membre expert
    Bonjour,

    Pour celles et ceux qui souhaiteraient tester ce que donne le jeu sous Android et qui ne disposent pas de la partie mobile de Delphi, je viens de le mettre sur le Play Store de Google : https://play.google.com/store/apps/details?id=fr.gbesoft.FMXPong.

    Le jeu ne nécessite aucune autorisation, aucune donnée n'est collectée, il est gratuit et sans pub
  • gvasseur58
    Responsable Lazarus & Pascal
    Merci pour ce tutoriel bien fait et instructif qui rend un grand service à la communauté .

    J'espère qu'il incitera d'autres à écrire pour FMX, car ce framework puissant manque de tutoriels adaptés aux débutants, surtout dans le domaine de la 3D.
    Ce bravo s'accompagne par conséquent d'une invitation pressante aux contributeurs...
    Encore merci .
  • ShaiLeTroll
    Expert éminent sénior
    Intéressant !
    Merci pour ce Tutoriel !

    Sinon
    Sur Delphi 10.1 Berlin Version 24.0.22858.6822 (la version gratuite), tout est rouge, pourtant, dans l'inspecteur on voit bien White, Aquamarine, Coral
    Sur Delphi XE7, cela provoque des erreurs à l'ouverture, mais tout est rouge aussi

    En fait, en supprimant, les "Materiel" c'est les TCube qui sont Rouge pourtant pas de trace de propriété Color
    La texture est appliqué malgré le tout Rouge
    J'ai ajouté d'autres TCube, TSpehre, ils sont Rouge par défaut et ignore les "Materiel" que l'on peut leur affecter
    Dans XFM, je n'ai rien trouvé pouvant mettre du Rouge

    Suffit de modifier "Lumiere" de Directional en Point pour que tout soit Vert et en Spot pour que tout soit Bleu !
  • Cirec
    Membre éprouvé
    Bonjour à tous,

    j'ai téléchargé et compilé le projet sous Delphi Berlin 10.1 Starter ...
    le tout sans aucun soucis de couleur ou autre problème décrit par ShaiLeTroll.
    le jeux fonctionne bien ... Merci à l'auteur pour cet excellent exemple

    Matériel: Windows Seven 32-Bit avec un vieux AMD 64 3800+ 2.40 GHz et une GeForce GT 230 !!!
  • Thierry Laborde
    Membre émérite
    Bonjour,

    Avez-vous fait le test avec :

    Code :
    FMX.Types.GlobalUseGPUCanvas:= True;
    ATTENTION, cette variable (Comme le GlobalUseDXSoftware d'ailleurs) est à mettre à jour dans le source du projet avant le : Application.Initialize
  • Paul TOTH
    Expert éminent sénior
    Bonjour,

    Rien à voir avec le problème de couleur, mais dans cet exemple, on a 3 objets identiques (ou presque) : base, bordDroit et bordGauche...et bien il est tout à fait possible d'en remplacer deux par un TProxyObject

    le projet est facile à modifier pour ce faire:
    1) ouvrir le DFM en mode texte (Alt + F12)
    2) remplacer TCube par TProxyObject sur les objets bordDroit et bordGauche
    3) ouvrir le DFM comme une fiche (Alt + F12)
    4) valider les deux erreurs sur la propriété Material inexistante
    5) renseigner la propriété SourceObject des deux bords pour pointer sur Base

    on obtient exactement le même résultat, mais les deux bords sont dessinés par via l'objet "base" dont les dimensions et la position sont surchargées dynamiquement au moment du rendu.

    Bon ok, ça ne va pas révolutionner le projet vu que l'objet de base est des plus simple, mais cette technique est utilisable aussi avec des objets plus complexes (Mesh, Path, Model) qui gagnent à ne pas être copier/coller dans une scène.
  • Thierry Laborde
    Membre émérite
    Envoyé par chantal04
    Du coup questions subsidiaires

    Comment puis-je savoir si il faut mettre cette variable à True ?
    Sur certains postes l'appli fonctionne très bien sans cette ligne. Si je la mets tout le temps à True, est-ce que cela va dégrader les performances ?
    Est ce que des problèmes similaires ont été rencontrés sur d'autre plateformes (Android ou IOS) ?

    Chantal
    En fait :

    Mettre
    • Mettre FMX.Types.GlobalUseDirect2D à False va forcer l'utilisation de GDI+
    • Mettre FMX.Types.GlobalUseDX10Software à True va utiliser L’accélération logiciel (au lieu de Hardware) pour Direct2D (Et Direct3D)
    • Mettre FMX.Types.GlobalUseGPUCanvas à True va forcer l'utilisation du processeur graphique (GPU)


    Et en fait selon les choix des Canvas différents seront créés quand on fera du graphisme sur Firemonkey :

    • TCanvasGDIPlus > Unité : FMX.Canvas.GDIP
    • TCanvasD2D > Unité : FMX.Canvas.D2D
    • TCanvasGpu > Unité : FMX.Canvas.GPU


    Après si on regarde l'unité FMX.Canvas.D2D et la classe TCustomCanvasD2D on se rend compte qu'on a des propriétés :

    • Direct3DHardware
    • Direct3DSupport
    • Direct3DLevel


    Donc je me pencherai là dessus pour en savoir plus sur la config graphique dispo et donc pour déterminer quelles variables mettre à jour ou non.

    Attention cela concerne seulement Windows car sur Mac on utilise un Canvas Quartz : TCanvasQuartz, unité : FMX.Canvas.Mac
  • Fab4v
    Membre à l'essai
    Bonjour à toutes et à tous,

    J'ai le même problème que Chantal (normal, on travaille dans le même bureau, et on a la même config )

    Après lecture de cet article (Source), on a voulu réécrire/mettre à jour la code présent dans la page avec la dll DX11, puisque c'est maintenant le minimum requis pour Firemonkey.
    Les premiers retours de la fonction D3D11CreateDevice étaient bon, que ce soit avec le paramètre D3D_DRIVER_TYPE_HARDWARE ou D3D_DRIVER_TYPE_WARP.
    J'avais une seule différence, c'était au niveau du FeatureLevel : celui du Warp était plus élevé qu'en Hardware.

    on a donc essayé d'implémenter le code suivant afin de solutionner le problème, par contre, on ne sais pas si c'est vraiment la bonne méthode à appliquer :

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    program FMXPong;
    
    uses
      System.StartUpCopy,
      FMX.Forms,
      FMX.Types,
    
    {$IFDEF MSWINDOWS}
      System.Math,
      Winapi.Windows,
      Winapi.D3D11,
      Winapi.D3DCommon,
      FMX.Context.DX11,
    {$ENDIF}
    
      principale in 'principale.pas' {fPrincipale};
    
    var
      PrevFPUState: TArithmeticExceptionMask;
    
    {$R *.res}
    
    {$IFDEF MSWINDOWS}
    
    // *****************************************************************************
    // Copiée de la fonction du même nom dans FMX.Context.DX11. ***
    
    procedure SaveClearFPUState; inline;
    begin
      PrevFPUState:= GetExceptionMask;
      SetExceptionMask(exAllArithmeticExceptions);
    end;
    
    // *****************************************************************************
    // Copiée de la fonction du même nom dans FMX.Context.DX11. ***
    
    procedure RestoreFPUState; inline;
    begin
      SetExceptionMask(PrevFPUState);
    end;
    
    // *****************************************************************************
    
    procedure TryUseWARPCanvas;
    const
      FeatureLevels : array [0 .. 8] of D3D_FEATURE_LEVEL = (
        D3D_FEATURE_LEVEL_12_1,
        D3D_FEATURE_LEVEL_12_0,
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
        D3D_FEATURE_LEVEL_9_3,
        D3D_FEATURE_LEVEL_9_2,
        D3D_FEATURE_LEVEL_9_1
      );
    
    var
      DX11Library : THandle;
      vResultHardware, vResultWarp: Integer;
      // Variables output de la fonction D3D11CreateDevice. ***
      TestDevice : ID3D11Device;
      LD3DDeviceContext : ID3D11DeviceContext;
      vFeatureLevelHardware, vFeatureLevelWarp: D3D_FEATURE_LEVEL;
    
    begin
      // J'essaye de charger la dll de directX 11. ***
      DX11Library := LoadLibrary(winapi.D3D11.D3D11dll);
      if DX11Library <> 0 then
      begin
        try
          SaveClearFPUState; // copié de la fonction du même nom dans FMX.Context.DX11. ***
          try
            // Est-ce que la procédure "D3D11CreateDevice" est dispo dans la Dll ? ***
            if GetProcAddress(DX11Library, 'D3D11CreateDevice') <> nil then
            begin
              // Test sur le hardware. ***
              vResultHardware := D3D11CreateDevice(
                nil,
                D3D_DRIVER_TYPE_HARDWARE,
                0,
                D3D11_CREATE_DEVICE_BGRA_SUPPORT,
                @FeatureLevels,
                Length(FeatureLevels),
                D3D11_SDK_VERSION,
                TestDevice,
                vFeatureLevelHardware,
                LD3DDeviceContext
              );
              if (Succeeded(vResultHardware)) and (vFeatureLevelHardware < D3D_FEATURE_LEVEL_11_0) then
              begin
                // Test du "high-performance software rasterizer" (cf. msdn "D3D_DRIVER_TYPE enumeration"). ***
                vResultWarp := D3D11CreateDevice(
                  nil,
                  D3D_DRIVER_TYPE_WARP,
                  0,
                  D3D11_CREATE_DEVICE_BGRA_SUPPORT,
                  @FeatureLevels,
                  Length(FeatureLevels),
                  D3D11_SDK_VERSION,
                  TestDevice,
                  vFeatureLevelWarp,
                  LD3DDeviceContext
                );
                // Je regarde sur la FeatureLevel est plus élevée en Warp. ***
                if Succeeded(vResultWarp) and (vFeatureLevelWarp >= D3D_FEATURE_LEVEL_11_0) then
                begin
                  // C'est le cas, je force le flag "GlobalUseDXSoftware". ***
                  GlobalUseDXSoftware := True ;
                end;
              end;
            end;
          finally
            TestDevice := nil;
            RestoreFPUState; // copié de la fonction du même nom dans FMX.Context.DX11. ***
          end;
        finally
          FreeLibrary(DX11Library);
        end;
      end;
    end;
    
    // *****************************************************************************
    
    {$ENDIF}
    
    begin
    
    {$IFDEF MSWINDOWS}
      TryUseWARPCanvas;
    {$ENDIF}
    
      Application.Initialize;
      Application.CreateForm(TfPrincipale, fPrincipale);
      Application.Run;
    end.
    Est-ce qu'on a le droit de faire ça ?

    D'avance, un grand merci pour votre aide.
    Cordialement.

    Edit1:
    J'ai modifié le code en testant le FeatureLevel pour le 11.0 (c'est celui qui prend en charge le shader model 5, étant maintenant le minimum requis pour Firemonkey)
    Dans mes tests sur ma config, le hardware était en shader model 4 alors que le Warp était en 5.
  • gbegreg
    Membre expert
    Bonjour ShaiLeTroll,

    Le tutoriel a été fait sous Delphi 10 Seattle Pro. Depuis je suis passé à Berlin et tout récemment à l'update 1 de Berlin (toujours la version Pro). Je n'ai pas les erreurs que tu indiques. Il est vrai que je n'ai pas testé avec des versions antérieures de Delphi.

    Tu as la version gratuite de Delphi Berlin ? C'est la starter edition non ? Elle intègre FMX ?