DEV Community

Zlormack
Zlormack

Posted on

Pourquoi j’ai créé ZlormaEngine au lieu d’utiliser un moteur existant

Pourquoi j’ai créé ZlormaEngine au lieu d’utiliser un moteur existant

Développement d’un moteur de jeu léger en Rust pour créer Gemfall Arena

Je travaille actuellement sur ZlormaEngine, un petit moteur de jeu développé en Rust pour faire fonctionner mon premier jeu vitrine : Gemfall Arena.

Je ne cherche pas à créer un concurrent de Unity, Unreal Engine ou Godot. Ces moteurs sont beaucoup plus complets et sont développés par des équipes expérimentées.

Mon objectif est différent : je veux construire un moteur simple, léger et spécialisé dans les jeux de tir en vue du dessus. Cela me permet de comprendre ce qui se passe réellement derrière une fenêtre de jeu, un déplacement, un tir ou une collision.

ZlormaEngine est donc à la fois un projet de moteur et un projet d’apprentissage.
Pourquoi créer mon propre moteur ?

J’aurais pu utiliser un moteur déjà existant pour développer Gemfall Arena plus rapidement.

Cependant, je voulais répondre à plusieurs objectifs personnels :

apprendre Rust avec un projet concret ;

comprendre la boucle principale d’un jeu ;

contrôler directement le rendu et le gameplay ;

limiter le nombre de dépendances ;

produire un exécutable très compact ;

générer une grande partie du contenu sans assets externes ;

créer une technologie adaptée précisément à Gemfall Arena.
Enter fullscreen mode Exit fullscreen mode

Utiliser mon propre moteur demande plus de travail. Par exemple, une fonctionnalité simple dans un moteur existant peut demander plusieurs fichiers et de nombreux tests dans ZlormaEngine.

Mais chaque problème résolu m’aide à mieux comprendre la programmation d’un jeu.
Une architecture Rust volontairement simple

L’architecture de ZlormaEngine reste volontairement légère.

Le projet peut être séparé en plusieurs parties principales :
Plain Text
Copy

ZlormaEngine
├── platform
│ ├── linux_x11
│ └── windows_win32
├── renderer
├── input
├── game
├── entities
├── procedural
├── waves
├── difficulty
└── shop

La partie platform gère les différences entre Linux et Windows.

Le renderer dessine les formes, les projectiles, les ennemis, les gems et les effets.

La partie game contient la boucle principale et les règles de Gemfall Arena.

Les autres modules s’occupent des systèmes plus précis comme les vagues, la boutique ou la génération procédurale.

Je préfère pour le moment une architecture claire et compréhensible plutôt qu’un système très abstrait. Mon niveau évolue en même temps que le projet, donc je garde du code que je peux relire et modifier facilement.
La boucle principale du moteur

Comme beaucoup de jeux, Gemfall Arena utilise une boucle principale.

De manière simplifiée, elle fonctionne comme ceci :
Plain Text
Copy

while game.is_running() {
platform.process_events();
game.update();
renderer.clear();
game.render(&mut renderer);
platform.present(renderer.framebuffer());
}

À chaque image, le moteur :

récupère les actions du clavier et de la souris ;

met à jour le joueur et les ennemis ;

vérifie les collisions ;

met à jour les projectiles et les particules ;

dessine la nouvelle image ;

affiche le framebuffer dans la fenêtre.
Enter fullscreen mode Exit fullscreen mode

Cette boucle paraît simple, mais beaucoup de systèmes sont exécutés à l’intérieur.
L’objectif des 3 Mio

L’un des objectifs de ZlormaEngine est de garder le moteur et le jeu final sous une limite d’environ 3 Mio.

Cette limite est surtout un défi technique personnel. Elle m’oblige à réfléchir à ce qui est réellement nécessaire.

Pour réduire la taille, j’utilise notamment un profil Rust orienté vers la taille :
Plain Text
Copy

