Desenvolvendo em Realidade Virtual: Tutorial de HTC Vive no Unity

Tutorial de HTC Vive no Unity

  • (95% traduzido para Português)

link original: https://www.raywenderlich.com/149239/htc-vive-tutorial-unity

O HTC Vive é um headset de realidade virtual desenvolvido pela HTC e Valve Corporation. Ele permite que você entre em um mundo virtual e experimente-o por si mesmo, ao invés de usar um avatar na tela.

Se você é um desenvolver Unity, fazer jogos de VR com o HTC Vive é fácil. Nesse tutorial de HTC Vive para Unity você aprenderá como integrar o HTC Vive em seus próprios jogos de Unity. Especificamente, você aprenderá à:

  • Fazer download do SteamVR e configurá-lo;

  • Lidar com as entradas dos controles;

  • Interagir com a física dos objetos em VR;

  • Fazer um laser pointer;

  • Se teletransportar por uma área.

Começando

Antes de começar esse tutorial, tenha certeza de que você possui os seguintes itens:

Tenha certeza que o HTC está ligado e conectado!

Faça download do projeto inicial. Dê unzip e abra-o no Unity. Dê uma olhada nos folders no “Project window”:

  • Materials: materiais usados para a cena (scene), que nesse caso inclui as bolas azuis;
  • Models: todos os models;
  • Physics Materials: materiais da física das bolas;
  • Scenes: a scene do jogo está aqui;
  • Scripts: todos os scripts;
  • Textures: a textura compartilhada por todos os objetos na scene.

Teste o jogo, aperte o botão play.

Até agora nada de muito especial aconteceu porque ainda não foi instalado o SteamVR ao projeto para que ele possa conectar o HTC ao Unity.

Antes de configurar o SteamVR, expanda a aba Level no menu hierárquico (Hierarchy) e selecione Floor. No menu Inspector, selecione o Layer para CanTeleport. Faça-o mesmo se já estiver selecionado.

Configurando o SteamVR

O SteamVR SDK é uma biblioteca oficial feia pela Valve que torna mais fácil o desenvolvimento para o HTC Vive. Atualmente é gratuita no Asset Store e pode ser usado para o Oculus Rift, além, claro, do HTC Vive.

Abra o Asset Store selecionando o menu Window na barra superior:

AssetStoreSelect

Uma vez carregada, escreva SteamVR na barra de pesquisa que fica no topo e dê enter. Procure por:

SteamVRAssetStore

Clique em download e espere um pouco. Ao acabar você verá uma janela para que sejam importados alguns packages. Importe-os.

ImportPackage

Ao término das importações você poderá ver a seguinte mensagem:

ApiUpdate

Clique em I Made a Backup. Você deverá ver algo do tipo depois:

SteamVrSettings

Clique em Accept All. Feche a Asset Store e volte para a Scene View. Você agora terá um folder chamado SteamVR no seu Project Window:

SteamVRFolder

Abra esse folder e olhe para os folders internos. Abra o folder Prefabs:

SteamVrPrefabsFolder

Selecione ambos [CameraRig] e [SteamVR] e arraste-os para o Hierarchy Window:

DragVRPrefabs

SteamVR lida com algumas coisas importantes. Ele pausa o jogo automaticamente quando o player abra o menu e sincroniza a taxa de atualização física com o sistema de renderização. Ele também lida com o alisamento do movimento em uma experiência room-scale.

Reveja as propriedades no Painel Inspector:

SteamVrComponent

CameraRig é mais interessante ainda porque controla o headset Vive e os controles. Selecione-o no painel Inspector e defina sua Position em (X:0, Y:0, Z:-1.1).

ZPosCameraRig

Delete a Main Camera do menu Hierarchy porque ela causará interferência no CameraRig e sua câmera embutida.

Ligue os controles e inicie o jogo. Pegue os dois controles e mexa-os. Você perceberá que agora você pode ver os controles virtualmente pela Scene.

ControllerMoveScene

Uma vez detectado os controles, serão criado suas versões virtuais. Também são criados dois Controller, que podem ser achados expandindo CameraRig no menu Hierarchy.

ControllersHierarchy

Agora, enquanto ainda está sendo testada a Scene, selecione Camera(eye) no menu Hierarchy e cuidadosamente remova seu headset. Mova-o e o rotacione e veja a Scene View:

HeadMove

A câmera está ligada ao Headset e precisamente detecta todo movimento.

Com a Camera(eye) ainda selecionada, adicione um componente Steam VR_Update Poses a ela. Isso concertará um bug que acontece a partir do Unity 5.6 onde os controles não são detectados.

