Banque de cours - Cours 12 - Unreal C++ Suite de Fibonacci
31 min de lecture
# Cours 12 - Unreal C++ : Suite de Fibonacci
Où est Charlie le Bug ?
class Shape {public: void draw() { cout << "Shape"; } ~Shape() { }};class Circle : public Shape {public: void draw() { cout << "Circle"; }};Shape* s = new Circle();s->draw();delete s;class Animal {public: virtual void speak() = 0;};class Dog : public Animal { };void process(Shape shape) { shape.draw();}
Solution
class Shape {public: void draw() { cout << "Shape"; } // Bug 1: pas virtual - pas de polymorphisme ! ~Shape() { } // Bug 2: destructeur pas virtual !};class Circle : public Shape {public: void draw() { cout << "Circle"; }};Shape* s = new Circle();s->draw(); // Affiche "Shape" au lieu de "Circle" !delete s; // Seul ~Shape() est appelé !class Animal {public: virtual void speak() = 0;};class Dog : public Animal { }; // Bug 3: n'implémente pas speak() - toujours abstrait !void process(Shape shape) { // Bug 4: slicing - passé par valeur, la partie Circle est perdue ! shape.draw();}
Environnement Unreal C++
Préparer MSVC 2022
Démarrez, dans la barre de recherche, “Visual Studio Installer”
Si vous voyez “Visual Studio Community 2026” dans l’onglet “Installed”, il faut le désinstaller
Si vous n’avez pas “Visual Studio Community 2022”, il faut l’installer (allez dans la section “Available).
Si on vous suggère de mettre a jour Visual Studio Community 2022, go for it
Appuyer sur “Modify” sur Visual Studio Community 2022
Dans les Workloads, cochez Destop development with C++ dans la section “Desktop & Mobile
Cochez également Game development with C++ dans la section Gaming et assurez-vous de tout cocher sauf Windows 10 SDK et suivants dans Installation Details
Cliquez sur le bouton Modify pour installer ce qui manque
Installer UE 5.7.x si ce dernier n’est pas installé (5.7.4 au moment de ce document)
Allez dans Options
Ajoutez MetaHuman Creator Core Data, Editor symbols for debugging et au moins Windows ARM64 comme Target Platform)
Cliquer sur ”Apply”
Vous avez 30 GB à installer
Ouvrir Unreal Engine 5.7.x
Créer un nouveau projet → Choisir un modèle (e.g. First-Person)
Important** :** Sélectionner C++ (et non “Blueprint”) dans la section ProjectDefaults
Target Platform → Desktop et Quality Preset → Maximum
Nommer votre projet “MyCpp” par exemple, choisir l’emplacement (ou laisser celui par défaut)
☝🏼
Vos machines manquent de mémoire et il est possible que UE ne puisse pas compiler durant la phase “Compiling C++ Project” ;
Le cas échéant, n’ouvrez pas Visual Studio comme vous le suggère Unreal, faites plutôt ceci :
Appuyez sur Win + R et tapez…:
SystemPropertiesAdvanced
…puis Enter.
De là:
Sous Performance, clickez Settings
Allez à l’onglet Advanced
Sous Virtual memory, clickez Change
Déselectionnez Automatically manage paging file size for all drives
Si vous un disque rapide, choisissez celui-là,. sinon restez sur le drive C:.
Choisir Custom size
Initial size: 65536 MB
Maximum size: 65536 MB
Clickez sur Set, puis OK
Rebootez votre ordi
…
…
Vous avez rebooté ?
Redémarrer Epic Game Launcher
Dans la section Library Unreal Engine, double-cliquez sur votre projet MyCpp
Cliquez “Yes” lorsque UE vous dit “Missing MyCpp Modules”, Would you like to rebuild them now?
☝🏼
Vos machines vont prendre énormément de temps à Compiler les shaders d’Unreal la première fois.
Installer et configurer JetBrains Rider (édition non commerciale)
Rider est l'IDE recommandé pour Unreal C++ : meilleure indexation que Visual Studio sur les gros projets Unreal, support natif des macros (UCLASS, UPROPERTY, UFUNCTION), navigation intégrée vers les Blueprints, et débogueur complet. Depuis octobre 2024, JetBrains offre Rider gratuitement pour usage non commercial (apprentissage, projets personnels, contenu open source). C'est notre cas pour ce cours.
⚠️
Très important : si vous avez d’anciennes installations de JetBrains Rider (par exemple our Unity et C#, désintallez d’abord, sinon la configuration et l’intégration avec Unreal Engine risque d’échouer !
S’inscrire avec votre courriel étudiant Rubika (recommandé) ou personnel
Vérifier votre courriel pour activer le compte
2. Télécharger Rider
☝🏼
N’oubliez pas de tout cocher dans la fenêtre “Installation Options” de JetBrains Rider Setup
3. Activer la licence non commerciale
Au premier lancement de Rider :
Choisir “Log in to JetBrains Account”
Se connecter avec le compte créé à l’étape 1
Dans la fenêtre de licence, sélectionner “Non-Commercial Use”
Cocher la case confirmant l’usage non commercial (cours, projets personnels, etc.)
Cliquer “Start Trial / Activate”
La licence non commerciale envoie des données d’usage anonymisées à JetBrains (c’est la condition de la gratuité). Aucun code source n’est transmis.
4. Configuration spécifique pour Unreal Engine
Ouvrir l’Unreal Engine Editor 5.7.4
Menu Edit → Editor Preferences
Chercher “Source Code”
Dans Source Code Editor, sélectionner Rider Uproject (Attention, pas “Rider”, mais bien “Rider Uproject”)
Redémarrez UE Editor
Le menu Tools affiche maintenant Refresh Rider Uproject Project et Open Rider Uproject
Cliquer Tools → Refresh Rider Uproject Project pour générer/mettre à jour les fichiers de projet
Rider va prendre un peu de temps à s’installer, va vous poser quelques questions. Puis, à la fin (soyez patients, ça peut prendre du temps), Rider va vous demander “RiderLink plugin is recommended”:
Faites “Install plugin in Engine”.
☝🏼
Vous avez fait “Install plugin in Engine ? Sinon, refaites-le en suivant les instructions de la section ci-après (Plugin RiderLink):
Plugin RiderLink
RiderLink est le plugin qui connecte Rider à l'éditeur Unreal : logs en direct dans Rider, navigation Blueprint vers C++, points d'arrêt dans le PIE, etc. Il existe deux endroits où l'installer.
Emplacement
Où le plugin est installé
Quand choisir
Install RiderLink in Game Plugins
<Projet>/Plugins/Developer/RiderLink/
Le plugin suit le projet. Si vous partagez le projet, chaque collaborateur le récompile. Pratique si vous avez plusieurs versions d’UE en parallèle.
Install RiderLink in Engine Plugins
<UE_5.7>/Engine/Plugins/Developer/RiderLink/
Disponible pour tous les projets UE 5.7.x de cette machine. Recommandé pour ce cours (une seule machine, une seule version).
☝🏼
Fermez Autant Rider que Unreal Engine Editor !
5. Vérification
Dans Rider, vous devriez voir :
Le projet listé dans l’arborescence avec les dossiers Source/, Plugins/, Content/
Les macros Unreal colorées correctement (UCLASS, UPROPERTY, UFUNCTION en jaune/orange selon le thème)
L’autocomplétion qui propose les types Unreal (AActor, UStaticMeshComponent, FVector, etc.)
Un bouton “Build” et un bouton “Run” en haut à droite avec une configuration DebugGame Editor ou Development Editor
Une icône Unreal (avec le cercle) au bas du sidebar de Rider (c’est le Unreal Link), duquel vous pouvez voir en temps réel les logs d’Unreal Engine Editor !
Si l’autocomplétion ne fonctionne pas immédiatement : laisser Rider terminer l’indexation (barre de progression en bas), puis File → Invalidate Caches → Restart au besoin.
Test rapide de la liaison Rider <-> Unreal (RiderLink)
Pour valider que RiderLink communique bien avec l'éditeur Unreal :
Dans Unreal Editor, vérifier que le plugin est actif : Edit → Plugins, chercher “Rider Integration” (c’est le nom d’affichage de RiderLink), la case doit être cochée.
Garder l’éditeur Unreal ouvert et lancer Rider sur le même projet (Tools → Open Rider Uproject)
Dans Rider, ouvrir la fenêtre Unreal Log : View → Tool Windows → UnrealLink (ou l’icône Unreal dans le sidebar). Les logs de UE doivent défiler en direct quand on agit dans l’éditeur (compilation, sélection d’un actor, lancement du PIE, etc.).
Ferme UE et Rider. Redémarre ton projet MyCpp
Tools → Open Rider Unreal
Si Rider te propose d’installer le plugin, choisis Install RiderLink in Engine Plugins
Tu devrais voir deux plugins en cherchant pour Rider maintenant dans Edit → Plugins dans UE
Si ces deux signaux (logs en direct + “Open in Rider”) fonctionnent, la liaison est opérationnelle.
Pour enfin tester que l’intégration C++ BluePrint fonctionne :
Dans UE : Tools > New C++ Class > Actor, donne-lui un nom (ex. MyNode), crée-le.
UE va lancer la compilation et Rider devrait s’ouvrir (ou prendre le focus) sur MyNode.h.
Créer ensuite un Blueprint Class qui hérite de MyNode (clic droit dans Content Browser > Blueprint Class > chercher MyNode dans “All Classes”).
Double-clique sur ce Blueprint.
En haut à droite, tu devrais voir “Parent Class” et si tu cliques, le code va s’ouvrir dans Rider.
Pareillement, dans Rider, juste en dessous de UCLASS dans le fichier .h de ta classe (MyNode.h), Rider devrait t’indiquer “1 derived blueprint class…”. Si tu clic dessus, le blueprint editor va s’ouvrir dans Unreal.
☝🏼
Si vous ne voyez pas « 1 derived blueprint class… », c’est que, sans doute, le Blueprint n’a pas été sauvegardé dans Unreal (une étoile devant l’icône du blueprint devrait être présent dans le Content Browser. Right-click → Save sur cet icône !
Pourquoi des macros ? Le système de réflexion
En C++ standard, le compilateur "oublie" presque tout après la compilation. Le binaire final ne sait pas, à l'exécution :
Cette absence d’introspection est un choix de design du C++ (performance, simplicité (sérieux, simplicité ?!?!?)),/ mais elle pose un vrai problème pour un moteur de jeu comme Unreal :
Comment sauvegarder un Actor sur disque sans connaître ses champs ?
Comment afficher ses propriétés dans le panneau Details de l’éditeur ?
Comment laisser un Blueprint appeler une fonction C++ qu’il ne connaît qu’à l’exécution ?
Comment le garbage collector sait-il quels objets sont encore vivants ?
Comment répliquer un champ à travers le réseau en multijoueur ?
Pour répondre à tout cela, Unreal ajoute une couche de réflexion par-dessus le C++ : un système qui décrit, à l’exécution, la structure de votre code. C’est exactement ce que font les macros UCLASS, USTRUCT, UPROPERTY, UFUNCTION et compagnie. Sans elles, votre code C++ existe, mais le moteur ne le voit pas.
Comment ça marche en coulisses : l'Unreal Header Tool
Avant chaque compilation C++ classique (MSVC ou Clang), Unreal lance un outil maison : l'Unreal Header Tool (UHT - Pas le lait Ultra Haute Température). Cet outil scanne tous vos fichiers .h, repère les macros U-quelque-chose, et génère automatiquement du code C++ supplémentaire dans des fichiers nommés MaClasse.generated.h. Ce code généré contient toute la "plomberie" qui décrit votre classe au moteur : tables de noms, accesseurs réflexifs, code de sérialisation, hooks pour le GC, etc.
Le flux complet :
.h écrit par vous │ ▼UHT lit les macros UCLASS / UPROPERTY / UFUNCTION... │ ▼.generated.h créé automatiquement (le "pont" vers le moteur) │ ▼Compilateur C++ compile votre code + le code généré ensemble │ ▼Binaire final qui contient les métadonnées Unreal │ ▼Editeur, Blueprint, GC, save, réseau peuvent enfin "voir" votre code
C’est pour cette raison que vous verrez toujours ces deux conventions dans tout fichier Unreal :
#include "MaClasse.generated.h" // DOIT etre le DERNIER include du .h
Et dans le corps de chaque classe ou struct exposée :
GENERATED_BODY() // PREMIERE ligne du corps de la classe
GENERATED_BODY() est l’emplacement où UHT injecte le code généré. Sans elle, le pont n’est pas branché et la compilation échoue avec une erreur cryptique. Mais bon, les erreurs en C++ sont en général toujours cryptiques 😞
Convention : préfixes obligatoires
Avant de voir les macros une par une, retenez la convention de nommage Unreal. UHT vérifie le préfixe de chaque classe et refuse de compiler si vous l'oubliez.
Préfixe
Hérite de…
Exemple
Garbage Collected ?
A
AActor ou un descendant (objets placés dans le monde)
AMyActor, APawn, ACharacter
Oui
U
UObject ou un descendant non-Actor (composants, libraries)
Aucun héritage UObject (struct simple, type valeur)
FVector, FRotator
Non (type valeur, peut contenir des UPROPERTY)
T
Template
TArray, TMap<K,V>, TSubclassOf
Non (mais voir note sur UPROPERTY)
I
Interface C++ pure, implémentée par les classes UObject
IMonInterface
Non (le jumeau UInterface l’est)
E
Enum
EColorMode
Non
S
SWidget (UI Slate)
SButton, SCompoundWidget
Non (TSharedPtr, comptage de références)
b
Variable booléenne (convention de nommage, pas une classe)
bIsAlive, bCanJump
s/o
Précisions importantes
UCLASS() - Marque une classe
UCLASS()class AMyActor : public AActor{ GENERATED_BODY() // ...};
Ce que ça débloque :
La classe devient visible dans l’éditeur (menu “Add C++ Class”, possibilité de créer un Blueprint enfant qui en hérite).
Les instances sont gérées par le garbage collector d’Unreal.
La classe peut être sérialisée (sauvegarde dans un .umap ou un .uasset).
Spécificateurs utiles :Blueprintable (autorise la création de Blueprint enfant), BlueprintType (autorise le type comme variable Blueprint), Abstract (interdit l’instanciation directe). Pour la plupart des cas, UCLASS() sans option suffit.
USTRUCT() - Marque un struct
USTRUCT(BlueprintType)struct FMyColor{ GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite) float Value = 5.0f;};
Ce que ça débloque :
Le struct peut être utilisé comme type de UPROPERTY dans une autre classe.
Le struct est sérialisable (sauvegardé avec l’objet qui le contient).
UPROPERTY() - Expose une variable membre
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats")int32 Health = 100;UPROPERTY(VisibleAnywhere, BlueprintReadOnly)UStaticMeshComponent* Mesh;
Ce que ça débloque :
La variable apparaît dans le panneau Details de l’éditeur.
La variable est sauvegardée avec l’objet (sérialisation).
La variable est visible ou manipulable depuis Blueprint (selon les spécificateurs).
Si c’est un pointeur vers un UObject, il devient suivi par le GC. Très important : un UObject* SANS UPROPERTY peut être détruit sous vos pieds par le garbage collector au moment le plus inattendu.
Spécificateurs courants (combinables) :
Spécificateur
Effet
EditAnywhere
Modifiable dans le Details panel, sur l’instance comme sur le default
EditDefaultsOnly
Modifiable uniquement sur les valeurs par défaut de la classe (pas sur l’instance placée dans le monde)
EditInstanceOnly
Modifiable uniquement sur les instances placées dans le monde
VisibleAnywhere
Visible mais NON modifiable
BlueprintReadWrite
Lisible ET assignable depuis Blueprint (Get + Set)
BlueprintReadOnly
Lisible mais pas assignable depuis Blueprint (Get seulement)
Category = "Nom"
Regroupe la propriété sous “Nom” dans le Details panel
Règle pratique :Edit* contrôle l’éditeur, Blueprint* contrôle le Blueprint. Les deux familles sont indépendantes. On peut combiner EditAnywhere, BlueprintReadOnly (modifiable dans l’éditeur, lecture seule depuis Blueprint), ou VisibleAnywhere, BlueprintReadWrite (juste affichée dans l’éditeur, mais lisible et assignable depuis Blueprint).
La fonction peut être appelée depuis Blueprint (devient un noeud).
La fonction peut être liée à un événement réseau (RPC) ou à une animation.
Permet aussi BlueprintImplementableEvent et BlueprintNativeEvent pour des fonctions implémentées (ou surchargées) côté Blueprint.
Spécificateurs courants :
Spécificateur
Effet sur le noeud Blueprint
BlueprintCallable
Noeud avec pin d’exécution (entrée et sortie blanches). Pour les fonctions qui ont un effet de bord (modifient l’état).
BlueprintPure
Noeud SANS pin d’exécution (uniquement des pins de données). Pour les fonctions qui calculent et retournent une valeur sans rien modifier (souvent const).
BlueprintImplementableEvent
Pas d’implémentation C++. La fonction sera écrite en Blueprint par les designers.
BlueprintNativeEvent
Implémentation C++ par défaut, mais peut être surchargée en Blueprint. Idéal pour donner un comportement par défaut modifiable.
Category = "Nom"
Catégorie dans le menu contextuel (clic droit) du graph Blueprint. La barre verticale `
Règle de pouceBlueprintCallable vs BlueprintPure : si votre fonction a const à la fin et ne modifie rien, c’est probablement une candidate à BlueprintPure. Sinon, BlueprintCallable.
GENERATED_BODY() - Le pont vers le code généré
À placer en première ligne du corps de toute classe UCLASS ou struct USTRUCT. Ce n'est pas une option : sans elle, l'objet est invisible pour le moteur et la compilation échoue.
UCLASS()class AMyActor : public AActor{ GENERATED_BODY() // <- TOUJOURS en premier, juste apres l'accolade ouvrantepublic: // votre code ici};
UENUM() et UINTERFACE() (mention)
UENUM(BlueprintType) : expose un enum à Blueprint.
UINTERFACE() + IInterface : déclare une interface utilisable à la fois en C++ et en Blueprint. Plus avancé, on n’en aura pas besoin pour le ColorLab.
À retenir
Une variable ou une fonction sans macro U* n'existe pas pour Unreal. Elle reste du C++ "pur", invisible pour l'éditeur, les Blueprints, le garbage collector et le système de save. Les macros sont la frontière entre votre code et le moteur.
Dans la suite du cours, presque chaque classe que vous écrirez aura cette structure :
#include "CoreMinimal.h"// autres includes...#include "MaClasse.generated.h" // toujours en dernierUCLASS()class VOTREPROJET_API UMaClasse : public UneClasseDeBase{ GENERATED_BODY() // toujours en premier dans la classepublic: UPROPERTY(...) Type MaVariable; UFUNCTION(...) Type MaFonction(...);};
Votre premier nœud Blueprint en C++
Le super-pouvoir du Tech Artist
Le rôle du Tech Artist dans une équipe : écrire des outils en C++ que les designers utilisent en Blueprint sans jamais toucher au code. Vous écrivez la logique complexe une fois, vous l'exposez comme un noeud Blueprint avec les bonnes macros, et les artistes l'utilisent comme n'importe quel autre noeud du moteur.
Pour cette première démonstration, on va construire un noeud qui génère une spirale de Fibonacci (le motif des graines de tournesol) à partir de trois paramètres : nombre de points, espacement, hauteur. Les designers pourront ensuite faire spawner ce qu’ils veulent (sphères, fleurs, ennemis…) à chaque position retournée.
Qu'est-ce qu'une Blueprint Function Library (BFL) ?
Une Blueprint Function Library, souvent abrégée BFL, est un type de classe Unreal qui sert de boîte à outils de fonctions statiques exposées comme noeuds Blueprint. Caractéristiques :
Hérite de UBlueprintFunctionLibrary.
Toutes ses fonctions sont **static** : on ne crée jamais d’instance de la classe, on appelle directement les fonctions.
Les fonctions sont accessibles depuis n’importe quel Blueprint (Level Blueprint, Actor Blueprint, Widget Blueprint…) via clic droit dans le graph.
C’est exactement le mécanisme derrière les noeuds Blueprint que vous connaissez déjà : Get All Actors of Class, Print String, les fonctions math (Abs, Lerp, Clamp), les fonctions de chaîne (Append, Contains)… toutes viennent de BFL fournies par Epic.
Quand utiliser une BFL plutôt qu’une classe normale ?
Vous avez des fonctions utilitaires sans état (calculs, conversions, génération de données).
Vous voulez des noeuds Blueprint disponibles partout sans avoir à instancier quoi que ce soit.
Le code n’a pas besoin de stocker de variables membres entre appels.
Si votre code a besoin d’état (variables qui survivent entre appels), il faut un Actor ou un UObject, pas une BFL.
Premier noeud en classe : "Hello from C++" avec ChannelTo255
Avant d'écrire un noeud algorithmique complexe, on commence par valider le workflow complet avec une fonction plus simple : convertir une valeur de canal couleur normalisée (0.0 à 1.0) en valeur 8-bit (0-255). C'est exactement le genre de conversion qu'un Tech Artist fait en permanence quand il jongle entre des couleurs FLinearColor (UE, valeurs 0-1) et des valeurs 8-bit (UI, export, hex).
Étape 1 - Créer la BFL ColorLabLibrary depuis Unreal
Dans l’éditeur Unreal, d’abord ouvrir Rider (Tools → Open Rider Uproject)
Toujours dans l’éditeur Unreal, Tools -> New C++ Class…
Cliquer sur le bouton Public pour s’assurer que la classe est dans le dossier publique.
Nommer ColorLabLibrary (sans préfixe : Unreal ajoute U automatiquement).
Cliquer Create Class
Unreal génère les deux fichiers, compile une première fois, et ouvre Rider sur ColorLabLibrary.h.
Étape 2 - Compléter le header
Dans ColorLabLibrary.h (qui se trouve dans le dossier Private de votre projet MyCpp dans Rider), le squelette est déjà là. Ajouter dans le corps de la classe (après GENERATED_BODY()) :
Étape 3 - Implémenter la fonction
Dans ColorLabLibrary.cpp (qui lui se trouve dans le dossier Public), sous l'include du header, ajouter :
#include "Public/ColorLabLibrary.h" // Attention, il se peut que Rider/UE aient ajouté ce include mais sans le parent "Public/", il faut le mettre, car le .h n'est pas au même endroit que le .cppint32 UColorLabLibrary::ChannelTo255(float NormalizedValue){ return FMath::Clamp(static_cast<int32>(NormalizedValue * 255.0f), 0, 255);}
Décortiquons cette ligne unique :
NormalizedValue * 255.0f : la conversion mathématique de base. Si l’entrée vaut 0.5, on obtient 127.5.
static_cast<int32>(...) : tronque la partie décimale pour passer de float à int32. 127.5 devient 127.
FMath::Clamp(..., 0, 255) : borne le résultat à [0, 255] au cas où l’entrée serait hors de [0.0, 1.0] (ex. -0.2 ou 1.3 par accident).
Étape 4 - Compiler
Dans Rider : menu **Build -> Build and Reload (Le marteau avec une petite feuille, **Ctrl+F9 sur PC ou Cmd+F9 sur Mac). Vérifier la fenêtre Build en bas : elle affiche Build successful quand tout passe. En cas d'erreur, double-cliquer sur la ligne d'erreur pour sauter à la ligne fautive.
Unreal Editor a aussi un bouton “Compile” / “Live Coding” dans sa toolbar, mais il est déplacé ou masqué selon les versions de UE 5.x. Compiler depuis Rider est plus fiable et donne de meilleurs messages d’erreur. Si UE Editor est ouvert pendant le build Rider et que Live Coding est activé, le hot-reload se fait automatiquement.
Étape 5 - Tester dans le Level
Plutôt que d'utiliser le Level Blueprint (dont le bouton d'accès change de place selon les versions de UE 5.x), on crée un Blueprint Actor dédié et on le glisse dans le viewport. C'est aussi le workflow standard en production.
Dans le Content Browser,, clic droit sur le dossier “Content”, puis “New folder” → “MyCpp”
À partir de maintenant, ce qu’on va créer, ce sera dans ce dossier donc cliquez dessus pour qu’il devienne le dossier activé.
Dans le Content Browser, à l’intérieur du dossier vide MyCpp, clic droit -> Blueprint Class -> Actor. Le nommer BP_TestColorLab.
Double-cliquer pour l’ouvrir.
Onglet Event Graph. Sur Event BeginPlay, tirer le pin d’exécution -> chercher Channel To 255, placer (le noeud apparaît dans la catégorie ColorLab -> First Steps).
Sur le noeud, mettre Normalized Value = 0.5.
Du pin Return Value (vert/turquoise, int), tirer un fil et lâcher dans le vide. Dans le menu contextuel, décocher “Context Sensitive” en haut à droite : sinon Print String n’apparaît pas, car il attend une string et le pin source est un int. Une fois décoché, chercher Print String et placer. UE insère automatiquement un noeud de conversion To String (Integer) entre les deux pins.
Connecter les pins d’exécution des deux noeuds (le pin blanc d’exécution du Channel To 255 vers celui du Print String).
☝🏼
Context Sensitive est utile (filtre du bruit) quand vous cherchez un noeud, mais cache des noeuds qui peuvent quand même être utilisés. Ici, si Context sensitive est coché, Ue voit que Return Value est un Int, et ne montre donc pas le noeud “Print String” ; cela va vous pousser à utiliser d’abord un “To String (int)” après-lequel, seulement, apparaîtra le noeud “Print String”. Deux noeuds au lieu d’un (pas trop grave). Sachez que le noeud “Print String” est capable de convertir les types de base en string.
Compile + Save dans la barre d’outils du Blueprint (en haut à gauche).
Fermer la fenêtre du Blueprint, glisser BP_TestColorLab du Content Browser dans le viewport (n’importe où), puis lancer Play.
À l’écran (en haut à gauche, typiquement en vert), vous voyez 127. Vous venez d’écrire votre premier noeud Blueprint en C++.
Ce qui vient de se passer
Vous avez :
Créé une classe C++ héritant de UBlueprintFunctionLibrary.
Marqué une fonction avec UFUNCTION(BlueprintCallable, ...) pour la rendre visible côté Blueprint.
Compilé depuis Rider (avec hot-reload via Live Coding si UE Editor est ouvert).
Appelé la fonction depuis un Blueprint Actor et vu le résultat à l’écran.
C’est le workflow complet C++ -> Blueprint, validé. Si quelque chose ne marche pas pour ChannelTo255 (3 lignes utiles), c’est que la chaîne tooling/macros/compilation a un problème, pas l’algorithme. Avec ce repère, vous saurez où chercher quand un exercice plus complexe ne marchera pas.
Variantes rapides (si le temps le permet en classe)
**Linear255To1(int32 Value) -> float** : la fonction inverse, retourne Value / 255.0f clampé à [0.0, 1.0].
**MakeRGBColor(int32 R, int32 G, int32 B) -> FLinearColor** : retourne un FLinearColor à partir de 3 entiers 0-255 (en utilisant Linear255To1 sur chaque canal). Tester en passant le résultat à un Print String (le texte affiché prend la couleur, vous obtenez un debug visuel naturel).
Ces deux variantes ajoutent ~10 lignes et préparent le terrain pour la Spirale (devoir) en consolidant le workflow.
La Spirale de Tournesol
On passe maintenant à un cran au-dessus du ChannelTo255 validé en classe : générer une spirale de Fibonacci (motif des graines de tournesol) comme noeud Blueprint, puis spawner des sphères à chaque position retournée. Tout le pas-à-pas (création de la classe, code complet, test depuis un Blueprint Actor avec For Each Loop et Spawn Actor From Class) est détaillé dans les sections qui suivent.
L’objectif : appliquer le workflow validé en classe à un cas plus riche, qui retourne un TArray<FVector> au lieu d’un simple int32, et qui se branche à plusieurs noeuds Blueprint en cascade. Le code de la Spirale est aussi un classique en Tech Art (placement procédural d’objets, distribution naturelle), donc bien le maîtriser ouvre la porte à beaucoup d’outils de level design.
Étape 0 - Créer un dossier Fibo (pour Fibonacci) sous Content
Dans Unreal Editor, cliquez droit sur le dossier Content, → New Folder et crééz un dossier nommé “Fibo”
Étape 1 - Créer la classe depuis Unreal Editor
On laisse Unreal créer la BFL pour nous, plutôt que de créer les fichiers à la main. Cela garantit que le système de build connaît les nouveaux fichiers et que les noms respectent les conventions.
Assurez-vous d’être dans le dossier Fibo dans le Content Browser
Dans l’éditeur Unreal, menu Tools -> New C++ Class…
La fenêtre “Choose Parent Class” s’ouvre. En haut, cocher** All Classes**.
Dans le champ de recherche, taper BlueprintFunctionLibrary. Sélectionner BlueprintFunctionLibrary dans la liste, puis cliquer Next.
Dans “Name”, saisir ProceduralLibrary (sans préfixe : Unreal ajoutera U tout seul, comme on l’a vu dans la convention de nommage).
Vérifier que la classe est placée dans Public en choisissant “Class Type” = Public (ainsi, header dans Source/VotreProjet/Public/, implémentation dans Source/VotreProjet/Private/).
Cliquer Create Class.
Unreal va :
Créer ProceduralLibrary.h dans Source/VotreProjet/Public/.
Créer ProceduralLibrary.cpp dans Source/VotreProjet/Private/.
Lancer une première compilation (peut prendre 30 à 60 secondes).
Ouvrir Rider sur ProceduralLibrary.h (grâce à votre configuration Rider Uproject de la Période 1).
☝🏼
Si Rider ne s’ouvre pas tout seul : ouvrir Rider manuellement, ouvrir le .uproject correspondant, puis naviguer dans l’arborescence vers Source/VotreProjet/Public/ProceduralLibrary.h.
Étape 2 - Compléter le header (ProceduralLibrary.h)
Dans Rider, ouvrir ProceduralLibrary.h. Vous voyez le squelette généré par Unreal :
UFUNCTION(BlueprintCallable, ...) : on veut un noeud Blueprint avec pin d’exécution (la fonction crée un tableau, c’est une opération avec effet, pas un simple getter).
Category = "Procedural|Patterns" : la barre verticale | crée une sous-catégorie. Le noeud apparaitra sous Procedural -> Patterns dans le menu contextuel Blueprint.
static : obligatoire pour une BFL.
TArray<FVector> : le type de retour. TArray est l’équivalent Unreal de std::vector, et FVector est un point ou vecteur 3D (X, Y, Z floats).
Trois paramètres, dont ZHeight qui a une valeur par défaut de 0 (donc optionnel quand on appelle la fonction).
Sauvegarder le fichier (Ctrl+S).
Étape 3 - Implémenter l'algorithme (ProceduralLibrary.cpp)
Ouvrir ProceduralLibrary.cpp. Le squelette généré contient juste l'include du header. Ajouter l'implémentation :
#include "Public/ProceduralLibrary.h"TArray<FVector> UProceduralLibrary::GenerateSunflowerSpiral( int32 NumPoints, float Spacing, float ZHeight){ TArray<FVector> Points; const float GoldenAngle = 137.5f * (PI / 180.f); for (int32 i = 0; i < NumPoints; i++) { float Angle = i * GoldenAngle; float Radius = Spacing * FMath::Sqrt(static_cast<float>(i)); float X = Radius * FMath::Cos(Angle); float Y = Radius * FMath::Sin(Angle); Points.Add(FVector(X, Y, ZHeight)); } return Points;}
Détails importants :
Pas de **UFUNCTION** ici : la macro est déclarée sur la signature dans le .h uniquement. Dans le .cpp on écrit la signature normale C++.
PI est une constante définie par UE (pas besoin d’inclure <cmath>).
FMath::Sqrt, FMath::Cos, FMath::Sin sont les versions Unreal des fonctions math. **Toujours utiliser ****FMath::*** plutôt que std::* dans du code Unreal : c’est plus rapide sur certaines plateformes (consoles), et c’est la convention du moteur.
Points.Add(...) est l’équivalent de std::vector::push_back.
static_cast<float>(i) convertit l’int32 en float avant le Sqrt (sinon ambiguïté de surcharge).
Sauvegarder.
Étape 4 - Compiler
Dans Rider, menu Build -> Build Solution (raccourci Ctrl+F9). La fenêtre Build en bas affiche la progression et, en cas de succès, Build successful. En cas d'erreur, double-cliquer sur la ligne d'erreur pour sauter directement à la ligne fautive.
Si la compilation échoue :
“missing GENERATED_BODY()” : vérifier que GENERATED_BODY() est bien en première ligne du corps de classe.
“Cannot find generated.h” ou “fatal error: file not found” : vérifier l’ordre des includes (.generated.h doit être le dernier#include).
“redefinition of class” : vérifier qu’il n’y a qu’un seul UCLASS() et un seul class UProceduralLibrary dans le fichier.
“unresolved external symbol” sur la fonction : vous avez déclaré la fonction dans le .h mais oublié son implémentation dans le .cpp.
Une compilation réussie affiche Build successful dans la fenêtre Build de Rider.
Étape 5 - Tester le noeud avec un Blueprint Actor
Étape 5.1 - Créer un Blueprint qui porte le mesh à spawner
Pourquoi cette étape avant le spawner ? Si on demande à Spawn Actor From Class de spawner un StaticMeshActor brut (la classe nue d’UE), les acteurs spawnent bel et bien (on les voit apparaître dans l’Outliner pendant le Play), mais leur composant Static Mesh est vide : aucun mesh assigné par défaut, donc rien de visible dans le viewport. On crée donc d’abord une sous-classe Blueprint avec une sphère assignée, qu’on utilisera comme classe à spawner à l’étape suivante.
En production, on aurait souvent un acteur métier (un BP_Pickup, un BP_FoliageInstance, etc.) à spawner. Ici, BP_SphereChip joue ce rôle de placeholder visible.
Dans le Content Browser (Dans le dossier Fibo), clic droit -> Blueprint Class. Dans la fenêtre qui s’ouvre, déplier All Classes (en bas), taper StaticMeshActor, sélectionner la classe et cliquer Select.
Nommer le Blueprint BP_SphereChip.
Double-cliquer pour l’ouvrir.
Dans le panneau Components (en haut à gauche), sélectionner le composant StaticMeshComponent (le seul présent par héritage).
Dans le panneau Details (à droite), section Static Mesh, cliquer le dropdown et choisir Engine/BasicShapes/Sphere (taper “Sphere” dans la barre de recherche du dropdown si besoin).
☝🏼
Si la liste des contenus du moteur n’apparaît pas, activer dans le Content Browser le filtre Settings -> Show Engine Content.
Changer le Scale (dans Transform) à 0.2 sur les 3 axes
7. Cliquer Compile, puis Save. Fermer le Blueprint.
Étape 5.2 - Créer le spawner et brancher les noeuds
Dans le Content Browser, dans le dossier Fibo, clic droit -> Blueprint Class -> Actor. Le nommer BP_SunflowerSpawner. Double-cliquer pour l’ouvrir.
Onglet Event Graph. Sur le noeud Event BeginPlay (déjà présent ; sinon clic droit dans le graph, chercher “Begin Play”), faire glisser le pin d’exécution blanc (à droite du noeud) vers la droite et lâcher dans le vide. Un menu contextuel
s’ouvre.
Dans la barre de recherche, taper Generate Sunflower Spiral. Le noeud doit apparaître sous la catégorie Procedural -> Patterns. Cliquer dessus pour le placer.
Sur le noeud Generate Sunflower Spiral, régler les paramètres directement :
Num Points : 500
Spacing : 50
Z Height : 500 (pour que les sphères apparaissent au-dessus du sol et du joueur)
Du pin de sortie Return Value (un tableau de Vector, pin bleu avec icône grille), tirer un fil et chercher For Each Loop. Placer le noeud.
Connecter le pin d’exécution (blanc) du Generate Sunflower Spiral à l’entrée d’exécution du For Each Loop.
Du pin Array Element du For Each Loop (chaque position retournée par le générateur), tirer un fil et chercher Spawn Actor From Class. Placer le noeud.
Sur le Spawn Actor From Class :
Dans le dropdown Class, choisir BP_SphereChip (la classe créée à l’étape 5a). C’est un dropdown : rien à connecter au pin Class, juste sélectionner dans la liste. Si on choisit ici Static Mesh Actor (la classe nue), les acteurs spawnent
mais sont invisibles parce qu’ils n’ont aucun mesh assigné.
Le pin Spawn Transform attend un FTransform. Faire clic droit sur ce pin -> Split Struct Pin. On voit maintenant Location, Rotation, Scale séparés.
Connecter Array Element (du For Each Loop) à Spawn Transform Location.
Connecter le pin d’exécution Loop Body du For Each Loop à l’entrée d’exécution du Spawn Actor From Class.
Cliquer Compile puis Save dans la barre d’outils du Blueprint (en haut à gauche).
Fermer la fenêtre du Blueprint. Dans le Content Browser, glisser BP_SunflowerSpawner dans le viewport (n’importe où dans le niveau).
Appuyer sur Play (bouton triangulaire en haut du viewport).
Vous devriez voir 500 sphères apparaître en spirale de Fibonacci, centrées sur la position où vous avez placé BP_SunflowerSpawner dans le niveau. Si la scène est noire, ajouter une lumière (clic droit dans le viewport -> Place Actor -> Lights ->
Directional Light), puis refaire Play.
Si rien n’apparaît dans le Level pendant le Play :
Ouvrir l’Outliner pendant que le jeu tourne. Si on voit 500 entrées BP_SphereChip apparaître : le spawn fonctionne, c’est un problème de visibilité (mesh manquant, échelle nulle, caméra mal placée). Si on ne voit aucune entrée : c’est le branchement
du graph qui est en cause (Compile pas refait, fil oublié, BP_SunflowerSpawner pas glissé dans le niveau avant Play).
Vérifier que le Blueprint a bien été compilé après les branchements (bouton Compile en haut à gauche, pas juste Save).
Vérifier que BP_SunflowerSpawner est bien glissé dans le viewport avant d’appuyer sur Play. Sans instance dans le niveau, BeginPlay ne se déclenche jamais.
Si on voit des StaticMeshActor (et pas BP_SphereChip) dans l’Outliner, c’est qu’on a oublié de changer le dropdown Class du Spawn Actor From Class après avoir créé BP_SphereChip. Repasser à l’étape 8.
Changements clés :
Étape 5 scindée en 5a (créer BP_SphereChip qui hérite de StaticMeshActor avec sphère assignée) et 5b (le spawner avec son graph).
Note explicite à l’étape 8 : le pin Class est un dropdown, on n’y branche pas de fil, et on choisit BP_SphereChip pas Static Mesh Actor brut (la classe nue spawne des acteurs invisibles).
Bloc de dépannage final qui distingue “spawn marche mais pas visible” vs “spawn ne marche pas du tout” via l’Outliner.
Comprendre l'algorithme
Pour chaque point i de 0 a N: angle = i x 137.5° (angle d'or, converti en radians) rayon = spacing x √i (les points s'éloignent du centre) x = rayon x cos(angle) y = rayon x sin(angle) z = hauteur fixe
Pourquoi 137.5° ? C’est 360° / phi² (où phi = nombre d’or ≈ 1.618). Cet angle “irrationnel” garantit qu’aucun point ne tombe jamais sur un alignement radial avec un autre point déjà placé. Résultat : empilement maximal, aucun trou. La nature a “trouvé” cet angle dans les graines de tournesol, les pommes de pin, l’ananas, les pétales.
Pourquoi **√i** pour le rayon ? Pour que la densité de points reste constante quand on s’éloigne du centre. Si le rayon était linéaire (i x spacing), les points seraient denses au centre et clairsemés en périphérie. Avec √i, l’aire couverte par chaque “anneau” de points reste constante. C’est le même principe qui régit la croissance des plantes.
Pourquoi c'est puissant
Vous venez d'écrire ~20 lignes de C++ qui deviennent un outil utilisable par n'importe quel designer du studio, dans n'importe quel Blueprint, sans qu'ils aient à comprendre le code. Et vous pouvez maintenant :
Changer l’algorithme (spirale carrée, distribution Poisson, fractale…) en modifiant le .cpp et en recompilant. Tous les Blueprints qui utilisent le noeud bénéficient immédiatement du changement.
Ajouter d’autres fonctions à la même BFL (placement sur grille, dispersion aléatoire, distribution de Voronoï…).
Donner cette BFL à toute l’équipe, qui l’utilisera dans leurs propres niveaux sans dupliquer le code.
Travail personnel
Variante 1 : changer le motif en passant 60.0f au lieu de 137.5f pour le GoldenAngle. Observer le résultat (pas de spirale, alignements visibles). Comprendre pourquoi 137.5° est si particulier.
**Variante 2 : ajouter un paramètre ****FLinearColor TintColor** à la fonction. Pour le moment ce paramètre n’a aucun effet sur l’algorithme, mais vous verrez son pin coloré apparaître dans le noeud Blueprint. C’est l’occasion de tester d’autres types de paramètres exposés à Blueprint.
Variante 3 : créer une deuxième fonction dans la même BFL, par exemple GenerateGridPoints(int32 Rows, int32 Cols, float Spacing) qui retourne un TArray<FVector> représentant une grille régulière. Une seule BFL peut contenir autant de fonctions utilitaires que vous voulez.