Tutoriel pour commencer en VR partie 1

Introduction

../../../../_images/starter_vr_tutorial_sword.png

Ce tutoriel vous montrera comment réaliser un projet de jeu de VR pour débutants dans Godot.

Gardez à l'esprit que l'une des choses les plus importantes lorsque vous créez un contenu de VR est de faire en sorte que l'échelle de vos assets soit correcte ! Il faut parfois beaucoup de pratique et d'itérations pour y parvenir, mais il y a quelques petites choses que vous pouvez faire pour faciliter les choses :

  • Dans la VR, une unité est généralement considérée comme un mètre. Si vous concevez vos assets en fonction de cette norme, vous pouvez vous épargner bien des maux de tête.

  • Dans votre programme de modélisation 3D, voyez s'il existe un moyen de mesurer et d'utiliser les distances du monde réel. Dans Blender, vous pouvez utiliser l'add-on MeasureIt ; dans Maya, vous pouvez utiliser l'outil de mesure.

  • Vous pouvez faire des modèles approximatifs en utilisant un outil comme Google Blocks, puis les affiner dans un autre programme de modélisation 3D.

  • Testez souvent, car les assets peuvent avoir une apparence très différente en VR et sur un écran plat !

Tout au long de ce tutoriel, nous couvrirons :

  • Comment dire à godot de s'exécuter en mode VR.

  • Comment réaliser un système de locomotion par téléportation qui utilise les contrôleurs VR.

  • Comment réaliser un système de locomotion à mouvement artificiel qui utilise les contrôleurs VR.

  • Comment créer un système basé sur RigidBody qui permet de ramasser, déposer et lancer des nœuds RigidBody en utilisant les contrôleurs VR.

  • Comment créer une simple cible destructible.

  • Comment créer des objets spéciaux :ref:`RigidBody <class_RigidBody>`qui peuvent détruire les cibles.

Astuce

Bien que ce tutoriel puisse être complété par des débutants, il est fortement conseillé d'avoir fini Votre premier jeu en 2D, si vous êtes nouveau dans Godot et/ou dans le développement de jeu.

Une certaine expérience dans la réalisation de jeux en 3D est requise avant de suivre cette série de tutoriels. Ce tutoriel suppose que vous avez de l'expérience avec l'éditeur Godot, le GDScript et le développement de jeux 3D de base. Un casque prêt pour l'OpenVR et deux contrôleurs prêts pour l'OpenVR sont nécessaires.

Ce tutoriel a été écrit et testé à l'aide d'un casque et de contrôleurs Windows Mixed Reality. Ce projet a également été testé sur le HTC Vive. Des ajustements de code peuvent être nécessaires pour d'autres casques de VR, tels que le Oculus Rift.

Le projet Godot pour ce tutoriel se trouve dans le dépôt GitHub de OpenVR. Les éléments de départ de ce tutoriel se trouvent dans la section releases du dépôt GitHub. Les assets de démarrage contiennent des modèles 3D, des sons, des scripts et des scènes qui sont configurés pour ce tutoriel.

Note

Crédits pour les assets fournis :

  • Le ciel panoramique a été créé par CGTuts.

  • La police utilisée est Titillium-Regular

    • La police est sous licence SIL Open Font License, Version 1.1

  • Les fichiers audio utilisés proviennent de plusieurs sources différentes, toutes téléchargées à partir du pack Sonniss #GameAudioGDC Bundle (License PDF)

    • Les dossiers dans lesquels les fichiers audio sont stockés portent le même nom que les dossiers du paquet audio Sonniss.

  • L'addon OpenVR a été créé par Bastiaan Olij et est publié sous la licence MIT. On peut le trouver à la fois sur la Godot Asset Library et sur GitHub. Le code tierce partie et les bibliothèques utilisées dans l'addon OpenVR peuvent être sous une licence différente.

  • Le projet initial, les modèles 3D et les scripts ont été créés par TwistedTwigleg et est publié sous la licence du MIT.

Astuce

Vous pouvez trouver le projet fini dans le dépôt OpenVR GitHub.

Tout préparer

Si vous ne l'avez pas encore fait, allez dans le dépôt GitHub OpenVR et téléchargez le fichier "Starter Assets" depuis les releases. Une fois que vous avez téléchargé les éléments de départ, ouvrez le projet dans Godot.

Note

Ces assets de départ ne sont pas nécessaires pour utiliser les scripts fournis dans ce tutoriel. Les ressources de départ comprennent plusieurs scènes et scripts pré-faits qui seront utilisés tout au long du tutoriel.

Au premier chargement du projet, la scène Game.tscn sera ouverte. Ce sera la scène principale utilisée pour le tutoriel. Elle comprend plusieurs nœuds et scènes déjà placés dans la scène, une musique de fond et plusieurs nœuds MeshInstance liés à l'interface graphique.


Les nœuds MeshInstance liés à l'interface graphique ont déjà des scripts qui leur sont attachés. Ces scripts vont définir la texture d'un nœud Viewport à la texture d'albédo du matériau du nœud MeshInstance. Il est utilisé pour afficher du texte dans le cadre du projet de VR. N'hésitez pas à consulter le script, "GUI.gd", si vous le souhaitez. Nous ne reviendrons pas sur la manière d'utiliser les nœuds Viewport pour afficher l'interface utilisateur sur les nœuds MeshInstance dans ce tutoriel.

Si vous souhaitez savoir comment utiliser les nœuds Viewport pour afficher l'interface utilisateur sur les nœuds MeshInstance, consultez le tutoriel Utilisation d'un Viewport comme texture. Il explique comment utiliser un Viewport comme texture de rendu, ainsi que comment appliquer cette texture sur un nœud MeshInstance.


Avant de passer au tutoriel, prenons un moment pour parler du fonctionnement des nœuds utilisés pour la VR.

Le nœud ARVROrigin est le point central du système de suivi de la VR. La position du ARVROrigin est la position que le système de VR considère comme le point 'center' sur le sol. Le ARVROrigin a une propriété world scale qui affecte la taille de l'utilisateur dans la scène de VR. Pour ce tutoriel, il est réglé sur 1.4, car le monde n'était à l'origine qu'un peu trop grand. Comme mentionné précédemment, il est important de maintenir une échelle relativement cohérente dans la VR.

L' ARVRCamera est le casque du joueur et sa vue dans la scène. L' ARVRCamera est décalée sur l'axe Y de la hauteur de l'utilisateur de la VR, ce qui sera important plus tard lorsque nous ajouterons la locomotion téléportation. Si le système de VR supporte le suivi de salle, alors l' ARVRCamera se déplacera au fur et à mesure que le joueur se déplace. Cela signifie que le nœud ARVRCamera n'est pas garanti d'être dans la même position que le nœud ARVROrigin.

Le nœud ARVRController représente un contrôleur VR. Le ARVRController suivra la position et la rotation du contrôleur VR par rapport au nœud ARVROrigin. Toutes les entrées pour les contrôleurs VR se font par le nœud ARVRController. Un nœud ARVRController avec un ID de 1 représente le contrôleur VR gauche, tandis qu'un nœud ARVRController avec un ID de 2 représente le contrôleur VR droit.

