Gestos e controladores de movimento no UnityGestures and motion controllers in Unity

Há duas maneiras principais de agir em sua olhar no Unity, gestos de mão e controladores de movimento no HoloLens e HMD de imersão.There are two key ways to take action on your gaze in Unity, hand gestures and motion controllers in HoloLens and Immersive HMD. Você acessa os dados de ambas as fontes de entrada espacial por meio das mesmas APIs no Unity.You access the data for both sources of spatial input through the same APIs in Unity.

O Unity fornece duas maneiras principais de acessar dados de entrada espaciais para a realidade mista do Windows, as APIs comuns Input. getbutton/Input. getaxis que funcionam em vários SDKs do Unity XR e uma API interactionmanager/GestureRecognizer específica para a realidade mista do Windows que expõe o conjunto completo de dados de entrada espaciais disponíveis.Unity provides two primary ways to access spatial input data for Windows Mixed Reality, the common Input.GetButton/Input.GetAxis APIs that work across multiple Unity XR SDKs, and an InteractionManager/GestureRecognizer API specific to Windows Mixed Reality that exposes the full set of spatial input data available.

Tabela de mapeamento de botões/eixos do UnityUnity button/axis mapping table

As IDs de botão e eixo na tabela abaixo têm suporte no Gerenciador de entrada do Unity para controladores de movimento de realidade mista do Windows por meio das APIs Input. getbutton/getaxis , enquanto a coluna "Windows Mr-specific" refere-se às propriedades disponíveis no tipo InteractionSourceState .The button and axis IDs in the table below are supported in Unity's Input Manager for Windows Mixed Reality motion controllers through the Input.GetButton/GetAxis APIs, while the "Windows MR-specific" column refers to properties available off of the InteractionSourceState type. Cada uma dessas APIs é descrita detalhadamente nas seções a seguir.Each of these APIs are described in detail in the sections below.

Os mapeamentos de ID de botão/eixo para a realidade mista do Windows geralmente correspondem às IDs de eixo/botão Oculus.The button/axis ID mappings for Windows Mixed Reality generally match the Oculus button/axis IDs.

Os mapeamentos de ID de botão/eixo para a realidade mista do Windows diferem dos mapeamentos do OpenVR de duas maneiras:The button/axis ID mappings for Windows Mixed Reality differ from OpenVR's mappings in two ways:

  1. O mapeamento usa IDs de touchpad que são diferentes de Thumbstick para dar suporte a controladores com Thumbsticks e touchpads.The mapping uses touchpad IDs that are distinct from thumbstick, to support controllers with both thumbsticks and touchpads.
  2. O mapeamento evita sobrecarregar as IDs de botão a e X para os botões de menu, para deixá-las disponíveis para botões físicos de ABXY.The mapping avoids overloading the A and X button IDs for the Menu buttons, to leave those available for physical ABXY buttons.
EntradaInput APIs comuns do UnityCommon Unity APIs
(Input. getbutton/getaxis)(Input.GetButton/GetAxis)
API de entrada específica do Windows MRWindows MR-specific Input API
XR. WSA. Entrada(XR.WSA.Input)
À esquerdaLeft hand À direitaRight hand
Selecionar gatilho pressionadoSelect trigger pressed Eixo 9 = 1,0Axis 9 = 1.0 Eixo 10 = 1,0Axis 10 = 1.0 selectPressedselectPressed
Selecionar valor analógico do gatilhoSelect trigger analog value Eixo 9Axis 9 Eixo 10Axis 10 selectPressedAmountselectPressedAmount
Selecionar gatilho parcialmente pressionadoSelect trigger partially pressed Botão 14 (o compatível com gamepad) Button 14 (gamepad compat) Botão 15 (compatível com gamepad) Button 15 (gamepad compat) selectPressedAmount > 0,0selectPressedAmount > 0.0
Botão de menu pressionadoMenu button pressed Botão 6 \*Button 6\* Botão 7 \*Button 7\* menuPressedmenuPressed
Botão de alça pressionadoGrip button pressed Eixo 11 = 1,0 (sem valores analógicos)Axis 11 = 1.0 (no analog values)
Botão 4 (compatível com gamepad) Button 4 (gamepad compat)
Eixo 12 = 1,0 (sem valores analógicos)Axis 12 = 1.0 (no analog values)
Botão 5 (compatível com gamepad) Button 5 (gamepad compat)
compreenderamgrasped
Thumbstick X (esquerda:-1,0, direita: 1,0) Thumbstick X (left: -1.0, right: 1.0) Eixo 1Axis 1 Eixo 4Axis 4 thumbstickPosition. xthumbstickPosition.x
Thumbstick Y (superior:-1,0, inferior: 1,0) Thumbstick Y (top: -1.0, bottom: 1.0) Eixo 2Axis 2 Eixo 5Axis 5 thumbstickPosition. ythumbstickPosition.y
Thumbstick pressionadoThumbstick pressed Botão 8Button 8 Botão 9Button 9 thumbstickPressedthumbstickPressed
Touchpad X (esquerda:-1,0, direita: 1,0) Touchpad X (left: -1.0, right: 1.0) Eixo 17 \*Axis 17\* Eixo 19 \*Axis 19\* touchpadPosition. xtouchpadPosition.x
Touchpad Y (superior:-1,0, inferior: 1,0) Touchpad Y (top: -1.0, bottom: 1.0) Eixo 18 \*Axis 18\* Eixo 20 \*Axis 20\* touchpadPosition. ytouchpadPosition.y
Touchpad tocadoTouchpad touched Botão 18 \*Button 18\* Botão 19 \*Button 19\* touchpadTouchedtouchpadTouched
Touchpad pressionadoTouchpad pressed Botão 16 \*Button 16\* Botão 17 \*Button 17\* touchpadPressedtouchpadPressed
pose da alça de 6DoF de pose ou de ponteiro6DoF grip pose or pointer pose Segure somente pose: XR. InputTracking. GetLocalPositionGrip pose only: XR.InputTracking.GetLocalPosition
XR. InputTracking.GetLocalRotationXR.InputTracking.GetLocalRotation
Passar alça ou ponteiro como um argumento: SourceState. sourcePose. TryGetPositionPass Grip or Pointer as an argument: sourceState.sourcePose.TryGetPosition
origemstate. sourcePose. TryGetRotationsourceState.sourcePose.TryGetRotation
Estado de acompanhamentoTracking state A precisão da posição e o risco de perda de origem só estão disponíveis por meio da API específica do MR Position accuracy and source loss risk only available through MR-specific API origemstate. sourcePose. positionAccuracysourceState.sourcePose.positionAccuracy
SourceState. Properties. sourceLossRisksourceState.properties.sourceLossRisk

