Tutorial VR para principiantes parte 1¶
Introducción¶

Este tutorial te mostrará cómo crear un proyecto de juego VR básico en Godot.
Tenga en cuenta, una de las cosas más importantes al hacer el contenido de la RV es conseguir la escala de tus recursos correctamente! Puede requerir mucha práctica e iteraciones para hacerlo bien, pero hay algunas cosas que puedes hacer para hacerlo más fácil:
En la RV, una unidad se considera típicamente 1 metro. Si diseñas tus activos en torno a ese estándar, puedes ahorrarte muchos dolores de cabeza.
En tu programa de modelado 3D, vea si hay una forma de medir y usar las distancias del mundo real. En Blender, puedes usar el complemento MeasureIt; en Maya, puedes usar la Herramienta de Medición.
You can make rough models using a tool like Google Blocks, and then refine in another 3D modelling program.
¡Pruebe a menudo, ya que los recursos pueden verse dramáticamente diferentes en la RV que en una pantalla plana!
A lo largo del curso de este tutorial, cubriremos:
Cómo decirle a Godot que corra en RV.
Cómo hacer un sistema de locomoción de teletransportación que utilice los controladores VR.
Cómo hacer un sistema de locomoción de movimiento artificial que utiliza los controladores VR.
Cómo hacer un sistema basado en RigidBody que permita agarrar, soltar y arrojar nodos RigidBody usando controladores VR.
Cómo crear objetos destruibles simples.
Cómo hacer un sistema basado en objetos :ref:`RigidBody <class_RigidBody>`que puedan destruir blancos.
Truco
While this tutorial can be completed by beginners, it is highly advised to complete Tu primer juego 2D, if you are new to Godot and/or game development.
Se requiere cierta experiencia en la creación de juegos 3D antes de pasar por esta serie de tutoriales. Este tutorial asume que tienes experiencia con el editor de Godot, GDScript, y el desarrollo básico de juegos 3D. Se requieren unos auriculares listos para OpenVR y dos controladores listos para OpenVR.
Este tutorial fue escrito y probado usando un auricular y controladores de Realidad Mixta de Windows. Este proyecto también ha sido probado en el HTC Vive. Es posible que se requieran ajustes de código para otros auriculares de RV, como el Oculus Rift.
The Godot project for this tutorial is found on the OpenVR GitHub repository. The starter assets for this tutorial can be found in the releases section on the GitHub repository. The starter assets contain some 3D models, sounds, scripts, and scenes that are configured for this tutorial.
Nota
Créditos por los recursos proporcionados:
The sky panorama was created by CGTuts.
La fuente utilizada es Titillium-Regular
La fuente está licenciada bajo la SIL Open Font License, Version 1.1
The audio used are from several different sources, all downloaded from the Sonniss #GameAudioGDC Bundle (License PDF)
Las carpetas donde se almacenan los archivos de audio tienen el mismo nombre que las carpetas del paquete de audio de Sonniss.
The OpenVR addon was created by Bastiaan Olij and is released under the MIT license. It can be found both on the Godot Asset Library and on GitHub. 3rd party code and libraries used in the OpenVR addon may be under a different license.
The initial project, 3D models, and scripts were created by TwistedTwigleg and is released under the MIT license.
Truco
You can find the finished project on the OpenVR GitHub repository.
Preparando todo¶
If you have not already, go to the OpenVR GitHub repository and download the "Starter Assets" file from the releases. Once you have the starter assets downloaded, open up the project in Godot.
Nota
Los recursos ofrecidos no son requeridos para usar los scripts provistos en este tutorial. Estos recursos incluyen escenas prefabricadas y scripts que se usarán a lo largo del tutorial.
Cuando el proyecto se cargue por primera vez, se abrirá la escena de Game.tscn. Esta será la escena principal usada para el tutorial. Incluye varios nodos y escenas ya colocadas a lo largo de la escena, algo de música de fondo, y varios nodos relacionados con la interfaz gráfica MeshInstance.
Los nodos relacionados con el GUI MeshInstance ya tienen scripts adjuntos. Estos scripts fijarán la textura de un nodo Viewport a la textura del albedo del material del nodo MeshInstance. Esto se utiliza para mostrar el texto dentro del proyecto VR. Siéntete libre de echar un vistazo al script, GUI.gd
, si quieres. No vamos a repasar cómo usar los nodos Viewport para mostrar la interfaz de usuario en los nodos MeshInstance de este tutorial.
Si le interesa cómo usar los nodos Viewport para mostrar la interfaz de usuario en los nodos MeshInstance, vea el tutorial Usar un Viewport como textura. Cubre cómo usar un Viewport como una textura de renderizado, junto con cómo aplicar esa textura en un nodo MeshInstance.
Antes de comenzar con el tutorial, nos tomaremos un momento paraver cómo funcionan los nodos utilizados para VR.
El nodo ARVROrigin es el punto central del sistema de seguimiento de la RV. La posición del nodo ARVROrigin es la posición que el sistema de RV considera como el punto 'central' en el suelo. El ARVROrigin tiene una propiedad de escala mundial que afecta al tamaño del usuario dentro de la escena de RV. Para este tutorial, está configurado en 1.4, ya que el mundo era originalmente un poco más grande. Como se mencionó anteriormente, mantener la escala relativamente consistente es importante en la RV.
El ARVRCamera es el auricular del jugador y la vista en la escena. El ARVRCamera está desplazado en el eje Y por la altura del usuario del VR, lo que será importante más tarde cuando añadamos la locomoción de teletransportación. Si el sistema de RV soporta el rastreo de habitaciones, entonces el ARVRCamera se moverá a medida que el jugador se mueva. Esto significa que el ARVRCamera no está garantizado que esté en la misma posición que el nodo ARVROrigin.
El nodo ARVRController representa un controlador de RV. El ARVRController seguirá la posición y rotación del controlador VR relativo al nodo ARVROrigin. Toda la entrada de los controladores VR pasa por el nodo ARVRController. Un nodo ARVRController con un ID
de 1
representa el controlador VR izquierdo, mientras que un ARVRController controlador con un ID
de 2
representa el controlador VR derecho.
Para resumir:
El nodo ARVROrigin es el centro del sistema de seguimiento de la RV y está situado en el suelo.
El ARVRCamera es el auricular de RV del jugador y la vista en la escena.
El nodo ARVRCamera está desplazado en el eje Y por la altura del usuario.
Si el sistema de RV soporta el rastreo de habitaciones, entonces el nodo ARVRCamera puede ser desplazado en los ejes X y Z a medida que el jugador se mueve.
Los nodos ARVRController representan los controladores de RV y manejan todas las entradas de los controladores de RV.
Iniciando con la VR¶
Ahora que hemos repasado los nodos VR, empecemos a trabajar en el proyecto. Mientras que en Game.tscn
, selecciona el nodo Game
y haz un nuevo script llamado Game.gd
. En el archivo Game.gd
, añade el siguiente código:
extends Spatial
func _ready():
var VR = ARVRServer.find_interface("OpenVR")
if VR and VR.initialize():
get_viewport().arvr = true
OS.vsync_enabled = false
Engine.target_fps = 90
# Also, the physics FPS in the project settings is also 90 FPS. This makes the physics
# run at the same frame rate as the display, which makes things look smoother in VR!
using Godot;
using System;
public class Game : Spatial
{
public override void _Ready()
{
var vr = ARVRServer.FindInterface("OpenVR");
if (vr != null && vr.Initialize())
{
GetViewport().Arvr = true;
OS.VsyncEnabled = false;
Engine.TargetFps = 90;
// Also, the physics FPS in the project settings is also 90 FPS. This makes the physics
// run at the same frame rate as the display, which makes things look smoother in VR!
}
}
}
Veamos lo que hace este código.
En la función _ready
, primero obtenemos la interfaz OpenVR VR usando la función find_interface
en el ARVRServer y la asignamos a una variable llamada VR`. Si ARVRServer encuentra una interfaz con el nombre OpenVR, la devolverá, de lo contrario devolverá null
.
Nota
The OpenVR VR interface is not included with Godot by default. You will need to download the OpenVR asset from the Asset Library or GitHub.
El código combina entonces dos condicionales, uno para comprobar si la variable VR NO es nula (if VR
) y otro llama a la función de inicialización, que devuelve un booleano basado en si la interfaz OpenVR fue capaz de inicializar o no. Si ambos condicionales devuelven true, entonces podemos convertir el Godot principal Viewport en un viewport ARVR.
Si la interfaz de RV se inicializa con éxito, entonces obtenemos la raíz Viewport y ponemos la propiedad arvr como true
. Esto le dirá a Godot que use la interfaz ARVR inicializada para manejar la pantalla Viewport.
Finalmente, desactivamos VSync para que el monitor de la computadora no limite los fotogramas por segundo (FPS). Después de esto le decimos a Godot que renderice a 90
fotogramas por segundo, que es el estándar para la mayoría de los auriculares VR. Sin desactivar el VSync, el monitor normal de la computadora puede limitar la velocidad de fotogramas del auricular VR a la velocidad de fotogramas del monitor de la computadora.
Nota
En la configuración del proyecto, en la pestaña Physics->Common
, el FPS de física se ha establecido en "90". Esto hace que el motor de física funcione a la misma velocidad de fotograma que la pantalla de RV, lo que hace que las reacciones físicas se vean más suaves cuando se está en RV.
¡Eso es todo lo que tenemos que hacer para que Godot lance OpenVR dentro del proyecto! Adelante, inténtalo si quieres. Asumiendo que todo funcione, podrás mirar alrededor del mundo. Si tienes unos auriculares de RV con rastreo de habitaciones, entonces podrás moverte por la escena dentro de los límites del rastreo de habitaciones.
Creando los controladores¶

Ahora mismo todo lo que el usuario de RV puede hacer es quedarse parado, lo cual no es realmente lo que buscamos a menos que estemos trabajando en una película de RV. Escribamos el código para los controladores de RV. Vamos a escribir todo el código para los controladores de RV de una sola vez, por lo que el código es bastante largo. Dicho esto, una vez que hayamos terminado, podrás teletransportarte por la escena, moverte artificialmente usando el touchpad/joystick del controlador VR, y ser capaz de recoger, soltar y lanzar RigidBody nodos basados en la RV.
Primero necesitamos abrir la escena usada para los controladores VR. Left_Controller.tscn
o Right_Controller.tscn
. Repasemos brevemente cómo se configura la escena.
Cómo se configura la escena del controlador VR¶
En ambas escenas el nodo raíz es un nodo ARVRController. La única diferencia es que la escena Left_Controller
tiene la propiedad Controller Id
puesta a 1
mientras que la Right_Controller
tiene la propiedad Controller Id
puesta a 2
.
Nota
El ARVRServer intenta usar estas dos identificaciones para los controladores VR izquierdo y derecho. Para los sistemas de RV que soportan más de 2 controladores/objetos rastreados, estos IDs pueden necesitar un ajuste.
El siguiente es el nodo Hand
MeshInstance. Este nodo se usa para mostrar la malla de mano que se usará cuando el controlador de VR no esté sosteniendo un nodo RigidBody. La mano en la escena Left_Controller
es una mano izquierda, mientras que la mano en la escena Right_Controller
es una mano derecha.
El nodo llamado Raycast
es un nodo Raycast que se usa para apuntar a dónde teletransportarse cuando el controlador VR se teletransporta. La longitud del Raycast se establece en -16
del eje Y y se gira para que apunte con el dedo índice de la mano. El nodo Raycast
tiene un único nodo hijo, Mesh
, que es un "ref:MeshInstance <class_MeshInstance>. Esto se usa para mostrar visualmente a dónde apunta la teletransportación Raycast.
El nodo llamado Area
es un nodo Area que se usará para agarrar RigidBody nodos basados en el modo de agarre del controlador VR cuando el modo de agarre del controlador VR esté configurado en AREA
. El nodo Area
tiene un nodo hijo único, CollisionShape
, que define una esfera CollisionShape. Cuando el controlador de VR no tiene ningún objeto y se pulsa el botón de agarre, el primer nodo basado en RigidBody dentro del nodo Area
será recogido.
Lo siguiente es un nodo Position3D llamado Grab_Pos
. Esto se usa para definir la posición que seguirán los nodos RigidBody que son mantenidos por el controlador VR.
Un gran nodo Area llamado SleepArea
se usa para deshabilitar el dormir para cualquier nodo de RigidBody dentro de su ColisionShape, simple llamado ColisionShape
. Esto es necesario porque si un nodo RigidBody se duerme, entonces el controlador de VR será incapaz de agarrarlo. Usando Sleep_Area
, podemos escribir código que haga que cualquier nodo RigidBody dentro de él no pueda dormir, permitiendo así que el controlador de VR lo agarre.
Un nodo AudioStreamPlayer3D llamado AudioStreamPlayer3D
tiene cargado un sonido que usaremos cuando un objeto haya sido recogido, dejado o lanzado por el controlador VR. Aunque esto no es necesario para la funcionalidad del controlador de VR, hace que el agarrar y soltar objetos se sienta más natural.
Finalmente, los últimos nodos son el nodo Grab_Cast
y el nodo hijo único, Mesh
. El nodo Grab_Cast
se usará para los nodos basados en agarrado RigidBody cuando el modo agarrar del controlador VR esté en RAYCAST
. Esto permitirá al controlador VR agarrar objetos que están ligeramente fuera de alcance usando un Raycast. El nodo Mesh
se utiliza para mostrar visualmente a dónde apunta la teletransportación Raycast.
Este es un rápido resumen de cómo se configuran las escenas del controlador VR, y cómo usaremos los nodos para proporcionarles la funcionalidad. Ahora que hemos visto la escena del controlador VR, vamos a escribir el código que los impulsará.
El código para los controles VR¶
Selecciona el nodo raíz de la escena, ya sea Right_Controller
o Left_Controller
, y haz un nuevo script llamado VR_Controller.gd
. Ambas escenas usarán el mismo script, así que no importa cuál uses primero. Con VR_Controller.gd
abierto, añade el siguiente código:
Truco
Puedes copiar y pegar el código de esta página directamente en el editor de scripts.
Si haces esto, todo el código copiado estará usando espacios en lugar de tabulaciones.
Para convertir los espacios en tabulaciones en el editor de scripts, haz clic en el menú Edit
y selecciona Convert Indent To Tabs
. Esto convertirá todos los espacios en pestañas. Puedes seleccionar Convert Indent To Spaces
para convertir las pestañas de nuevo en espacios.
extends ARVRController
var controller_velocity = Vector3(0,0,0)
var prior_controller_position = Vector3(0,0,0)
var prior_controller_velocities = []
var held_object = null
var held_object_data = {"mode":RigidBody.MODE_RIGID, "layer":1, "mask":1}
var grab_area
var grab_raycast
var grab_mode = "AREA"
var grab_pos_node
var hand_mesh
var hand_pickup_drop_sound
var teleport_pos = Vector3.ZERO
var teleport_mesh
var teleport_button_down
var teleport_raycast
# A constant to define the dead zone for both the trackpad and the joystick.
# See https://web.archive.org/web/20191208161810/http://www.third-helix.com/2013/04/12/doing-thumbstick-dead-zones-right.html
# for more information on what dead zones are, and how we are using them in this project.
const CONTROLLER_DEADZONE = 0.65
const MOVEMENT_SPEED = 1.5
const CONTROLLER_RUMBLE_FADE_SPEED = 2.0
var directional_movement = false
func _ready():
# Ignore the warnings the from the connect function calls.
# (We will not need the returned values for this tutorial)
# warning-ignore-all:return_value_discarded
teleport_raycast = get_node("RayCast")
teleport_mesh = get_tree().root.get_node("Game/Teleport_Mesh")
teleport_button_down = false
teleport_mesh.visible = false
teleport_raycast.visible = false
grab_area = get_node("Area")
grab_raycast = get_node("Grab_Cast")
grab_pos_node = get_node("Grab_Pos")
grab_mode = "AREA"
grab_raycast.visible = false
get_node("Sleep_Area").connect("body_entered", self, "sleep_area_entered")
get_node("Sleep_Area").connect("body_exited", self, "sleep_area_exited")
hand_mesh = get_node("Hand")
hand_pickup_drop_sound = get_node("AudioStreamPlayer3D")
connect("button_pressed", self, "button_pressed")
connect("button_release", self, "button_released")
func _physics_process(delta):
if rumble > 0:
rumble -= delta * CONTROLLER_RUMBLE_FADE_SPEED
if rumble < 0:
rumble = 0
if teleport_button_down == true:
teleport_raycast.force_raycast_update()
if teleport_raycast.is_colliding():
if teleport_raycast.get_collider() is StaticBody:
if teleport_raycast.get_collision_normal().y >= 0.85:
teleport_pos = teleport_raycast.get_collision_point()
teleport_mesh.global_transform.origin = teleport_pos
if get_is_active() == true:
_physics_process_update_controller_velocity(delta)
if held_object != null:
var held_scale = held_object.scale
held_object.global_transform = grab_pos_node.global_transform
held_object.scale = held_scale
_physics_process_directional_movement(delta);
func _physics_process_update_controller_velocity(delta):
controller_velocity = Vector3(0,0,0)
if prior_controller_velocities.size() > 0:
for vel in prior_controller_velocities:
controller_velocity += vel
controller_velocity = controller_velocity / prior_controller_velocities.size()
var relative_controller_position = (global_transform.origin - prior_controller_position)
controller_velocity += relative_controller_position
prior_controller_velocities.append(relative_controller_position)
prior_controller_position = global_transform.origin
controller_velocity /= delta;
if prior_controller_velocities.size() > 30:
prior_controller_velocities.remove(0)
func _physics_process_directional_movement(delta):
var trackpad_vector = Vector2(-get_joystick_axis(1), get_joystick_axis(0))
var joystick_vector = Vector2(-get_joystick_axis(5), get_joystick_axis(4))
if trackpad_vector.length() < CONTROLLER_DEADZONE:
trackpad_vector = Vector2(0,0)
else:
trackpad_vector = trackpad_vector.normalized() * ((trackpad_vector.length() - CONTROLLER_DEADZONE) / (1 - CONTROLLER_DEADZONE))
if joystick_vector.length() < CONTROLLER_DEADZONE:
joystick_vector = Vector2(0,0)
else:
joystick_vector = joystick_vector.normalized() * ((joystick_vector.length() - CONTROLLER_DEADZONE) / (1 - CONTROLLER_DEADZONE))
var forward_direction = get_parent().get_node("Player_Camera").global_transform.basis.z.normalized()
var right_direction = get_parent().get_node("Player_Camera").global_transform.basis.x.normalized()
# Because the trackpad and the joystick will both move the player, we can add them together and normalize
# the result, giving the combined movement direction
var movement_vector = (trackpad_vector + joystick_vector).normalized()
var movement_forward = forward_direction * movement_vector.x * delta * MOVEMENT_SPEED
var movement_right = right_direction * movement_vector.y * delta * MOVEMENT_SPEED
movement_forward.y = 0
movement_right.y = 0
if movement_right.length() > 0 or movement_forward.length() > 0:
get_parent().global_translate(movement_right + movement_forward)
directional_movement = true
else:
directional_movement = false
func button_pressed(button_index):
if button_index == 15:
_on_button_pressed_trigger()
if button_index == 2:
_on_button_pressed_grab()
if button_index == 1:
_on_button_pressed_menu()
func _on_button_pressed_trigger():
if held_object == null:
if teleport_mesh.visible == false:
teleport_button_down = true
teleport_mesh.visible = true
teleport_raycast.visible = true
else:
if held_object is VR_Interactable_Rigidbody:
held_object.interact()
func _on_button_pressed_grab():
if teleport_button_down == true:
return
if held_object == null:
_pickup_rigidbody()
else:
_throw_rigidbody()
hand_pickup_drop_sound.play()
func _pickup_rigidbody():
var rigid_body = null
if grab_mode == "AREA":
var bodies = grab_area.get_overlapping_bodies()
if len(bodies) > 0:
for body in bodies:
if body is RigidBody:
if !("NO_PICKUP" in body):
rigid_body = body
break
elif grab_mode == "RAYCAST":
grab_raycast.force_raycast_update()
if grab_raycast.is_colliding():
var body = grab_raycast.get_collider()
if body is RigidBody:
if !("NO_PICKUP" in body):
rigid_body = body
if rigid_body != null:
held_object = rigid_body
held_object_data["mode"] = held_object.mode
held_object_data["layer"] = held_object.collision_layer
held_object_data["mask"] = held_object.collision_mask
held_object.mode = RigidBody.MODE_STATIC
held_object.collision_layer = 0
held_object.collision_mask = 0
hand_mesh.visible = false
grab_raycast.visible = false
if held_object is VR_Interactable_Rigidbody:
held_object.controller = self
held_object.picked_up()
func _throw_rigidbody():
if held_object == null:
return
held_object.mode = held_object_data["mode"]
held_object.collision_layer = held_object_data["layer"]
held_object.collision_mask = held_object_data["mask"]
held_object.apply_impulse(Vector3(0, 0, 0), controller_velocity)
if held_object is VR_Interactable_Rigidbody:
held_object.dropped()
held_object.controller = null
held_object = null
hand_mesh.visible = true
if grab_mode == "RAYCAST":
grab_raycast.visible = true
func _on_button_pressed_menu():
if grab_mode == "AREA":
grab_mode = "RAYCAST"
if held_object == null:
grab_raycast.visible = true
elif grab_mode == "RAYCAST":
grab_mode = "AREA"
grab_raycast.visible = false
func button_released(button_index):
if button_index == 15:
_on_button_released_trigger()
func _on_button_released_trigger():
if teleport_button_down == true:
if teleport_pos != null and teleport_mesh.visible == true:
var camera_offset = get_parent().get_node("Player_Camera").global_transform.origin - get_parent().global_transform.origin
camera_offset.y = 0
get_parent().global_transform.origin = teleport_pos - camera_offset
teleport_button_down = false
teleport_mesh.visible = false
teleport_raycast.visible = false
teleport_pos = null
func sleep_area_entered(body):
if "can_sleep" in body:
body.can_sleep = false
body.sleeping = false
func sleep_area_exited(body):
if "can_sleep" in body:
# Allow the CollisionBody to sleep by setting the "can_sleep" variable to true
body.can_sleep = true
Esto es un poco de código para revisar. Repasemos lo que el código hace paso a paso.
Explicando el código de controles VR¶
Primero, veamos todas las variables de la clase en el script:
controller_velocity
: Una variable que contiene una aproximación aproximada de la velocidad del controlador VR.prior_controller_position
: Una variable para mantener la última posición en el espacio del controlador VR.prior_controller_velocities
: Un array para mantener las últimas 30 velocidades calculadas del controlador VR. Esto se utiliza para suavizar los cálculos de velocidad a lo largo del tiempo.held_object
: Una variable para mantener una referencia al objeto que el controlador VR está sosteniendo. Si el controlador VR no está sosteniendo ningún objeto, esta variable seránull
.held_object_data
: Un diccionario para guardar los datos del nodo RigidBody que está siendo guardado por el controlador de VR. Se utiliza para restablecer los datos de RigidBody cuando ya no están mantenidos.grab_area
: Una variable que contiene el nodo Area usado para agarrar objetos con el controlador VR.grab_raycast
: Una variable que contiene el nodo Raycast usado para agarrar objetos con el controlador VR.grab_mode
: Una variable para definir el modo de agarre que el controlador VR está usando. Sólo hay dos modos de agarrar objetos en este tutorial,AREA
yRAYCAST
."Agarrar el nodo de la posición": Una variable para mantener el nodo que se usará para actualizar la posición y rotación de los objetos mantenidos.
hand_mesh
: Una variable para mantener el nodo MeshInstance que contiene la malla de la mano para el controlador VR. Esta malla se mostrará cuando el controlador VR no sujete nada.hand_pickup_drop_sound
:Una variable para mantener el nodo AudioStreamPlayer3D que contiene el sonido pickup/drop.teleport_pos
: Una variable para mantener la posición a la que se teletransportará el jugador cuando el controlador VR lo haga.teleport_mesh
: Una variable que contiene el nodo MeshInstance usado para mostrar dónde se está teletransportando el jugador.teleport_button_down
:Una variable usada para rastrear si el botón de teleportación del controlador se mantiene pulsado. Esto se utilizará para detectar si este controlador VR está tratando de teleportar al jugador.teleport_raycast
:Una variable para mantener el nodo Raycast usado para calcular la posición de teleportación. Este nodo también tiene un MeshInstance que actúa como una 'mira láser' para apuntar.CONTROLLER_DEADZONE
:Una constante para definir la zona muerta tanto para el trackpad como para el joystick del controlador VR. Vea la nota de abajo para más información.MOVEMENT_SPEED
: Una constante para definir la velocidad a la que se mueve el jugador cuando usa el trackpad/joystick para moverse artificialmente.CONTROLLER_RUMBLE_FADE_SPEED
: Una constante para definir la velocidad con la que se desvanece el temblor del controlador VR.directional_movement
: Una variable para saber si este controlador VR está moviendo el reproductor con el touchpad/joystick.
Nota
Puedes encontrar un gran artículo explicando todo acerca de cómo manejar las zonas muertas del touchpad/joystick "aquí". <https://web.archive.org/web/20191208161810/http://www.third-helix.com/2013/04/12/doing-thumbstick-dead-zones-right.html>`__.
Estamos usando una versión traducida del código de la zona muerta radial escalada que se proporciona en ese artículo para el joystick/touchpad del controlador VR. El artículo es una gran lectura, ¡y sugiero encarecidamente que le eches un vistazo!
Son bastantes variables de clase. La mayoría de ellas se utilizan para mantener referencias a los nodos que necesitaremos en todo el código. A continuación vamos a empezar a ver las funciones, empezando por la función _ready
.
``_ready``explicación paso a paso de la función¶
Primero le decimos a Godot que silencie las advertencias sobre no usar los valores devueltos por la función connect
. No necesitaremos los valores devueltos para este tutorial.
A continuación obtenemos el nodo Raycast que vamos a usar para determinar la posición para el teletransporte y lo asignamos a la variable teleport_raycast
. Entonces obtenemos el nodo MeshInstance que usaremos para mostrar a dónde se teletransportará el reproductor. El nodo que usamos para teletransportarnos es un hijo de la escena Game
. Hacemos esto para que el nodo malla de teleportación no se vea afectado por los cambios en el controlador VR, y así la malla de teleportación puede ser utilizada por ambos controladores VR.
Entonces la variable teleport_button_down
se establece como falsa, teleport_mesh.visible
se establece como false
, y teleport_raycast.visible
se establece como false
. Esto configura las variables para teleportar el reproductor a su estado inicial, no para teleportar el reproductor.
El código entonces obtiene el nodo grab_area
, el nodo grab_raycast
, y el nodo grab_pos_node
y los asigna todos a sus respectivas variables para su uso posterior.
A continuación, el grab_mode
se establece en AREA
para que el controlador VR intente agarrar objetos usando el nodo Area definido en grab_area
cuando el botón de agarre del controlador VR sea presionado. También ponemos la propiedad grab_raycast
del nodo visible
en false
para que el nodo hijo de grab_raycast
no sea visible.
Después de eso, conectamos las señales body_entered
y body_exited
del nodo Sleep_Area
en el controlador VR con las funciones sleep_area_entered
y sleep_area_exited
. Las funciones sleep_area_entered
y sleep_area_exited
se usarán para hacer que los nodos :RigidBody <class_RigidBody> no puedan dormir cuando estén cerca del controlador VR.
Luego los nodos hand_mesh
y hand_pickup_drop_sound
se obtienen y asignan a sus respectivas variables para uso posterior.
Finalmente, las señales button_pressed
y button_release
del nodo ARVRController, que extiende el controlador VR, están conectadas a las funciones button_pressed
y button_released
respectivamente. Esto significa que cuando un botón del controlador VR es presionado o liberado, las funciones button_pressed
o button_released
definidas en este script serán llamadas.
_physics_process
explicación paso a paso de la función¶
Primero comprobamos si la variable rumble
es mayor que cero. Si la variable rumble
, que es una propiedad del nodo ARVRController, es más que cero, entonces el controlador VR retumba.
Si la variable rumble
es más que cero, entonces reducimos el rumble por "CONTROLLER_RUMBLE_FADE_SPEED" cada segundo restando "CONTROLLER_RUMBLE_FADE_SPEED" multiplicado por el delta. Hay entonces una condición if
para comprobar si rumble
es menor que cero, que pone a rumble
en cero si su valor es menor que cero.
Esta pequeña sección de código es todo lo que necesitamos para reducir el ruido del controlador VR. Ahora cuando ponemos rumble
a un valor, este código hará que se desvanezca automáticamente con el tiempo.
La primera sección del código comprueba si la variable teleport_button_down
es igual a true
, lo que significa que este controlador VR está intentando teletransportarse.
Si teleport_button_down
es igual a true
, forzamos al nodo teleport_raycast
Raycast a actualizarse usando la función force_raycast_update
. La función force_raycast_update
actualizará las propiedades dentro del nodo Raycast con la última versión del mundo de la física.
El código entonces comprueba si el teleport_raycast
colisionó con algo comprobando que la función de is_colliding
en el teleport_raycast
es verdadera. Si el Raycast colisionó con algo, entonces comprobamos si el PhysicsBody con el que colisionó el raycast es un StaticBody o no. Entonces comprobamos si el vector normal de colisión devuelto por el raycast es mayor o igual a 0.85
en el eje Y.
Nota
Lo hacemos porque no queremos que el usuario pueda teletransportarse a los nodos de RigidBody y sólo queremos que el jugador pueda teletransportarse a superficies similares al suelo.
Si se cumplen todas estas condiciones, entonces asignamos la variable teleport_pos
a la función get_collision_point
en teleport_raycast
. Esto asignará teleport_pos
a la posición en la que el raycast colisionó en el espacio mundial. Luego movemos la teleport_mesh
a la posición del mundo almacenada en teleport_pos
.
Esta sección del código obtendrá la posición a la que el jugador apunta con el raycast de teletransportación y actualizará la malla de teletransportación, dando una actualización visual sobre a dónde se teletransportará el usuario cuando suelte el botón de teletransportación.
La siguiente sección del código primero comprueba si el controlador VR está activo a través de la función get_is_active
, que está definida por ARVRController. Si el controlador VR está activo, entonces llama a la función physics_process_update_controller_
.
La función physics_process_update_controller_
calculará la velocidad del controlador VR a través de los cambios de posición. No es perfecta, pero este proceso da una idea aproximada de la velocidad del controlador de la RV, lo cual está bien para los propósitos de este tutorial.
La siguiente sección del código comprueba si el controlador VR sostiene un objeto comprobando si la variable held_object
no es igual a null
.
Si el controlador VR sostiene un objeto, primero guardamos su escala en una variable temporal llamada held_scale
. Luego ponemos la global_transform
del objeto retenido en la global_transform
del nodo held_object
. Esto hará que el objeto retenido tenga la misma posición, rotación y escala del nodo grab_pos_node
en el espacio mundial.
Sin embargo, como no queremos que el objeto retenido cambie de escala cuando es agarrado, necesitamos poner la propiedad scale
del nodo held_object
de vuelta a held_scale
.
Esta sección del código mantendrá el objeto retenido en la misma posición y rotación que el controlador VR, manteniéndolo sincronizado con el controlador VR.
Finalmente, la última sección del código simplemente llama a la función _physics_process_directional_movement
. Esta función contiene todo el código para mover el reproductor cuando se mueve el touchpad/joystick del controlador VR.
_physics_process_update_controller_velocity
explicación paso a paso de la función¶
Primero, esta función resetea la variable controller_velocity
a un Vector3 cero.
Entonces comprobamos si hay alguna velocidad de controlador de RV almacenada/en caché guardada en el array prior_controller_velocities
. Lo hacemos comprobando si la función size()
devuelve un valor mayor que 0
. Si hay velocidades en caché dentro de prior_controller_velocities
, entonces iteramos a través de cada una de las velocidades almacenadas usando un bucle for
.
Para cada una de las velocidades almacenadas, simplemente añadimos su valor a controller_velocity
. Una vez que el código ha pasado por todas las velocidades en prior_controller_velocities
, dividimos controller_velocity
por el tamaño del array prior_controller_velocities
, lo que nos dará el valor de la velocidad combinada. Esto ayuda a tener en cuenta las velocidades anteriores, haciendo que la dirección de la velocidad del controlador sea más precisa.
A continuación calculamos el cambio de posición que el controlador VR ha tomado desde la última llamada a la función physics_process
. Lo hacemos restando prior_controller_position
de la posición global del controlador VR, global_transform.origin
. Esto nos dará un Vector3 que apunta desde la posición en prior_controller_
a la posición actual del controlador VR, que almacenamos en una variable llamada relative_controller_position
.
A continuación, añadimos el cambio de posición a controller_velocity
para que el último cambio de posición se tenga en cuenta en el cálculo de la velocidad. Luego añadimos relative_controller_position
a prior_controller_velocities
para que se tenga en cuenta en el siguiente cálculo de la velocidad del controlador de la RV.
Entonces prior_controller_position
se actualiza con la posición global del controlador VR, global_transform.origin
. Luego dividimos controller_velocity
por delta
para que la velocidad sea más alta, dando resultados como los que esperamos, sin dejar de ser relativos a la cantidad de tiempo que ha pasado. No es una solución perfecta, pero los resultados se ven decentes la mayor parte del tiempo y para los propósitos de este tutorial, es suficientemente bueno.
Por último, la función comprueba si el prior_controller_velocities
tiene más de 30
velocidades en caché comprobando si la función size()
devuelve un valor mayor de 30
. Si hay más de 30
velocidades almacenadas en prior_controller_velocities
, entonces simplemente eliminamos la velocidad almacenada más antigua llamando a la función remove
y pasando a una posición índice de 0
.
Lo que esta función hace en última instancia es que obtiene una idea aproximada de la velocidad del controlador VR calculando los cambios relativos de posición del controlador VR en las últimas treinta llamadas al _physics_process
. Aunque esto no es perfecto, da una idea decente de la rapidez con la que el controlador de RV se mueve en el espacio tridimensional.
_physics_process_directional_movement
explicación paso a paso de la función¶
Primero esta función obtiene los ejes para el trackpad y el joystick y los asigna a las variables Vector2 llamadas trackpad_vector
y joystick_vector
respectivamente.
Nota
Es posible que tenga que rehacer los valores de índice del joystick y/o del touchpad dependiendo de su auricular y controlador de RV. Las entradas de este tutorial son los valores de índice de unos auriculares Windows Mixed Reality.
Entonces el trackpad_vector
y joystick_vector
tienen sus zonas muertas. El código para esto se detalla en el artículo a continuación, con ligeros cambios al convertir el código de C# a GDScript.
Una vez que las variables trackpad_vector
y joystick_vector
tienen sus zonas muertas, el código obtiene los vectores de dirección delantera y derecha relativos a la transformación global de la ref:ARVRCamera <class_ARVRCamera>. Lo que esto hace es que nos da vectores que apuntan hacia adelante y hacia la derecha relativos a la rotación de la cámara del usuario, la ARVRCamera, en el espacio mundial. Estos vectores apuntan en la misma dirección que las flechas azules y rojas cuando seleccionas un objeto en el editor de Godot con el botón local space mode
activado. El vector de dirección hacia adelante se almacena en una variable llamada forward_direction
, mientras que el vector de dirección hacia la derecha se almacena en una variable llamada right_direction
.
A continuación el código añade las variables trackpad_vector
y joystick_vector
juntas y normaliza los resultados usando la función normalized
. Esto nos da la dirección de movimiento combinada de ambos dispositivos de entrada, por lo que podemos usar una sola Vector2 para mover al usuario. Asignamos la dirección combinada a una variable llamada movement_vector
.
Entonces calculamos la distancia que el usuario se moverá hacia adelante, en relación con la dirección de avance almacenada en forward_direction
. Para calcular esto, multiplicamos la forward_direction
por movement_vector.x
, delta
y MOVEMENT_SPEED
. Esto nos dará la distancia que el usuario se moverá hacia adelante cuando el trackpad/joystick sea empujado hacia adelante o hacia atrás. Asignamos esto a una variable llamada movement_forward
.
Hacemos un cálculo similar para la distancia que el usuario se moverá a la derecha, en relación con la dirección correcta almacenada en right_direction
. Para calcular la distancia que el usuario se moverá a la derecha, multiplicamos right_direction
por movement_vector.y
, delta
, and MOVEMENT_SPEED
. Esto nos dará la distancia que el usuario se moverá a la derecha cuando el trackpad/joystick sea empujado a la derecha o a la izquierda. Asignamos esto a una variable llamada movement_right
.
Luego eliminamos cualquier movimiento en el eje Y
del movement_forward
y movement_right
asignando sus valores Y
a 0
. Hacemos esto para que el usuario no pueda volar/caer con sólo mover el trackpad o el joystick. Sin hacer esto, el jugador podría volar en la dirección en la que se encuentra.
Por último, comprobamos si la función length
de movement_right
o movement_forward
es mayor que 0
. Si lo es, entonces necesitamos mover al usuario. Para mover el usuario, realizamos una traducción global al nodo ARVROrigin usando get_parent().global_translate
y le pasamos la variable movement_right
con la variable movement_forward
añadida. Esto moverá al jugador en la dirección en la que apunta el trackpad/joystick, en relación con la rotación del auricular VR. También ponemos la variable directional_movement
en true
para que el código sepa que este controlador VR está moviendo al jugador.
Si la función length
en movement_right
o movement_forward
es menor o igual a 0
, entonces simplemente ponemos la variable directional_movement
en false
para que el código sepa que este controlador VR no está moviendo al jugador.
Lo que esta función hace en última instancia es tomar la entrada del trackpad y el joystick del controlador VR y mover el reproductor en la dirección en la que lo está empujando. El movimiento es relativo a la rotación de los auriculares VR, así que si el jugador empuja hacia delante y gira la cabeza hacia la izquierda, se moverá hacia la izquierda.
explicación paso a paso de la función _pickup_rigidbody
¶
Primero la función hace una variable llamada rigid_body
, que usaremos para almacenar el RigidBody que el controlador de VR va a recoger, suponiendo que haya un RigidBody para recoger.
Entonces la función comprueba si la variable grab_mode
es igual a AREA
. Si lo es, entonces obtiene todos los nodos PhysicsBody dentro del grab_area
usando las funciones get_overlapping_bodies
. Esta función devolverá un array de nodos PhysicsBody. Asignamos el array de PhysicsBody a una nueva variable llamada bodies
.
Luego comprobamos si la longitud de la variable bodies
es mayor que 0
. Si lo es, pasamos por cada uno de los nodos PhysicsBody en bodies
usando un bucle for.
Para cada nodo PhysicsBody, comprobamos si es, o extiende, un nodo RigidBody usando if body is RigidBody
, que devolverá true
si el : ref:PhysicsBody <class_PhysicsBody> es o extiende el nodo RigidBody. Si el objeto es un RigidBody, entonces comprobamos que no hay una variable/constante llamada NO_PICKUP
definida en el cuerpo. Hacemos esto porque si quieres tener RigidBody nodos que no pueden ser recogidos, todo lo que tienes que hacer es definir una constante/variable llamada NO_PICKUP
y el controlador VR será incapaz de recogerla. Si el nodo RigidBody no tiene una variable/constante definida con el nombre NO_PICKUP
, entonces asignamos la variable rigid_body
al nodo RigidBody y rompemos el bucle for.
Lo que esta sección del código hace es pasar por todos los cuerpos físicos dentro del grab_area
y agarra el primer RigidBody nodo que no tiene una variable/constante llamada NO_PICKUP
y la asigna a la variable rigid_body
para que podamos hacer algún postprocesamiento adicional más tarde en esta función.
Si la variable grab_mode
no es igual a AREA
, entonces comprobamos si es igual a RAYCAST
en su lugar. Si es igual RAYCAST
, forzamos al nodo grab_raycast
a actualizarse usando la función force_raycast_update
. La función force_raycast_update
actualizará el Raycast con los últimos cambios en el mundo de la física. Luego comprobamos si el nodo grab_raycast
colisionó con algo usando la función is_colliding
, que devolverá true si el Raycast golpea algo.
Si el grab_raycast
golpea algo, obtenemos el nodo PhysicsBody golpeando con la función get_collider
. El código entonces comprueba si el nodo golpeado es un nodo RigidBody usando if body is RigidBody
, que devolverá true
si el nodo PhysicsBody es o extiende el nodo RigidBody. Entonces el código comprueba si el nodo RigidBody no tiene una variable llamada NO_PICKUP
, y si no la tiene, entonces asigna el nodo RigidBody a la variable rigid_body
.
Lo que esta sección del código hace es enviar el nodo grab_raycast
Raycast y comprueba si ha colisionado con un nodo RigidBody que no tiene una variable/constante llamada NO_PICKUP
. Si colisiona con un RigidBody sin NO_PICKUP
, asigna el nodo a la variable rigid_body
para que podamos hacer algún postprocesamiento adicional más tarde en esta función.
La última sección del código primero comprueba si el rigid_body
no es igual al "null". Si rigid_body
no es igual a null
, entonces el controlador VR encontró un nodo basado en :ref: `RigidBody <class_RigidBody> `que puede ser recogido.
Si hay un controlador VR para recoger, asignamos held_object
al nodo RigidBody almacenado en rigid_body
. Luego guardamos el nodo RigidBody del nodo mode
, collision_layer
, y collision_mask
en held_object_data
usando mode
, layer
, y mask
como claves para los valores respectivos. Esto es para que podamos volver a aplicarlas más tarde cuando el objeto sea soltado por el controlador de VR.
Luego ponemos el modo RigidBody's a MODE_STATIC
, es collision_layer
a cero, y es collision_mask
a cero. Esto hará que donde el sostenido RigidBody no pueda interactuar con otros objetos del mundo de la física cuando sea sostenido por el controlador de VR.
Luego la hand_mesh
MeshInstance se hace invisible poniendo la propiedad visible
a false
. Esto es para que la mano no se interponga en el camino del objeto sostenido. De la misma manera, la gray_raycast
'laser sight' se hace invisible poniendo la propiedad visible
a false
.
Entonces el código comprueba si el objeto retenido extiende una clase llamada VR_Interactable_Rigidbody
. Si lo hace, entonces establece una variable llamada controller
en held_object
a self
, y llama a la función picked_up
en held_object
. Aunque todavía no hemos hecho VR_Interactable_Rigidbody
, lo que esto hará es decirle a la clase VR_Interactable_Rigidbody
que está siendo retenida por un controlador VR, donde la referencia al controlador se almacena en la variable controller
, a través de la llamada a la función picked_up
.
Truco
¡No te preocupes, cubriremos el VR_Interactable_Rigidbody
después de esta sección!
El código debería tener más sentido después de completar la parte 2 de esta serie de tutoriales, donde en realidad usaremos VR_Interactable_Rigidbody
.
Lo que hace esta sección de código es que si se encuentra un RigidBody usando el grab Area o Raycast, lo configura para que pueda ser transportado por el controlador de VR.
explicación paso a paso de la función _throw_rigidbody
¶
Primero la función comprueba si el controlador VR no está sosteniendo ningún objeto, comprobando si la variable held_object
es igual a null
. Si lo es, entonces simplemente llama a return
para que no pase nada. Aunque esto no debería ser posible, la función throw_rigidbody
sólo debería ser llamada si un objeto es retenido, esta comprobación ayuda a asegurar que si algo extraño sucede, esta función reaccionará como se espera.
Después de comprobar si el controlador VR está sosteniendo un objeto, asumimos que lo está y ponemos los datos almacenados RigidBody de vuelta al objeto sostenido. Tomamos los datos mode
, layer
y mask
almacenados en el diccionario held_object_data
y los volvemos a aplicar al objeto en held_object
. Esto hará que el RigidBody vuelva al estado que tenía antes de ser recogido.
Luego llamamos apply_impulse
al held_object
para que el RigidBody sea lanzado en la dirección de la velocidad del controlador VR, controller_velocity
.
Entonces comprobamos si el objeto retenido extiende una clase llamada VR_Interactable_Rigidbody
. Si lo hace, entonces llamamos a una función llamada dropped
en held_object
y ponemos held_object.controller
en null
. Aunque todavía no hemos hecho VR_Interactable_Rigidbody
, lo que hará es llamar a la función dropped
para que el RigidBody pueda hacer lo que sea necesario cuando se deje caer, y ponemos la variable controller
en null
para que el RigidBody sepa que no está siendo retenido.
Truco
¡No te preocupes, cubriremos el VR_Interactable_Rigidbody
después de esta sección!
El código debería tener más sentido después de completar la parte 2 de esta serie de tutoriales, donde en realidad usaremos VR_Interactable_Rigidbody
.
Independientemente de si held_object
extiende VR_Interactable_Rigidbody
o no, entonces ponemos held_object
en null
para que el controlador de VR sepa que ya no está sosteniendo nada. Debido a que el controlador de VR ya no está sosteniendo nada, hacemos la hand_mesh
visible estableciendo hand_mesh.visible
a true.
Finalmente, si la variable grab_mode
está puesta en RAYCAST
, ponemos grab_raycast.visible
en true
para que la 'mira láser' para el Raycast en grab_raycast
sea visible.
explicación paso a paso de la función sleep_area_entered
¶
La única sección de código en esta función comprueba si el nodo PhysicsBody que entró en el nodo Sleep_Area
tiene una variable llamada can_sleep
. Si lo hace, entonces pone la variable can_sleep
en false
y pone la variable sleeping
en false
.
Sin hacer esto, los nodos durmientes PhysicsBody no podrían ser recogidos por el controlador de VR, incluso si el controlador de VR está en la misma posición que el nodo PhysicsBody. Para solucionar esto, simplemente 'despertamos' PhysicsBody los nodos que están cerca del controlador de VR.
explicación paso a paso de la función sleep_area_exited
¶
La única sección de código en esta función comprueba si el nodo PhysicsBody que entró en el nodo Sleep_Area
tiene una variable llamada can_sleep
. Si lo hace, entonces pone la variable can_sleep
en true
.
Esto permite RigidBody nodos que dejan el Sleep_Area
para dormir de nuevo, ahorrando rendimiento.
Bien, ¡wow! ¡Eso fue un montón de código! Añade el mismo script, VR_Controller.gd
a la otra escena de controladores VR para que ambos controladores VR tengan el mismo script.
¡Ahora sólo tenemos que hacer una cosa antes de probar el proyecto! Ahora mismo estamos haciendo referencia a una clase llamada VR_Interactable_Rigidbody
, pero aún no la hemos definido. Aunque no usaremos VR_Interactable_Rigidbody
en este tutorial, vamos a crearla rápidamente para que el proyecto pueda ser ejecutado.
Creando una clase base para objetos VR interactivos¶
Con la pestaña Script
aún abierta, crea un nuevo GDScript llamado VR_Interactable_Rigidbody.gd
.
Truco
Puedes crear GDScripts en la pestaña Script
presionando File -> New Script...
.
Una vez que tengas VR_Interactable_Rigidbody.gd
abierto, agrega el siguiente código:
class_name VR_Interactable_Rigidbody
extends RigidBody
# (Ignore the unused variable warning)
# warning-ignore:unused_class_variable
var controller = null
func _ready():
pass
func interact():
pass
func picked_up():
pass
func dropped():
pass
Analicemos rápidamente el script.
Primero empezamos el script con class_name VR_Interactable_Rigidbody
. Lo que esto hace es que le dice a Godot que este GDScript es una nueva clase que se llama VR_Interactable_Rigidbody
. Esto nos permite comparar nodos con la clase VR_Interactable_Rigidbody
en otros archivos de script sin tener que cargar el script directamente o hacer algo especial. Podemos comparar la clase como todas las clases de Godot incorporadas.
Lo siguiente es una variable de clase llamada controller
. controller
se usará para mantener una referencia al controlador VR que actualmente sostiene el objeto. Si un controlador VR no está sosteniendo el objeto, entonces la variable controller
será null
. La razón por la que necesitamos tener una referencia al controlador de VR es para que los objetos retenidos puedan acceder a los datos específicos del controlador de VR, como controller_velocity
.
Finalmente, tenemos cuatro funciones. La función _ready
está definida por Godot y todo lo que hacemos es simplemente tener un pass
, ya que no hay nada que tengamos que hacer cuando el objeto se añade a la escena en VR_Interactable_Rigidbody
.
La función interact
es una función que se llamará cuando el botón de interacción del controlador VR, el disparador en este caso, sea presionado mientras el objeto es sostenido.
Truco
Una función stub es una función que está definida pero no tiene ningún código. Las funciones de stub generalmente están diseñadas para ser sobrescritas o extendidas. En este proyecto, estamos usando las funciones stub para que haya una interfaz consistente en todos los objetos interactivos RigidBody.
Las funciones picked_up
y dropped
son funciones de stub que serán llamadas cuando el objeto sea recogido y soltado por el controlador VR.
¡Eso es todo lo que tenemos que hacer por ahora! En la próxima parte de esta serie de tutoriales, empezaremos a hacer objetos interactivos especiales RigidBody.
Ahora que la clase base está definida, el código del controlador VR debería funcionar. Adelante, prueba el juego de nuevo, y deberías encontrar que puedes teletransportarte pulsando el panel táctil, y puedes agarrar y lanzar objetos usando los botones de agarrar/empuñar.
Ahora, puede que quieras intentar moverte usando los trackpads y/o joysticks, pero puede hacerte sentir mal por el movimiento!
Una de las principales razones por las que esto puede hacer que te sientas mal del movimiento es porque tu visión te dice que te estás moviendo, mientras que tu cuerpo no se está moviendo. Este conflicto de señales puede hacer que el cuerpo se sienta enfermo. Añadamos un shader de viñetas para ayudar a reducir el mareo mientras se mueve en RV!
Reducir el mareo por movimiento¶
Nota
There are plenty of ways to reduce motion sickness in VR, and there is no one perfect way to reduce motion sickness. See this page on the Oculus Developer Center for more information on how to implement locomotion and reducing motion sickness.
Para ayudar a reducir el mareo al moverse, vamos a añadir un efecto de viñeta que sólo será visible mientras el jugador se mueve.
First, quickly switch back to Game.tscn
. Under the ARVROrigin node there is a child node called Movement_Vignette
. This node is going to apply a simple
vignette to the VR headset when the player is moving using the VR controllers. This should help reduce motion sickness.
Open up Movement_Vignette.tscn
, which you can find in the Scenes
folder. The scene is just a ColorRect node with a custom
shader. Feel free to look at the custom shader if you want, it is just a slightly modified version of the vignette shader you can find in the
Godot demo repository.
Escribamos el código que hará visible el shader de viñetas cuando el jugador se mueva. Selecciona el nodo Movement_Vignette
y crea un nuevo script llamado Movement_Vignette.gd
. Añade el siguiente código:
extends Control
var controller_one
var controller_two
func _ready():
yield(get_tree(), "idle_frame")
yield(get_tree(), "idle_frame")
yield(get_tree(), "idle_frame")
yield(get_tree(), "idle_frame")
var interface = ARVRServer.primary_interface
if interface == null:
set_process(false)
printerr("Movement_Vignette: no VR interface found!")
return
rect_size = interface.get_render_targetsize()
rect_position = Vector2(0,0)
controller_one = get_parent().get_node("Left_Controller")
controller_two = get_parent().get_node("Right_Controller")
visible = false
func _process(_delta):
if controller_one == null or controller_two == null:
return
if controller_one.directional_movement == true or controller_two.directional_movement == true:
visible = true
else:
visible = false
Como este script es bastante simple, veamos rápidamente lo que hace.
Explicando el código de viñeta¶
Hay dos variables de clase, controller_one
y controller_two
. Estas variables contendrán referencias a los controladores VR izquierdo y derecho.
En la función _ready
primero espera cuatro fotogramas usando yield
. La razón por la que esperamos cuatro fotogramas es porque queremos asegurarnos de que la interfaz de RV está lista y accesible.
Después de esperar, la interfaz primaria de RV se recupera usando ARVRServer.primary_interface
, que se asigna a una variable llamada interface
. El código entonces comprueba si interfaz
es igual a null
. Si interfaz
es igual a null
, entonces _process
se deshabilita usando set_process
con un valor de false
.
Si la interface
no es null
, entonces ponemos el rect_size
del shader de viñetas en el tamaño de renderización de la ventana VR para que ocupe toda la pantalla. Necesitamos hacer esto porque los diferentes auriculares de la RV tienen diferentes resoluciones y relaciones de aspecto, así que tenemos que redimensionar el nodo en consecuencia. También establecemos la rect_position
del shader de viñetas en cero para que esté en la posición correcta con respecto a la pantalla.
Los controladores de RV izquierdo y derecho se recuperan y se asignan a controller_one
y controller_two
respectivamente. Finalmente, el shader de viñetas se hace invisible de forma predeterminada al establecer su propiedad visible
como false
.
En _process
el código primero comprueba si controller_one
o controller_two
es igual a null
. Si cualquiera de los nodos es igual a null
, entonces se llama return
para que no pase nada.
Luego el código comprueba si alguno de los controladores de RV está moviendo el reproductor usando el touchpad/joystick comprobando si directional_movement
es igual a true
en controller_one
o controller_two
. Si alguno de los controladores de RV está moviendo el reproductor, entonces el shader de viñetas se hace visible estableciendo su propiedad visible
como true
. Si ninguno de los controladores de VR está moviendo el reproductor, entonces directional_movement
es false
en ambos controladores de VR, entonces el shader de viñetas se hace invisible ajustando su propiedad visible
a false
.
¡Ese es todo el script! Ahora que hemos escrito el código, adelante e intenta moverte con el trackpad y/o el joystick. ¡Deberíais descubrir que es menos enfermizo que antes!
Nota
As previously mentioned, there are plenty of ways to reduce motion sickness in VR. Check out this page on the Oculus Developer Center for more information on how to implement locomotion and reducing motion sickness.
Notas finales¶

Ahora tienes controladores de RV completamente funcionales que pueden moverse por el entorno e interactuar con objetos basados en RigidBody. En la próxima parte de esta serie de tutoriales, crearemos algunos objetos especiales basados en RigidBody para que el jugador los use!
Advertencia
Puedes descargar el proyecto terminado para esta serie de tutoriales en el repositorio Godot OpenVR GitHub, en la pestaña de releases!