Pour résumer :

  • Le nœud ARVROrigin est le centre du système de suivi de la VR et est positionné au sol.

  • L' ARVRCamera est le casque VR du joueur et sa vue dans la scène.

  • Le nœud ARVRCamera est décalé sur l'axe Y de la hauteur de l'utilisateur.

  • Si le système de VR supporte le suivi de salle, alors le nœud ARVRCamera peut être décalé sur les axes X et Z lorsque le joueur se déplace.

  • Les nœuds ARVRController représentent les contrôleurs VR et gèrent toutes les entrées des contrôleurs VR.

Commencer en VR

Maintenant que nous avons passé en revue les nœuds VR, commençons à travailler sur le projet. Dans Game.tscn, sélectionnez le nœud Game et créez un nouveau script appelé Game.gd. Dans le fichier Game.gd, ajoutez le code suivant :

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!

Voyons ce que fait ce code.


Dans la fonction _ready, nous obtenons d'abord l'interface VR OpenVR en utilisant la fonction find_interface dans l' ARVRServer et nous l'assignons à une variable appelée VR. Si l' ARVRServer trouve une interface avec le nom OpenVR, il la renverra, sinon il renverra null.

Note

L'interface OpenVR VR n'est pas incluse par défaut avec Godot. Vous devrez télécharger l'asset OpenVR à partir de la Bibliothèque d'assets ou GitHub.

Le code combine alors deux conditionnels, l'un pour vérifier si la variable VR n'est PAS nulle (if VR) et l'autre appelle la fonction d'initialisation, qui renvoie un booléen basé sur le fait que l'interface OpenVR a pu s'initialiser ou non. Si ces deux conditions renvoient vrai, alors nous pouvons transformer le Viewport Godot principal en un viewport ARVR.

Si l'interface VR s'est initialisée avec succès, nous obtenons alors la racine Viewport et définissons la propriété arvr à true. Cela indiquera à Godot d'utiliser l'interface ARVR initialisée pour piloter l'affichage Viewport.

Enfin, nous désactivons VSync pour que le nombre d'images par seconde (FPS) ne soit pas plafonné par l'écran de l'ordinateur. Après cela, nous disons à Godot de rendre à 90 images par seconde, ce qui est la norme pour la plupart des casques VR. Sans désactiver VSync, l'écran normal de l'ordinateur peut limiter la fréquence d'images du casque VR à celle de l'écran d'ordinateur.

Note

Dans les paramètres du projet, sous l'onglet Physics->Common, le FPS physique a été réglé sur 90. Le moteur physique fonctionne ainsi à la même fréquence d'images que l'affichage de la VR, ce qui donne aux réactions physiques une apparence plus lisse en VR.


C'est tout ce que nous devons faire pour que Godot lance OpenVR dans le cadre du projet ! Allez-y, essayez si vous voulez. En supposant que tout fonctionne, vous pourrez regarder le monde autour. Si vous avez un casque VR avec suivi de pièce, vous pourrez alors vous déplacer dans les limites du suivi de pièce.

Création des contrôleurs

../../../../_images/starter_vr_tutorial_hands.png

Pour l'instant, tout ce que l'utilisateur VR peut faire, c'est rester debout, ce qui n'est pas vraiment ce que nous recherchons, à moins de travailler sur un film de VR. Écrivons le code pour les contrôleurs de la VR. Nous allons écrire tout le code pour les contrôleurs de la VR en une seule fois, donc le code est assez long. Cela dit, une fois que nous aurons terminé, vous pourrez vous téléporter dans la scène, vous déplacer artificiellement à l'aide du touchpad/joystick du contrôleur VR, et être capable de prendre, déposer et lancer des nœuds basés sur RigidBody.

Nous devons d'abord ouvrir la scène utilisée pour les contrôleurs de la VR. Left_Controller.tscn ou Right_Controller.tscn. Voyons brièvement comment la scène est mise en place.

Comment la scène du contrôleur de la VR est configurée

Dans les deux scènes, le nœud racine est un nœud ARVRController. La seule différence est que la scène Left_Controller a la propriété Controller Id fixée à 1 alors que la scène Right_Controller a la propriété Controller Id fixée à 2.

Note

L' ARVRServer tente d'utiliser ces deux ID pour les contrôleurs VR gauche et droit. Pour les systèmes de VR qui prennent en charge plus de 2 contrôleurs/objets suivis, ces identifiants peuvent nécessiter un ajustement.

Ensuite, il y a le nœud Hand MeshInstance. Ce nœud est utilisé pour afficher le maillage de la main qui sera utilisé lorsque le contrôleur VR ne tient pas un nœud RigidBody. La main dans la scène Left_Controller est une main gauche, tandis que la main dans la scène Right_Controller est une main droite.

Le nœud nommé Raycast est un nœud Raycast qui est utilisé pour viser où téléporter lorsque le contrôleur VR téléporte. La longueur du Raycast est fixée à -16 sur l'axe Y et est tournée de manière à pointer depuis le doigt de la main. Le nœud Raycast a un seul nœud enfant, Mesh, qui est un MeshInstance. Il est utilisé pour montrer visuellement où le Raycast de téléportation vise.

Le nœud nommé Area est un nœud Area sera utilisé pour la saisie RigidBody des nœuds basés sur le contrôleur VR lorsque le mode de saisie est réglé sur AREA. Le nœud Area a un seul nœud enfant, CollisionShape, qui définit une sphère : ref:CollisionShape <class_CollisionShape>. Lorsque le contrôleur VR ne tient aucun objet et que le bouton de saisie est enfoncé, le premier nœud basé sur RigidBody dans le nœud Area sera récupéré.

Ensuite, il y a un nœud Position3D appelé Grab_Pos. Celui-ci est utilisé pour définir la position où ont été saisi les nœuds RigidBody qui suivront et seront tenus par le contrôleur VR.

Un grand nœud Area appelé Sleep_Area est utilisé pour désactiver la mise en sommeil de tout nœud RigidBody dans la zone de sa CollisionShape, simplement appelé CollisionShape. Ceci est nécessaire car si un nœud RigidBody s'endort, alors le contrôleur VR sera incapable de le saisir. En utilisant Sleep_Area, nous pouvons écrire un code qui rend n'importe quel nœud RigidBody à l'intérieur de celui-ci incapable de dormir, permettant ainsi au contrôleur VR de le saisir.

Un nœud AudioStreamPlayer3D appelé AudioStreamPlayer3D a un son chargé que nous utiliserons quand un objet a été ramassé, déposé ou lancé par le contrôleur VR. Bien que cela ne soit pas nécessaire pour la fonctionnalité du contrôleur VR, cela donne une impression plus naturel lorsqu'on saisit et laisse tomber des objets.

Enfin, les derniers nœuds sont les nœuds Grab_Cast et son seul enfant, Mesh. Le nœud Grab_Cast sera utilisé pour la saisie des nœuds basés sur RigidBody lorsque le mode de saisie du contrôleur VR est réglé sur RAYCAST. Cela permettra au contrôleur VR de saisir des objets qui sont juste un peu hors de portée en utilisant un Raycast. Le nœud Mesh est utilisé pour montrer visuellement où le Raycast de téléportation vise.