Observação

Essas IDs de botão/eixo diferem das IDs que o Unity usa para OpenVR devido a colisões nos mapeamentos usados por gamepads, Oculus Touch e OpenVR.These button/axis IDs differ from the IDs that Unity uses for OpenVR due to collisions in the mappings used by gamepads, Oculus Touch and OpenVR.

Segurar pose vs. ponto de apontarGrip pose vs. pointing pose

O Windows Mixed Reality dá suporte a controladores de movimento em uma variedade de fatores forma, sendo que o design de cada controlador difere em sua relação entre a posição da mão do usuário e a direção natural "encaminhar" que os aplicativos devem usar para apontar ao renderizar o controle.Windows Mixed Reality supports motion controllers in a variety of form factors, with each controller's design differing in its relationship between the user's hand position and the natural "forward" direction that apps should use for pointing when rendering the controller.

Para representar melhor esses controladores, há dois tipos de poses que você pode investigar para cada origem de interação, a pose de alça e a pose do ponteiro.To better represent these controllers, there are two kinds of poses you can investigate for each interaction source, the grip pose and the pointer pose. As coordenadas de pose pose e ponteiro representam expressas por todas as APIs do Unity nas coordenadas do mundo global do Unity.Both the grip pose and pointer pose coordinates are expressed by all Unity APIs in global Unity world coordinates.

Segurar poseGrip pose

A alça de pose representa o local da palma de uma mão detectada por um HoloLens ou o Palm que está segurando um controlador de movimento.The grip pose represents the location of either the palm of a hand detected by a HoloLens, or the palm holding a motion controller.

Em headsets de imersão, a alça de fixação é mais bem usada para renderizar a mão do usuário ou um objeto mantido na mão do usuário, como uma gumes ou uma arma.On immersive headsets, the grip pose is best used to render the user's hand or an object held in the user's hand, such as a sword or gun. A pose de alça também é usada ao visualizar um controlador de movimento, pois o modelo renderizado fornecido pelo Windows para um controlador de movimento usa a alça de pose como sua origem e o centro de rotação.The grip pose is also used when visualizing a motion controller, as the renderable model provided by Windows for a motion controller uses the grip pose as its origin and center of rotation.

A pose de alça é definida especificamente da seguinte maneira:The grip pose is defined specifically as follows:

  • A posição de alça: o Palm centróide ao manter o controlador naturalmente, ajustado para a esquerda ou para a direita para centralizar a posição dentro da alça.The grip position: The palm centroid when holding the controller naturally, adjusted left or right to center the position within the grip. No controlador de movimento de realidade mista do Windows, essa posição geralmente se alinha com o botão compreender.On the Windows Mixed Reality motion controller, this position generally aligns with the Grasp button.
  • O eixo direito da orientação de alça: quando você abre completamente a mão para formar uma pose plana de 5 dedos, o raio normal para o Palm (para frente do Palm esquerdo, para trás do Palm direito)The grip orientation's Right axis: When you completely open your hand to form a flat 5-finger pose, the ray that is normal to your palm (forward from left palm, backward from right palm)
  • O eixo de encaminhamento da orientação de alça: quando você fecha a sua mão parcialmente (como se você mantiver o controlador), o raio que aponta para "encaminhar" por meio do tubo formado por seus dedos não-thumbs.The grip orientation's Forward axis: When you close your hand partially (as if holding the controller), the ray that points "forward" through the tube formed by your non-thumb fingers.
  • O eixo superior da orientação de alça: o eixo superior implícito pelas definições direita e avançar.The grip orientation's Up axis: The Up axis implied by the Right and Forward definitions.

Você pode acessar a alça de pose por meio da API de entrada entre fornecedores do Unity ( XR. InputTracking. GetLocalPosition/Rotation) ou por meio da API específica do Windows Mr (SourceState. SourcePose. TryGetPosition/Rotation, solicitando dados de pose para o nó de fixação ).You can access the grip pose through either Unity's cross-vendor input API (XR.InputTracking.GetLocalPosition/Rotation) or through the Windows MR-specific API (sourceState.sourcePose.TryGetPosition/Rotation, requesting pose data for the Grip node).

Pose de ponteiroPointer pose

A pose do ponteiro representa a ponta do controlador que está apontando para frente.The pointer pose represents the tip of the controller pointing forward.

A pose de ponteiro fornecida pelo sistema é mais bem usada para Raycast quando você está renderizando o próprio modelo de controlador.The system-provided pointer pose is best used to raycast when you are rendering the controller model itself. Se você estiver renderizando algum outro objeto virtual no lugar do controlador, como uma arma virtual, você deve apontar com um raio mais natural para esse objeto virtual, como um Ray que viaja ao longo do cilindro do modelo de pressão definido pelo aplicativo.If you are rendering some other virtual object in place of the controller, such as a virtual gun, you should point with a ray that is most natural for that virtual object, such as a ray that travels along the barrel of the app-defined gun model. Como os usuários podem ver o objeto virtual e não o controlador físico, apontar com o objeto virtual provavelmente será mais natural para aqueles que usam seu aplicativo.Because users can see the virtual object and not the physical controller, pointing with the virtual object will likely be more natural for those using your app.

Atualmente, a pose do ponteiro está disponível no Unity somente por meio da API específica do Windows Sr, SourceState. sourcePose. TryGetPosition/Rotation, passando InteractionSourceNode. pointer como o argumento.Currently, the pointer pose is available in Unity only through the Windows MR-specific API, sourceState.sourcePose.TryGetPosition/Rotation, passing in InteractionSourceNode.Pointer as the argument.

Estado de controle do controladorController tracking state

Assim como os headsets, o controlador de movimento do Windows Mixed Reality não requer nenhuma configuração de sensores de controle externo.Like the headsets, the Windows Mixed Reality motion controller requires no setup of external tracking sensors. Em vez disso, os controladores são acompanhados por sensores no próprio headset.Instead, the controllers are tracked by sensors in the headset itself.

