Comment supprimer la gigue de l'entrée de mouvement?

9

J'écris un mod Minecraft qui prend en charge les entrées du Razer Hydra . Il s'agit d'une paire de contrôleurs de mouvement (un pour chaque main) qui fournissent des informations incroyablement précises sur la position et la rotation.

Pour les besoins de cette question, tourner le contrôleur droit sur l'axe Y fait que le personnage du joueur regarde à gauche ou à droite (lacet), et tourner sur l'axe X fait regarder le joueur de haut en bas (tangage).

L'entrée du contrôleur est directement mappée au titre du personnage. Si le contrôleur est tourné de 30 degrés vers la gauche, le personnage tourne de 30 degrés vers la gauche.

Le problème est que l'entrée "tremble". Si j'essaie de maintenir le contrôleur parfaitement immobile, le titre du personnage se déplace de manière erratique dans un très petit cône (peut-être 1 degré).

Cela est probablement dû à mes mains tremblantes, car les données du contrôleur sont apparemment exactes.

J'ai essayé de filtrer les entrées en faisant la moyenne des données des dernières images X, mais cela donne une apparence beurrée.

Ma question est: comment puis-je filtrer les données de rotation pour supprimer la gigue sans perdre en précision?

Pommes
la source
avez-vous envisagé d'ignorer de très petits changements de mouvement?
Philipp
1
Ce Razer Hyrdra est intéressant ... la première fois que j'en ai entendu parler, il est encore plus intéressant d'écrire un mod Minecraft pour le compléter ... Mon hypothèse est: tout comme une image pixélisée, afin de "réduire le bruit" dont vous avez besoin pour brouiller l'image ... Fondamentalement, vous pouvez soit vivre avec l'image pixélisée ou vivre avec l'image floue ... J'ai l'impression que c'est le même principe, vous devez choisir ce que vous préférez, un jeu tremblant ou moins de précision ... ... C'est juste ce que je pense ...
Luke San Antonio Bialecki
1
Vous pourriez tirer le meilleur parti des deux. Stockez toujours les derniers 50 mots. Mais en moyenne seulement sur quelques-uns d'entre eux, en fonction de la quantité de mouvement d'entrée. Pour les grands mouvements, ne comptez que sur les 5 dernières images par exemple et pour les petits mouvements (autrement tremblants), utilisez les 30 dernières images.
danijar
@sharethis C'est une idée intéressante. Je pourrais finir par mettre en œuvre quelque chose comme ça. La gigue n'est pas un problème une fois que l'entrée dépasse un certain seuil, donc je pourrais simplement faire la moyenne des petites entrées pour supprimer la gigue, et ne rien faire du tout pour une grande entrée.
Pommes

Réponses:

8

Ce problème de décalage par rapport à la réactivité est la situation avec pratiquement tous les contrôleurs de mouvement, que ce soit l'Hydra, la télécommande Wii, le Kinect ou le PlayStation Move.

Le problème est le suivant:

Lorsqu'un flux d'entrée arrive, vous décidez trame par trame de faire confiance ou non aux données d'entrée; si les tendances que vous voyez maintenant se sont poursuivies dans les données, vous recevez une douzaine de millisecondes à partir de maintenant. Par exemple, s'il y a un décalage soudain vers la droite de ce cadre, vous ne savez pas s'il s'agit d'un vrai bit de données d'entrée (et vous devez donc agir dessus) ou s'il s'agit simplement d'une gigue (et vous devez donc l'ignorer). Quel que soit votre choix, si vous découvrez plus tard que vous vous êtes trompé, vous avez soit autorisé la gigue d'entrée à entrer dans votre jeu (dans le premier cas), soit introduit un retard dans votre jeu (dans le dernier cas).

Il n'y a pas de bonne solution à cela. Une solution "correcte" pour déterminer si l'entrée est réelle ou instable nécessite de savoir ce que le flux d'entrée va faire à l'avenir, ainsi que ce qu'il a fait dans le passé. Nous ne pouvons pas faire cela dans les jeux, pour des raisons évidentes. Donc non, il n'y a aucun moyen de filtrer les données de rotation pour supprimer la gigue sans perdre en précision, dans le contexte d'un jeu qui travaille avec des données d'entrée en temps réel.