C'est un aperçu rapide de la manière dont les scènes du contrôleur VR sont configurées et de la manière dont nous utiliserons les nœuds pour leur fournir les fonctionnalités. Maintenant que nous avons examiné la scène du contrôleur de la VR, écrivons le code qui les pilotera.

Le code pour les contrôleurs VR

Sélectionnez le nœud racine de la scène, soit Right_Controller soit Left_Controller, et faites un nouveau script appelé VR_Controller.gd. Les deux scènes utiliseront le même script, donc peu importe lequel vous utilisez en premier. Avec VR_Controller.gd ouvert, ajoutez le code suivant :

Astuce

Vous pouvez copier et coller le code de cette page directement dans l'éditeur de script.

Si vous faites cela, tout le code copié utilisera des espaces au lieu de tabulations.

Pour convertir les espaces en tabulations dans l'éditeur de script, cliquez sur le menu Edit et sélectionnez Convert Indent To Tabs. Ceci convertira tous les espaces en tabulations. Vous pouvez sélectionner Convert Indent To Spaces pour convertir les tabulations en espaces.

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

C'est beaucoup de code à parcourir. Passons en revue, étape par étape, ce que fait le code.

Explication du code du contrôleur VR

Tout d'abord, passons en revue toutes les variables de classe dans le script :

  • controller_velocity : Une variable qui contient une approximation de la vitesse du contrôleur VR.

  • prior_controller_position : Une variable pour maintenir la dernière position du contrôleur VR dans l'espace 3D.

  • prior_controller_velocities : Un Array qui contient les 30 dernières vitesses calculées du contrôleur VR. Cela permet de lisser les calculs de vitesse dans le temps.

  • held_object : Une variable qui contient une référence à l'objet que le contrôleur VR détient. Si le contrôleur VR ne déient aucun objet, cette variable sera null.

  • held_object_data : Un dictionnaire pour contenir les données du nœud RigidBody détenu par le contrôleur VR. Ceci est utilisé pour réinitialiser les données du RigidBody lorsqu'elles ne sont plus conservées.

  • grab_area : Une variable pour contenir le nœud Area utilisé pour saisir des objets avec le contrôleur VR.

  • grab_raycast : Une variable pour contenir le nœud Raycast utilisé pour saisir des objets avec le contrôleur VR.

  • grab_mode : Une variable permettant de définir le mode de saisie utilisé par le contrôleur VR. Il n'y a que deux modes de saisie d'objets dans ce tutoriel, AREA et RAYCAST.

  • grab_pos_node : Une variable qui contient le nœud qui sera utilisée pour mettre à jour la position et la rotation des objets tenus.

  • hand_mesh : Une variable pour contenir le nœud MeshInstance qui contient le maillage de la main du contrôleur VR. Ce maillage s'affiche lorsque le contrôleur de la RV ne tient rien.

  • hand_pickup_drop_sound : Une variable pour contenir le nœud AudioStreamPlayer3D qui contient le son pickup/drop.

  • teleport_pos : Une variable pour maintenir la position à laquelle le joueur sera téléporté lorsque le contrôleur de la VR téléporte le joueur.

  • teleport_mesh : Une variable pour contenir le nœud MeshInstance utilisé pour montrer où le lecteur se téléporte.

  • teleport_button_down : Une variable utilisée pour savoir si le bouton de téléportation du contrôleur est maintenu enfoncé. Cela sera utilisé pour détecter si ce contrôleur VR essaie de téléporter le joueur.

  • teleport_raycast : Une variable qui contient le nœud Raycast utilisé pour calculer la position de téléportation. Ce nœud a également un MeshInstance qui agit comme un 'laser sight' pour la visée.

  • CONTROLLER_DEADZONE : Une constante permettant de définir la zone morte pour le trackpad et le joystick du contrôleur VR. Voir la note ci-dessous pour plus d'informations.

  • MOVEMENT_SPEED : Une constante pour définir la vitesse à laquelle le joueur se déplace lorsqu'il utilise le trackpad/joystick pour se déplacer artificiellement.

  • CONTROLLER_RUMBLE_FADE_SPEED : Une constante pour définir la vitesse à laquelle le grondement du contrôleur VR s'estompe.

  • directional_movement : Une variable qui permet de savoir si ce contrôleur de VR déplace le joueur à l'aide du touchpad/joystick.

Note

Vous pouvez trouver un bon article qui explique tout sur la façon de gérer les zones mortes des touchpad/joystick : ici.

Nous utilisons une version traduite du code de zone morte radiale mis à l'échelle fourni dans cet article pour le joystick/touchpad du contrôleur VR. L'article est une excellente lecture, et je vous conseille vivement d'y jeter un coup d'œil !

Cela représente un certain nombre de variables de classe. La plupart d'entre eux sont utilisés pour contenir des références aux nœuds dont nous aurons besoin tout au long du code. Commençons ensuite par examiner les fonctions, en commençant par la fonction _ready.


Explication étape par étape de la fonction _ready

Nous disons d'abord à Godot de faire taire les avertissements concernant la non-utilisation des valeurs renvoyées par la fonction connect. Nous n'aurons pas besoin des valeurs renvoyées pour ce tutoriel.

Ensuite, nous obtenons le nœud Raycast que nous allons utiliser pour déterminer la position de téléportation et l'assignons à la variable teleport_raycast. Nous obtenons alors le nœud MeshInstance que nous utiliserons pour montrer où le joueur se téléportera. Le nœud que nous utilisons pour la téléportation est un enfant de la scène Game. Nous faisons cela pour que le nœud de maillage de téléportation ne soit pas affecté par des changements dans le contrôleur VR, et donc que le maillage de téléportation puisse être utilisé par les deux contrôleurs VR.

Ensuite, la variable teleport_button_down est définie sur false, teleport_mesh.visible est définie sur false et teleport_raycast.visible est définie sur false. Ceci définit les variables pour téléporter le joueur dans son état initial, ne téléporte pas le joueur.

Le code obtient alors le nœud grab_area, le nœud grab_raycast et le nœud grab_pos_node et les assigne tous à leurs variables respectives pour une utilisation ultérieure.

Ensuite, le grab_mode est réglé sur AREA donc le contrôleur VR va tenter de saisir des objets en utilisant le nœud Area défini dans grab_area lorsque le bouton de saisie du contrôleur VR est pressé. Nous avons également défini la propriété visible du nœud grab_raycast à false, de sorte que le nœud enfant 'laser sight' de grab_raycast n'est pas visible.

Ensuite, nous connectons les signaux body_entered et body_exited du nœud Sleep_Area du contrôleur VR aux fonctions sleep_area_entered et sleep_area_exited. Les fonctions sleep_area_entered et sleep_area_exited seront utilisées pour rendre les nœuds RigidBody incapables de dormir lorsqu'ils sont à proximité du contrôleur VR.

Ensuite, les nœuds hand_mesh et hand_pickup_drop_sound sont obtenus et assignés à leurs variables respectives pour une utilisation ultérieure.

Enfin, les signaux button_pressed et button_release dans le nœud ARVRController, que le contrôleur VR étend, sont connectés aux fonctions button_pressed et button_released respectivement. Cela signifie que lorsqu'un bouton du contrôleur VR est pressé ou relâché, les fonctions button_pressed or button_released définies dans ce script seront appelées.