Agora coloque o headset, pegue os controles e olhe e ande em volta da Scene.

Você poderá se desapontar caso tente interagir com os objetos – pois nada acontece. Para adicionar funcionalidades além do rastreamento do movimento em si, você precisará fazer alguns scripts.

Lidando com o Controle

Pegue um dos controles e dê uma boa olhada. Cada controle tem as seguintes entradas:

ViveControllerButtons

O Touchpad atua como um botão e um joystick. O controle também adquire velocidade e velocidade de rotação na medida em que você o move e o rotaciona. Isso será provado especialmente quando você interagir com objetos.

Crie um novo Script em C# no folder Scripts, nomeie ViveControllerInputTest e abra-o no seu editor de códigos favorito.

Remova o método Start() e adicione o seguinte acima do método Update():

// 1
private SteamVR_TrackedObject trackedObj;
// 2
private SteamVR_Controller.Device Controller
{
    get { return SteamVR_Controller.Input((int)trackedObj.index); }
}

Aqui, você fez:

  1. Uma referência ao Objeto sendo detectado. Nesse caso, um controle.
  2. Uma propriedade de um dispositivo para providenciar acesso fácil ao controle. Ele usa o índice do objeto sendo detectado para retornar a entrada do controle (the controller’s input).

Tanto o headset quanto os controles são tracked objects – seus movimentos e rotação no mundo real são rastreados pelas base stations do HTC Vive e são enviados ao mundo virtual.

Agora, adicione o seguinte método acima do método Update():

void Awake()
{
    trackedObj = GetComponent<SteamVR_TrackedObject>();
}

Uma vez carregado o Script, o trackObj pega uma referência do componente SteamVR_TrackedObject que está anexado aos controles:

ControllerTrackedObj

Agora que você tem acesso ao controle, você pode facilmente ler suas entradas. Adicione o seguinte código dentro do método Update():

// 1
if (Controller.GetAxis() != Vector2.zero)
{
    Debug.Log(gameObject.name + Controller.GetAxis());
}

// 2
if (Controller.GetHairTriggerDown())
{
    Debug.Log(gameObject.name + " Trigger Press");
}

// 3
if (Controller.GetHairTriggerUp())
{
    Debug.Log(gameObject.name + " Trigger Release");
}

// 4
if (Controller.GetPressDown(SteamVR_Controller.ButtonMask.Grip))
{
    Debug.Log(gameObject.name + " Grip Press");
}

// 5
if (Controller.GetPressUp(SteamVR_Controller.ButtonMask.Grip))
{
    Debug.Log(gameObject.name + " Grip Release");
}

O código acima cobre grande parte das formas que você pode acessar as entradas do jogador enquanto elas são feitas no mundo virtual. Ele escreve o nome do GameObject no console para facilmente diferenciar o controle esquerdo e direito. Aqui está um detalhamento do código acima:

  1. Pega a posição do dedo quando ele está no touchpad e escreve isso no Console;
  2. Quando você aperta o hair trigger, essa linha avisa no console. O hair trigger possui outros métodos especiais, como: GetHairTrigger(), GetHairTriggerDown() e GetHairTriggerUp();
  3. Se você soltar o Hair Trigger, essa instrução escreverá isso no console.
  4. Se você pressionar um grip button, essa instrução o escreverá no console. Usar o GetPressDown() é a maneira padrão de checar se um button foi pressionado;
  5. Quando você solta um dos grip buttons, essa instrução escreverá isso no Console. Usar o método GetPressUp() é o modo padrão de verificar se um button foi solto.

O Script agora está pronto para ser testado. Salve-o e retorne para o Unity.

Selecione ambos os controles no menu Hierarchy e adicione o componente ViveControllerInputTest a eles.

AddControllerTestComponents

Rode o jogo novamente, pegue os controles e olhe para o Console.

ConsoleDebugLocation

Pressione os botões, aperte o trigger, mova seu dedo ao redor dos touchpads. Você verá algo como isso (pois cada ação está sendo registrada):

ControllerInputDebug

Isso é o básico de input config. Agora você tem o poder de manipular o mundo virtual na ponta dos seus dedos – literalmente!

Os controles e a Física dos Objetos

A realidade virtual proporciona aos usuários muitas oportunidades que não são possíveis no “mundo real”, incluindo pegar objetos e examiná-los de diversos ângulos, e depois eliminá-los sem ter que se preocupar com algo. O HTC Vive pemite que você crie essa experiência virtual empregando apenas alguns colisores e fazendo um pouco de código.

