| 🎯 Technique d’optimisation | ⚙️ Implémentation | 📊 Gain de performance | 💡 Conseil clé |
| Cache de parties | Recycler les parts au lieu de créer/détruire (Cache:GetPart / ReturnPart) | +30-40% | Réduit la charge du garbage collector |
| Une seule boucle pour tous les zombies | CollectionService avec task.wait(1/6) au lieu de boucles individuelles | Critique (évite 200+ boucles parallèles) | 6 exécutions/seconde suffisent pour les zombies |
| Optimiser les Humanoids | Désactiver états inutiles (Seated, Flying, Swimming, Climbing) ou utiliser TweenService | +20-30% | Humanoids natifs sont mal optimisés |
| Pathfinding intelligent | Cache de chemins, recalcul seulement si nécessaire (toutes les 2-3s) | Majeur (PathfindingService très coûteux) | Réutiliser le même chemin pour tous si possible |
| Gestion des collisions | Désactiver CanQuery/CanTouch, CollisionGroups pour ignorer zombies entre eux | Majeur (réduit calculs physiques) | SetNetworkOwner(nil) pour contrôle serveur |
| Attributs vs ObjectValues | :SetAttribute() au lieu de créer des instances Value | Modéré (moins de charge mémoire) | Stockage direct sur l’instance sans nouveaux objets |
| Limite intelligente | Max 50-100 zombies, augmenter taille/HP au lieu de quantité | Double/triple la capacité | Système de priorité selon distance joueur |
| Réplication réseau | Mouvement côté client avec Tweens, serveur envoie positions clés uniquement | Réduit drastiquement le lag | Évite de répliquer 200 objets 60x/seconde |
Vous développez un jeu tower defense sur Roblox et vos zombies provoquent des chutes de framerate ? Vous n’êtes pas seul dans cette situation. La gestion des NPCs non-humanoïdes est l’un des défis les plus complexes pour les créateurs de jeux sur cette plateforme. Dans cet article, je vais partager avec vous toutes les techniques et astuces pour créer un système de zombies performant qui peut gérer des centaines d’ennemis sans faire exploser votre CPU.
Comprendre le problème de performance des zombies sur Roblox
Avant de plonger dans les solutions, il faut bien saisir pourquoi les zombies causent autant de problèmes de performance. Lorsque vous créez un jeu de type tower defense, vous générez potentiellement des dizaines voire des centaines d’entités qui doivent toutes effectuer des calculs simultanément.
Le principal coupable est souvent la manière dont les scripts sont structurés. Beaucoup de développeurs débutants créent une boucle while wait() pour chaque zombie spawné, ce qui signifie que si vous avez 200 zombies, vous avez 200 boucles qui tournent en parallèle. Cette approche est extrêmement coûteuse en ressources.
Les Humanoids natifs de Roblox sont également très mal optimisés pour ce type d’utilisation. Chaque Humanoid possède de nombreux états et fonctionnalités que vous n’utilisez probablement pas, mais qui continuent de consommer des ressources en arrière-plan.
La solution du cache de parties pour économiser les ressources
L’une des premières optimisations que vous devriez implémenter est un système de cache de parties. Au lieu de créer et détruire constamment des parts pour vos zombies, vous pouvez les recycler. Cette technique réduit considérablement la charge sur le garbage collector de Roblox.
Voici comment fonctionne cette approche : vous créez un pool de parts réutilisables au début du jeu. Lorsqu’un zombie meurt, au lieu de détruire sa part, vous la désactivez et la remettez dans le cache. Quand un nouveau zombie spawn, vous récupérez une part du cache plutôt que d’en créer une nouvelle.
Dans le code fourni par les développeurs expérimentés, on voit cette ligne : Cache:GetPart() qui récupère une part depuis le cache, et Cache:ReturnPart(self.Part) qui la remet dans le pool après utilisation. Cette simple technique peut améliorer vos performances de 30 à 40%.
Utiliser un seul loop pour tous vos zombies avec CollectionService
C’est probablement l’optimisation la plus importante : arrêtez de créer une boucle par zombie. Au lieu de cela, utilisez CollectionService avec un seul loop qui gère tous vos NPCs simultanément.
Le principe est simple : vous taggez tous vos zombies avec CollectionService, puis vous créez une seule boucle qui itère sur tous les objets ayant ce tag. Voici les avantages de cette méthode :
- Une seule boucle à exécuter au lieu de centaines
- Meilleure gestion de la mémoire
- Plus facile à debugger et maintenir
- Possibilité de prioriser certains zombies selon leur distance avec les joueurs
Au lieu d’utiliser RunService.Heartbeat qui s’exécute 60 fois par seconde, vous pouvez utiliser task.wait() avec un intervalle contrôlé. Par exemple, task.wait(1/6) n’exécutera votre code que 6 fois par seconde, ce qui est largement suffisant pour la plupart des mécaniques de zombies.
Remplacer ou optimiser les Humanoids pour de meilleures performances
Les Humanoids sont pratiques mais terriblement inefficaces. Si vous voulez vraiment optimiser votre jeu, vous avez deux options : les optimiser ou les remplacer complètement.
Désactiver les états inutiles des Humanoids
Si vous décidez de garder les Humanoids, désactivez tous les états dont vous n’avez pas besoin. La plupart des zombies n’ont pas besoin de nager, grimper, s’asseoir ou faire des ragdolls. Voici les états que vous pouvez généralement désactiver en toute sécurité :
- Seated (assis)
- Flying (vol)
- PlatformStanding (debout sur plateforme)
- Jumping (saut, sauf si vos zombies doivent sauter)
- Ragdoll (physique ragdoll)
- Swimming (nage)
- Climbing (escalade)
Pour désactiver un état, utilisez : Humanoid:SetStateEnabled(Enum.HumanoidStateType.NomDeLEtat, false). Cette simple modification peut réduire la charge CPU de vos Humanoids de 20 à 30%.
Utiliser un système basé sur les Tweens
Une alternative très performante consiste à abandonner complètement les Humanoids et à utiliser TweenService pour déplacer vos zombies. Cette approche offre plusieurs avantages majeurs :
- Contrôle total sur le mouvement
- Performance nettement supérieure
- Pas de calculs physiques inutiles
- Animations fluides et prévisibles
Dans l’exemple de code fourni, on voit une implémentation élégante avec TweenService qui calcule la distance jusqu’à la prochaine position et crée un tween avec une durée proportionnelle à la distance et à la vitesse du zombie. Le tween se lance, puis déclenche l’attribut « Active » une fois terminé pour passer à la position suivante.
Optimiser le pathfinding pour éviter les ralentissements
Le PathfindingService de Roblox est extrêmement coûteux en termes de performance. Recalculer un chemin complet 60 fois par seconde pour chaque zombie est une recette garantie pour le désastre.
Ne recalculez les chemins que lorsque c’est nécessaire
Au lieu de recalculer le chemin à chaque frame, faites-le seulement quand c’est vraiment nécessaire. Par exemple :
- Lorsque le zombie spawne pour la première fois
- Lorsque la cible change significativement de position
- Lorsque le zombie est bloqué pendant plus de X secondes
- À intervalles réguliers (toutes les 2-3 secondes par exemple)
Vous pouvez également vérifier si le zombie a une ligne de vue directe avec sa cible. Si c’est le cas et qu’il n’y a pas d’obstacle, pourquoi utiliser le pathfinding ? Déplacez-le simplement en ligne droite vers la cible.
Utiliser un système de cache de chemins
Si vos zombies suivent tous le même chemin (ce qui est courant dans les tower defense), calculez ce chemin une seule fois et réutilisez-le pour tous les zombies. Vous pouvez stocker les waypoints dans une table et faire suivre chaque zombie le même parcours prédéfini.
Cette technique est particulièrement efficace si votre environnement ne change pas dynamiquement. Vous économisez ainsi énormément de calculs puisque PathfindingService:CreatePath() et path:ComputeAsync() ne sont appelés qu’une seule fois au lieu de centaines de fois.
Gérer les collisions et les propriétés physiques intelligemment
Les calculs de collision sont l’une des charges les plus lourdes pour le moteur physique de Roblox. Heureusement, il existe plusieurs façons de les optimiser drastiquement.
Tout d’abord, désactivez CanQuery et CanTouch sur toutes les parts qui n’en ont pas besoin. Ces propriétés forcent le moteur à vérifier constamment les interactions avec ces parts, ce qui devient très coûteux avec des centaines d’objets. Si une part est purement décorative ou n’a pas besoin de détecter les touches, désactivez ces propriétés.
Utilisez également les CollisionGroups pour que vos zombies ne se percutent pas entre eux. Dans la plupart des tower defense, il n’y a aucune raison pour que les zombies se bloquent mutuellement. Créez un groupe de collision spécifique pour vos ennemis et configurez-le pour qu’ils s’ignorent entre eux. Cela réduit considérablement les calculs de collision.
Pour les zombies utilisant la physique, assurez-vous de définir le NetworkOwner sur nil avec :SetNetworkOwner(nil). Cela garantit que le serveur garde le contrôle du mouvement, évitant les problèmes de réplication et de synchronisation.
Implémenter un système de timers au lieu de boucles multiples
Une technique avancée consiste à utiliser des variables de timer plutôt que des boucles wait() multiples. Cette approche vous donne un contrôle beaucoup plus fin sur la fréquence d’exécution de différentes parties de votre code.
L’idée est de connecter une fonction à RunService.Heartbeat, puis d’utiliser le paramètre deltaTime pour décrémenter différents timers. Lorsqu’un timer atteint zéro, vous exécutez l’action correspondante et réinitialisez le timer. Voici un exemple de structure :
Vous créez un timer pour les tests de proximité qui se déclenche toutes les 0.5 secondes, un autre pour le pathfinding qui se déclenche toutes les 2 secondes, et un troisième pour les attaques qui se déclenche toutes les 0.3 secondes. Tous ces timers tournent dans une seule fonction Heartbeat, ce qui est beaucoup plus efficace que des boucles séparées.
Utiliser les attributs au lieu des ObjectValues
Si vous stockez des données sur vos zombies avec des ObjectValues, IntValues ou autres instances Value, vous perdez de la performance inutilement. Les attributs sont beaucoup plus légers et performants.
Au lieu de créer un ObjectValue pour stocker la cible d’un zombie, utilisez simplement :SetAttribute(« Target », targetValue). Les attributs ne créent pas de nouveaux objets dans l’arborescence, ils sont stockés directement sur l’instance, ce qui réduit la charge mémoire et améliore les performances.
Vous pouvez également écouter les changements d’attributs avec :GetAttributeChangedSignal(), ce qui est parfait pour déclencher des actions spécifiques. Dans le code exemple, on voit cette technique utilisée pour déclencher le mouvement du zombie quand l’attribut « Active » change.
Limiter intelligemment le nombre de zombies actifs
Voici un truc psychologique qui fonctionne très bien : au lieu d’augmenter constamment le nombre de zombies à chaque vague, augmentez leur taille et leurs points de vie. Les joueurs auront l’impression de progresser en difficulté sans que votre jeu s’écroule sous le poids de centaines d’entités.
Gardez le nombre de zombies à un maximum raisonnable (50-100 selon votre jeu) et compensez la difficulté avec d’autres paramètres. Vous pouvez aussi implémenter un système de priorité où seuls les zombies proches des joueurs ou des tours sont entièrement actifs, tandis que ceux qui sont loin sont en mode « simplifié ».
Par exemple, les zombies très éloignés pourraient ne mettre à jour leur position qu’une fois par seconde au lieu de 6 fois, ou même être temporairement désactivés s’ils sont hors de vue. Cette technique de « level of detail » pour l’IA peut doubler voire tripler le nombre de zombies que votre jeu peut supporter.
Optimiser la réplication réseau pour réduire le lag