Explication étape par étape de la fonction _physics_process

Nous vérifions d'abord si la variable rumble est supérieure à zéro. Si la variable rumble, qui est une propriété du nœud ARVRController, est supérieure à zéro, alors le contrôleur VR gronde.

Si la variable rumble est supérieure à zéro, alors nous réduisons le grondement par CONTROLLER_RUMBLE_FADE_SPEED toutes les secondes en soustrayant CONTROLLER_RUMBLE_FADE_SPEED multiplié par delta. Il y a ensuite une condition if pour vérifier si rumble est inférieur à zéro, qui met rumble à zéro si sa valeur est inférieure à zéro.

Cette petite section de code est tout ce dont nous avons besoin pour réduire le grondement du contrôleur VR. Maintenant, lorsque nous fixons une valeur à rumble, ce code la fera automatiquement disparaître avec le temps.


La première section du code vérifie si la variable teleport_button_down est égale à true, ce qui signifie que ce contrôleur VR essaie de téléporter.

Si teleport_button_down est égal à true, nous forçons le nœud teleport_raycast Raycast à se mettre à jour en utilisant la fonction force_raycast_update. La fonction force_raycast_update met à jour les propriétés du nœud Raycast avec la dernière version du monde physique.

Le code vérifie ensuite si le teleport_raycast est entré en collision avec quelque chose en vérifiant si la fonction is_colliding dans teleport_raycast est vraie. Si le Raycast a heurté quelque chose, nous vérifions alors si le PhysicsBody avec lequel le raycast s'est heurté est un StaticBody ou non. Nous vérifions ensuite si le vecteur normal de collision renvoyé par le raycast est supérieur ou égal à 0.85 sur l'axe Y.

Note

Nous faisons cela parce que nous ne voulons pas que l'utilisateur puisse se téléporter sur des nœuds RigidBody et nous voulons seulement que le joueur puisse se téléporter sur des surfaces ressemblant à un sol.

Si toutes ces conditions sont remplies, alors nous assignons la variable teleport_pos à la fonction get_collision_point dans teleport_raycast. Cela permettra d'assigner teleport_pos à la position à laquelle le raycast est entré en collision dans l'espace mondial. Nous déplaçons ensuite le teleport_mesh vers la position mondiale stockée dans teleport_pos.

Cette section de code permet d'obtenir la position que le joueur vise avec le raycast de téléportation et de mettre à jour le maillage de téléportation, donnant une mise à jour visuelle de l'endroit où l'utilisateur se téléportera quand il relâchera le bouton de téléportation.


La section suivante du code vérifie d'abord si le contrôleur VR est actif grâce à la fonction get_is_active, qui est définie par ARVRController. Si le contrôleur VR est actif, alors il appelle la fonction _physics_process_update_controller_velocity.

La fonction physics_process_update_controller_velocity calculera la vitesse du contrôleur VR en fonction des changements de position. Il n'est pas parfait, mais ce procédé permet d'avoir une idée approximative de la vitesse du contrôleur VR, ce qui est très bien pour les besoins de ce tutoriel.


La section suivante du code vérifie si le contrôleur VR tient un objet en vérifiant si la variable held_object n'est pas égale à null.

Si le contrôleur VR tient un objet, nous stockons d'abord son échelle dans une variable temporaire appelée held_scale. Nous définissons ensuite la global_transform de l'objet retenu à la global_transform du nœud held_object. L'objet tenu aura ainsi la même position, la même rotation et la même échelle que le nœud grab_pos_node dans l'espace mondial.

Cependant, comme nous ne voulons pas que l'objet retenu change d'échelle lorsqu'il est saisi, nous devons remettre la propriété scale du nœud held_object à held_scale.

Cette section de code permet de maintenir l'objet tenu dans la même position et rotation que le contrôleur VR, en le gardant synchronisé avec le contrôleur VR.


Enfin, la dernière section du code appelle simplement la fonction _physics_process_directional_movement. Cette fonction contient tout le code permettant de déplacer le joueur lorsque le touchpad/joystick du contrôleur VR se déplace.

Explication étape par étape de la fonction _physics_process_update_controller_velocity

Tout d'abord, cette fonction réinitialise la variable controller_velocity à zéro Vector3.


Ensuite, nous vérifions s'il y a des vitesses de contrôleur VR stockées/en cache dans le tableau prior_controller_velocities. Nous le faisons en vérifiant si la fonction size() renvoie une valeur supérieure à 0. S'il y a des vitesses mises en cache dans prior_controller_velocities, alors nous itérons à travers chacune des vitesses stockées en utilisant une boucle for.

Pour chacune des vitesses mises en cache, nous ajoutons simplement sa valeur à controller_velocity. Une fois que le code a traversé toutes les vitesses mises en cache dans prior_controller_velocities, nous divisons controller_velocity par la taille du tableau prior_controller_velocities, ce qui nous donnera la valeur de vitesse combinée. Cela permet de prendre en compte les vitesses précédentes, ce qui rend la direction de la vitesse du contrôleur plus précise.


Ensuite, nous calculons le changement de position du contrôleur VR depuis le dernier appel de fonction physics_process. Nous le faisons en soustrayant la prior_controller_position de la position globale du contrôleur VR, global_transform.origin. Cela nous donnera un Vector3 qui pointe de la position dans prior_controller_position à la position actuelle du contrôleur VR, que nous stockons dans une variable appelée relative_controller_position.

Ensuite, nous ajoutons le changement de position à controller_velocity, de sorte que le dernier changement de position est pris en compte dans le calcul de la vitesse. Nous ajoutons ensuite la relative_controller_position à la prior_controller_velocities afin qu'elle puisse être prise en compte dans le prochain calcul de la vitesse du contrôleur VR.

Ensuite, prior_controller_position est mis à jour avec la position globale du contrôleur VR, global_transform.origin. Nous divisons ensuite la controller_velocity par le delta pour que la vitesse soit plus élevée, ce qui donne des résultats comme ceux que nous attendons, tout en étant relatif au temps qui s'est écoulé. Ce n'est pas une solution parfaite, mais les résultats semblent décents la plupart du temps et pour les besoins de ce tutoriel, ils sont suffisants.

Enfin, la fonction vérifie si la prior_controller_velocities a plus de 30 vitesses en cache en vérifiant si la fonction size() renvoie une valeur supérieure à 30. S'il y a plus de 30 vitesses mises en cache dans les prior_controller_velocities, alors nous retirons simplement la vitesse la plus ancienne mise en cache en appelant la fonction remove et en passant à une position d'index de 0.


Cette fonction permet en fin de compte d'obtenir une idée approximative de la vitesse du contrôleur VR en calculant les changements relatifs de position du contrôleur VR au cours des trente derniers appels de _physics_process. Bien que cela ne soit pas parfait, cela donne une idée décente de la vitesse à laquelle le contrôleur VR se déplace dans l'espace 3D.

Explication étape par étape de la fonction _physics_process_directional_movement

D'abord, cette fonction récupère les axes du trackpad et du joystick et les assigne à des variables Vector2 appelées respectivement trackpad_vector et joystick_vector.

Note

