Mark
Prix Créer un compte
← Tous les articles

Suivre les succès dans un jeu Godot

Mike Dalton Par Mike Dalton ·

Une fenêtre de jeu et le logo Godot à côté du logo Game Stats AI, avec une flèche pointant vers une carte de tableau de bord montrant un trophée et un graphique à barres.

Le suivi des succès enregistre les jalons que les joueurs atteignent dans votre jeu : terminer un niveau, trouver un objet caché, 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 Godot, nous avons intégré l'API de Game Stats AI dans Dodge the Creeps, le jeu construit dans le tutoriel Your first 2D game de Godot. Le projet terminé se trouve dans notre dépôt d'exemple. Il utilise Godot 4.7 et GDScript.

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

Associez les succès à des moments qui existent déjà dans votre jeu. Dodge the Creeps n'a pas de niveaux : le score augmente d'un point pour chaque seconde de survie. Les succès sont donc des paliers de survie à 10, 30 et 60 secondes.

Le score est déjà mis à jour dans main.gd, c'est donc là que va l'appel qui débloque le succès :

const SURVIVAL_ACHIEVEMENTS = {
    10: "Survived 10 Seconds",
    30: "Survived 30 Seconds",
    60: "Survived 60 Seconds",
}


func _on_ScoreTimer_timeout():
    score += 1
    $HUD.update_score(score)
    if score in SURVIVAL_ACHIEVEMENTS:
        GameStats.unlock(SURVIVAL_ACHIEVEMENTS[score])

GameStats est l'autoload que le reste de ce guide construit.

Étape 2 : créer un token client

Game Stats AI reçoit 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.

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

Enregistrez l'identifiant du compte et le token dans un fichier game_stats.cfg à la racine du projet :

[game_stats]

account_id="your_account_id"
api_token="client_your_token"

Ajouter le fichier à .gitignore garde le token hors de votre dépôt, mais ne le cache pas aux joueurs : le fichier est embarqué dans chaque build exporté.

Étape 3 : ajouter un autoload

Un autoload est un nœud que Godot crée au lancement du jeu et qui reste actif d'une scène à l'autre, ce qui en fait un bon emplacement pour du code que n'importe quelle scène peut appeler. Créez game_stats.gd et enregistrez-le sous le nom GameStats dans Project > Project Settings > Globals > Autoload. Cela ajoute ceci à project.godot :

[autoload]

GameStats="*res://game_stats.gd"

Dans _ready, l'autoload lit le fichier de configuration et construit l'URL et les en-têtes une seule fois. Si le fichier est absent, il se désactive et le jeu fonctionne normalement :

func _ready():
    var config = ConfigFile.new()
    if config.load(CONFIG_PATH) != OK:
        push_warning("Game Stats AI is disabled: %s is missing." % CONFIG_PATH)
        return

    var base_url = config.get_value("game_stats", "base_url", "https://gamestats.ai")
    var account_id = config.get_value("game_stats", "account_id")
    var api_token = config.get_value("game_stats", "api_token")
    endpoint_url = "%s/api/v1/accounts/%s/achievement_events" % [base_url, account_id]
    headers = PackedStringArray([
        "Authorization: Bearer %s" % api_token,
        "Content-Type: application/json",
    ])
    version_name = ProjectSettings.get_setting("application/config/version")

    load_state()

    http_request = HTTPRequest.new()
    http_request.timeout = 10.0
    http_request.request_completed.connect(_on_request_completed)
    add_child(http_request)

    enabled = true
    send_next_event()

La version provient de Project Settings > Application > Config > Version. Mettez-la à jour avant de publier chaque build : Game Stats AI regroupe les déblocages par version.

Étape 4 : construire le payload

Le corps comporte quatre champs obligatoires :

func unlock(achievement_name):
    if not enabled or achievement_name in unlocked_achievements:
        return

    unlocked_achievements.append(achievement_name)
    pending_events.append({
        "version_name": version_name,
        "player_username": player_username,
        "achievement_name": achievement_name,
        "occurred_at": Time.get_datetime_string_from_system(true) + "Z",
    })
    save_state()
    send_next_event()