Selecione ambos os controles no menu Hierarchy e adicione um componente Rigidbody a eles. (Add Component > Physics > Rigidbody).

Cheque se Is Kinematic está selecionado e desmarque o Use Gravity.

RigidBodySetup

Adicione um Box Collider (add Component > Physics > Box Collider) à ambos os controles e selecione o campo Is Trigger.

O collider padrão é imenso, então você precisará modificar seu tamanho. Defina Center para (X:0, Y:-0.04, Z:0.02) e Size para (X:0.14, Y:0.07, Z:0.05). Esses valores são precisos porque mesmo um centésimo de unidade afeta o colisor.

ControllerBoxCollider

Rode o jogo novamente. Selecione um controle no menu Hierarchy e pegue o controle real. Olhe para a Scene View e foque no controle que você está segurando (pressione F). O colisor fica na parte superior do controle, que é a parte usada para pegar objetos.

ControllerColliderScene

Sem um Script, esse colisor não é mais que um cubo qualquer. Crie uma novo Script C# no folder Scripts e nomeei-o para ControllerGrabObject e abra-o.

Remova o método Start() e adicione esse código em seu lugar:

private SteamVR_TrackedObject trackedObj;

private SteamVR_Controller.Device Controller
{
    get { return SteamVR_Controller.Input((int)trackedObj.index); }
}

void Awake()
{
    trackedObj = GetComponent();
}


Esse é o mesmo código que você usou no teste de Input. Ele ‘pegará’ o controle e armazenará uma referência a ele para usar mais tarde.

Adicione essas variáveis logo abaixo de trackedObj:

// 1
private GameObject collidingObject; 
// 2
private GameObject objectInHand;

Cada variável tem um propósito:

  1. Armazenar o GameObject que está sendo colidido, assim você terá a habilidade de pegar (segurar) esse objeto.
  2. Serve como uma referência ao GameObject que o player está atualmente segurando.

Adicione isso por baixo do método Awake():

private void SetCollidingObject(Collider col)
{
    // 1
    if (collidingObject || !col.GetComponent())
    {
        return;
    }
    // 2
    collidingObject = col.gameObject;
}

Esse método aceita um colisor como um parâmetro e usa seu GameObject como o collidingObject para agarrar e soltar. Além disso:

  1. Não faz do GameObject um potencial alvo de captura se o jogador já estiver segurando algo ou se o objeto não tiver Rigidbody.
  2. Assina o objeto como um potencial alvo de captura.

Agora adicione esses seguintes métodos de disparo (Trigger Method):

// 1
public void OnTriggerEnter(Collider other)
{
    SetCollidingObject(other);
}

// 2
public void OnTriggerStay(Collider other)
{
    SetCollidingObject(other);
}

// 3
public void OnTriggerExit(Collider other)
{
    if (!collidingObject)
    {
        return;
    }

    collidingObject = null;
}

These methods handle what should happen when the trigger collider enters and exits another collider.

  1. When the trigger collider enters another, this sets up the other collider as a potential grab target.
  2. Similar to section one (// 1), but different because it ensures that the target is set when the player holds a controller over an object for a while. Without this, the collision may fail or become buggy.
  3. When the collider exits an object, abandoning an ungrabbed target, this code removes its target by setting it to nul

– RayWenderlich.com

Agora você adicionará o código para pegar um objeto:

private void GrabObject()
{
    // 1
    objectInHand = collidingObject;
    collidingObject = null;
    // 2
    var joint = AddFixedJoint();
    joint.connectedBody = objectInHand.GetComponent();
}

// 3
private FixedJoint AddFixedJoint()
{
    FixedJoint fx = gameObject.AddComponent();
    fx.breakForce = 20000;
    fx.breakTorque = 20000;
    return fx;
}

Aqui você:

  1. Remove o GameObject da variável collidingObject.
  2. Adiciona um novo ponto que conecta o controle ao objeto usando o método AddFixedJoint().
  3.  Faz um novo ponto conector (joint) fixo, adiciona ao controle e então configura-o.

O que pode ser segurado pode ser solto. Esse próximo bloco lida com a soltura (releasing) de objetos:

private void ReleaseObject()
{
    // 1
    if (GetComponent())
    {
        // 2
        GetComponent().connectedBody = null;
        Destroy(GetComponent());
        // 3
        objectInHand.GetComponent().velocity = Controller.velocity;
        objectInHand.GetComponent().angularVelocity = Controller.angularVelocity;
    }
    // 4
    objectInHand = null;
}

This code removes the grabbed object’s fixed joint and controls its speed and rotation when the player tosses it away. The controller’s velocity is key here. Without using it, the discarded object would drop straight down no matter how perfect your throw might be. Trust me, it doesn’t feel right. :]