Se o usuário mover os controladores para fora do campo de exibição do headset, na maioria dos casos, o Windows continuará inferindo as posições do controlador e as fornecerá ao aplicativo.If the user moves the controllers out of the headset's field of view, in most cases Windows will continue to infer controller positions and provide them to the app. Quando o controlador tiver perdido o acompanhamento Visual por tempo suficiente, as posições do controlador serão descartadas para as posições de precisão aproximada.When the controller has lost visual tracking for long enough, the controller's positions will drop to approximate-accuracy positions.

Neste ponto, o sistema bloqueará o corpo do controlador para o usuário, controlando a posição do usuário à medida que eles se movimentam, ao mesmo tempo em que ainda expõe a orientação verdadeira do controlador usando seus sensores de orientação interna.At this point, the system will body-lock the controller to the user, tracking the user's position as they move around, while still exposing the controller's true orientation using its internal orientation sensors. Muitos aplicativos que usam controladores para apontar e ativar elementos de interface do usuário podem operar normalmente em precisão aproximada sem que o usuário perceba.Many apps that use controllers to point at and activate UI elements can operate normally while in approximate accuracy without the user noticing.

A melhor maneira de ter uma ideia para isso é experimentá-lo por conta própria.The best way to get a feel for this is to try it yourself. Confira este vídeo com exemplos de conteúdo de imersão que funciona com controladores de movimento em vários Estados de controle:Check out this video with examples of immersive content that works with motion controllers across various tracking states:


Raciocínio sobre o estado de rastreamento explicitamenteReasoning about tracking state explicitly

Os aplicativos que desejam tratar as posições de forma diferente com base no estado de controle podem ir além e inspecionar as propriedades no estado do controlador, como SourceLossRisk e PositionAccuracy:Apps that wish to treat positions differently based on tracking state may go further and inspect properties on the controller's state, such as SourceLossRisk and PositionAccuracy:

Estado de acompanhamentoTracking state SourceLossRiskSourceLossRisk PositionAccuracyPositionAccuracy TryGetPositionTryGetPosition
Alta precisão High accuracy < 1,0< 1.0 AltaHigh {1>true<1}true
Alta precisão (com risco de perda) High accuracy (at risk of losing) = = 1,0== 1.0 AltaHigh {1>true<1}true
Precisão aproximada Approximate accuracy = = 1,0== 1.0 AproximadoApproximate {1>true<1}true
Sem posição No position = = 1,0== 1.0 AproximadoApproximate {1>false<1}false

Esses Estados de acompanhamento do controlador de movimento são definidos da seguinte maneira:These motion controller tracking states are defined as follows:

  • Alta precisão: Embora o controlador de movimento esteja dentro do campo de exibição do headset, ele geralmente fornecerá posições de alta precisão, com base no rastreamento visual.High accuracy: While the motion controller is within the headset's field of view, it will generally provide high-accuracy positions, based on visual tracking. Observe que um controlador móvel que deixa momentaneamente o campo de exibição ou que é momentaneamente obscurecido dos sensores do headset (por exemplo, por outro lado do usuário) continuará retornando poses de alta precisão por um curto período, com base no acompanhamento inércia do controlador próprio.Note that a moving controller that momentarily leaves the field of view or is momentarily obscured from the headset sensors (e.g. by the user's other hand) will continue to return high-accuracy poses for a short time, based on inertial tracking of the controller itself.
  • Alta precisão (com risco de perda): Quando o usuário move o controlador de movimento para cima da borda do campo de exibição do headset, o headset em breve não será capaz de rastrear visualmente a posição do controlador.High accuracy (at risk of losing): When the user moves the motion controller past the edge of the headset's field of view, the headset will soon be unable to visually track the controller's position. O aplicativo sabe quando o controlador atingiu esse limite de FOV vendo o SourceLossRisk REACH 1,0.The app knows when the controller has reached this FOV boundary by seeing the SourceLossRisk reach 1.0. Nesse ponto, o aplicativo pode optar por pausar gestos do controlador que exigem um fluxo constante de poses de alta qualidade.At that point, the app may choose to pause controller gestures that require a steady stream of very high-quality poses.
  • Precisão aproximada: Quando o controlador tiver perdido o acompanhamento Visual por tempo suficiente, as posições do controlador serão descartadas para as posições de precisão aproximada.Approximate accuracy: When the controller has lost visual tracking for long enough, the controller's positions will drop to approximate-accuracy positions. Neste ponto, o sistema bloqueará o corpo do controlador para o usuário, controlando a posição do usuário à medida que eles se movimentam, ao mesmo tempo em que ainda expõe a orientação verdadeira do controlador usando seus sensores de orientação interna.At this point, the system will body-lock the controller to the user, tracking the user's position as they move around, while still exposing the controller's true orientation using its internal orientation sensors. Muitos aplicativos que usam controladores para apontar para e ativar elementos da interface do usuário podem operar normalmente em precisão aproximada sem que o usuário perceba.Many apps that use controllers to point at and activate UI elements can operate as normal while in approximate accuracy without the user noticing. Os aplicativos com requisitos de entrada mais pesados podem optar por detectar essa queda de alta precisão à precisão aproximada inspecionando a propriedade PositionAccuracy , por exemplo, para dar ao usuário um hitbox mais generosa em destinos fora da tela durante esse tempo.Apps with heavier input requirements may choose to sense this drop from High accuracy to Approximate accuracy by inspecting the PositionAccuracy property, for example to give the user a more generous hitbox on off-screen targets during this time.
  • Sem posição: Embora o controlador possa operar com precisão aproximada por um longo tempo, às vezes o sistema sabe que até mesmo uma posição bloqueada pelo corpo não é significativa no momento.No position: While the controller can operate at approximate accuracy for a long time, sometimes the system knows that even a body-locked position is not meaningful at the moment. Por exemplo, um controlador que acabou de ser ativado pode nunca ter sido observado visualmente ou um usuário pode colocar um controlador selecionado por outra pessoa.For example, a controller that was just turned on may have never been observed visually, or a user may put down a controller that's then picked up by someone else. Naqueles momentos, o sistema não fornecerá nenhuma posição para o aplicativo e TryGetPosition retornará false.At those times, the system will not provide any position to the app, and TryGetPosition will return false.

APIs comuns do Unity (Input. getbutton/getaxis)Common Unity APIs (Input.GetButton/GetAxis)

Namespace: UnityEngine, UnityEngine. XRNamespace: UnityEngine, UnityEngine.XR
Tipos: Input, XR. InputTrackingTypes: Input, XR.InputTracking

