Chap 6 – Édition de Niveaux, Gestion de Mémoire et Sauvegarde JSON
Introduction
Dans cet article, nous allons explorer en détail les concepts abordés dans le dernier épisode d’une série sur la création de jeux vidéo avec le langage de programmation Odin et le framework Raylib, une bibliothèque puissante pour le développement de jeux en 2D et 3D.
Nous verrons comment développer un éditeur de niveaux, gérer les fuites de mémoire, et sauvegarder/charger les niveaux en format JSON.
Chaque étape inclut des exemples de code tirés directement de la vidéo pour faciliter l’intégration dans vos projets.
Pourquoi un Éditeur de Niveaux ?
L’éditeur de niveaux est une fonctionnalité cruciale dans la création de jeux, permettant aux développeurs de placer des objets ou obstacles de façon dynamique.
Dans notre exemple, nous ajoutons la possibilité de placer des plateformes où le personnage du jeu (un chat) peut sauter et se déplacer.
Voici le code initial qui définit un simple niveau sans fonctionnalité d’édition :
struct Platform {
pos: rl.Vector2, // Coordonnées de la plateforme
}
struct Level {
platforms: []Platform, // Tableau dynamique de plateformes
}
level: Level = {
platforms = [
Platform{ pos = rl.Vector2{ x = -20, y = 20 } },
Platform{ pos = rl.Vector2{ x = 90, y = 50 } },
],
}
Ce code initialise une struct Level
avec des plateformes définies de manière statique. Pour rendre l’expérience plus interactive, passons à l’ajout de capacités d’édition de niveaux.
2. Ajout de Fonctions d’édition de niveaux
Pour permettre l’édition, nous ajoutons un mode interactif activé par la touche F2. Le joueur peut alors placer ou supprimer des plateformes à l’aide de la souris. Cette fonctionnalité repose sur un tableau dynamique pour que les plateformes puissent être ajoutées ou supprimées en cours de jeu.
// Activer/désactiver le mode édition avec la touche F2
var editing: bool = false if rl.IsKeyPressed(rl.KEY_F2) {
editing = !editing
}
Pour placer une nouvelle plateforme, nous utilisons rl.GetScreenToWorld2D
de Raylib pour convertir la position de la souris en coordonnées du monde, ce qui permet un placement précis en fonction de la position du joueur dans le niveau.
// Placer une plateforme avec le clic gauche
if editing && rl.IsMouseButtonPressed(rl.MOUSE_LEFT_BUTTON) {
mp := rl.GetScreenToWorld2D(rl.GetMousePosition(), camera)
level.platforms = append(level.platforms, Platform{ pos = mp })
}
Pour supprimer une plateforme, nous vérifions si le curseur de la souris se trouve au-dessus d’une plateforme existante. Si oui, elle est retirée du tableau dynamique :
// Supprimer une plateforme avec le clic droit if editing && rl.IsMouseButtonPressed(rl.MOUSE_RIGHT_BUTTON) {
for i, platform in level.platforms {
if rl.CheckCollisionPointRec(mp, PlatformCollider(platform.pos)) {
level.platforms = remove_unordered(level.platforms, i)
break
}
}
}
3. Gestion des Fuites de Mémoire
Les jeux vidéo créent souvent de nombreux objets et allouent dynamiquement de la mémoire. Si cette mémoire n’est pas libérée, elle entraîne des fuites qui peuvent ralentir le jeu, voire provoquer des plantages. Odin propose des outils pour gérer cela, notamment l’allocateur de suivi (tracking allocator
). Voici comment l’utiliser :
import core.m track := m.TrackingAllocator{}
m.tracking_allocator_init(&track, context.allocator)
context.allocator = track
À la fin de l’exécution du programme, l’allocateur de suivi est détruit pour libérer toute la mémoire allouée :
defer {
for _, entry in track.allocation_map {
fmt.println("Memory leak detected:", entry.location, "Size:", entry.size)
}
m.tracking_allocator_destroy(&track)
}
4. Sauvegarde et Chargement des Niveaux avec JSON
Pour sauvegarder les données de niveau dans un fichier JSON, nous utilisons la bibliothèque JSON intégrée d’Odin. Cela rend les données facilement modifiables, car elles sont stockées dans un format lisible.
Conversion des Données de Niveau en JSON
La fonction suivante prend notre struct Level
, le convertit en JSON, puis l’écrit dans un fichier nommé level.json
:
import core.encoding.json import core.os if level_data, err := json.Marshal(level, allocator = context.temp_allocator); err == nil {
os.write_entire_file("level.json", level_data) }
Chargement des Données depuis le Fichier JSON
Au démarrage, le programme lit les données JSON depuis level.json
pour charger les positions de toutes les plateformes enregistrées.
if level_data, ok := os.read_entire_file("level.json", context.temp_allocator); ok {
if err := json.Unmarshal(level_data, &level); err != nil {
fmt.println("Error loading level:", err)
}
}
En cas d’erreur de chargement, nous ajoutons une plateforme par défaut pour éviter de commencer avec un niveau vide :
else {
level.platforms = append(level.platforms, Platform{ pos = rl.Vector2{ x = -20, y = 20 } })
}
5. Optimisation et Conseils sur la Mémoire Temporaire
Dans les jeux, des allocations temporaires sont souvent nécessaires pour des données qui ne persistent pas d’une frame à l’autre. Odin fournit un temp allocator
pour ce type de mémoire temporaire, qui peut être nettoyée en fin de chaque exécution :
// Libération de la mémoire temporaire
defer context.temp_allocator.free_all()
Cela garantit que toute mémoire allouée temporairement durant le cycle de jeu est libérée dans la prochaine boucle d’affichage , ce qui est particulièrement utile pour des données comme des chaînes de texte affichées temporairement.
6. Conclusion et Projets Futurs
En conclusion, ce tutoriel a couvert :
- L’ajout d’un éditeur de niveaux interactif avec un système de plateformes dynamiques.
- La gestion efficace de la mémoire et la prévention des fuites.
- La sauvegarde et le chargement de données de jeu en utilisant JSON pour la flexibilité et la transparence des données.
Ces techniques constituent des bases solides pour la création de jeux en Odin et Raylib. En continuant, il est possible de complexifier l’éditeur en ajoutant des éléments variés ou même des fonctionnalités de sauvegarde avancées.