J'ai vu un grand fabricant recommander aux développeurs de résoudre ce problème en demandant aux joueurs de maintenir un bouton enfoncé tout en faisant tourner le contrôle, afin que le jeu puisse désactiver son code anti-gigue à ce stade, il n'est donc pas décalé. (Je ne recommande pas cela, mais c'est une approche).

J'ai vu quelques bibliothèques de middleware d'entrée de mouvement qui traitent ce problème en introduisant un retard artificiel dans l'entrée - il y a un tampon d'un quart de seconde dans lequel les données d'entrée entrent, et votre jeu n'entend parler de l'entrée qu'un quart de seconde plus tard, afin que la bibliothèque puisse atténuer la gigue pour vous, en sachant ce qui se passe avant et après le "présent" du point de vue du jeu. Cela fonctionne très bien, à part introduire un quart de seconde de retard sur tout. Mais c'est une façon de résoudre le problème, et il peut faire un travail impressionnant de représenter avec précision un mouvement sans gigue, au détriment d'un décalage constant.

Mais sans aller à cet extrême, il y a encore des choses que nous pouvons faire pour améliorer considérablement le comportement, même si nous savons qu'il y aura toujours des «pires scénarios» qui se comporteront de manière non idéale.

L'idée principale est que nous ne nous soucions vraiment de la gigue que lorsque le contrôleur est principalement stationnaire, et nous ne nous soucions vraiment du décalage que lorsque le contrôleur est déplacé. Donc, notre stratégie devrait être d'essayer de gérer les choses de manière à ce que nous ayons du décalage lorsque le contrôleur est à l'arrêt et de la gigue lorsque le contrôleur est en mouvement.

Voici deux façons possibles de le faire:

Une approche courante est un système "verrouillé / déverrouillé", dans lequel vous gardez une trace de l'orientation de l'appareil, et s'il ne change pas pendant un court instant (une demi-seconde environ), vous "verrouillez" cette orientation, sans action basée sur l'orientation rapportée de l'appareil jusqu'à ce qu'elle diffère suffisamment pour se «déverrouiller» à nouveau. Cela peut complètement atténuer la gigue basée sur l'orientation, sans introduire de décalage lorsque l'orientation change activement. Il peut y avoir un soupçon de décalage avant que le code décide qu'il doit passer en mode "déverrouillé", mais ce sera beaucoup mieux que d'avoir du décalage partout.

Une autre approche consiste à faire la moyenne des données d'entrée des trames. Le point important ici est de ne faire la moyenne qu'ensemble des données d'entrée des images où les données d'entrée étaient vaguement similaires - Cela signifiait que les petits tremblements se brouillaient et s'adoucissaient, mais les changements plus importants ne se brouillaient pas, car leurs données n'étaient pas assez similaire aux données des trames précédentes.

Il existe également d'autres façons d'obtenir un effet similaire. L'idée de base est que vous ne pouvez pas avoir à la fois sans gigue et sans décalage dans votre jeu en temps réel, car cela nécessiterait une connaissance de l'avenir. Vous devez donc choisir quand biaiser le comportement de votre contrôle vers l'acceptation de la gigue et quand le biaiser vers l'acceptation du décalage, afin de rendre l'expérience globale la plus mauvaise possible.

Trevor Powell
la source
Je viens de poster une réponse, pouvez-vous donner votre avis sur ma solution?
Pommes
2

Je développe un logiciel qui convertit les entrées de mouvement en entrées de souris réactives et précises, et je gère un site Web qui essaie d'aider les développeurs à mettre en œuvre eux-mêmes des solutions tout aussi bonnes. Je déconseille généralement les seuils de mouvement, même si cela dépend de la réactivité et de la précision que les joueurs veulent, je suis heureux que cela fonctionne pour vous dans votre situation. Mais ici, je vais proposer une solution différente:

J'utilise quelque chose appelé lissage progressif doux . L'idée est que nous détournons les entrées via différents algorithmes de lissage en fonction de l'amplitude actuelle de la vitesse du gyroscope (en pratique, l'un de ces algorithmes de lissage est simplement "pas de lissage"). C'est la partie "étagée". La partie "douce" est que nous pouvons réellement diviser en douceur l'entrée entre différents algorithmes de lissage en fonction de la façon dont elle se compare à 2 seuils.

Il préserve correctement le déplacement et n'ajoute aucun décalage aux mouvements rapides.

En pratique, vous avez deux seuils. Lorsque l'amplitude de la vitesse d'entrée est inférieure au seuil inférieur, nous utilisons un algorithme de lissage simple (moyenne sur plusieurs images). Lorsqu'il est supérieur à l'autre seuil, nous n'utilisons aucun algorithme de lissage. Mais dans ce cas, nous transmettons toujours des zéros à l'algorithme de lissage de seuil inférieur.

Lorsque la vitesse d'entrée se situe entre les deux seuils, nous divisons l'entrée entre les deux algorithmes en conséquence.

Voici un extrait de l'article ci-dessus:

GetSoftTieredSmoothedInput(Vec2 input, float threshold1, float threshold2) {
    // this will be length(input) for vectors
    float inputMagnitude = Abs(input);

    float directWeight = (inputMagnitude - threshold1) / (threshold2 - threshold1);
    directWeight = clamp(directWeight, 0, 1);

    return GetDirectInput(input * directWeight) +
        GetSmoothedInput(input * (1.0 - directWeight));
}