Vous devrez peut-être redéfinir les valeurs d'index du joystick et/ou du touchpad en fonction de votre casque et de votre contrôleur VR. Les entrées de ce tutoriel sont les valeurs d'index d'un casque Windows Mixed Reality.

Ensuite, trackpad_vector et joystick_vector ont leurs zones mortes. Le code est détaillé dans l'article ci-dessous, avec de légères modifications au fur et à mesure de la conversion du code C# en GDScript.

Une fois que les variables trackpad_vector et joystick_vector ont eu prises en compte leurs zones mortes, le code obtient alors les vecteurs de direction avant et droite par rapport à la transformation globale de la ARVRCamera. Cela nous donne des vecteurs qui pointent vers l'avant et vers la droite par rapport à la rotation de la caméra utilisateur, la ARVRCamera, dans l'espace mondial. Ces vecteurs pointent dans la même direction que les flèches bleues et rouges lorsque vous sélectionnez un objet dans l'éditeur Godot avec le bouton local space mode activé. Le vecteur de direction avant est stocké dans une variable appelée forward_direction, tandis que le vecteur de direction droite est stocké dans une variable appelée right_direction.

Ensuite, le code ajoute les variables trackpad_vector et joystick_vector ensemble et normalise les résultats en utilisant la fonction normalized. Cela nous donne la direction de mouvement combinée des deux périphériques d'entrée, de sorte que nous pouvons utiliser un seul Vecteur2 pour déplacer l'utilisateur. Nous assignons la direction combinée à une variable appelée movement_vector.

Ensuite, nous calculons la distance que l'utilisateur va parcourir, par rapport à la direction d'avance stockée dans forward_direction. Pour calculer cela, nous multiplions forward_direction par movement_vector.x, delta et MOVEMENT_SPEED. Cela nous donnera la distance de laquelle l'utilisateur avancera lorsque le trackpad/joystick sera poussé vers l'avant ou vers l'arrière. Nous l'attribuons à une variable appelée movement_forward.

Nous faisons un calcul similaire pour la distance que l'utilisateur va parcourir vers la droite, par rapport direction droite stockée dans right_direction. Pour calculer la distance à laquelle l'utilisateur se déplacera vers la droite, nous multiplions la right_direction par movement_vector.y, delta et la MOVEMENT_SPEED. Cela nous donnera la distance à laquelle l'utilisateur se déplacera vers la droite lorsque le trackpad/joystick est poussé à droite ou à gauche. Nous l'attribuons à une variable appelée movement_right.

Ensuite, nous supprimons tout mouvement sur l'axe Y de movement_forward et movement_right en attribuant leur valeur Y à 0. Nous faisons cela pour que l'utilisateur ne puisse pas voler/chuter simplement en déplaçant le trackpad ou le joystick. Sans cela, le joueur pourrait voler dans la direction à laquelle il fait face.

Enfin, nous vérifions si la fonction length de movement_right ou movement_forward est supérieure à 0. Si c'est le cas, nous devons déplacer l'utilisateur. Pour déplacer l'utilisateur, nous effectuons une translation globale vers le nœud ARVROrigin en utilisant get_parent().global_translate et nous passons dans la variable movement_right avec la variable movement_forward ajoutée à celle-ci. Le joueur se déplacera ainsi dans la direction indiquée par le trackpad/joystick, par rapport à la rotation du casque VR. Nous avons aussi mis la variable directional_movement à true pour que le code sache que ce contrôleur VR déplace le joueur.

Si la fonction length de movement_right ou movement_forward est inférieure ou égale à 0, alors nous mettons simplement la variable directional_movement à false pour que le code sache que ce contrôleur VR ne déplace pas le joueur.


Cette fonction permet de prendre les entrées du trackpad et du joystick du contrôleur VR et de déplacer le joueur dans la direction où il les pousse. Le mouvement est relatif à la rotation du casque VR, donc si le joueur pousse vers l'avant et tourne sa tête vers la gauche, il se déplacera vers la gauche.

Explication étape par étape de la fonction button_pressed

Cette fonction vérifie si le bouton VR qui vient d'être enfoncé est égal à l'un des boutons VR utilisés dans ce projet. La variable button_index est passée par le signal button_pressed dans ARVRController, que nous avons connecté dans la fonction ready.

Il n'y a que trois boutons que nous recherchons dans ce projet : le bouton de déclenchement, le bouton de saisie et le bouton de menu.

Note

Vous devrez peut-être redéfinir ces valeurs d'index de bouton en fonction de votre casque VR et de vos contrôleurs. Les entrées de ce tutoriel sont les valeurs d'index d'un casque Windows Mixed Reality.

Nous vérifions d'abord si button_index est égal à 15, ce qui devrait correspondre au bouton de déclenchement du contrôleur VR. Si le bouton pressé est le bouton de déclenchement, alors la fonction on_button_pressed_trigger est appelée.

Si button_index est égal à 2, alors le bouton de saisie vient d'être enfoncé. Si le bouton pressé est le bouton de saisie, la fonction on_button_pressed_grab est appelée.

Enfin, si button_index est égal à 1, alors le bouton de menu vient d'être enfoncé. Si le bouton pressé est le bouton de menu, la fonction on_button_pressed_menu est appelée.

Explication étape par étape de la fonction _on_button_pressed_trigger

Cette fonction vérifie d'abord si le contrôleur VR ne tient rien en vérifiant si held_object est égal à null. Si le contrôleur VR ne tient rien, alors nous supposons que la pression sur le déclencheur du contrôleur VR était pour la téléportation. Nous nous assurons ensuite que teleport_mesh.visible est égal à false. Nous l'utilisons pour savoir si l'autre contrôleur VR essaie de téléporter ou non, car teleport_mesh sera visible si l'autre contrôleur VR téléporte.

Si teleport_mesh.visible est égal à false, alors nous pouvons nous téléporter avec ce contrôleur VR. Nous définissons la variable teleport_button_down à true, nous définissons teleport_mesh.visible à true, et nous définissons teleport_raycast.visible à true. Cela indiquera au code du _physics_process que ce contrôleur VR va téléporter, il rendra le teleport_mesh visible pour que l'utilisateur sache où il se téléporte, et rendra le teleport_raycast visible pour que le joueur dispose d'un 'laser sight' qu'il pourra utiliser pour viser la position de téléportation.


Si held_object n'est pas égal à null, alors le contrôleur VR tient quelque chose. Nous vérifions ensuite si l'objet qui est tenu, held_object, étend une classe appelée VR_Interactable_Rigidbody. Nous n'avons pas encore créé VR_Interactable_Rigidbody, mais VR_Interactable_Rigidbody sera une classe personnalisée que nous utiliserons sur tous les nœuds basés sur les RigidBody spéciaux/personnalisés dans le projet.

Astuce

Ne vous inquiétez pas, nous couvrirons VR_Interactable_Rigidbody après cette section !

Si l'held_object étend le VR_Interactable_Rigidbody, alors nous appelons la fonction interact, de sorte que l'objet tenu peut faire ce qu'il est censé faire lorsque le déclencheur est pressé et que l'objet est tenu par le contrôleur VR.

Explication étape par étape de la fonction _on_button_pressed_grab

Cette fonction vérifie d'abord si teleport_button_down est égal à true. Si c'est le cas, elle appelle return. Nous faisons cela parce que nous ne voulons pas que l'utilisateur puisse prendre des objets pendant la téléportation.