Même si votre code est parfaitement optimisé côté serveur, vous pouvez encore avoir des problèmes si vous envoyez trop de données aux clients. La réplication réseau devient un goulot d’étranglement majeur avec beaucoup de zombies.
La meilleure approche est de gérer le mouvement des zombies côté client avec des vérifications de sécurité côté serveur. Le serveur envoie les positions clés (spawn, changement de direction, mort) et le client interpole le mouvement entre ces points. Cela réduit drastiquement la quantité de données envoyées.
Si vous utilisez des Tweens, assurez-vous qu’ils sont calculés et rendus côté client. Cela signifie que vous n’avez pas besoin de répliquer 200 objets en mouvement 60 fois par seconde. Le serveur envoie simplement les instructions « ce zombie va de A à B en X secondes » et le client fait le reste.
Surveiller et détecter les fuites mémoire
Les fuites mémoire peuvent ruiner les performances de votre jeu au fil du temps. Même si tout semble bien fonctionner au début, après 10 minutes de jeu, tout ralentit. C’est souvent signe d’une fuite mémoire.
Pour vérifier l’utilisation mémoire, ouvrez la console développeur dans le menu Roblox pendant le jeu. Naviguez jusqu’à PlaceScriptMemory et observez si la valeur augmente constamment sans jamais redescendre. Vous pouvez également utiliser gcinfo() dans votre code pour mesurer l’utilisation mémoire avant et après certaines opérations.
Les causes communes de fuites mémoire incluent :
- Des connexions d’événements non déconnectées (:Disconnect())
- Des tables de joueurs qui ne sont pas nettoyées quand ils quittent
- Des parts ou modèles qui s’accumulent dans workspace sans être détruits
- Des références circulaires dans les tables qui empêchent le garbage collection
Utilisez toujours :Disconnect() sur vos connexions d’événements quand elles ne sont plus nécessaires, ou encore mieux, utilisez :Once() si l’événement ne doit se déclencher qu’une seule fois. Nettoyez systématiquement les données des joueurs quand ils quittent avec Players.PlayerRemoving.
Tester la performance dans des conditions réalistes
Avant de publier votre jeu, faites des tests de stress approfondis. Ne testez pas seulement avec quelques zombies dans Studio, mais poussez vraiment le système à ses limites. Essayez avec 500, 1000, voire 1500 zombies si votre code le permet.
Testez également sur différents appareils. Ce qui fonctionne parfaitement sur votre PC gaming peut être injouable sur mobile. Utilisez l’émulateur d’appareil dans Studio ou testez directement sur un téléphone ou une tablette.
Dans la console développeur, vous pouvez modifier le replication lag à 2 pour simuler une mauvaise connexion internet. Cela vous permet de voir comment votre jeu se comporte pour les joueurs avec un ping élevé.
Surveillez également le Server Receive dans les statistiques réseau. Si cette valeur est très élevée, cela signifie que votre serveur est surchargé et que vous devez déplacer certaines opérations côté client avec des vérifications de sécurité serveur.
En appliquant toutes ces techniques, vous devriez pouvoir créer un système de zombies capable de gérer des centaines d’ennemis simultanément avec des performances acceptables même sur des appareils moins puissants. L’optimisation est un processus itératif, alors continuez à tester, mesurer et améliorer votre code jusqu’à atteindre vos objectifs de performance. Votre jeu tower defense en sera transformé et vos joueurs apprécieront une expérience fluide et agréable.