Time.get_datetime_string_from_system(true) renvoie l'heure UTC actuelle sans suffixe de fuseau horaire ; le Z l'indique donc comme UTC. Les succès, joueurs et versions sont créés à la première utilisation, il n'y a donc rien à déclarer à l'avance.

L'événement reçoit son occurred_at une seule fois, au moment où le joueur débloque le succès, et il est stocké avec cet horodatage. Game Stats AI ignore un déblocage déjà reçu seulement si le joueur, le succès, la version et occurred_at correspondent tous. Un renvoi qui reconstruirait le payload avec un nouvel horodatage enregistrerait un second déblocage.

Étape 5 : envoyer les événements avec un nœud HTTPRequest

HTTPRequest est un nœud qui effectue des requêtes HTTP et HTTPS sans bloquer le jeu. Vous appelez request, le jeu continue de tourner, et le nœud émet request_completed quand la réponse arrive.

Chaque nœud HTTPRequest traite une seule requête à la fois : l'autoload envoie donc le premier événement en attente et attend la réponse avant d'envoyer le suivant :

func send_next_event():
    if request_in_flight or pending_events.is_empty():
        return

    var body = JSON.stringify(pending_events[0])
    if http_request.request(endpoint_url, headers, HTTPClient.METHOD_POST, body) == OK:
        request_in_flight = true
    else:
        retry_later()


func _on_request_completed(result, response_code, _headers, body):
    request_in_flight = false

    if result == HTTPRequest.RESULT_SUCCESS and response_code == 201:
        pending_events.pop_front()
    elif result == HTTPRequest.RESULT_SUCCESS and response_code == 422:
        push_warning("Game Stats AI rejected an event: %s" % body.get_string_from_utf8())
        pending_events.pop_front()
    else:
        retry_later()
        return

    save_state()
    send_next_event()


func retry_later():
    get_tree().create_timer(RETRY_DELAY_SECONDS).timeout.connect(send_next_event)

Un 201 signifie que l'événement a été accepté. Un 422 signifie que le payload lui-même est invalide, par exemple {"errors":["Version name can't be blank"]} : le renvoyer ne servira à rien, alors journalisez-le et passez au suivant. Tout autre résultat, y compris une absence totale de connexion, laisse l'événement en tête de file et le renvoie 30 secondes plus tard.

Étape 6 : sauvegarder les déblocages entre les sessions

L'autoload conserve trois informations dans user://game_stats_state.cfg : un nom de joueur généré, les succès déjà débloqués et les événements pas encore envoyés.

func load_state():
    var state = ConfigFile.new()
    state.load(STATE_PATH)
    player_username = state.get_value("player", "username", "")
    if player_username.is_empty():
        player_username = "player_" + Crypto.new().generate_random_bytes(6).hex_encode()
    unlocked_achievements = state.get_value("achievements", "unlocked", [])
    pending_events = state.get_value("achievements", "pending", [])
    save_state()


func save_state():
    var state = ConfigFile.new()
    state.set_value("player", "username", player_username)
    state.set_value("achievements", "unlocked", unlocked_achievements)
    state.set_value("achievements", "pending", pending_events)
    state.save(STATE_PATH)

Grâce à la liste des succès débloqués, chaque succès est envoyé une fois par joueur, et non une fois par partie. Grâce à la liste des événements en attente, un déblocage obtenu hors ligne n'est pas perdu : il est envoyé avec son horodatage d'origine au prochain lancement du jeu. Si votre jeu a déjà des comptes joueurs, utilisez cet identifiant pour player_username au lieu d'un nom généré.

Étape 7 : inclure le fichier de configuration dans les exports

Par défaut, Godot n'exporte que les fichiers de ressources, et game_stats.cfg n'en est pas un. Sans étape supplémentaire, le jeu exporté ne trouve pas le fichier et l'intégration se désactive.

Dans Project > Export, ouvrez l'onglet Resources de chaque preset d'export et saisissez game_stats.cfg dans Filters to export non-resource files/folders.

Toute personne qui télécharge votre jeu peut lire ce fichier : c'est pourquoi il doit contenir un token client, jamais un token serveur.

L'intégration complète se trouve dans le dépôt d'exemple.

Continuer la lecture

© 2026 Rowhome Labs, LLC