Ensuite, nous vérifions si le contrôleur VR ne tient rien actuellement en vérifiant si held_object est égal à null. Si le contrôleur VR ne contient rien, alors la fonction _pickup_rigidbody est appelée. Si le contrôleur VR tient quelque chose, held_object n'est pas égal à null, alors la fonction throw_rigidbody est appelée.

Enfin, le son de pick-up/drop est joué en appelant la fonction play sur hand_pickup_drop_sound.

Explication étape par étape de la fonction _pickup_rigidbody

D'abord, la fonction crée une variable appelée rigid_body, que nous utiliserons pour stocker le RigidBody que le contrôleur VR va ramasser, en supposant qu'il y ait un RigidBody à ramasser.


Ensuite, la fonction vérifie si la variable grab_mode est égale à AREA. Si c'est le cas, il récupère tous les nœuds PhysicsBody dans la grab_area en utilisant les fonctions get_overlapping_bodies. Cette fonction renvoie un tableau de nœuds PhysicsBody. Nous assignons le tableau de PhysicsBody à une nouvelle variable appelée bodies.

Nous vérifions ensuite si la longueur de la variable bodies est supérieure à 0. Si c'est le cas, nous passons par chacun des nœuds PhysicsBody dans bodies en utilisant une boucle for.

Pour chaque nœud PhysicsBody, nous vérifions s'il s'agit, ou s'il étend, d'un nœud RigidBody en utilisant if body is RigidBody, qui retournera true si le PhysicsBody est ou étend du nœud RigidBody. Si l'objet est un RigidBody, alors nous vérifions qu'il n'y a pas une variable/constante appelée NO_PICKUP définie dedans. Nous faisons cela parce que si vous voulez avoir des nœuds RigidBody qui ne peuvent pas être ramassés, tout ce que vous avez à faire est de définir une constante/variable appelée NO_PICKUP et le contrôleur VR ne pourra pas les ramasser. Si le nœud RigidBody n'a pas de variable/constante définie avec le nom NO_PICKUP, alors nous assignons la variable rigid_body au nœud RigidBody et cassons la boucle for.

Cette section de code passe par tous les physics bodies de la grab_area et prend le premier nœud RigidBody qui n'a pas de variable/constante nommée NO_PICKUP et l'assigne à la variable rigid_body pour que nous puissions faire un post-traitement supplémentaire plus tard dans cette fonction.


Si la variable grab_mode n'est pas égale à AREA, nous vérifions alors si elle est égale à RAYCAST à la place. S'il est égal à RAYCAST, nous forçons le nœud grab_raycast à se mettre à jour en utilisant la fonction force_raycast_update. La fonction force_raycast_update mettra à jour le Raycast avec les derniers changements dans le monde physique. Nous vérifions ensuite si le nœud grab_raycast est entré en collision avec quelque chose en utilisant la fonction is_colliding, qui retournera vrai si le Raycast a touché quelque chose.

Si le grab_raycast touche quelque chose, nous obtenons le nœud PhysicsBody touché en utilisant la fonction get_collider. Le code vérifie ensuite si le nœud touché est un nœud RigidBody en utilisant if body is RigidBody, qui retournera true si le nœud PhysicsBody est ou étend du nœud RigidBody. Ensuite, le code vérifie si le nœud RigidBody n'a pas une variable nommée NO_PICKUP, et si ce n'est pas le cas, alors il assigne le nœud RigidBody à la variable rigid_body.

Cette section de code envoie le nœud grab_raycast Raycast et vérifie s'il est entré en collision avec un nœud RigidBody qui n'a pas de variable/constante nommée NO_PICKUP. S'il entre en collision avec un RigidBody sans NO_PICKUP, il assigne le nœud à la variable rigid_body pour que nous puissions faire un post-traitement supplémentaire plus tard dans cette fonction.


La dernière section du code vérifie d'abord si rigid_body n'est pas égal à null. Si rigid_body n'est pas égal à null, alors le contrôleur VR a trouvé un nœud basé sur RigidBody qui peut être récupéré.

S'il y a un contrôleur VR effectuant un ramassage, nous assignons held_object au nœud RigidBody stocké dans rigid_body. Nous stockons ensuite mode, collision_layer, et collision_mask du nœud RigidBody dans held_object_data en utilisant mode, layer, et mask comme clés pour les valeurs respectives. Cela nous permet de les réappliquer plus tard, lorsque l'objet est déposé par le contrôleur VR.

Nous réglons ensuite le mode du RigidBody sur MODE_STATIC, son collision_layer à zéro, et son collision_mask à zéro. Cela fera en sorte que le RigidBody ne puisse pas interagir avec d'autres objets dans le monde physique lorsqu'il est tenu par le contrôleur VR.

Ensuite, le MeshInstance hand_mesh est rendu invisible en réglant la propriété visible sur false. Ainsi la main ne gêne pas l'objet tenu. De même, le 'laser sight' de grab_raycast est rendu invisible en réglant la propriété visible sur false.

Ensuite, le code vérifie si l'objet tenu étend d'une classe appelée VR_Interactable_Rigidbody. Si c'est le cas, alors il définit une variable appelée controller sur held_object à self, et appelle la fonction picked_up sur held_object. Bien que nous n'ayons pas encore créé de VR_Interactable_Rigidbody, cela va permettre de dire à la classe VR_Interactable_Rigidbody qu'elle est détenue par un contrôleur VR, où la référence au contrôleur est stockée dans la variable controller, en appelant la fonction picked_up.

Astuce

Ne vous inquiétez pas, nous couvrirons VR_Interactable_Rigidbody après cette section !

Le code devrait avoir plus de sens après avoir terminé la deuxième partie de cette série de tutoriels, où nous utiliserons VR_Interactable_Rigidbody.

Ce que fait cette section de code est que si un RigidBody a été trouvé en utilisant le grab Area ou Raycast, il le configure de manière à ce qu'il puisse être porté par le contrôleur VR.

Explication étape par étape de la fonction _throw_rigidbody

Tout d'abord, la fonction vérifie si le contrôleur VR ne contient pas d'objet en vérifiant si la variable held_object est égale à null. Si c'est le cas, il se contente d'appeler return pour que rien ne se passe. Bien que cela ne devrait pas être possible, la fonction throw_rigidbody ne devrait être appelée que si un objet est tenu, cette vérification permet de s'assurer que si quelque chose d'étrange se produit, cette fonction réagira comme prévu.

Après avoir vérifié si le contrôleur VR tient un objet, nous supposons que c'est le cas et nous remettons les données RigidBody stockées dans l'objet détenu. Nous prenons les données mode, layer et mask stockées dans le dictionnaire held_object_data et les appliquons à nouveau à l'objet dans held_object. Cela va remettre le RigidBody dans l'état où il était avant d'être ramassé.

Ensuite, nous appelons apply_impulse sur l'objet held_object de sorte que le RigidBody soit lancé dans la direction de la vitesse du contrôleur VR, controller_velocity.

