Necrown - Devlog #14 [Game Dev] : Un monde découpé en Chunks
Splix Il y a 10 mois Premium Pro - Adhésion à vie0

Salut les gamecodeurs!!!

On reprend aujourd’hui où nous en étions restés lors du précédent devlog avec la génération du monde. Nous avions créé une carte du monde, il nous faut maintenant nous en servir pour lui donner vie… Et pour cela, on va utiliser un système de chunks. Un chunk, pour ceux qui l’ignorent, c’est simplement un morceau du monde. Avec ce système, votre monde ne sera plus un immense tableau de tuiles, mais sera découpé en morceaux, dont seuls quelques-uns seront chargés en mémoire, qui contiendront un certain nombre de tuiles. Dans le schéma suivant, qui montre le principe, vous avez 15 chunks de 16 tuiles par 16:

L’intérêt des chunks est de faciliter l’accès mémoire en évitant d’éditer un fichier contenant l’entièreté du monde a chaque sauvegarde ou chargement. En effet, il est bien plus rapide de sauvegarder ou de charger un morceau du monde que le monde en entier. Le principe est relativement simple, la plus grosse difficulté reste en général de maintenir un niveau de performances suffisant pour ne pas créer de latences en cours de jeu…

Première étape pour créer notre système de chunks: créer une classe chunk!!! Notre système orienté objet nous est encore une foi très utile, surtout la manière dont nous l’avions lié à notre module de gestion de fichiers, qui nous simplifiera grandement la sauvegarde et le chargement de nos chunks:

chunk = class.new("chunk"):extend("object")
chunk.size = 16
chunk.nb = 0

function chunk:new(p_xc, p_yc)
  local obj = self:create()
  obj.xc = p_xc
  obj.yc = p_yc
  obj.tiles = {}
  
  return obj
end

function chunk:getTile(p_xtc, p_ytc)
  if p_xtc >= 1 and p_ytc >= 1 and p_xtc <= chunk.size and p_ytc <= chunk.size then
    return tiles[self.tiles[p_xtc + (p_ytc - 1) * chunk.size]]
  end
  return nil
end

function chunk:setTile(p_xtc, p_ytc, p_tile)
  if p_xtc >= 1 and p_ytc >= 1 and p_xtc <= chunk.size and p_ytc <= chunk.size then
    self.tiles[p_xtc + (p_ytc - 1) * chunk.size] = p_tile.id
    return true
  end
  return false
end

Nous enverrons simplement la position (en chunks) dans le monde, et le tableau tiles contiendra les tuiles du chunk. La variable chunk.nb peut être considérée comme une variable statique en POO: Elle est commune à toutes les instances de chunk (en fait c’est parce qu’elle fait partie de la classe et non de l’objet que l’on instancie), et elle sera chargée de compter le nombre de chunks chargés en cours de partie…

Maintenant que nous avons une classe chunk, nous allons revenir dans la génération du monde. Une fois la carte générée, nous allons pré-générer tous nos chunks et les sauvegarder. Ainsi nous n’aurons pas à les générer à la volée en cours de partie:

for xc = 1, world.wc do
  for yc = 1, world.hc do
    local c = chunk:new(xc, yc)
    
    for xtc = 1, chunk.size do
      for ytc = 1, chunk.size do
        local xt = ((xc - 1) * chunk.size) + xtc
        local yt = ((yc - 1) * chunk.size) + ytc

        local tile = tiles[genMap.tiles[xt + (yt - 1) * world.wt]]
        c:setTile(xtc, ytc, tile)
      end
    end
    
    c:save()
  end
end

Nos chunks étant pré-générés, il nous faut charger ceux dont on a besoin au lancement de la partie. Nous allons nous baser sur la position de la caméra et charger les chunks qui se trouvent autour. On chargera un rectangle de 7 * 7 = 49 chunks, la caméra (qui suivra à terme le joueur) se trouvant dans le chunks central.

Pour la gestion de nos chunks, nous allons créer trois listes: chunks (qui contiendra simplement la liste des chunks actifs), inChunks (qui contiendra les chunks en attente de chargement et d’insertion dans la liste des chunks actifs) et outChunks (qui contiendra ceux en attente de sauvegarde et de suppression de la liste des chunks actifs). Le principe de la gestion des chunks est plutôt simple: Imaginons que notre caméra se déplace vers le haut, comme dans le schéma ci-dessous…

Lorsqu’elle passera la limite d’un chunk, les chunks se trouvant trop éloignés (en rouge) seront mis en attente de sauvegarde et de suppression dans la liste outChunks, et ceux qui ne sont pas chargés, mais sont à la bonne distance pour l’être (en vert) seront mis en attente de chargement dans la liste inChunks…

Ensuite nous chargeront et sauvegarderont les chunks à une certaine fréquence (un chunks tous les 15 updates par exemple, soit une fréquence d’environs 4 chunks par secondes) en donnant la priorité aux chunks à charger plutôt que ceux à sauvegarder, puisque nous auront sans doutes besoin d’eux rapidement…

Nous n’avons pas besoin de plus pour notre jeu. Ce système, bien qu’un peut brut (on utilise même pas de thread séparé pour le chargement et le déchargement des données) est parfaitement fonctionnel, et nous allons bientôt pouvoir parcourir notre monde. Mais nous verrons cela dans un prochain devlog. En attendant…

Bon code et à bientôt!!!

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.