[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
strip = true

Ces options permettent de demander au compilateur de privilégier un exécutable plus petit.

J’essaie également de :

limiter les dépendances externes ;

éviter d’intégrer des textures volumineuses ;

générer les éléments graphiques directement ;

utiliser des structures de données simples ;

ne compiler que les fonctions réellement nécessaires ;

séparer le code Linux et Windows avec la compilation conditionnelle.
Enter fullscreen mode Exit fullscreen mode

La limite de 3 Mio reste un objectif. Elle doit encore être vérifiée après chaque nouvelle version, car chaque fonctionnalité peut augmenter la taille du programme.
Un rendu logiciel

ZlormaEngine utilise un rendu logiciel.

Cela signifie que le moteur dessine directement les pixels dans une zone mémoire appelée framebuffer.

Par exemple, pour dessiner un pixel :
Plain Text
Copy

fn put_pixel(buffer: &mut [u32], x: i32, y: i32, color: u32) {
if x < 0 || y < 0 {
return;
}

let x = x as usize;
let y = y as usize;

if x >= WIDTH || y >= HEIGHT {
    return;
}

buffer[y * WIDTH + x] = color;
Enter fullscreen mode Exit fullscreen mode

}

À partir de cette fonction, il est possible de construire :

des rectangles ;

des cercles ;

des lignes ;

des projectiles ;

des particules ;

des barres de vie ;

des ennemis ;

des gems.
Enter fullscreen mode Exit fullscreen mode

Ce système reste moins puissant qu’un rendu moderne utilisant directement le GPU, mais il est suffisant pour mon jeu actuel.

Il me permet aussi de comprendre précisément comment l’image est créée.
Windows avec Win32

La première architecture était uniquement compatible avec X11. Elle pouvait fonctionner sous Linux, mais elle ne pouvait pas produire directement une version Windows fonctionnelle.

J’ai donc prévu un second backend basé sur Win32 et GDI.

Le gameplay reste identique. Seule la couche liée au système change.

Rust permet de séparer les plateformes avec des conditions de compilation :
Plain Text
Copy

[cfg(target_os = "linux")]

mod linux_x11;

[cfg(target_os = "windows")]

mod windows_win32;

Cela permet d’utiliser X11 uniquement sous Linux et Win32 uniquement sous Windows.

C’est une étape importante pour ZlormaEngine, car je souhaite proposer Gemfall Arena sur les deux systèmes.
La génération procédurale

La génération procédurale est une partie importante de l’identité du moteur.

Au lieu de charger de nombreux fichiers externes, ZlormaEngine peut générer directement certains éléments à partir de nombres et de règles.

Cela concerne notamment :

les positions des obstacles ;

les variations de couleurs ;

les formes des ennemis ;

les particules ;

les explosions ;

les trajectoires ;

les vagues ;

certaines variations de difficulté.
Enter fullscreen mode Exit fullscreen mode

Un générateur pseudo-aléatoire simple peut être utilisé pour obtenir des résultats différents :
Plain Text
Copy

struct Random {
state: u32,
}

impl Random {
fn next(&mut self) -> u32 {
self.state ^= self.state << 13;
self.state ^= self.state >> 17;
self.state ^= self.state << 5;
self.state
}
}

L’utilisation d’une graine permet également de recréer une même génération.

Ce système est pratique pour produire du contenu sans augmenter fortement la taille du jeu.

Le système de vagues

La démo de Gemfall Arena contient actuellement 10 vagues.

Chaque vague détermine :

le nombre d’ennemis ; leurs points de vie ; leur vitesse ; les types d’ennemis disponibles ; la quantité potentielle de gems ; la pression exercée sur le joueur.

Une formule simple peut servir de base :

let enemy_count = 4 + wave * 2; let enemy_health = 1.0 + wave as f32 * 0.15; let enemy_speed = 0.8 + wave as f32 * 0.04;

Je dois toutefois faire attention à ne pas augmenter uniquement les nombres.

Ajouter trop de points de vie peut rendre les ennemis longs à éliminer sans rendre le combat réellement plus intéressant.

Je préfère donc mélanger plusieurs changements : vitesse, quantité, position et types d’ennemis.
Une difficulté adaptative

Le numéro de la vague ne suffit pas toujours à déterminer une bonne difficulté.

Deux joueurs peuvent atteindre la même vague avec des performances très différentes.

ZlormaEngine peut donc analyser plusieurs éléments :

le temps nécessaire pour terminer une vague ;

les dégâts reçus ;

la précision des tirs ;

la quantité de vie restante ;

le score ;

les améliorations déjà achetées.
Enter fullscreen mode Exit fullscreen mode

Le moteur produit ensuite une valeur de difficulté.
Plain Text
Copy

let mut difficulty = wave as f32;

if accuracy > 0.70 {
difficulty += 0.4;
}

if damage_received == 0 {
difficulty += 0.3;
}

if clear_time > expected_time {
difficulty -= 0.2;
}

Ce système est encore en cours d’équilibrage.

Le but n’est pas de punir un bon joueur, mais de conserver une tension intéressante pendant toute la partie.
La Zlorma Gem Forge

La Zlorma Gem Forge est la boutique de Gemfall Arena et l’une des signatures que je souhaite conserver dans les jeux créés avec ZlormaEngine.

Les ennemis peuvent laisser tomber des gems lorsqu’ils sont éliminés.

Le joueur les récupère dans l’arène, puis peut les dépenser entre les vagues.

La particularité est que chaque amélioration possède un avantage et un inconvénient.

Par exemple :

Amélioration

Bonus

Malus

Rapid Core

cadence de tir augmentée

ennemis plus rapides

Power Shard

dégâts augmentés

santé maximale réduite

Vital Shell

santé maximale augmentée

déplacement plus lent

Phase Boots

vitesse augmentée

prix plus élevés

Gem Magnet

attraction des gems améliorée

ennemis plus résistants

Je trouve ce système plus intéressant qu’une boutique où chaque achat rend simplement le joueur plus puissant.

Le joueur doit réfléchir aux conséquences de son build.

Des prix liés au score

Le prix des améliorations peut évoluer selon la vague et le score.

Une formule simple peut ressembler à ceci :

fn calculate_price(base_price: u32, wave: u32, score: u32) -> u32 { base_price + wave * 3 + score / 500 }

Un joueur performant gagne davantage de gems, mais les améliorations peuvent également devenir plus coûteuses.

Ce système doit rester lisible. Si la formule est trop complexe ou trop punitive, le joueur ne comprend plus pourquoi les prix changent.

L’équilibrage de la boutique sera donc amélioré progressivement grâce aux retours des joueurs.
Les erreurs rencontrées

Créer un moteur apporte forcément des erreurs.
Le projet ne trouvait pas le script Bash

Lors des premières versions, le script de génération se trouvait dans le dossier de téléchargement, mais il était lancé depuis un autre dossier.

Linux indiquait alors :
Plain Text
Copy

Aucun fichier ou dossier de ce nom

La solution était simplement de se placer dans le bon dossier ou d’utiliser le chemin complet.

Cette erreur m’a rappelé qu’un script destiné aux utilisateurs doit afficher clairement où il se trouve et où il crée les fichiers.
Le moteur était uniquement compatible #Linux

La première version utilisait directement X11.

Le code ne pouvait donc pas être compilé tel quel pour Windows.

La solution a été de séparer le moteur en une partie commune et plusieurs backends de plateforme.
L’image du jeu était trop petite

La résolution interne était affichée directement sans agrandissement adapté.

Le jeu apparaissait dans un coin avec beaucoup d’espace noir.

La solution a été de conserver la petite résolution pour les calculs, mais de l’agrandir proprement lors de l’affichage.
Les ennemis se superposaient

Lorsque plusieurs ennemis poursuivaient le joueur, ils pouvaient se placer exactement au même endroit.

J’ai ajouté une petite force de séparation entre les ennemis proches.

Cela donne des déplacements plus naturels et rend les groupes plus lisibles.
Les fonctionnalités augmentent la complexité

Chaque nouvelle fonction peut créer des effets secondaires.

Une amélioration de vitesse peut modifier l’équilibrage. Une nouvelle particule peut affecter les performances. Une nouvelle plateforme peut ajouter des erreurs de compilation.

Je dois donc avancer étape par étape et tester régulièrement.
Ce que j’ai appris jusqu’à présent

Le développement de ZlormaEngine m’a déjà appris plusieurs choses importantes.

Un moteur n’est pas seulement un système de rendu. Il doit relier la plateforme, les entrées, les règles de jeu, les collisions, l’affichage et la gestion des ressources.

J’ai également compris que l’optimisation ne consiste pas uniquement à rendre le code plus rapide.

Elle concerne aussi :

la taille de l’exécutable ;

la consommation de mémoire ;

la lisibilité du code ;

la facilité de compilation ;

la compatibilité entre systèmes ;

l’expérience du joueur.
Enter fullscreen mode Exit fullscreen mode

Je suis encore en apprentissage, mais Gemfall Arena me donne un projet concret pour progresser.
Les prochaines étapes

Mes prochains objectifs pour ZlormaEngine sont :

stabiliser les versions Linux et Windows ;

améliorer les effets sonores procéduraux ;

mieux équilibrer les 10 vagues ;

rendre la difficulté adaptative plus naturelle ;

améliorer la Zlorma Gem Forge ;

ajouter de nouveaux comportements ennemis ;

vérifier régulièrement la limite de 3 Mio ;

publier davantage de DevLogs techniques ;

recueillir les retours des joueurs et développeurs.
Enter fullscreen mode Exit fullscreen mode

ZlormaEngine reste un moteur jeune, mais il fait déjà fonctionner une démonstration complète.

Je souhaite continuer à l’améliorer progressivement, sans perdre son objectif principal : rester petit, compréhensible et spécialisé.
Conclusion

J’ai créé ZlormaEngine parce que je voulais apprendre en construisant réellement les différentes parties d’un jeu.

Cette méthode est plus longue que l’utilisation d’un moteur existant, mais elle me permet de comprendre mes erreurs, de tester mes idées et de développer une technologie adaptée à Gemfall Arena.

Je ne cherche pas à prétendre que mon moteur est meilleur que les solutions existantes.

Je veux simplement créer un moteur qui correspond à mon projet, à mon niveau actuel et à ma manière d’apprendre.

Chaque nouvelle version de Gemfall Arena représente aussi une nouvelle étape dans le développement de #ZlormaEngine.

Zlormack
Développeur indépendant de ZlormaEngine et Gemfall Arena

Construire petit, comprendre mieux, progresser à chaque build.
Enter fullscreen mode Exit fullscreen mode

🎮 Gemfall Arena :
🎬 Trailer :

Top comments (0)