Qu'est-ce qui bouge réellement dans un coureur sans fin?

107

Par exemple, le célèbre jeu Flappy Bird, ou quelque chose de ce genre, est le joueur (dans ce cas, l'oiseau, ou la caméra, selon votre préférence) qui avance ou le monde entier qui recule (l'oiseau ne change que la position Y et a une position X constante)?

Lendrit Ibrahimi
la source
1
Nous avons supprimé de nombreux commentaires hors sujet de cette question et de ses réponses. Nous avons également déplacé plusieurs discussions raisonnables pour discuter, dans certains cas à plusieurs reprises. Malgré cela, plusieurs utilisateurs ont ressenti le besoin d'ignorer le fait que les commentaires n'étaient pas destinés à des discussions prolongées et qu'ils continuaient. En tant que telle, cette question a été temporairement verrouillée.
Josh

Réponses:

146

Je suis légèrement en désaccord avec la réponse de Philipp ; ou du moins avec la façon dont il l'a présenté. Cela donne l'impression que déplacer le monde autour du joueur pourrait être une meilleure idée; quand c'est exactement le contraire. Alors voici ma propre réponse ...


Les deux options peuvent fonctionner, mais c’est généralement une mauvaise idée d’inverser la physique en déplaçant le monde autour du joueur plutôt que le monde entier.


Perte de performance / gaspillage:

Le monde aura généralement beaucoup d'objets; beaucoup sinon la plupart, statique ou en sommeil. Le joueur aura un ou relativement peu d'objets. Déplacer le monde entier autour du lecteur signifie déplacer tout ce qui se trouve dans la scène sauf le joueur. Objets statiques, objets dynamiques en sommeil, objets dynamiques actifs, sources de lumière, sources de son, etc. tous doivent être déplacés.

C'est (évidemment) considérablement plus coûteux que de ne déplacer que ce qui bouge réellement (le joueur et peut-être un peu plus de choses).


Maintenabilité et extensibilité:

