Séparer les données des objets à l’écran.
Pour construire ce système, j’ai distingué trois états : un objet stocké sur le serveur, un objet connu du client et une entité réellement créée dans le moteur. Un objet peut exister dans les données sans avoir à occuper une place dans le rendu.
J’ai réparti le travail en conséquence. Mon serveur organise les données par zone et les transmet aux clients concernés. Côté client, je décide quels objets doivent recevoir un handle moteur, c’est-à-dire une entité que le jeu peut afficher et manipuler. Le coût de création dépend ainsi de la sélection locale.
J’ai choisi une grille de chunks.
Chaque objet reçoit une adresse spatiale calculée à partir de sa position XY. Je le range dans un chunk, avec un index par identifiant pour le retrouver. Le client s’abonne ensuite à un voisinage autour de sa cellule centrale.
Dans la configuration que je présente ici, mon mode de test impose des chunks de 58 unités et un rayon de voisinage de 1, diagonales incluses. Le client demande donc neuf cellules autour de lui. Ce réglage remplace le rayon des profils de qualité tant que ce mode est actif.
Mon calcul d’index prend la valeur absolue de la position avant de réappliquer son signe, sauf pour zéro. Cela évite deux identifiants distincts pour la cellule centrale autour des axes. Cette cellule couvre les deux côtés de l’origine ; j’ai placé la démonstration dans le quadrant positif pour montrer une grille régulière.
PSEUDO-CODE EXPLICATIFindex = floor(abs(position) / taille)
si position < 0 et index ≠ 0 : index = -index
clé = indexX + "," + indexY- Dans le monde simulé
- 420
- Dans les cellules consultées
- 56
- Dans la portée
- 56
Les chunks déterminent le périmètre chargé avant le filtre de distance.
Choisir les objets qui méritent un handle.
Dans ma passe de sélection, je parcours le cache client, j’écarte les collections désactivées et je vérifie que les handles existants sont encore valides. Je calcule ensuite la distance 3D entre chaque objet et le joueur.
Je trie les candidats dans la portée en donnant priorité aux objets ordinaires sur les objets de stress. La distance au carré vient ensuite, puis l’identifiant pour rendre les égalités déterministes. Je prends les premiers candidats dans la limite du budget disponible : eux seuls peuvent déclencher de nouvelles créations.
J’utilise un tri complet avec table.sort. Pour C candidats, cela représente un coût de l’ordre de C log C. Le découpage en chunks réduit d’abord le périmètre chargé ; le tri ordonne ensuite les candidats. J’ai ajouté une sélection de rétention distincte pour conserver certains handles déjà présents, sans en créer de nouveaux.
Pourquoi une grille plutôt qu’un quadtree ?
Mon implémentation repose sur une grille. Elle me donne une adresse directe pour chaque objet et un voisinage simple à souscrire. Le quadtree répond autrement au problème : il subdivise une région en quatre, puis répète cette opération pour détailler les zones denses.
Avec un quadtree, il faudrait aussi parcourir et maintenir cette arborescence. L’intérêt dépendrait de la distribution des objets et des requêtes à effectuer. Je ne peux pas déduire un avantage de performance du seul nom de la structure : il faudrait comparer les deux sur les mêmes données.
J’ai ajouté un mode quadtree au schéma pour illustrer cette différence. Il interroge un cercle, tandis que mon mode streamer commence par neuf chunks. Les compteurs permettent de suivre ces deux sélections ; ils ne comparent pas leur vitesse.
Garder chaque décision explicable.
J’ai construit ce pipeline pour pouvoir examiner chaque étape séparément : l’adresse d’un objet, les abonnements du client, les candidats retenus et les créations ou suppressions de handles. Lorsqu’un objet n’apparaît pas, cette séparation permet de chercher à quel niveau il a été écarté.
J’ai aussi intégré des compteurs de candidats, d’objets conservés, de tentatives de création et de durée de passe. Ils donnent des points d’observation concrets pour ajuster les paramètres selon la densité des zones et les déplacements du joueur.
Le principePoursuivre l’explorationJ’ai séparé ce que le client connaît de ce que le moteur doit créer. Cette distinction structure tout mon système de streaming.