GetDirectInput renvoie simplement ce qui lui est donné, mais c'est pour montrer qu'un autre algorithme de lissage pourrait être utilisé ici. GetSmoothedInput prend une vélocité et renvoie une vélocité lissée.

Avec le lissage progressif doux, aucun lissage n'est appliqué aux mouvements manifestement intentionnels (au-dessus du seuil supérieur), un lissage est appliqué pour couvrir de petites quantités de gigue, ce qui affectera également de très petits mouvements, mais lorsque vous atteignez vos seuils, ce n'est pas très perceptible. Et il y a une transition très douce entre les deux (sans laquelle, la gigue peut en fait être amplifiée).

Alors que les autres réponses ont raison de dire qu'il est difficile de reconnaître la gigue à l'instant où une entrée est reçue, il est également vrai que la gigue est presque toujours à très faible vitesse, et le décalage d'entrée qui vient avec le lissage est beaucoup moins perceptible pour les entrées à faible vitesse. .

Comme le mentionne l'article, cela est utilisé à quelques endroits dans mon outil open source JoyShockMapper , un mappeur d'entrée qui transforme l'entrée gyroscopique en entrée souris. Même pour les personnes utilisant d'autres outils de remappage comme Steam ou reWASD, certains utilisent JoyShockMapper en même temps uniquement pour ses commandes gyroscopiques.

Cette réponse suppose que l'entrée est donnée en vitesse angulaire (ce qui est courant avec les contrôleurs qui ont des commandes de mouvement), pas en orientation absolue (ce qui ressemble à la Razer Hydra qui vous donne). Avec une orientation absolue, j'espère que vous pouvez utiliser la différence entre l'orientation actuelle et l'orientation précédemment signalée pour obtenir une vitesse, mais je ne sais pas avec certitude si cela fonctionnera aussi bien qu'avec des contrôleurs qui auto-rapportent la vitesse angulaire .

Une solution de lissage courante lorsque vous avez affaire à une position / orientation absolue plutôt qu'à des vitesses est d'interpoler vers l'orientation de votre objectif au fil du temps - cela est décrit en détail très utile dans cet article de Gamasutra. Cela peut également fonctionner avec le lissage progressif progressif. Vous calculerez l'amplitude de la vitesse en utilisant la différence entre cette entrée et l'entrée précédente signalée. Vous appliquerez la différence d'orientation entre ce cadre et le dernier cadre multipliée par votre valeur "directWeight" calculée dans l'extrait ci-dessus. La dernière étape consiste à ajouter l'entrée lissée, mais en raison de la façon dont fonctionne le lissage d'orientation interpolé, vous appliquez simplement le changement d'orientation interpolé comme d'habitude - il n'a pas besoin de considérer du tout "directWeight". Définissez simplement votre orientation cible (c'est ce vers quoi vous interpolez avec le lissage décrit dans cet article Gamasutra) à l'orientation que vous obtenez de l'appareil, et interpolez votre orientation vers elle comme décrit dans cet article.

Jibb Smart
la source
1

C'est bizarre de répondre à ma propre question, mais je pense avoir trouvé ma solution.

//Pseudo-Java

update()
{
    //deltaYaw is the change in yaw of the controller since last update
    //yawBuffer is initialized to zero, and only modified here
    //coneAngle is the stabilizing cone

    deltaYaw = getData().yaw;

    yawBuffer += deltaYaw;
    if (abs(yawBuffer) >= coneAngle)
    {
        player.yaw += (abs(yawBuffer)-coneAngle) * sign(yawBuffer);
        yawBuffer = coneAngle * sign(yawBuffer);
    }
}

Au lieu de modifier directement le cap du joueur, je "pousse" simplement un cône d'un angle donné (dans mon cas, 2,5 degrés). J'ai fait une petite démo HTML5 de cette technique.

Une fois que vous commencez à pousser le cône, il est zéro retard et une précision totale. Cependant, si vous poussez le cône vers la gauche et souhaitez ensuite viser vers la droite, vous devez vous déplacer sur l'angle complet du cône pour voir un effet.

Il résout donc les problèmes de retard temporel et d'horrible lissage, mais introduit le nouveau problème d'un seuil de mouvement. Cependant, si le cône de stabilisation est réglé correctement, le seuil est imperceptible.

Pommes
la source
Cela semble une autre façon raisonnable de le faire. Il y avait des caméscopes (chers) qui stabilisaient leur image en utilisant cette approche; il vous donne une précision lorsque vous continuez un mouvement dans une direction, mais a du retard lorsque vous changez de direction. Si cela fonctionne bien pour votre jeu, alors allez-y absolument. Vous n'allez pas trouver une seule solution qui fonctionne le mieux pour chaque jeu; c'est toujours un compromis où vous devez peser les inconvénients de chaque approche par rapport aux besoins spécifiques du jeu que vous créez. :)
Trevor Powell