La réponse directe : comment calculer les valeurs de minuteur d’événement en jeu
Si vous avez besoin de connaître la logique de calcul de minuteur d’événement en jeu sans dépendre d’un moteur, ancrez-vous sur trois primitives : le temps réel delta, un multiplicateur d’échelle de temps et une référence d’époque. Le minuteur en cours est la somme du temps delta de chaque frame multiplié par votre échelle de temps. Pour un compte à rebours, le restant est égal à la durée moins cette somme accumulée.
Pour trouver l’écart entre l’événement A et l’événement B, soustrayez leurs horodatages d’époque ; multipliez par l’échelle si vous voulez du temps de jeu plutôt que du temps réel. Cet article vous donne un pseudo-code réutilisable et un convertisseur pour que vous puissiez l’implémenter dès aujourd’hui.
La plupart des tutoriels de moteurs vous entraînent vers Time.deltaTime d’Unity ou RunService de Roblox avant d’expliquer les mathématiques sous-jacentes. J’ai porté des systèmes de minuteurs sur quatre moteurs, et les mathématiques n’ont jamais changé—seule l’API a changé. Voici la version sans wrapper.
Lorsque j’ai d’abord construit un minuteur de récolte pour un RPG mobile de 2019, j’ai lié la croissance à des compteurs de frames à un FPS supposé de 60. Sur des appareils limités tournant à 30 FPS, les cultures prenaient deux fois plus de temps. Cela a été publié pendant un week-end avant les plaintes. La correction a été un accumulateur de temps delta, que nous détaillons maintenant.
Le modèle mental central : temps delta, échelle de temps et époques
Intégrez la Trinité du minuteur : temps delta, échelle de temps et époque. Le temps delta (dt) est le nombre de secondes réelles depuis la dernière mise à jour. L’échelle de temps est un multiplicateur convertissant les secondes réelles en secondes de jeu. L’époque est une référence absolue (généralement en millisecondes depuis 1970-01-01) pour la persistance et les écarts hors ligne.
Quand quelqu’un demande comment calculer le minuteur pour un événement générique, ils veulent dire un compte à rebours ou un chronomètre. La formule est :
accumulatedGameTime += dt * timeScale
remaining = durationGameTime - accumulatedGameTime
Ce que personne ne vous dit sur le temps delta, c’est qu’il n’est jamais parfaitement constant. Même avec une boucle à pas fixe, le système d’exploitation peut limiter votre processus. Si vous accumulez avec des flottants 32 bits, vous perdez la précision sub-milliseconde après environ 24 heures de jeu continu.
Pourquoi le comptage de frames échoue spectaculairement
Le comptage de frames suppose un taux fixe. J’ai vu des équipes publier des événements d’été où le compte à rebours ralentissait sur les appareils bas de gamme parce qu’elles incrémentaient un compteur à chaque Update(). La bonne approche est de demander à la plateforme le temps réel écoulé.
Lors d’un lancement en 2020, nous avons enregistré les horodatages d’événements en heure locale sur les builds coréens et en UTC sur les builds européens. L’intervalle entre l’événement A et B semblait négatif pour certains joueurs. Stockez toujours les époques en UTC ; convertissez uniquement pour l’affichage.
L’échelle de temps n’est pas seulement le ralenti
L’échelle de temps modélise aussi la progression hors ligne et les jours de jeu accélérés. Une échelle de 1 signifie temps réel. Une échelle de 60 signifie qu’une minute réelle équivaut à une heure de jeu. Quand vous calculez le temps de jeu, multipliez le delta réel par ce facteur.
Attention : si l’échelle change en cours d’événement (par exemple, un boost de week-end), vous devez segmenter l’accumulation ou stocker l’échelle par intervalle. J’ai un jour appliqué un boost d’échelle global rétroactivement, ce qui a divisé par deux les minuteurs restants de chaque joueur actif et déclenché des tickets de support.
Liste de contrôle : la Trinité du minuteur en pratique
- Capturez le dt depuis une horloge monotone ou d’époque, jamais un index de frame.
- Stockez le temps accumulé en millisecondes int64 ou en secondes double.
- Enregistrez les horodatages d’événements en époque UTC, pas en heure locale.
- Documentez si les minuteurs restants sont en temps réel ou en temps de jeu.
Pourquoi l’époque bat « secondes jouées »
Un fichier de sauvegarde naïf pourrait stocker secondsPlayed comme total cumulé. Cela fonctionne jusqu’à ce que le jeu plante avant d’écrire, ou que le joueur utilise un éditeur de sauvegarde. Un horodatage d’époque est ancré à une horloge murale absolue, donc même si vous manquez une écriture, le chargement suivant calcule le véritable écart.
Dans un portage console sur lequel j’ai travaillé, la certification exigeait qu’un jeu en pause n’avance aucun minuteur. Utiliser la soustraction d’époque a rendu la conformité triviale : en pause, nous ne faisions simplement pas d’appel de mise à jour avec la nouvelle époque.
Comment calculer le temps entre deux événements (événement A et événement B)
La question « comment calculer le temps entre deux événements ? » est une mathématique d’intervalle. Quand l’événement A se déclenche, enregistrez epochA. Quand l’événement B se déclenche, enregistrez epochB. L’intervalle en temps réel est epochB – epochA. Multipliez par l’échelle de temps active pour l’intervalle en temps de jeu.
Cela répond directement à « comment calculer l’événement A et l’événement B » sans ambiguïté. Pseudo-code :
function timeBetween(eventA, eventB, timeScale):
realMs = eventB.epoch - eventA.epoch
if realMs < 0: error('Event B preceded A')
gameMs = realMs * timeScale
return {realMs: realMs, gameMs: gameMs}
Dans un projet de survie coopératif de 2021, nous avons mesuré le temps entre les morts de joueurs avec cette méthode. Le piège était de stocker initialement uniquement des minuteurs relatifs (« mort 120 s après l'apparition »). Quand le serveur s'est arrêté pour maintenance, notre intervalle a rétréci parce que les minuteurs relatifs ignoraient la pause.
Gestion des fuseaux horaires et de l'autorité du serveur
Si l'événement A est enregistré sur un client en UTC+9 et l'événement B sur un client en UTC-5, la soustraction naïve des horloges locales donne des absurdités. Convertissez toujours en époque UTC avant le stockage. Pour les jeux compétitifs, l'époque doit provenir d'un serveur pour empêcher les clients de simuler des complétions rapides.
Selon la Division du temps et des fréquences du NIST, UTC fournit la référence standard pour une telle synchronisation. Je traite l'UTC du serveur comme la seule source de vérité pour les événements inter-joueurs.
Cas limite : événements s'étendant sur sauvegarde/chargement
Supposons que l'événement A soit « quête acceptée » à l'époque 1 700 000 000 000. Le joueur quitte. L'événement B « quête rendue » après rechargement à 1 700 036 000 000. L'intervalle est de 36 000 000 ms (10 heures de jeu si échelle 1). Parce que les époques sont absolues, le calcul est identique que le jeu ait tourné en continu ou ait été fermé.
La plupart des gens ne réalisent pas que si vous stockez seulement « secondes depuis le lancement », vous ne pouvez pas calculer les intervalles après un crash. La persistance d'époque est le héros méconnu de l'analytique fiable des événements.
Valeur absolue et intervalles négatifs
Parfois, l'événement B est enregistré avant l'événement A en raison d'un réordonnancement réseau. La soustraction brute donne un nombre négatif. En analytique, je prends la valeur absolue et signale la paire pour examen plutôt que de faire planter le pipeline d'équilibrage. Ne laissez jamais un intervalle négatif corrompre silencieusement un classement.
Conversion temps réel en temps de jeu : comment calculer le temps de jeu
Une autre recherche courante est « comment calculer le temps de jeu ? » La conversion générique est linéaire mais a un décalage si votre jeu commence à une horloge de jeu non nulle. Formule :
gameTimeNow = gameTimeStart + (realNowEpoch - realStartEpoch) * timeScale
Si votre jeu commence au temps de jeu zéro, le décalage disparaît. Pour un cycle jour/nuit où 1 heure réelle = 24 heures de jeu, l'échelle = 24. Une session réelle de 90 minutes équivaut à 36 heures de jeu—exactement 1,5 jour de jeu.
Pour éviter le calcul manuel, le Calculateur de minuteur d'événement en jeu sur notre site applique cette conversion exacte et vous permet de changer d'échelle instantanément. Je l'utilise pour équilibrer les fenêtres d'événements pour les opérations en direct car il élimine les erreurs de doigt des formules de tableur.
Tableau de référence d'échelles courantes
| Durée réelle | Échelle | Durée de jeu | Cas d'utilisation |
|---|---|---|---|
| 1 seconde réelle | 1 | 1 seconde de jeu | Action en temps réel |
| 1 minute réelle | 60 | 1 heure de jeu | Artisanat passif |
| 1 heure réelle | 24 | 1 jour de jeu | Cycle jour/nuit |
| 1 jour réel | 7 | 1 semaine de jeu | Verrouillage de raid hebdomadaire |
Ce tableau est un point de départ ; j’ai utilisé l’échelle 1000 pour les builds de test « hyper-speed » afin de simuler une saison complète en quelques minutes. Assurez-vous simplement que votre UI affiche la valeur convertie, sinon les joueurs penseront que le minuteur est cassé.
Créer un convertisseur réutilisable
Voici un pseudo-code indépendant du moteur que vous pouvez intégrer dans n’importe quel projet :
struct TimeConverter:
realStartEpoch: int64
gameStart: float
scale: float
function toGame(realEpoch):
return gameStart + (realEpoch - realStartEpoch) * scale / 1000.0
function toReal(gameTime):
return realStartEpoch + (gameTime - gameStart) / scale * 1000.0
La plupart des gens ne réalisent pas que diviser par l’échelle peut produire des millisecondes de jeu fractionnaires que vous devez arrondir de manière cohérente. Dans un jeu au tour par tour que j’ai livré, nous avons tronqué ; les joueurs ont remarqué que des conversions répétées perdaient 1 à 2 secondes de jeu par jour. Passer à l’arrondi a corrigé la dérive.
Formater le temps de jeu pour l’UI
Convertir en millisecondes de jeu brutes ne suffit pas ; les joueurs attendent « 3j 4h 12m ». Écrivez un formateur qui divise les ms de jeu par des constantes de jour/heure indépendantes de l’échelle. Je garde une fonction pure formatGameTime(gameMs) dans le code partagé pour que chaque événement utilise un libellé identique.
FPS variable et progression hors ligne : garder les minuteurs honnêtes
Les taux de rafraîchissement variables sont la norme. La solution est d’ignorer les compteurs de frames et d’accumuler le temps delta. Mais qu’en est-il lorsque l’application est fermée ? C’est la progression hors ligne, et elle utilise la même soustraction d’epoch que nous avons utilisée pour l’événement A/B.
Au chargement, calculez elapsedReal = currentEpoch – savedEpoch. Multipliez par l’échelle, ajoutez au temps de jeu stocké, et plafonnez si votre conception interdit les gains hors ligne infinis. Lorsque j’ai implémenté pour la première fois la croissance des cultures hors ligne, j’ai oublié de plafonner ; un joueur revenant après deux semaines avait plus de monnaie que notre économie ne pouvait en supporter. Nous avons corrigé à chaud avec un maximum de 12 heures.
Pour les genres idle, notre Calculateur de gains hors ligne pour jeux idle modélise ces plafonds en utilisant la même mathématique delta-epoch, ce qui vous évite de répéter mon erreur qui a cassé l’économie.
Horloges monotones vs horloge murale
Si vous n’avez besoin que de minuteurs pendant une session, préférez une horloge monotone (temps depuis le démarrage) pour éviter les sauts lorsque le système d’exploitation synchronise l’heure. Pour les sessions croisées, vous avez besoin de l’epoch de l’horloge murale, mais vous devriez envisager un horodatage serveur pour empêcher les joueurs de régler leur téléphone sur 2099 pour accélérer les minuteurs.
Ce compromis entre commodité et sécurité est réel ; les jeux solo peuvent l’ignorer, les jeux multijoueurs ne le peuvent pas. J’ai appris cela quand un classement affichait des temps de complétion d’événements impossibles parce que les clients utilisaient des horloges locales.
Les boucles à pas fixe ont toujours besoin de delta
Même avec un tick logique fixe à 30 Hz, vous devriez calculer dt à partir de l’horloge réelle et l’accumuler, puis le consommer en tranches fixes. Cela évite la spirale de la mort sur les longues frames. Le modèle est : acc += dtReal; while(acc >= step) { update(step); acc -= step; }. C’est indépendant du moteur et garde les minuteurs fluides.
Tester hors ligne avec des epochs factices
Avant de livrer, j’écris un hook de débogage qui définit savedEpoch à huit jours en arrière. Si la logique de chargement respecte le plafond et ne divise pas par zéro, vous êtes en sécurité. Sauter ce test est la façon dont un collègue a un jour livré un minuteur qui accordait des récompenses négatives au retour.
Un framework pseudo-code unifié pour les minuteurs d’événements
Voici un framework compact et réutilisable qui couvre les compte à rebours, les intervalles et les durées mises à l’échelle. Il répond à comment calculer le minuteur dans n’importe quel contexte. J’ai utilisé une variante de cela dans trois titres livrés sur HTML5, Unity et mobile natif.
class GameEventTimer:
durationGameMs: float
accumulatedGameMs: float = 0
lastRealEpoch: int64
scale: float = 1.0
function start(nowEpoch):
lastRealEpoch = nowEpoch
accumulatedGameMs = 0
function update(nowEpoch):
dtRealMs = nowEpoch - lastRealEpoch
lastRealEpoch = nowEpoch
accumulatedGameMs += dtRealMs * scale
if accumulatedGameMs > durationGameMs:
fireEvent()
function remainingGameMs():
return max(0, durationGameMs - accumulatedGameMs)
function remainingRealMs():
return remainingGameMs() / scale
Cette classe gère automatiquement le FPS variable car elle ne suppose jamais un dt fixe. Elle gère aussi la pause : si vous n’appelez pas update() pendant la pause, aucun temps ne s’accumule. Pour le hors ligne, appelez update(currentEpoch) une fois après le chargement et il sautera en conséquence—respectant votre plafond si vous en ajoutez un.
Calcul d’intervalle entre événements arbitraires
Pour les analyses ou les chaînes de quêtes, vous avez aussi besoin d’un enregistreur d’intervalles persistant :
struct EventLog:
epochs: map<string, int64>
function record(name, nowEpoch):
epochs[name] = nowEpoch
function intervalMs(nameA, nameB, scale):
return (epochs[nameB] - epochs[nameA]) * scale
Remarquez que cela réutilise le même principe de soustraction. Je garde cette structure dans un fichier de sauvegarde pour que l’ajustement de l’équilibrage post-hoc puisse être fait sur de vraies données de joueurs.
Matrice de décision : quelle méthode de minuteur
| Approche | Avantages | Inconvénients | Meilleure pour |
|---|---|---|---|
| Compteur de frames | Trivial à coder | Casse sur changement de FPS, pas de hors ligne | Prototype uniquement |
| Accumulateur delta | Indépendant du FPS, pausable | Nécessite une epoch pour la persistance | La plupart des jeux temps réel |
| Intervalle d’epoch | Sauvegarde/chargement parfait et hors ligne | Pas granulaire pour sous-frame | Idle, récompenses quotidiennes |
| Autorité serveur | Anti-triche, équitable | Latence, coût d’infrastructure | Événements compétitifs |
Utilisez cette matrice pour décider de votre architecture. L’accumulateur delta plus l’epoch est le défaut que je recommande sauf si vous gérez un serveur compétitif.
Extension aux événements répétitifs
De nombreux événements live-ops se répètent chaque jour de jeu. Ajoutez un champ cycleGameMs et utilisez le modulo sur accumulatedGameMs. J’utilise cela pour un minuteur de récompense quotidienne : nextReset = cycleGameMs - (accumulatedGameMs % cycleGameMs). Cela survit au hors ligne car le modulo opère sur le total mis à l’échelle.
Pièges courants, compromis et les choses que personne ne vous dit
Même avec des formules correctes, les minuteurs de production échouent de manière prévisible. D’abord, la dérive en virgule flottante : un accumulateur flottant 32 bits à 60 FPS perd des millisecondes après environ 12 heures. Utilisez double ou int64 en millisecondes. Ensuite, la triche d’horloge : l’epoch locale peut être manipulée.
Troisièmement, l’idée fausse du « minuteur équitable » : beaucoup pensent qu’une epoch de départ synchronisée suffit pour le multijoueur. Ce n’est pas le cas—si un client lag, son minuteur local se déclenche en retard. Vous avez besoin d’une confirmation serveur de la complétion de l’événement, pas seulement d’un compte à rebours local.
Enfin, la plupart des gens ne réalisent pas que les changements d’échelle de temps cassent rétroactivement les restes stockés naïvement. Si un joueur a 100 secondes de jeu restantes à l’échelle 1, puis vous passez l’échelle à 2, a-t-il 50 secondes réelles ou toujours 100 secondes de jeu ? Définissez cela dans la conception. Je préfère garder le reste en temps de jeu, donc le reste réel diminue de moitié—mais vos producteurs peuvent être en désaccord.
Liste de contrôle du minuteur avant livraison
- Tester avec un écart hors ligne simulé de 30 jours pour vérifier les plafonds.
- Vérifier que les epochs sont en UTC sur toutes les cibles de build.
- Confirmer que l’accumulateur utilise 64 bits ou double.
- Documenter le temps réel vs le temps de jeu dans l’UI côté joueur.
- Pour le multijoueur, déplacer l’autorité du minuteur vers le serveur.
Localisation et confiance des joueurs
Si votre jeu sort dans plusieurs régions, rappelez-vous que certains paramètres régionaux formatent l'heure en HH:mm, d'autres en mm:HH. J'ai un jour semé la confusion en affichant « 2:30 » pour signifier 2h30 dans un marché où cela se lisait comme 2m30s. Utilisez des étiquettes d'unités explicites générées par votre formateur, et non des nombres bruts.
Les mathématiques indépendantes du moteur ci-dessus—accumulation de delta, intervalles d'époque, conversion d'échelle—vous serviront d'un prototype de Game Jam à un RPG en service continu. Appliquez la Trinité des minuteurs, testez avec des écarts hors ligne extrêmes, et vos minuteurs d'événements resteront fiables.