Nous vérifions ensuite si l'objet tenu étend d'une classe appelée VR_Interactable_Rigidbody. Si c'est le cas, alors nous appelons une fonction appelée dropped dans held_object et nous mettons held_object.controller à null. Bien que nous n'ayons pas encore fait de VR_Interactable_Rigidbody, ce que cela va faire, c'est appeler la fonction droppped de sorte que le RigidBody puisse faire tout ce qu'il doit faire lorsqu'il est déposé, et nous avons mis la variable controller à null de sorte que le RigidBody sache qu'il n'est plus tenu.

Astuce

Ne vous inquiétez pas, nous couvrirons VR_Interactable_Rigidbody après cette section !

Le code devrait avoir plus de sens après avoir terminé la deuxième partie de cette série de tutoriels, où nous utiliserons VR_Interactable_Rigidbody.

Peu importe que held_object étende de VR_Interactable_Rigidbody ou non, nous mettons alors held_object à null pour que le contrôleur VR sache qu'il ne tient plus rien. Comme le contrôleur VR ne tient plus rien, nous rendons le hand_mesh visible en mettant hand_mesh.visible à true.

Enfin, si la variable grab_mode est définie sur RAYCAST, nous définissons grab_raycast.visible sur true, de sorte que le 'laser sight' Raycast dans grab_raycast soit visible.

Explication étape par étape de la fonction _on_button_pressed_menu

Cette fonction vérifie d'abord si la variable grab_mode est égale à AREA. Si c'est le cas, alors il met grab_mode à RAYCAST. Il vérifie ensuite si le contrôleur VR ne tient rien en vérifiant si held_object est égal à null. Si le contrôleur VR ne tient rien, alors grab_raycast.visible est réglé sur true, de sorte que la 'laser sight' sur le grab raycast est visible.

Si la variable grab_mode n'est pas égale à AREA, alors il vérifie si elle est égale à RAYCAST. Si c'est le cas, il règle le grab_mode à AREA et règle grab_raycast.visible à false, de sorte que la 'laser sight' sur le grab raycast n'est pas visible.

Cette section de code change simplement la façon dont le contrôleur VR va saisir les nœuds basés sur RigidBody lorsque le bouton de saisie est pressé. Si grab_mode est réglé sur AREA, alors le nœud Area dans grab_area sera utilisé pour détecter les nœuds RigidBody, tandis que si grab_mode est réglé sur RAYCAST, le nœud Raycast dans grab_raycast sera utilisé pour détecter les nœuds RigidBody.

Explication étape par étape de la fonction button_released

L'unique section de code dans cette fonction vérifie si l'index du bouton qui vient d'être relâché, button_index, est égal à 15, ce qui devrait correspondre au bouton de déclenchement du contrôleur VR. La variable button_index est passée par le signal button_release dans ARVRController, que nous avons connecté dans la fonction _ready.

Si le bouton de déclenchement vient d'être relâché, alors la fonction on_button_released_trigger est appelée.

Explication étape par étape de la fonction _on_button_released_trigger

L'unique section de code dans cette fonction vérifie d'abord si le contrôleur VR essaie d téléporter en vérifiant si la variable teleport_button_down est égale à true.

Si la variable teleport_button_down est égale à true, le code vérifie alors s'il y a une position de téléportation définie et si le maillage de téléportation est visible. Il le fait en vérifiant si teleport_pos n'est pas égal à null et si teleport_mesh.visible est égal à true.

Si une position de téléportation est définie et que le maillage de téléportation est visible, le code calcule alors le décalage de la caméra vers le nœud ARVROrigin, qui est supposé être le nœud parent du contrôleur VR. Pour calculer le décalage, la position globale (global_transform.origin) du nœud Player_Camera est soustraite de la position globale du ARVROrigin. Le résultat sera un vecteur qui pointe de ARVROrigin vers ARVRCamera, que nous stockons dans une variable appelée camera_offset.

La raison pour laquelle nous devons connaître le décalage est que certains casques VR utilisent le room tracking, où la caméra du joueur peut être décalée du nœud ARVROrigin. C'est pourquoi, lorsque nous nous téléportons, nous voulons conserver le décalage créé par le room tracking, de sorte que lorsque le joueur se téléporte, le décalage créé par le room tracking ne soit pas appliqué. Sans cela, si vous vous déplacez dans une pièce et que vous vous téléportez ensuite, au lieu d'apparaître à la position où vous voulez vous téléporter, votre position serait décalée de la distance que vous avez par rapport au nœud ARVROrigine.

Maintenant que nous connaissons le décalage entre la caméra VR et l'origine VR, nous devons supprimer la différence sur l'axe Y. Nous le faisons parce que nous ne voulons pas compenser en fonction de la taille de l'utilisateur. Si nous ne le faisions pas, lors de la téléportation, la tête du joueur serait au niveau du sol.

Ensuite, nous pouvons 'teleporter' le joueur en fixant la position globale (global_transform.origin) du nœud ARVROrigin à la position stockée dans teleport_pos avec camera_offset soustrait de celle-ci. Cela permet de téléporter le joueur et de supprimer le décalage de room tracking, de sorte que les utilisateurs apparaissent exactement là où ils veulent lorsqu'ils se téléportent.

Enfin, que le contrôleur VR ait ou non téléporté l'utilisateur, nous réinitialisons les variables liées à la téléportation. teleport_button_down est réglé sur false, teleport_mesh.visible est réglé sur false donc le maillage est invisible, teleport_raycast.visible est réglé sur false, et teleport_pos est réglé sur null.

Explication étape par étape de la fonction sleep_area_entered

L'unique section de code dans cette fonction vérifie si le nœud PhysicsBody qui est entré dans le nœud Sleep_Area a une variable appelée can_sleep. Si c'est le cas, alors il met la variable can_sleep à false et met la variable sleeping à false.

Sans cela, les nœuds dormants PhysicsBody ne pourraient pas être ramassés par le contrôleur VR, même si le contrôleur VR est à la même position que le nœud PhysicsBody. Pour contourner ce problème, il suffit de 'réveiller' les nœuds PhysicsBody qui sont proches du contrôleur VR.

Explication étape par étape de la fonction sleep_area_exited

L'unique section de code dans cette fonction vérifie si le nœud PhysicsBody qui est entré dans le nœud Sleep_Area a une variable appelée can_sleep. Si c'est le cas, alors il met la variable can_sleep à true.

Cela permet aux nœuds RigidBody qui quittent la Sleep_Area de se rendormir, ce qui permet de gagner en performance.


D'accord, ouf ! C'était beaucoup de code ! Ajoutez le même script, VR_Controller.gd à l'autre scène de contrôleur VR pour que les deux contrôleurs VR aient le même script.

Il ne nous reste plus qu'une chose à faire avant de tester le projet ! Pour l'instant, nous faisons référence à une classe appelée VR_Interactable_Rigidbody, mais nous ne l'avons pas encore définie. Bien que nous n'utiliserons pas VR_Interactable_Rigidbody dans ce tutoriel, créons-le très rapidement pour que le projet puisse être exécuté.

Création d'une classe de base pour les objets VR interactifs

Avec l'onglet Script encore ouvert, créez un nouveau GDScript appelé VR_Interactable_Rigidbody.gd.

Astuce

Vous pouvez créer des GDScripts dans l'onglet Script en appuyant sur Fichier -> Nouveau Script....