Section-by-section breakdown:

  1. Make sure there’s a fixed joint attached to the controller.
  2. Remove the connection to the object held by the joint and destroy the joint.
  3. Add the speed and rotation of the controller when the player releases the object, so the result is a realistic arc.
  4. Remove the reference to the formerly attached object.

– RayWenderlich.com

Finalmente, adicione esse código dentro do método Update() para lidar com a entrada do controle:

// 1
if (Controller.GetHairTriggerDown())
{
    if (collidingObject)
    {
        GrabObject();
    }
}

// 2
if (Controller.GetHairTriggerUp())
{
    if (objectInHand)
    {
        ReleaseObject();
    }
}
  1. Quando o player apertar o trigger e tiver um alvo em potencial, o alvo será pego.
  2. Se o player soltar o trigger e tiver um objeto anexado ao controle, ele o soltará.

Salve o Script e retorne ao editor.

Selecione ambos os controles no menu Hierarchy e arraste seu novo Script para dentro deles (por meio do menu Inspector).

DragGrabScript

Hora de se divertir! Pegue seus controles, inicie o jogo e coloque seu headset.

GrabbingPlay

Fazendo um Laser Pointer

Um laser pointer é muito útil em um mundo de realidade virtual, e por muitas razões. Você pode usá-lo para estourar balões virtuais, apontar armas de forma mais adequada ou detonar cozinhas digitais.

Fazer um é simples. Você apenas precisará de um cubo e outro Script. Comece criando um novo Cube no menu Hierarchy (Create > 3D Object > Cube).

CreateCube

Nomeei-o para Laser, defina sua posição para (X:0, Y:5, Z:0), mude sua escala para (X:0.005, Y:0.005, Z:0) e remova o componente Box Collider.

FloatLaser

Lasers não devem ter sombra, e eles são sempre da mesma cor. Então, você poderá obter esse efeito usando um material. Crise um novo material no folder Materials e nomeei-o para Laser, e então mude seu shader para Unlit/Color e defina Main Color para vermelho.

LaserMat

Adicione o material ao laser arrastando-o do seu folder até o Scene View:

DragLaserMat

Finalmente, arraste o Laser para o folder Prefabs e delete o original do menu Hierarchy.

DragLaserToPrefabs

Agora faça um novo Script C# chamado LaserPointer no folder Scripts e abra-o. Adicione esse código:

private SteamVR_TrackedObject trackedObj;

private SteamVR_Controller.Device Controller
{
    get { return SteamVR_Controller.Input((int)trackedObj.index); }
}

void Awake()
{
    trackedObj = GetComponent<SteamVR_TrackedObject>();
}

Adicione essas variáveis abaixo de trackedObj:

// 1
public GameObject laserPrefab;
// 2
private GameObject laser;
// 3
private Transform laserTransform;
// 4
private Vector3 hitPoint;
  1. Essa é uma referência ao prefab do Laser.
  2. laser armazena uma referência à uma instância do laser.
  3. O componente transform está armazenado para facilitar seu uso.
  4. Essa é a posição onde o laser está sendo lançado.

Adicione esse método para exibir o laser:

private void ShowLaser(RaycastHit hit)
{
    // 1
    laser.SetActive(true);
    // 2
    laserTransform.position = Vector3.Lerp(trackedObj.transform.position, hitPoint, .5f);
    // 3
    laserTransform.LookAt(hitPoint); 
    // 4
    laserTransform.localScale = new Vector3(laserTransform.localScale.x, laserTransform.localScale.y,
        hit.distance);
}

Esse método leva um RaycastHit como um parâmetro porque ele contém a posição do ponto atingido e a distância que ele percorreu.

  1. Exibe o laser.
  2. Posiciona o laser entre o controle e o ponto onde o raycast atinge.
  3. Aponta o laser para a posição onde raycast atinge.
  4. Dimensiona o laser para que ele se encaixe perfeitamente entre as duas posições.

Adicione o seguinte código dentro do método Update() para fazer uso da entrada do player:

// 1
if (Controller.GetPress(SteamVR_Controller.ButtonMask.Touchpad))
{
    RaycastHit hit;

    // 2
    if (Physics.Raycast(trackedObj.transform.position, transform.forward, out hit, 100))
    {
        hitPoint = hit.point;
        ShowLaser(hit);
    }
}
else // 3
{
    laser.SetActive(false);
}
  1. Se o touchpad for pressionado…
  2. Atire um raio do controle. Se atingir algo, armazene o ponto onde atingiu e mostre o laser.
  3. Oculte o laser quando o player soltar o touchpad.

