Logo
Créer un compte
← Tous les articles

Suivre les succès dans un jeu LÖVE 2D

Mike Dalton Par Mike Dalton ·

Le logo Game Stats AI avec des icônes de trophée, de manette de jeu et de graphique à barres à côté d'une baleine bleue souriante.

Le suivi des succès enregistre les jalons que les joueurs atteignent dans votre jeu : entrer dans une nouvelle zone, trouver un objet, vaincre un boss. Ces données montrent jusqu'où vont les joueurs, où ils s'arrêtent et quel contenu ils ne trouvent jamais.

Pour montrer à quoi cela ressemble dans un vrai jeu LÖVE 2D, nous avons intégré l'API de Game Stats AI dans Cavern, un jeu de plateforme open source.

Ce guide déroule l'intégration étape par étape.

Étape 1 : décider ce qui compte comme un succès

Faites correspondre les succès à des moments qui existent dans votre jeu. Pour Cavern, cela voulait dire la première visite de chaque salle, les six objets à ramasser, les points de sauvegarde, la victoire contre le boss et la fin du jeu.

Chacun de ces endroits appelle un module gamestats, que la suite de ce guide construit :

-- source/levels/map_loader.lua, when the player enters a room
gamestats:enterRoom(newMap)

-- source/pickup.lua, when the player collects an item
gamestats:pickup(p.name)

-- source/enemies/boss.lua, when the boss dies
gamestats:unlock("Defeated the Boss")

Étape 2 : embarquer une bibliothèque JSON

Lua n'a pas de JSON intégré, alors embarquez rxi/json.lua : un unique fichier à déposer dans votre projet.

json = require("source/libraries/json")

json.encode construit le corps de la requête, et json.decode permet d'inspecter les rejets de l'API comme {"errors":["Occurred at can't be blank"]}.

Étape 3 : construire le payload

Game Stats AI accepte les événements de succès sur un seul endpoint :

POST https://gamestats.ai/api/v1/accounts/{account_id}/achievement_events
Authorization: Bearer <token>
Content-Type: application/json

Ce token est embarqué dans votre jeu, que les joueurs téléchargent et exécutent : considérez-le donc comme public. Créez un token client, pas un token serveur. Dans Paramètres > Tokens API, choisissez « Client » au moment de créer le token. Un token client ne peut qu'envoyer de la télémétrie ; s'il fuite, il ne peut pas lire les rapports de votre compte. Réservez les tokens serveur aux requêtes que vous faites depuis un backend que vous contrôlez.

L'écran de création d'un token API, avec un champ nom et un choix de type de token Client ou Serveur

Le corps comporte quatre champs obligatoires :

local payload = json.encode({
  version_name = "1.0.0",
  player_username = "cavern_player_a7f3c2",
  achievement_name = name,
  occurred_at = os.date("!%Y-%m-%dT%H:%M:%SZ"),
})

Les succès, joueurs et versions sont créés à la première utilisation, il n'y a donc rien à déclarer à l'avance.

Étape 4 : utiliser le module https de LÖVE 12

LÖVE 12 embarque lua-https, donc vous n'avez pas besoin d'une bibliothèque externe pour faire une requête HTTPS.

local https = require("https")

local code, body = https.request(url, {
  method = "POST",
  headers = {
    ["Authorization"] = "Bearer " .. token,
    ["Content-Type"] = "application/json",
  },
  data = payload,
})

Si vous êtes encore sur LÖVE 11.x, il n'y a pas de support HTTPS natif ; il faudrait compiler lua-https vous-même ou passer la requête par un outil externe.

Étape 5 : déplacer la requête dans un thread séparé

N'appelez pas https.request sur le thread principal. L'appel bloque jusqu'à la réponse du serveur, donc chaque succès figerait le jeu le temps d'un aller-retour réseau complet. Pousser un message dans un canal love.thread ne coûte qu'une fraction de milliseconde.

La solution est donc un thread de travail persistant. Le thread principal ne fait que pousser dans un canal ; le worker exécute l'appel bloquant et renvoie le résultat :

-- source/threads/gamestats_worker.lua
local https = require("https")

local requests = love.thread.getChannel("gamestats")
local results = love.thread.getChannel("gamestats_result")

while true do
  local job = requests:demand()
  if job == "__quit__" then break end

  local id, url, token, body =
    string.match(job, "^([^\n]*)\n([^\n]*)\n([^\n]*)\n(.*)$")

  local ok, code, response = pcall(https.request, url, {
    method = "POST",
    headers = {
      ["Authorization"] = "Bearer " .. token,
      ["Content-Type"] = "application/json",
    },
    data = body,
  })

  local status = (ok and code) and tostring(code) or "000"
  results:push(id .. "|" .. status .. "|" .. tostring(response or ""))
end

Démarrez-le une fois au chargement :

gamestats.thread = love.thread.newThread("source/threads/gamestats_worker.lua")
gamestats.thread:start()

Le pcall est important : une connexion coupée lève une erreur au lieu de renvoyer un code de statut, et cela doit compter comme un échec à retenter, pas tuer le thread.

Un piège : ajoutez un handler love.quit qui pousse "__quit__" et attend le thread, sinon le jeu se fige à la fermeture :

function love.quit()
  gamestats:quit() -- pushes "__quit__", then thread:wait()
end

Étape 6 : mettre les événements en file et traiter les réponses

Gardez les événements en attente dans une file, une simple table Lua en mémoire, et transmettez-les au worker depuis cette file. Cavern va un cran plus loin et écrit d'abord chaque payload sur disque : les événements survivent ainsi à un plantage ou à une session hors ligne et peuvent être réexpédiés tels quels au lancement suivant. Cette persistance est optionnelle : commencez avec la file en mémoire et n'ajoutez l'écriture sur disque que si perdre un événement lors d'un plantage compte pour votre jeu.

N'expédiez qu'un seul événement en file par frame, pour qu'une rafale de déblocages ne submerge jamais le worker.

L'intégration complète, y compris le code de file, de renvoi et d'identité que ce guide condense, se trouve dans notre fork de Cavern.

© 2026 Rowhome Labs, LLC