Une fois que vous avez VR_Interactable_Rigidbody.gd ouvert, ajoutez le code suivant :

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

Passons rapidement en revue ce qu'est ce script.


Nous commençons d'abord le script par class_name VR_Interactable_Rigidbody. Ce que cela fait, c'est qu'il indique à Godot que ce GDScript est une nouvelle classe qui s'appelle VR_Interactable_Rigidbody. Cela nous permet de comparer les nœuds avec la classe VR_Interactable_Rigidbody dans d'autres fichiers de script sans avoir à charger directement le script ou à faire quoi que ce soit de spécial. Nous pouvons comparer la classe comme toutes les classes Godot intégrées.

Ensuite, il y a une variable de classe appelée controller. controller sera utilisé pour contenir une référence au contrôleur VR qui détient actuellement l'objet. Si un contrôleur VR ne tient pas d'objet, alors la variable controller sera null. La raison pour laquelle nous avons besoin d'une référence au contrôleur VR est que les objets tenus peuvent accéder aux données spécifiques du contrôleur VR, comme controller_velocity.

Enfin, nous avons quatre fonctions. La fonction _ready est définie par Godot et tout ce que nous faisons c'est simplement avoir pass car il n'y a rien à faire lorsque l'objet est ajouté à la scène dans VR_Interactable_Rigidbody.

La fonction interact est une fonction stub qui sera appelée lorsque le bouton interact du contrôleur VR, le déclencheur dans ce cas, est pressé alors que l'objet est maintenu.

Astuce

Une fonction stub est une fonction qui est définie mais qui n'a pas de code. Les fonctions stub sont généralement conçues pour être écrasées ou étendues. Dans ce projet, nous utilisons les fonctions stub afin d'obtenir une interface cohérente pour tous les objets interactifs RigidBody.

Les fonctions picked_up et dropped sont des fonctions stub qui seront appelées lorsque l'objet sera ramassé et déposé par le contrôleur VR.


C'est tout ce que nous devons faire pour l'instant ! Dans la prochaine partie de cette série de tutoriels, nous allons commencer à faire les objets spéciaux interactifs RigidBody.

Maintenant que la classe de base est définie, le code dans le contrôleur VR devrait fonctionner. Essayez à nouveau le jeu et vous devriez découvrir que vous pouvez vous téléporter en appuyant sur le touch pad et que vous pouvez saisir et lancer des objets en utilisant les boutons de saisie.

Maintenant, vous pouvez essayer de vous déplacer en utilisant les trackpads et/ou les joysticks, mais cela pourrait vous rendre malade !

L'une des principales raisons pour lesquelles cela peut vous donner le mal des transports est que votre vision vous dit que vous bougez, alors que votre corps ne bouge pas. Ce conflit de signaux peut rendre le corps malade. Ajoutons un shader vignette pour aider à réduire le mal des transports lors des déplacements VR !

Réduire le mal des transports

Note

Il existe de nombreuses façons de réduire le mal des transports en VR, et il n’y a pas une seule façon parfaite de réduire le mal des transports. Voir cette page sur l'Oculus Developer Center, pour plus d’informations sur la façon d’implémenter la locomotion et de réduire le mal des transports.

Pour aider à réduire le mal des transports pendant les déplacements, nous allons ajouter un effet de vignette qui ne sera visible que lorsque le joueur se déplacera.

Tout d'abord, revenez rapidement à Game.tscn. Sous le nœud ARVROrigin il y a un nœud enfant appelé Movement_Vignette. Ce nœud va appliquer une simple vignette au casque VR lorsque le joueur se déplace à l'aide des contrôleurs VR. Cela devrait contribuer à réduire le mal des transports.

Ouvrez le fichier Movement_Vignette.tscn, que vous trouverez dans le dossier Scenes. La scène est juste un nœud ColorRect avec un shader personnalisé. N'hésitez pas à regarder le shader personnalisé si vous le souhaitez, c'est juste une version légèrement modifiée du shader vignette que vous pouvez trouver dans le dépôt de démo Godot.

Écrivons le code qui rendra le shader vignette visible lorsque le joueur est en mouvement. Sélectionnez le nœud Movement_Vignette et créez un nouveau script appelé Movement_Vignette.gd. Ajouter le code suivant :

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

Comme ce script est assez bref, passons rapidement en revue son contenu.

Explication du code de vignette

Il y a deux variables de classe, controller_one et controller_two. Ces variables contiendront des références aux contrôleurs VR gauche et droit.


Dans la fonction _ready, on attend d'abord quatre images en utilisant yield. Si nous attendons quatre images, c'est parce que nous voulons nous assurer que l'interface VR est prête et accessible.

Après avoir attendu, l'interface VR primaire est récupérée en utilisant ARVRServer.primary_interface, qui est assignée à une variable appelée interface. Le code vérifie ensuite si interface est égal à null. Si interface est égal à null, alors _process est désactivé en utilisant set_process avec une valeur de false.

Si interface n'est pas null, alors nous fixons la rect_size du shader vignette à la taille de rendu du viewport VR de façon à ce qu'elle occupe tout l'écran. Nous devons le faire parce que les différents casques de VR ont des résolutions et des rapports d'aspect différents, nous devons donc redimensionner le nœud en conséquence. Nous avons également mis à zéro la rect_position du shader vignette afin qu'il soit dans la bonne position par rapport à l'écran.

Les contrôleurs VR gauche et droit sont alors récupérés et affectés respectivement au controller_one et au controller_two. Enfin, le shader vignette est rendu invisible par défaut en réglant sa propriété visible sur false.


Dans _process, le code vérifie d'abord si controller_one ou controller_two est égal à null. Si l'un des nœuds est égal à null, alors return est appelé de sorte que rien ne se passe.

Ensuite, le code vérifie si l'un des contrôleurs VR déplace le joueur en utilisant le touchpad/joystick en vérifiant si directional_movement est égal à true dans controller_one ou controller_two. Si l'un des contrôleurs VR déplace le joueur, alors le shader vignette se rend visible en réglant sa propriété visible à true. Si aucun des deux contrôleurs VR ne déplace le joueur, donc directional_movement est false dans les deux contrôleurs VR, alors le shader vignette se rend invisible en mettant sa propriété visible à false.


C'est tout le script ! Maintenant que nous avons écrit le code, allez-y et essayez de vous déplacer avec le trackpad et/ou le joystick. Vous devriez constater que cela rend moins malade qu'auparavant !

Note

Comme mentionné précédemment, il existe de nombreux moyens de réduire le mal des transports dans la VR. Consultez cette page sur le Oculus Developer Center pour plus d'informations sur la façon de mettre en œuvre la locomotion et de réduire le mal des transports.

Notes finales

../../../../_images/starter_vr_tutorial_hands.png

Vous disposez maintenant de contrôleurs VR entièrement fonctionnels qui peuvent bouger dans l'environnement et interagir avec des objets basés sur RigidBody. Dans la prochaine partie de cette série de tutoriels, nous allons créer des objets spéciaux basés sur RigidBody que le joueur pourra utiliser !

Avertissement

Vous pouvez télécharger le projet terminé pour cette série de tutoriels sur le dépôt GitHub de Godot OpenVR, sous l'onglet releases !