Adicione o seguinte código dentro do método vazio Start():

// 1
laser = Instantiate(laserPrefab);
// 2
laserTransform = laser.transform;
  1. Gera um novo laser e salva uma referência a ele em laser.
  2. Armazena o transform component do laser.

Salve esse Script e retorne ao Unity. Selecione ambos os controles no menu Hierarchy e arraste o script laser até eles por meio do menu Inspector.

Draglaser

Agora arraste o prefab Laser do folder Prefabs para tenho do slot Laser no Inspector.

DragLaserToField

Salve seu projeto e teste-o. Pegue os controles, coloque um headset e tente apertar o touchpad. Você verá um laser agora:
ShootLaser

Agora remova o componente input test dos controles. Eles serviam apenas para teste, e não são bons para a performance do jogo.

Movimentando-se

Movimentar-se em VR não é uma coisa simples. Uma maneira eficiente de fazê-lo é por meio de teleporte.

É melhor para a percepção do jogador uma mudança súbita de posição do que uma mudança gradual. Mudanças sutis geralmente prejudicam a sensação de equilíbrio e velocidade.

Você usará um reticle que pode ser encontrado no folder Prefabs:

Reticle

Para usar esse reticle você incrementará o Script LaserPoint, então abra-o e adicione essas variáveis ao início da classe:

// 1
public Transform cameraRigTransform; 
// 2
public GameObject teleportReticlePrefab;
// 3
private GameObject reticle;
// 4
private Transform teleportReticleTransform; 
// 5
public Transform headTransform; 
// 6
public Vector3 teleportReticleOffset; 
// 7
public LayerMask teleportMask; 
// 8
private bool shouldTeleport;

Cada variável desempenha um papel:

  1. É o transform da CameraRig.
  2. Armazena uma referência ao Prefab teleport reticle.
  3. Uma referência à instância do reticle.
  4. Armazena uma referencia ao transform do teleport reticle para facilitar o uso.
  5. Armazena uma referência à cabeça do player (a câmera).
  6. É o reticle offset.
  7. É um layer para filtrar as áreas onde teleporte estão habilitados.
  8. É definido para true quando um local válido para teleporte é encontrado.

No método Update(), substitua essa linha:

if (Physics.Raycast(trackedObj.transform.position, transform.forward, out hit, 100))

Por essa:

if (Physics.Raycast(trackedObj.transform.position, transform.forward, out hit, 100, teleportMask))

Também no método Update(), adicione esse código abaixo à chamada de ShowLaser():

// 1
reticle.SetActive(true);
// 2
teleportReticleTransform.position = hitPoint + teleportReticleOffset;
// 3
shouldTeleport = true;

Ainda no método Update(), ache laser.SetActive(false); e adicione a seguinte linha abaixo dele:

reticle.SetActive(false);

Adicione o seguinte método para lidar com o ato da teleportação em si:

private void Teleport()
{
    // 1
    shouldTeleport = false;
    // 2
    reticle.SetActive(false);
    // 3
    Vector3 difference = cameraRigTransform.position - headTransform.position;
    // 4
    difference.y = 0;
    // 5
    cameraRigTransform.position = hitPoint + difference;
}

TeleportDifference

Como você pode ver, a “diferença” desempenha um papel crucial ao posicionar com precisão a camera rig e colocar o jogador exatamente onde ele espera.

Adicione isso ao fim do método Update():

if (Controller.GetPressUp(SteamVR_Controller.ButtonMask.Touchpad) && shouldTeleport)
{
    Teleport();
}

Isso teletransporta o player se o touchpad for solto e está em um local onde o teleporte é válido. Finalmente, adicione esse código ao método Start():

 

É isso! Salve seu Script e retorne ao Unity.

Selecione ambos os controles no menu Hierarchy e veja esses novos campos:

NewFields

Arraste [CameraRig] para o slot Camera Rig Transform, arraste TeleportReticle do folder Prefabs para o slot Teleport Reticle Transform e  arraste a Camera (Head) para o slot Head Transform.

DraggingObjects

Agora defina o Teleport Reticle Offset para (X:0, Y: 0.05, Z:0) e defina o Teleport Mask para Can Teleport.

Agora jogue! Está tudo pronto e funcionando!

 

 

 

Deixe um comentário