No momento, o Unity usa suas APIs de entrada geral. getbutton/Input. getaxis para expor a entrada para o SDK do OCULUS, o SDK do OpenVR e a realidade mista do Windows, incluindo controladores de mãos e de movimento.Unity currently uses its general Input.GetButton/Input.GetAxis APIs to expose input for the Oculus SDK, the OpenVR SDK and Windows Mixed Reality, including hands and motion controllers. Se seu aplicativo usa essas APIs para entrada, ele pode facilmente dar suporte a controladores de movimento em vários SDKs do XR, incluindo a realidade mista do Windows.If your app uses these APIs for input, it can easily support motion controllers across multiple XR SDKs, including Windows Mixed Reality.

Obtendo o estado pressionado de um botão lógicoGetting a logical button's pressed state

Para usar as APIs de entrada gerais da Unity, você normalmente começará com a vinculação de botões e eixos a nomes lógicos no Gerenciador de entrada do Unity, ligando um botão ou IDs de eixo a cada nome.To use the general Unity input APIs, you'll typically start by wiring up buttons and axes to logical names in the Unity Input Manager, binding a button or axis IDs to each name. Em seguida, você pode escrever código que se refere a esse botão lógico/nome do eixo.You can then write code that refers to that logical button/axis name.

Por exemplo, para mapear o botão de gatilho do controlador de movimento à esquerda para a ação enviar, acesse editar > configurações do projeto > entrada no Unity e expanda as propriedades da seção enviar em eixos.For example, to map the left motion controller's trigger button to the Submit action, go to Edit > Project Settings > Input within Unity, and expand the properties of the Submit section under Axes. Altere o botão positivo ou a propriedade do botão Alt positivo para ler o botão 14 do joystick, desta forma:Change the Positive Button or Alt Positive Button property to read joystick button 14, like this:

](images/unity-input-manager.png) InputManager do ![UnityUnity's InputManager
InputManager do UnityUnity InputManager

O script pode, então, verificar a ação de envio usando Input. getbutton:Your script can then check for the Submit action using Input.GetButton:

if (Input.GetButton("Submit"))
{
  // ...
}

Você pode adicionar mais botões lógicos alterando a propriedade tamanho em eixos.You can add more logical buttons by changing the Size property under Axes.

Obtendo um estado de botão físico pressionado diretamenteGetting a physical button's pressed state directly

Você também pode acessar os botões manualmente por seu nome totalmente qualificado, usando Input. GetKey:You can also access buttons manually by their fully-qualified name, using Input.GetKey:

if (Input.GetKey("joystick button 8"))
{
  // ...
}

Obter uma pose do controlador de movimento ou mãoGetting a hand or motion controller's pose

Você pode acessar a posição e a rotação do controlador, usando o XR. InputTracking:You can access the position and rotation of the controller, using XR.InputTracking:

Vector3 leftPosition = InputTracking.GetLocalPosition(XRNode.LeftHand);
Quaternion leftRotation = InputTracking.GetLocalRotation(XRNode.LeftHand);

Observe que isso representa a pose de alça do controlador (onde o usuário mantém o controlador), que é útil para renderizar uma gumes ou uma arma na mão do usuário ou um modelo do próprio controlador.Note that this represents the controller's grip pose (where the user holds the controller), which is useful for rendering a sword or gun in the user's hand, or a model of the controller itself.

Observe que a relação entre essa pose de alça e a pose do ponteiro (onde a ponta do controlador está apontando) pode diferir entre os controladores.Note that the relationship between this grip pose and the pointer pose (where the tip of the controller is pointing) may differ across controllers. Neste momento, o acesso à pose do ponteiro do controlador só é possível por meio da API de entrada específica do MR, descrita nas seções a seguir.At this moment, accessing the controller's pointer pose is only possible through the MR-specific input API, described in the sections below.

APIs específicas do Windows (XR. WSA. EntradaWindows-specific APIs (XR.WSA.Input)

Namespace: UnityEngine. XR. WSA. InputNamespace: UnityEngine.XR.WSA.Input
Tipos: interactionmanager, InteractionSourceState, peractionname, InteractionSourceProperties, InteractionSourceKind, InteractionSourceLocationTypes: InteractionManager, InteractionSourceState, InteractionSource, InteractionSourceProperties, InteractionSourceKind, InteractionSourceLocation

Para obter informações mais detalhadas sobre a entrada da mão de realidade mista do Windows (para o HoloLens) e os controladores de movimento, você pode optar por usar as APIs de entrada espaciais específicas do Windows no namespace UnityEngine. XR. WSA. Input .To get at more detailed information about Windows Mixed Reality hand input (for HoloLens) and motion controllers, you can choose to use the Windows-specific spatial input APIs under the UnityEngine.XR.WSA.Input namespace. Isso permite que você acesse informações adicionais, como precisão de posição ou tipo de fonte, permitindo que você diga as mãos e os controladores.This lets you access additional information, such as position accuracy or the source kind, letting you tell hands and controllers apart.

Sondando o estado dos controladores de mãos e de movimentoPolling for the state of hands and motion controllers

Você pode sondar o estado deste quadro para cada fonte de interação (controlador de mão ou de movimento) usando o método GetCurrentReading .You can poll for this frame's state for each interaction source (hand or motion controller) using the GetCurrentReading method.

var interactionSourceStates = InteractionManager.GetCurrentReading();
foreach (var interactionSourceState in interactionSourceStates) {
    // ...
}

Cada InteractionSourceState que você retorna representa uma fonte de interação no momento atual.Each InteractionSourceState you get back represents an interaction source at the current moment in time. O InteractionSourceState expõe informações como:The InteractionSourceState exposes info such as:

  • Que tipos de prensas estão ocorrendo (Select/menu/Segure/Touchpad/Thumbstick)Which kinds of presses are occurring (Select/Menu/Grasp/Touchpad/Thumbstick)

    if (interactionSourceState.selectPressed) {
         // ...
    }
    
  • Outros dados específicos de controladores de movimento, como as coordenadas XY do Touchpad e/ou do Thumbstick e o estado tocadoOther data specific to motion controllers, such the touchpad and/or thumbstick's XY coordinates and touched state

    if (interactionSourceState.touchpadTouched && interactionSourceState.touchpadPosition.x > 0.5) {
         // ...
    }
    
  • O InteractionSourceKind para saber se a origem é uma mão ou um controlador de movimentoThe InteractionSourceKind to know if the source is a hand or a motion controller

    if (interactionSourceState.source.kind == InteractionSourceKind.Hand) {
         // ...
    }
    

Sondagem para encaminhar representações de renderização previstasPolling for forward-predicted rendering poses

  • Durante a sondagem de dados de origem de interação de mãos e controladores, as poses que você obtém são as mais previstas para o momento em que o fótons do quadro atingirá os olhos do usuário.When polling for interaction source data from hands and controllers, the poses you get are forward-predicted poses for the moment in time when this frame's photons will reach the user's eyes. Essas poses previstas antecipadas são mais bem usadas para renderizar o controlador ou um objeto mantido em cada quadro.These forward-predicted poses are best used for rendering the controller or a held object each frame. Se você estiver direcionando um determinado Press ou Release com o controlador, isso será mais preciso se você usar as APIs de eventos de histórico descritas abaixo.If you are targeting a given press or release with the controller, that will be most accurate if you use the historical event APIs described below.

    var sourcePose = interactionSourceState.sourcePose;
    Vector3 sourceGripPosition;
    Quaternion sourceGripRotation;
    if ((sourcePose.TryGetPosition(out sourceGripPosition, InteractionSourceNode.Grip)) &&
         (sourcePose.TryGetRotation(out sourceGripRotation, InteractionSourceNode.Grip))) {
         // ...
    }
    
  • Você também pode obter a pose de cabeça prevista para este quadro atual.You can also get the forward-predicted head pose for this current frame. Assim como acontece com o pose de origem, isso é útil para renderizar um cursor, embora o direcionamento de uma determinada prensa ou versão seja mais preciso se você usar as APIs de evento históricas descritas abaixo.As with the source pose, this is useful for rendering a cursor, although targeting a given press or release will be most accurate if you use the historical event APIs described below.

    var headPose = interactionSourceState.headPose;
    var headRay = new Ray(headPose.position, headPose.forward);
    RaycastHit raycastHit;
    if (Physics.Raycast(headPose.position, headPose.forward, out raycastHit, 10)) {
         var cursorPos = raycastHit.point;
         // ...
    }
    

Tratamento de eventos de origem de interaçãoHandling interaction source events

Para lidar com eventos de entrada à medida que eles acontecem com seus dados históricos de histórico precisos, você pode manipular eventos de origem de interação em vez de sondagem.To handle input events as they happen with their accurate historical pose data, you can handle interaction source events instead of polling.

Para lidar com eventos de origem de interação:To handle interaction source events:

  • Registre-se para um evento de entrada entre ações .Register for a InteractionManager input event. Para cada tipo de evento de interação em que você está interessado, você precisa assiná-lo.For each type of interaction event that you are interested in, you need to subscribe to it.

    InteractionManager.InteractionSourcePressed += InteractionManager_InteractionSourcePressed;
    
  • Manipule o evento.Handle the event. Depois de se inscrever em um evento de interação, você receberá o retorno de chamada quando apropriado.Once you have subscribed to an interaction event, you will get the callback when appropriate. No exemplo de SourcePressed , isso será depois que a origem for detectada e antes de ser liberada ou perdida.In the SourcePressed example, this will be after the source was detected and before it is released or lost.

    void InteractionManager_InteractionSourceDetected(InteractionSourceDetectedEventArgs args)
         var interactionSourceState = args.state;
    
         // args.state has information about:
            // targeting head ray at the time when the event was triggered
            // whether the source is pressed or not
            // properties like position, velocity, source loss risk
            // source id (which hand id for example) and source kind like hand, voice, controller or other
    }
    

Como parar de lidar com um eventoHow to stop handling an event

Você precisa parar de lidar com um evento quando não estiver mais interessado no evento ou se estiver destruindo o objeto que assinou o evento.You need to stop handling an event when you are no longer interested in the event or you are destroying the object that has subscribed to the event. Para parar de lidar com o evento, cancele a assinatura do evento.To stop handling the event, you unsubscribe from the event.

InteractionManager.InteractionSourcePressed -= InteractionManager_InteractionSourcePressed;

Lista de eventos de origem de interaçãoList of interaction source events

Os eventos de origem de interação disponíveis são:The available interaction source events are:

  • InteractionSourceDetected (a origem se torna ativa)InteractionSourceDetected (source becomes active)
  • InteractionSourceLost (torna-se inativo)InteractionSourceLost (becomes inactive)
  • InteractionSourcePressed (toque, pressionamento de botão ou "selecionar" desmarcado)InteractionSourcePressed (tap, button press, or "Select" uttered)
  • InteractionSourceReleased (fim de um toque, botão liberado ou fim de "selecionar" exmovida)InteractionSourceReleased (end of a tap, button released, or end of "Select" uttered)
  • InteractionSourceUpdated (move ou altera qualquer Estado)InteractionSourceUpdated (moves or otherwise changes some state)

Os eventos de direcionamento histórico representam que correspondem mais precisamente a uma prensa ou liberaçãoEvents for historical targeting poses that most accurately match a press or release

As APIs de sondagem descritas anteriormente fornecem às suas representações previstas de seu aplicativo.The polling APIs described earlier give your app forward-predicted poses. Embora essas poses previstas sejam melhores para renderizar o controlador ou um objeto portátil Virtual, as poses futuras não são ideais para o direcionamento, por dois motivos principais:While those predicted poses are best for rendering the controller or a virtual handheld object, future poses are not optimal for targeting, for two key reasons:

  • Quando o usuário pressiona um botão em um controlador, pode haver cerca de 20 ms de latência sem fio sobre o Bluetooth antes que o sistema receba a prensa.When the user presses a button on a controller, there can be about 20ms of wireless latency over Bluetooth before the system receives the press.
  • Em seguida, se você estiver usando uma pose prevista para o futuro, haveria mais 10 20 ms de previsão de encaminhamento aplicada ao destino quando o fótons do quadro atual atingirá os olhos do usuário.Then, if you are using a forward-predicted pose, there would be another 10-20ms of forward prediction applied to target the time when the current frame's photons will reach the user's eyes.

Isso significa que a sondagem fornece uma pose de origem ou uma pose de cabeça que é de 30 a 40ms para frente, de onde a cabeça do usuário e as mãos realmente estavam de volta quando a imprensa ou a versão ocorreu.This means that polling gives you a source pose or head pose that is 30-40ms forward from where the user's head and hands actually were back when the press or release happened. Para entrada à mão do HoloLens, enquanto não há atraso de transmissão sem fio, há um atraso de processamento semelhante para detectar a prensa.For HoloLens hand input, while there's no wireless transmission delay, there is a similar processing delay to detect the press.

Para ter um destino com precisão com base na intenção original do usuário para um pressionamento de mão ou de controlador, você deve usar a pose de origem histórica ou de cabeçalho desse evento de entrada InteractionSourcePressed ou InteractionSourceReleased .To accurately target based on the user's original intent for a hand or controller press, you should use the historical source pose or head pose from that InteractionSourcePressed or InteractionSourceReleased input event.

Você pode direcionar um Press ou Release com dados históricos de pose do cabeçalho do usuário ou de seu controlador:You can target a press or release with historical pose data from the user's head or their controller:

  • A parte de cabeça no momento em que uma ocorrência de gesto ou controlador ocorreu, que pode ser usada para determinar o que o usuário estava nuvensndo :The head pose at the moment in time when a gesture or controller press occurred, which can be used for targeting to determine what the user was gazing at:

    void InteractionManager_InteractionSourcePressed(InteractionSourcePressedEventArgs args) {
         var interactionSourceState = args.state;
         var headPose = interactionSourceState.headPose;
         RaycastHit raycastHit;
         if (Physics.Raycast(headPose.position, headPose.forward, out raycastHit, 10)) {
             var targetObject = raycastHit.collider.gameObject;
             // ...
         }
    }
    
  • A origem representa no momento em que uma ocorrência de controlador de movimento ocorreu, que pode ser usada para determinar a que o usuário estava apontando o controlador.The source pose at the moment in time when a motion controller press occurred, which can be used for targeting to determine what the user was pointing the controller at. Esse será o estado do controlador que sofreu a prensa.This will be the state of the controller that experienced the press. Se você estiver renderizando o próprio controlador, poderá solicitar a pose do ponteiro em vez da pose de alça, para atingir o direcionamento de raio a partir do que o usuário considerará a dica natural desse controlador renderizado:If you are rendering the controller itself, you can request the pointer pose rather than the grip pose, to shoot the targeting ray from what the user will consider the natural tip of that rendered controller:

    void InteractionManager_InteractionSourcePressed(InteractionSourcePressedEventArgs args)
    {
         var interactionSourceState = args.state;
         var sourcePose = interactionSourceState.sourcePose;
         Vector3 sourceGripPosition;
         Quaternion sourceGripRotation;
         if ((sourcePose.TryGetPosition(out sourceGripPosition, InteractionSourceNode.Pointer)) &&
             (sourcePose.TryGetRotation(out sourceGripRotation, InteractionSourceNode.Pointer))) {
             RaycastHit raycastHit;
             if (Physics.Raycast(sourceGripPosition, sourceGripRotation * Vector3.forward, out raycastHit, 10)) {
                 var targetObject = raycastHit.collider.gameObject;
                 // ...
             }
         }
    }
    

Exemplo de manipuladores de eventosEvent handlers example

using UnityEngine.XR.WSA.Input;

void Start()
{
    InteractionManager.InteractionSourceDetected += InteractionManager_InteractionSourceDetected;
    InteractionManager.InteractionSourceLost += InteractionManager_InteractionSourceLost;
    InteractionManager.InteractionSourcePressed += InteractionManager_InteractionSourcePressed;
    InteractionManager.InteractionSourceReleased += InteractionManager_InteractionSourceReleased;
    InteractionManager.InteractionSourceUpdated += InteractionManager_InteractionSourceUpdated;
}

void OnDestroy()
{
    InteractionManager.InteractionSourceDetected -= InteractionManager_InteractionSourceDetected;
    InteractionManager.InteractionSourceLost -= InteractionManager_InteractionSourceLost;
    InteractionManager.InteractionSourcePressed -= InteractionManager_InteractionSourcePressed;
    InteractionManager.InteractionSourceReleased -= InteractionManager_InteractionSourceReleased;
    InteractionManager.InteractionSourceUpdated -= InteractionManager_InteractionSourceUpdated;
}

void InteractionManager_InteractionSourceDetected(InteractionSourceDetectedEventArgs args)
{
    // Source was detected
    // args.state has the current state of the source including id, position, kind, etc.
}

void InteractionManager_InteractionSourceLost(InteractionSourceLostEventArgs state)
{
    // Source was lost. This will be after a SourceDetected event and no other events for this
    // source id will occur until it is Detected again
    // args.state has the current state of the source including id, position, kind, etc.
}

void InteractionManager_InteractionSourcePressed(InteractionSourcePressedEventArgs state)
{
    // Source was pressed. This will be after the source was detected and before it is 
    // released or lost
    // args.state has the current state of the source including id, position, kind, etc.
}

void InteractionManager_InteractionSourceReleased(InteractionSourceReleasedEventArgs state)
{
    // Source was released. The source would have been detected and pressed before this point. 
    // This event will not fire if the source is lost
    // args.state has the current state of the source including id, position, kind, etc.
}

void InteractionManager_InteractionSourceUpdated(InteractionSourceUpdatedEventArgs state)
{
    // Source was updated. The source would have been detected before this point
    // args.state has the current state of the source including id, position, kind, etc.
}

APIs de gesto de composição de alto nível (GestureRecognizer)High-level composite gesture APIs (GestureRecognizer)

Namespace: UnityEngine. XR. WSA. InputNamespace: UnityEngine.XR.WSA.Input
Tipos: GestureRecognizer, GestureSettings, InteractionSourceKindTypes: GestureRecognizer, GestureSettings, InteractionSourceKind

Seu aplicativo também pode reconhecer gestos de composição de nível superior para fontes de entrada espaciais, toque, retenção, manipulação e gestos de navegação.Your app can also recognize higher-level composite gestures for spatial input sources, Tap, Hold, Manipulation and Navigation gestures. Você pode reconhecer esses gestos compostos entre os controladores de mão e de movimento usando o GestureRecognizer.You can recognize these composite gestures across both hands and motion controllers using the GestureRecognizer.

Cada evento de gesto no GestureRecognizer fornece o SourceKind para a entrada, bem como a cabeça de destino Ray no momento do evento.Each Gesture event on the GestureRecognizer provides the SourceKind for the input as well as the targeting head ray at the time of the event. Alguns eventos fornecem informações adicionais específicas do contexto.Some events provide additional context specific information.

Há apenas algumas etapas necessárias para capturar gestos usando um reconhecedor de gestos:There are only a few steps required to capture gestures using a Gesture Recognizer:

  1. Criar um novo reconhecedor de gestosCreate a new Gesture Recognizer
  2. Especificar quais gestos observarSpecify which gestures to watch for
  3. Assinar eventos para esses gestosSubscribe to events for those gestures
  4. Começar a capturar gestosStart capturing gestures

Criar um novo reconhecedor de gestosCreate a new Gesture Recognizer

Para usar o GestureRecognizer, você deve ter criado um GestureRecognizer:To use the GestureRecognizer, you must have created a GestureRecognizer:

GestureRecognizer recognizer = new GestureRecognizer();

Especificar quais gestos observarSpecify which gestures to watch for

Especifique em quais gestos você está interessado por meio de SetRecognizableGestures () :Specify which gestures you are interested in via SetRecognizableGestures():

recognizer.SetRecognizableGestures(GestureSettings.Tap | GestureSettings.Hold);

Assinar eventos para esses gestosSubscribe to events for those gestures

Assine eventos para os gestos nos quais você está interessado.Subscribe to events for the gestures you are interested in.

void Start()
{
    recognizer.Tapped += GestureRecognizer_Tapped;
    recognizer.HoldStarted += GestureRecognizer_HoldStarted;
    recognizer.HoldCompleted += GestureRecognizer_HoldCompleted;
    recognizer.HoldCanceled += GestureRecognizer_HoldCanceled;
}

Observação

Os gestos de navegação e manipulação são mutuamente exclusivos em uma instância de um GestureRecognizer.Navigation and Manipulation gestures are mutually exclusive on an instance of a GestureRecognizer.

Começar a capturar gestosStart capturing gestures

Por padrão, um GestureRecognizer não monitora a entrada até que StartCapturingGestures () seja chamado.By default, a GestureRecognizer does not monitor input until StartCapturingGestures() is called. É possível que um evento de gesto possa ser gerado após StopCapturingGestures () ser chamado se a entrada tiver sido executada antes do quadro em que StopCapturingGestures () foi processado.It is possible that a gesture event may be generated after StopCapturingGestures() is called if input was performed before the frame where StopCapturingGestures() was processed. O GestureRecognizer se lembrará se ele estava ativado ou desativado durante o quadro anterior no qual o gesto realmente ocorreu e, portanto, é confiável para iniciar e parar o monitoramento de gestos com base no direcionamento de olhar deste quadro.The GestureRecognizer will remember whether it was on or off during the previous frame in which the gesture actually occurred, and so it is reliable to start and stop gesture monitoring based on this frame's gaze targeting.

recognizer.StartCapturingGestures();

Parar a captura de gestosStop capturing gestures

Para interromper o reconhecimento de gesto:To stop gesture recognition:

recognizer.StopCapturingGestures();

Removendo um reconhecedor de gestoRemoving a gesture recognizer

Lembre-se de cancelar a assinatura de eventos assinados antes de destruir um objeto GestureRecognizer .Remember to unsubscribe from subscribed events before destroying a GestureRecognizer object.

void OnDestroy()
{
    recognizer.Tapped -= GestureRecognizer_Tapped;
    recognizer.HoldStarted -= GestureRecognizer_HoldStarted;
    recognizer.HoldCompleted -= GestureRecognizer_HoldCompleted;
    recognizer.HoldCanceled -= GestureRecognizer_HoldCanceled;
}

Renderizando o modelo de controlador de movimento no UnityRendering the motion controller model in Unity

modelo de controlador de movimento e teleportaçãoMotion Controller model and teleportation
Modelo de controlador de movimento e teleportaçãoMotion controller model and teleportation

Para renderizar os controladores de movimento em seu aplicativo que correspondam aos controladores físicos que os usuários estão mantendo e articulados conforme vários botões são pressionados, você pode usar o MotionController pré-fabricado no Kit de ferramentas da realidade misturada.To render motion controllers in your app that match the physical controllers your users are holding and articulate as various buttons are pressed, you can use the MotionController prefab in the Mixed Reality Toolkit. Esse pré-fabricado dinamicamente carrega o modelo de glTF correto em tempo de execução do driver do controlador de movimento instalado do sistema.This prefab dynamically loads the correct glTF model at runtime from the system's installed motion controller driver. É importante carregar esses modelos dinamicamente, em vez de importá-los manualmente no editor, para que seu aplicativo mostre modelos 3D fisicamente precisos para todos os controladores atuais e futuros que seus usuários possam ter.It's important to load these models dynamically rather than importing them manually in the editor, so that your app will show physically accurate 3D models for any current and future controllers your users may have.

  1. Siga as instruções de introdução para baixar o kit de ferramentas de realidade misturada e adicioná-lo ao seu projeto do Unity.Follow the Getting Started instructions to download the Mixed Reality Toolkit and add it to your Unity project.
  2. Se você substituiu a câmera pelo MixedRealityCameraParent pré-fabricado como parte das etapas introdução, está pronto!If you replaced your camera with the MixedRealityCameraParent prefab as part of the Getting Started steps, you're good to go! Esse pré-fabricado inclui a renderização do controlador de movimento.That prefab includes motion controller rendering. Caso contrário, adicione assets/HoloToolkit/Input/pré-fabricados/MotionControllers. pré-fabricado à sua cena no painel do projeto.Otherwise, add Assets/HoloToolkit/Input/Prefabs/MotionControllers.prefab into your scene from the Project pane. Você desejará adicionar esse pré-fabricado como um filho de qualquer objeto pai usado para mover a câmera quando o usuário estiver em sua cena, para que os controladores acompanhem o usuário.You'll want to add that prefab as a child of whatever parent object you use to move the camera around when the user teleports within your scene, so that the controllers come along with the user. Se seu aplicativo não envolver a teleportabilidade, basta adicionar o pré-fabricado na raiz da sua cena.If your app does not involve teleporting, just add the prefab at the root of your scene.

Lançando objetosThrowing objects

A geração de objetos na realidade virtual é um problema mais difícil e, em primeiro lugar, pode parecer.Throwing objects in virtual reality is a harder problem then it may at first seem. Assim como acontece com a maioria das interações com base fisicamente, quando o jogo atua de forma inesperada, ele é imediatamente óbvio e quebra imersão.As with most physically based interactions, when throwing in game acts in an unexpected way, it is immediately obvious and breaks immersion. Nós gastamos algum tempo pensando em como representar um comportamento de lançamento fisicamente correto e apresentamos algumas diretrizes, habilitadas por meio de atualizações em nossa plataforma, que gostaríamos de compartilhar com você.We have spent some time thinking deeply about how to represent a physically correct throwing behavior, and have come up with a few guidelines, enabled through updates to our platform, that we would like to share with you.

Você pode encontrar um exemplo de como é recomendável implementar o lançamento aqui.You can find an example of how we recommend to implement throwing here. Este exemplo segue estas quatro diretrizes:This sample follows these four guidelines:

  • Use a velocidade do controlador em vez da posição.Use the controller’s velocity instead of position. Na atualização de novembro do Windows, apresentamos uma alteração no comportamento no estado de controle posicional ' ' aproximado'.In the November update to Windows, we introduced a change in behavior when in the ''Approximate'' positional tracking state. Quando nesse estado, as informações de velocidade sobre o controlador continuarão sendo relatadas durante o período em que acreditamos que seja alta precisão, o que geralmente é maior que a posição permanece com alta precisão.When in this state, velocity information about the controller will continue to be reported for as long as we believe it is high accuracy, which is often longer than position remains high accuracy.

  • Incorpore a velocidade angular do controlador.Incorporate the angular velocity of the controller. Essa lógica está incluída no arquivo throwing.cs no método estático GetThrownObjectVelAngVel, dentro do pacote vinculado acima:This logic is all contained in the throwing.cs file in the GetThrownObjectVelAngVel static method, within the package linked above:

    1. À medida que a velocidade angular é conservada, o objeto gerado deve manter a mesma velocidade angular que tinha no momento do lançamento: objectAngularVelocity = throwingControllerAngularVelocity;As angular velocity is conserved, the thrown object must maintain the same angular velocity as it had at the moment of the throw: objectAngularVelocity = throwingControllerAngularVelocity;

    2. Como o centro da massa do objeto gerado provavelmente não está na origem da pose de alça, ele provavelmente tem uma velocidade diferente do controlador no quadro de referência do usuário.As the center of mass of the thrown object is likely not at the origin of the grip pose, it likely has a different velocity then that of the controller in the frame of reference of the user. A parte da velocidade do objeto que contribuiu dessa forma é a velocidade tangential instantânea do centro da massa do objeto gerado em relação à origem do controlador.The portion of the object’s velocity contributed in this way is the instantaneous tangential velocity of the center of mass of the thrown object around the controller origin. Essa velocidade de tangential é o produto cruzado da velocidade angular do controlador com o vetor que representa a distância entre a origem do controlador e o centro da massa do objeto gerado.This tangential velocity is the cross product of the angular velocity of the controller with the vector representing the distance between the controller origin and the center of mass of the thrown object.

      Vector3 radialVec = thrownObjectCenterOfMass - throwingControllerPos;
      Vector3 tangentialVelocity = Vector3.Cross(throwingControllerAngularVelocity, radialVec);
      
    3. A velocidade total do objeto gerado é, portanto, a soma da velocidade do controlador e dessa velocidade de tangential: objectVelocity = throwingControllerVelocity + tangentialVelocity;The total velocity of the thrown object is thus the sum of velocity of the controller and this tangential velocity: objectVelocity = throwingControllerVelocity + tangentialVelocity;

  • Preste muita atenção à hora em que aplicamos a velocidade.Pay close attention to the time at which we apply the velocity. Quando um botão é pressionado, pode levar até 20 ms para que esse evento seja emergido por meio do Bluetooth para o sistema operacional.When a button is pressed, it can take up to 20ms for that event to bubble up through Bluetooth to the operating system. Isso significa que, se você sondar uma alteração de estado do controlador de pressionado para não pressionado ou vice-versa, o controlador apresentará as informações que você obtém com ele, na verdade, estará à frente dessa alteração no estado.This means that if you poll for a controller state change from pressed to not pressed or vice versa, the controller pose information you get with it will actually be ahead of this change in state. Além disso, a pose do controlador apresentada por nossa API de sondagem está prevista para refletir uma causa provável no momento em que o quadro será exibido, o que poderia ser mais 20 ms no futuro.Further, the controller pose presented by our polling API is forward predicted to reflect a likely pose at the time the frame will be displayed which could be more then 20ms in the future. Isso é bom para renderizar objetos mantidos, mas aumenta nosso problema de tempo para direcionar o objeto à medida que calculamos a trajetória para o momento em que o usuário lançou o seu lançamento.This is good for rendering held objects, but compounds our time problem for targeting the object as we calculate the trajectory for the moment the user released their throw. Felizmente, com a atualização de novembro, quando um evento do Unity como InteractionSourcePressed ou InteractionSourceReleased é enviado, o estado inclui os dados históricos de back quando o botão foi realmente pressionado ou liberado.Fortunately, with the November update, when a Unity event like InteractionSourcePressed or InteractionSourceReleased is sent, the state includes the historical pose data from back when the button was actually pressed or released. Para obter a renderização de controlador e o direcionamento de controlador mais precisos durante as jogadas, você deve usar corretamente a sondagem e o evento, conforme apropriado:To get the most accurate controller rendering and controller targeting during throws, you must correctly use polling and eventing, as appropriate:

  • Use a pose de alça.Use the grip pose. A velocidade angular e a velocidade são relatadas em relação à pose de alça, não à pose de ponteiro.Angular velocity and velocity are reported relative to the grip pose, not pointer pose.

O lançamento continuará a melhorar com futuras atualizações do Windows e você poderá esperar mais informações sobre ela aqui.Throwing will continue to improve with future Windows updates, and you can expect to find more information on it here.

Controladores de gesto e movimento no MRTK v2Gesture and Motion Controllers in MRTK v2

Você pode acessar o gesto e o controlador de movimento do Gerenciador de entrada.You can access gesture and motion controller from the input Manager.

Siga junto com os tutoriaisFollow along with tutorials

Os tutoriais passo a passo, com exemplos de personalização mais detalhados, estão disponíveis na Academia de realidade misturada:Step-by-step tutorials, with more detailed customization examples, are available in the Mixed Reality Academy:

Sr Input 213-controlador de movimentoMR Input 213 - Motion controller
Entrada MR 213-controlador de movimentoMR Input 213 - Motion controller

Consulte tambémSee also