En déplaçant le monde autour du joueur, le monde (et tout ce qu'il contient) devient le point où les choses se passent le plus activement. Tout bogue ou changement dans le système signifie que, potentiellement, tout change. Ce n'est pas une bonne façon de faire les choses. vous voulez que les bogues / modifications soient aussi isolés que possible, afin que vous n'ayez pas de comportement inattendu dans un endroit où vous n'avez pas apporté de modifications.

Il y a aussi beaucoup d'autres problèmes avec cette approche. Par exemple, il rompt de nombreuses hypothèses sur la manière dont les choses sont censées fonctionner dans le moteur. Vous ne pouvez pas utiliser dynamique RigidBodypour autre chose que le lecteur, par exemple; sous forme d'objet avec une RigidBodycinématique non attachée se comportera de manière inattendue lors du réglage de la position / rotation / échelle (comme vous le feriez pour chaque image, pour chaque objet de la scène, à l'exception du lecteur)


Donc, la réponse est de ne déplacer que le joueur!

Eh bien ... oui et non . Comme mentionné dans la réponse de Philipp, dans un type de jeu à coureurs infinis (ou tout jeu comportant une grande zone explorable sans soudure), aller trop loin de l'origine introduirait à terme des FPPE ( erreurs de précision en virgule flottante) remarquables , et même plus tard, éventuellement, Débordez le type numérique, soit en provoquant le plantage de votre jeu, soit, en gros, faites en sorte que la fumée du jeu disparaisse ... Sur des stéroïdes! 😵 (car à ce stade, les FPPE rendraient le jeu déjà sur du crack "normal")


La solution actuelle :

Ne faites ni l'un ni l'autre et faites les deux! Vous devriez garder le monde statique et déplacer le joueur autour de lui. Mais « re-root » à la fois , le joueur et le monde, lorsque le joueur commence à devenir trop loin de la racine (position [0, 0, 0]) de la scène.

Si vous conservez la position relative des éléments (lecteur par rapport au monde qui l'entoure) et effectuez ce processus en une seule mise à jour du cadre, le lecteur (réel) ne le remarquera même pas!

Pour ce faire, vous avez deux options principales:

  1. Déplacez le lecteur à la racine de la scène et déplacez le morceau de monde vers sa nouvelle position par rapport au joueur.
  2. Pensez au monde comme une grille. déplacez la partie de la grille où se trouve le joueur vers la racine et déplacez le joueur à sa nouvelle position par rapport à cette partie de la grille.

Voici un exemple de ce processus en action


Mais jusqu'où c'est trop loin?

Si vous regardez le code source de Unity, ils utilisent 1e-5( 0.00001) comme base pour considérer deux valeurs à virgule flottante "égales", à l'intérieur de Vector2et Vector3(les types de données responsables des positions des objets, des rotations et des échelles [euler-]). Étant donné que la perte de précision en virgule flottante se produit dans les deux sens loin de zéro, il est prudent de supposer que tout élément situé sous 1e+5( 100000) unités éloignées de la racine / origine de la scène peut être utilisé en toute sécurité.

Mais! Puisque...

  1. Il est plus approprié de créer un système pour gérer automatiquement ces processus de ré-enracinement.
  2. Quel que soit votre jeu, il n'est pas nécessaire qu'une "section" contiguë du monde soit large de 100 000 unités (mètres [?]).

... alors c'est probablement une bonne idée de réintroduire les racines beaucoup plus tôt / plus souvent que la marque des 100 000 unités. L'exemple de vidéo que j'ai fourni semble le faire toutes les 1000 unités environ, par exemple.

XenoRo
la source
2
Les commentaires ne sont pas pour une discussion prolongée; cette conversation a été déplacée pour discuter . Veuillez utiliser ce chat au lieu de poster plus de commentaires ici.
Alexandre Vaillancourt
> C'est (évidemment) considérablement plus coûteux que de déménager uniquement ce qui bouge réellement <Est-ce? Lors du rendu, vous devrez quand même effectuer une multiplication de matrices avec la matrice de la caméra sur tous les objets. Le fait de fixer la caméra à l'identité serait ainsi moins coûteux.
Matsemann
Ce moment où le développement du jeu devient un développement de l'unité ...
LJ ᛃ
@Matsemann - Si vous étiez allé bavarder, vous auriez remarqué que ce n'était pas un nouveau point. Comme déjà expliqué, restituez la scène NOT-EQUALS; ce sont des sujets et des pipelines différents et presque complètement indépendants. Faire une inversion du cadre de référence dans la façon dont les objets sont positionnés dans la scène signifie que vous aurez un processus plus lourd aux deux extrémités , plutôt qu’au rendu. --- En outre, même si la performance était tout aussi échangé que vous disputez, tous les autres problèmes avec l'inversion serait encore rester sans solution, ce qui approche encore pire que le réenracinant un.
XenoRo
@LJ ᛃ La question portait spécifiquement sur l'unité (voir les balises de OP avant les éditions de D. Everhard [dégradant]), et la réponse a donc été adaptée pour refléter cela . --- Mais voici la meilleure partie: que ce soit Unity, Unreal, CryEngine, Source, etc ... Les meilleurs et / ou les plus populaires moteurs fonctionnent de cette façon (et ils le font pour une raison), donc la réponse n'est pas seulement parfaitement valable en général, mais les points soulevés sont toujours au sommet de la précision . En règle générale, si vous inversez le cadre de référence de la physique, vous risquez de rencontrer des problèmes, en particulier pour les objets dynamiques.
XenoRo
89

Les deux options fonctionnent.

Mais si vous voulez que le coureur sans fin soit vraiment sans fin, vous devrez garder le joueur immobile et bouger le monde. Sinon, vous atteindrez éventuellement les limites des variables que vous utilisez pour stocker la position X. Un entier finirait par déborder et une variable à virgule flottante deviendrait de moins en moins précise, ce qui rendrait le gameplay glitchy au bout d'un moment. Mais vous pouvez éviter ce problème en utilisant un type suffisamment grand pour que personne ne rencontre ces problèmes dans les délais que l'on pourrait éventuellement jouer en une session (lorsqu'un joueur déplace de 1000 pixels par seconde, un entier de 32 bits débordera au bout de 49 jours).

Alors, faites ce qui vous semble conceptuellement plus intuitif.

Philipp
la source
Les commentaires ne sont pas pour une discussion prolongée; cette conversation a été déplacée pour discuter .
Josh
1
Je ne pense pas que cela résout rien. Au lieu de conserver la position du joueur (et de la caméra), vous conservez la position du défilement du monde - en quoi est-ce différent?
Agent_L
1
@Agent_L Généralement, les mondes sont générés au coup par coup et chaque pièce a sa position X / Y. Lorsque la pièce se trouve suffisamment derrière le joueur (par exemple, hors écran), elle est supprimée et elle est générée un certain montant à l'avance, de sorte qu'elle reste toujours dans une plage relativement petite - un jeu que j'ai créé n'a jamais eu de coordonnées plus grandes. que ~ 1000 ou moins que 0, bien qu’il soit un coureur infini généré aléatoirement, à cause de cela - j’ai suivi la position du joueur dans chaque morceau et l’index de chaque morceau dans la séquence.
Nic Hartley
Je n'achète pas le concept de "débordement variable". Si le joueur ne bouge pas, alors "l'arrière-plan" sera compensé négativement du même montant que le joueur serait décalé s'il bougeait. Les deux cas rencontreraient exactement le même problème (dans des directions opposées) et nécessiteraient un traitement spécial aux limites du débordement.
Spender
2
à 1000 pixels par seconde, il faudrait plus de 586 millions d'années pour dépasser un entier de 64 bits. Ce n'est vraiment un problème que si vous utilisez de très petits types tels que des entiers 16 bits.
Lyndon White
38

En vous basant sur la réponse de XenoRo , au lieu de la méthode de ré-enracinement décrite, vous pouvez procéder comme suit:

Créez un tampon circulaire de parties de votre carte infinie générées, que votre personnage déplace avec la position mise à jour avec une arithmétique modulo (afin que vous ne fassiez que contourner le tampon circulaire). Commencez à remplacer des parties de votre tampon dès que votre personnage quitte le bloc. L'équation de mise à jour des joueurs serait quelque chose comme:

player.position = (player.position + player.velocity) % worldBuffer.width;

Voici un exemple pictural de ce dont je parle:

entrez la description de l'image ici

Voici un exemple de ce qui se passe sur l'emballage à la fin.

entrez la description de l'image ici

Avec cette méthode, vous ne rencontrez jamais d'erreurs de précision, mais vous devrez peut-être créer une très grande mémoire tampon si vous avez l'intention de le faire en 3D avec une distance de vision très éloignée (car vous devrez toujours être capable de voir devant vous. ). Si son oiseau flappy, la taille de votre bloc sera probablement aussi grande que nécessaire pour contenir une seule scène pour un seul écran, et votre mémoire tampon peut être très petite.

Notez que vous commencerez à obtenir des résultats répétitifs avec N'IMPORTE QUELLE méthode, et que le nombre maximal de séquences non répétitives d'une génération PRNG est généralement inférieur à la longueur de la puissance (2, nombre de bits utilisés en interne). Avec Merzenne Twister, ce n'est pas Un problème, car il utilise 2,5k d’état interne, mais si vous utilisez la variante minuscule, vous avez 2 ^ 127-1 max d’itérations avant répétition (ou pire), le nombre reste toutefois astronomiquement élevé . Vous pouvez résoudre les problèmes de période répétée même si votre PRNG a une courte période via de bonnes fonctions de mélange par avalanche pour la graine (à mesure que vous ajoutez implicitement un état supplémentaire).

opa
la source
Les commentaires ne sont pas pour une discussion prolongée; cette conversation a été déplacée pour discuter .
Josh
15

Comme demandé et accepté, cela dépend vraiment de l'étendue et du style de votre jeu, mais comme cela n'a pas été mentionné: FlappyBird déplace l'obstacle sur l'écran plutôt que sur le joueur à travers le monde.

Un géniteur instancie des objets hors de l'écran avec une vitesse fixe dans la Vector2.leftdirection.

Stephan
la source
3
Cela fonctionne pour FlappyBird parce que c'est un jeu très simple. Comme mentionné dans ma réponse, la même technique, bien que toujours possible , serait très problématique pour tout ce qui est plus complexe (avec plus d'objets / détails du monde), comme Subway Surfer.
XenoRo
15
Je suis d'accord et votre réponse est très approfondie. Je n'ai simplement vu personne parler de la méthode réellement utilisée par les oiseaux batteurs, alors j'ai décidé de l'ajouter.
Stephan