Tutoriel pour commencer en VR partie 2

Introduction

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

Dans cette partie de la série de tutoriels pour débuter en VR, nous allons ajouter un certain nombre de nœuds spéciaux basés sur RigidBody qui peuvent être utilisés dans la VR.

Ceci continue là où nous nous sommes arrêtés dans la dernière partie du tutoriel, où nous venons de terminer de faire fonctionner les contrôleurs VR et de définir une classe personnalisée appelée VR_Interactable_Rigidbody.

Astuce

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

Ajout de cibles destructibles

Avant de créer des nœuds spéciaux basés sur RigidBody, nous avons besoin de leur faire faire quelque chose. Faisons une simple sphère cible qui se brisera en mille morceaux lorsqu'elle sera détruite.

Ouvrez le fichier Sphere_Target.tscn, qui se trouve dans le dossier Scenes. La scène est assez simple, avec juste un nœud StaticBody avec une sphère en forme de CollisionShape, un nœud MeshInstance affichant un maillage de sphère, et un nœud AudioStreamPlayer3D.

Les nœuds spéciaux RigidBody vont gérer les dommages causés à la sphère, c'est pourquoi nous utilisons un nœud StaticBody au lieu de quelque chose comme un nœud Area ou RigidBody. En dehors de cela, il n'y a pas vraiment grand-chose à dire, alors passons directement à l'écriture du code.

Sélectionnez le nœud Sphere_Target_Root et créez un nouveau script appelé Sphere_Target.gd`. Ajouter le code suivant :

extends Spatial

var destroyed = false
var destroyed_timer = 0
const DESTROY_WAIT_TIME = 80

var health = 80

const RIGID_BODY_TARGET = preload("res://Assets/RigidBody_Sphere.scn")


func _ready():
    set_physics_process(false)


func _physics_process(delta):
    destroyed_timer += delta
    if destroyed_timer >= DESTROY_WAIT_TIME:
        queue_free()


func damage(damage):
    if destroyed == true:
        return

    health -= damage

    if health <= 0:

        get_node("CollisionShape").disabled = true
        get_node("Shpere_Target").visible = false

        var clone = RIGID_BODY_TARGET.instance()
        add_child(clone)
        clone.global_transform = global_transform

        destroyed = true
        set_physics_process(true)

        get_node("AudioStreamPlayer").play()
        get_tree().root.get_node("Game").remove_sphere()

Voyons comment fonctionne le script.

Explication du code de Sphere Target

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

  • destroyed : Variable permettant de savoir si la cible de la sphère a été détruite.

  • destroyed_timer : Variable permettant de savoir depuis combien de temps la sphère cible a été détruite.

  • DESTROY_WAIT_TIME : une constante pour définir la durée pendant laquelle la cible peut être détruite avant de se libérer/se supprimer.

  • health : Une variable pour stocker la quantité de santé de la sphère cible.

  • RIGID_BODY_TARGET : Une constante pour contenir la scène de la cible sphère détruite.

Note

N'hésitez pas à jeter un coup d’œil à la scène RIGID_BODY_TARGET. Il s'agit juste d'un tas de nœuds RigidBody et d'un modèle de sphère brisée.

Nous allons instancier cette scène pour que, lorsque la cible sera détruite, on ait l'impression qu'elle s'est brisée en mille morceaux.

Explication étape par étape de la fonction _ready

Tout ce que fait la fonction _ready, c'est qu'elle empêche le _physics_process d'être appelé en appelant set_physics_process avec false comme argument. La raison pour laquelle nous faisons cela est que tout le code dans physics_process est destiné à détruire ce nœud lorsque suffisamment de temps s'est écoulé, ce que nous ne voulons faire que lorsque la cible a été détruite.

Explication étape par étape de la fonction _physics_process

D'abord, cette fonction ajoute le temps, delta, à la variable destroyed_timer. Il vérifie ensuite si destroyed_timer est supérieur ou égal à DESTROY_WAIT_TIME. Si destroyed_timer est supérieur ou égal à DESTROY_WAIT_TIME, alors la cible sphère se libère/se supprime en appelant la fonction queue_free.

Explication étape par étape de la fonction damage

La fonction damage sera appelée par les nœuds spéciaux RigidBody, qui transmettront la quantité de dommages faits à la cible, qui est une variable d'argument de fonction appelée damage. La variable damage contiendra la quantité de dommages que le nœud spécial RigidBody a fait à la sphère cible.

Cette fonction vérifie d'abord que la cible n'est pas déjà détruite en vérifiant si la variable destroyed est égale à true. Si destroyed est égal à true, alors la fonction appelle return, donc aucun autre code n'est appelé. Il s'agit simplement d'un contrôle de sécurité, de sorte que si deux choses endommagent la cible exactement au même moment, la cible ne peut pas être détruite deux fois.

Ensuite, la fonction enlève la quantité de dégâts subis, damage, de la santé de la cible, health. Il vérifie ensuite si la health est égale à zéro ou moins, ce qui signifie que la cible vient d'être détruite.

Si la cible vient d'être détruite, alors nous désactivons la propriété CollisionShape en mettant sa propriété disabled à true. Nous rendons alors la Sphere_Target MeshInstance invisible en réglant la propriété visible à false. Nous faisons cela pour que la cible ne puisse plus affecter le monde physique et pour que le maillage de la cible non brisé ne soit pas visible.

Ensuite, la fonction instance la scène RIGID_BODY_TARGET et l'ajoute comme enfant de la cible. Il fixe ensuite la global_transform de la nouvelle scène instanciée, appelée clone, à la global_transform de la cible non brisée. La cible brisée commence donc à la même position que la cible non brisée, avec la même rotation et la même échelle.

Ensuite, la fonction met la variable destroyed à true pour que la cible sache qu'elle a été détruite et appelle la fonction set_physics_process avec l'argument true. Cela lancera l'exécution du code dans _physics_process de sorte qu'après que DESTROY_WAIT_TIME secondes se soient écoulées, la cible de la sphère se libèrera/se détruira elle-même.

La fonction obtient alors le nœud AudioStreamPlayer3D et appelle la fonction play pour qu'il joue son son.

Enfin, la fonction remove_sphere est appelée dans Game.gd. Pour obtenir Game.gd, le code utilise l'arbre des scènes et se fraye un chemin de la racine de l'arbre des scènes à la racine de la scène Game.tscn.

Ajout de la fonction remove_sphere à Game.gd

Vous avez peut-être remarqué que nous appelons une fonction dans Game.gd, appelée remove_sphere, que nous n'avons pas encore définie. Ouvrez Game.gd et ajoutez les variables de classe supplémentaires suivantes :

var spheres_left = 10
var sphere_ui = null
  • spheres_left : La quantité de sphères cibles restantes dans le monde. Dans la scène Game fournie, il y a 10 sphères, ce qui correspond à la valeur initiale.

  • sphere_ui : Une référence à la sphère UI. Nous l'utiliserons plus tard dans le tutoriel pour afficher la quantité de sphères restantes dans le monde.

Avec ces variables définies, nous pouvons maintenant ajouter la fonction remove_sphere. Ajoutez le code suivant à Game.gd :

func remove_sphere():
    spheres_left -= 1

    if sphere_ui != null:
        sphere_ui.update_ui(spheres_left)

Passons rapidement en revue ce que fait cette fonction :

Tout d'abord, il retire un de la variable spheres_left. Il vérifie ensuite si la variable sphere_ui n'est pas égale à null, et si elle n'est pas égale à null, il appelle la fonction update_ui sur sphere_ui, en passant le nombre de sphères en argument de la fonction.

Note

Nous ajouterons le code pour sphere_ui plus tard dans ce tutoriel !

Maintenant, Sphere_Target est prête à être utilisée, mais nous n'avons aucun moyen de la détruire. Corrigeons cela en ajoutant des nœuds spéciaux basés sur RigidBody qui peuvent endommager les cibles.

Ajouter un pistolet

Ajoutons un pistolet comme premier nœud interactif RigidBody. Ouvrez le fichier Pistol.tscn, que vous trouverez dans le dossier Scenes.

Passons rapidement en revue quelques points importants de Pistol.tscn avant d'ajouter le code.

Tous les nœuds du fichier Pistol.tscn s'attendent à ce que le nœud racine soit tourné. Cela permet au pistolet de tourner correctement par rapport au contrôleur VR lorsqu'il est ramassé. Le nœud racine est un nœud RigidBody, dont nous avons besoin parce que nous allons utiliser la classe VR_Interactable_Rigidbody que nous avons créée dans la dernière partie de cette série de tutoriels.

Il y a un nœud MeshInstance appelé Pistol_Flash, qui est un simple maillage que nous utiliserons pour simuler l'éclair de bouche au bout du canon du pistolet. Un nœud MeshInstance appelé LaserSight est utilisé comme guide pour viser avec le pistolet, et il suit la direction du nœud Raycast, appelé Raycast, que le pistolet utilise pour détecter si sa 'balle' a touché quelque chose. Enfin, il y a un nœud AudioStreamPlayer3D à l'extrémité du pistolet que nous utiliserons pour jouer le son du tir du pistolet.

N'hésitez pas à regarder les autres parties de la scène si vous le souhaitez. La plupart des scènes sont assez simples, avec les changements majeurs mentionnés ci-dessus. Sélectionnez le nœud RigidBody appelé Pistol et faites un nouveau script appelé Pistol.gd. Ajouter le code suivant :

extends VR_Interactable_Rigidbody

var flash_mesh
const FLASH_TIME = 0.25
var flash_timer = 0

var laser_sight_mesh
var pistol_fire_sound

var raycast
const BULLET_DAMAGE = 20
const COLLISION_FORCE = 1.5


func _ready():
    flash_mesh = get_node("Pistol_Flash")
    flash_mesh.visible = false

    laser_sight_mesh = get_node("LaserSight")
    laser_sight_mesh.visible = false

    raycast = get_node("RayCast")
    pistol_fire_sound = get_node("AudioStreamPlayer3D")


func _physics_process(delta):
    if flash_timer > 0:
        flash_timer -= delta
        if flash_timer <= 0:
            flash_mesh.visible = false


func interact():
    if flash_timer <= 0:

        flash_timer = FLASH_TIME
        flash_mesh.visible = true

        raycast.force_raycast_update()
        if raycast.is_colliding():

            var body = raycast.get_collider()
            var direction_vector = raycast.global_transform.basis.z.normalized()
            var raycast_distance = raycast.global_transform.origin.distance_to(raycast.get_collision_point())

            if body.has_method("damage"):
                body.damage(BULLET_DAMAGE)
            elif body is RigidBody:
                var collision_force = (COLLISION_FORCE / raycast_distance) * body.mass
                body.apply_impulse((raycast.global_transform.origin - body.global_transform.origin).normalized(), direction_vector * collision_force)

        pistol_fire_sound.play()

        if controller != null:
            controller.rumble = 0.25


func picked_up():
    laser_sight_mesh.visible = true


func dropped():
    laser_sight_mesh.visible = false

Voyons comment fonctionne le script.

Explication du code du pistolet

Tout d'abord, remarquez comment, au lieu de extends RigidBody, nous avons extends VR_Interactable_Rigidbody. Cela fait que le script du pistolet étend la classe VR_Interactable_Rigidbody de sorte que les contrôleurs VR savent qu'il peuvent interagir avec cet objet et que les fonctions définies dans VR_Interactable_Rigidbody peuvent être appelées lorsque cet objet est tenu par un contrôleur VR.

Ensuite, regardons les variables de classe :

  • flash_mesh : Une variable pour tenir le nœud MeshInstance qui est utilisé pour simuler le flash de la bouche du canon du pistolet.

  • FLASH_TIME : Une constante pour définir la durée de visibilité de l'éclair de bouche du canon. Cela permettra également de définir la vitesse à laquelle le pistolet peut tirer.

  • flash_timer : Variable permettant de contenir la durée pendant laquelle le flash de la bouche du canon a été visible.

  • laser_sight_mesh : Une variable pour contenir le nœud MeshInstance qui agit comme la 'visée laser' du pistolet.

  • pistol_fire_sound : Une variable pour contenir le nœud AudioStreamPlayer3D utilisé pour le son de tir du pistolet.

  • raycast : Une variable pour contenir le nœud Raycast qui est utilisé pour calculer la position de la balle et normale lorsque le pistolet est tiré.

  • BULLET_DAMAGE : Une constante pour définir la quantité de dommages causés par une seule balle du pistolet.

  • COLLISION_FORCE : Une constante qui définit la quantité de force qui est appliquée aux nœuds RigidBody lorsque la balle du pistolet entre en collision.

Explication étape par étape de la fonction _ready

Cette fonction récupère les nœuds et les assigne à leurs propres variables. Pour les nœuds flash_mesh et laser_sight_mesh, les deux ont leur propriété visible fixée à false, donc ils ne sont pas visibles au départ.

Explication étape par étape de la fonction _physics_process

La fonction _physics_process vérifie d'abord si le flash de la bouche du canon du pistolet est visible en vérifiant si flash_timer est supérieur à zéro. Si flash_timer est supérieur à zéro, alors nous en retirons le temps, delta. Ensuite, nous vérifions si la variable flash_timer est zéro ou inférieure maintenant que nous en avons retiré delta. Si c'est le cas, le timer du flash de la bouche du canon du pistolet vient de se terminer et nous devons donc rendre flash_mesh invisible en définissant sa propriété visible à false.

Explication étape par étape de la fonction interact

La fonction interact vérifie d'abord si le flash de la bouche du canon du pistolet est invisible en vérifiant si le flash_timer est inférieur ou égal à zéro. Nous faisons cela pour pouvoir limiter la cadence de tir du pistolet à la durée pendant laquelle l'éclair de la bouche est visible, ce qui est une solution simple pour limiter la vitesse à laquelle le joueur peut tirer.

Si flash_timer est égal ou inférieur à zéro, nous réglons alors flash_timer sur FLASH_TIME afin qu'il y ait un délai avant que le pistolet puisse tirer à nouveau. Après cela, nous avons réglé flash_mesh.visible sur true pour que le flash de bouche du canon du pistolet soit visible tandis que flash_timer est supérieur à zéro.

Ensuite, nous appelons la fonction force_raycast_update sur le nœud Raycast dans raycast afin qu'il obtienne les dernières informations de collision du monde physique. Nous vérifions ensuite si le raycast a frappé quelque chose en vérifiant si la fonction is_colliding est égale à true.


Si le raycast frappe quelque chose, alors nous obtenons le PhysicsBody avec lequel il est entré en collision via la fonction get_collider. Nous affectons le PhysicsBody touché à une variable appelée body.

Nous obtenons alors la direction du Raycast en obtenant son axe directionnel positif Z à partir de la Basis sur la global_transform du nœud raycast. Cela nous donnera la direction dans laquelle le raycast pointe sur l'axe Z, qui est la même direction que la flèche bleue sur le gizmo Spatial lorsque le Local space mode est activé dans l'éditeur Godot. Nous stockons cette direction dans une variable appelée direction_vector.

Ensuite, nous obtenons la distance entre l'origine du Raycast et le point de collision du Raycast en obtenant la distance de la position globale, global_transform.origin du nœud raycast au point de collision de la Raycast, raycast.get_collision_point, en utilisant la fonction distance_to. Cela nous donnera la distance que le Raycast a parcourue avant sa collision, que nous stockons dans une variable appelée raycast_distance.

Ensuite, le code vérifie si le PhysicsBody, body, a une fonction/méthode appelée damage en utilisant la fonction has_method. Si le PhysicsBody a une fonction/méthode appelée dommage, alors nous appelons la fonction dommage et nous passons BULLET_DAMAGE pour qu'elle subisse les dommages de la balle qui entre en collision avec elle.

Que le nœud PhysicsBody ait une fonction dommage ou non, nous vérifions ensuite si body est un nœud basé sur RigidBody. Si body est un nœud basé sur RigidBody, alors nous voulons le pousser lorsque la balle entre en collision avec lui.

Pour calculer la quantité de force appliquée, nous prenons simplement COLLISION_FORCE et la divisons par raycast_distance, puis nous multiplions le tout par body.mass. Nous stockons ce calcul dans une variable appelée collision_force. Cela fera que les collisions sur une distance plus courte appliquent plus de force que celles sur de plus longues distances, donnant une réponse de collision légèrement plus réaliste.

Nous poussons ensuite le RigidBody en utilisant la fonction apply_impulse, où la position est un Vector3 zéro de sorte que la force est appliquée à partir du centre, et la force de collision est la variable collision_force que nous avons calculée.


Que la variable raycast ait touché quelque chose ou non, nous jouons alors le son du coup de pistolet en appelant la fonction play sur la variable pistol_fire_sound.

Enfin, nous vérifions si le pistolet est tenu par un contrôleur VR en vérifiant si la variable controller n'est pas égale à null. S'il n'est pas égal à null, nous réglons alors la propriété de rumble du contrôleur VR sur 0.25, de sorte qu'il y ait un léger grondement lorsque le pistolet fait feu.

Explication étape par étape de la fonction picked_up

Cette fonction rend simplement visible le laser_sight_mesh MeshInstance en mettant la propriété visible à true.

Explication étape par étape de la fonction dropped

Cette fonction rend simplement le laser_sight_mesh MeshInstance invisible en réglant la propriété visible sur false.

Pistolet fini

../../../../_images/starter_vr_tutorial_pistol.png

C'est tout ce qu'il nous faut pour avoir des pistolets en état de marche dans le projet ! Allez-y et lancez le projet. Si vous montez les escaliers et attrapez les pistolets, vous pouvez tirer sur les sphères cibles dans la scène en utilisant le bouton de déclenchement du contrôleur VR ! Si vous tirez sur les cibles assez longtemps, elles se briseront en morceaux.

Ajouter un fusil à pompe

Ensuite, ajoutons un fusil de chasse au projet VR.

Ajout d'un fusil de chasse spécial  RigidBody devrait être assez simple, car presque tout avec le fusil de chasse est pareil qu'avec le pistolet.

Ouvrez le fichier Shotgun.tscn, que vous trouverez dans le dossier Scenes, et jetez un coup d’œil à la scène. Presque tout est comme dans Pistol.tscn. La seule chose qui est différente, au-delà des changements de noms, est qu'au lieu d'un seul Raycast, il y a cinq nœuds Raycast. C'est parce qu'un fusil de chasse tire généralement en forme de cône, nous allons donc émuler cet effet en ayant plusieurs nœuds Raycast qui tourneront aléatoirement en forme de cône lorsque le fusil de chasse tire.

En dehors de cela, tout est plus ou moins comme Pistol.tscn.

Écrivons le code du fusil de chasse. Sélectionnez le nœud RigidBody appelé Shotgun et faites un nouveau script appelé Shotgun.gd. Ajouter le code suivant :

extends VR_Interactable_Rigidbody

var flash_mesh
const FLASH_TIME = 0.25
var flash_timer = 0

var laser_sight_mesh
var shotgun_fire_sound

var raycasts
const BULLET_DAMAGE = 30
const COLLISION_FORCE = 4


func _ready():
    flash_mesh = get_node("Shotgun_Flash")
    flash_mesh.visible = false

    laser_sight_mesh = get_node("LaserSight")
    laser_sight_mesh.visible = false

    raycasts = get_node("Raycasts")
    shotgun_fire_sound = get_node("AudioStreamPlayer3D")


func _physics_process(delta):
    if flash_timer > 0:
        flash_timer -= delta
        if flash_timer <= 0:
            flash_mesh.visible = false


func interact():
    if flash_timer <= 0:

        flash_timer = FLASH_TIME
        flash_mesh.visible = true

        for raycast in raycasts.get_children():

            if not raycast is RayCast:
                continue

            raycast.rotation_degrees = Vector3(90 + rand_range(10, -10), 0, rand_range(10, -10))

            raycast.force_raycast_update()
            if raycast.is_colliding():

                var body = raycast.get_collider()
                var direction_vector = raycasts.global_transform.basis.z.normalized()
                var raycast_distance = raycasts.global_transform.origin.distance_to(raycast.get_collision_point())

                if body.has_method("damage"):
                    body.damage(BULLET_DAMAGE)

                if body is RigidBody:
                    var collision_force = (COLLISION_FORCE / raycast_distance) * body.mass
                    body.apply_impulse((raycast.global_transform.origin - body.global_transform.origin).normalized(), direction_vector * collision_force)

        shotgun_fire_sound.play()

        if controller != null:
            controller.rumble = 0.25


func picked_up():
    laser_sight_mesh.visible = true


func dropped():
    laser_sight_mesh.visible = false

La majorité de ce code est exactement le même que celui du pistolet, avec seulement quelques changements minuscules qui sont principalement juste des noms différents. Étant donné la similitude de ces scripts, concentrons-nous sur les changements.

Explication du code du fusil de chasse

Comme pour le pistolet, le fusil de chasse étend VR_Interactable_Rigidbody, de sorte que les contrôleurs de la VR savent qu'ils peuvent interagir avec cet objet et quelles sont les fonctions disponibles.

Il n'y a qu'une seule nouvelle variable de classe :

  • raycasts : Une variable pour contenir le nœud qui a tous les nœuds Raycast comme ses enfants.

La nouvelle variable de classe remplace la variable raycast de Pistol.gd, car avec le fusil de chasse nous devons traiter plusieurs nœuds Raycast au lieu d'un seul. Toutes les autres variables de classe sont identiques à Pistol.gd et fonctionnent de la même manière, certaines sont juste renommées pour être non spécifiques au pistolet.

Explication étape par étape de la fonction interact

La fonction interact vérifie d'abord si le flash de la bouche du canon du fusil est invisible en vérifiant si le flash_timer est inférieur ou égal à zéro. Nous le faisons pour pouvoir limiter la cadence de tir du fusil à la durée pendant laquelle l'éclair de la bouche du canon est visible, ce qui est une solution simple pour limiter la vitesse à laquelle le joueur peut tirer.

Si flash_timer est égal ou inférieur à zéro, nous mettons alors flash_timer à FLASH_TIME afin qu'il y ait un délai avant que le fusil ne puisse tirer à nouveau. Après cela, nous avons réglé flash_mesh.visible à true, de sorte que le flash de la bouche à l'extrémité du fusil soit visible tandis que flash_timer est supérieur à zéro.

Ensuite, nous appelons la fonction force_raycast_update sur le nœud Raycast dans raycast afin qu'il obtienne les dernières informations de collision du monde physique. Nous vérifions ensuite si le raycast a frappé quelque chose en vérifiant si la fonction is_colliding est égale à true.

Ensuite, nous passons par chacun des nœuds enfants de la variable raycasts en utilisant une boucle for. De cette façon, le code passera par chacun des nœuds Raycast qui sont les enfants de la variable raycasts.


Pour chaque nœud, nous vérifions si raycast n'est pas un nœud Raycast. Si le nœud n'est pas un nœud Raycast, nous utilisons simplement continue pour le sauter.

Ensuite, nous faisons tourner le nœud raycast de façon aléatoire autour d'un petit cône de 10 degrés en réglant la variable rotation_degrees du raycast sur un Vector3 où les axes X et Z sont un nombre aléatoire compris entre -10 à 10. Ce nombre aléatoire est sélectionné à l'aide de la fonction rand_range.

Ensuite, nous appelons la fonction force_raycast_update sur le nœud Raycast dans raycast afin qu'elle obtienne les dernières informations de collision du monde physique. Nous vérifions ensuite si le raycast a touché quelque chose en vérifiant si la fonction is_colliding est égale à true.

Le reste du code est exactement le même, mais ce processus est répété pour chaque nœud Raycast qui est un enfant de la variable raycasts.


Si le raycast frappe quelque chose, alors nous obtenons le PhysicsBody avec lequel il est entré en collision via la fonction get_collider. Nous affectons le PhysicsBody touché à une variable appelée body.

Nous obtenons alors la direction du raycast en obtenant son axe directionnel Z positif à partir de la Basis sur la global_transform du nœud raycast. Cela nous donnera la direction dans laquelle le raycast pointe sur l'axe Z, qui est la même direction que la flèche bleue sur le gizmo Spatial lorsque le mode Local space mode est activé dans l'éditeur Godot. Nous stockons cette direction dans une variable appelée direction_vector.

Ensuite, nous obtenons la distance entre l'origine du raycast et le point de collision du raycast en obtenant la distancede la position globale, global_transform.origin du nœud raycast et le point de collision du raycast, raycast.get_collision_point, en utilisant la fonction distance_to. Cela nous donnera la distance que le Raycast a parcourue avant sa collision, que nous stockons dans une variable appelée raycast_distance.

Ensuite, le code vérifie si le PhysicsBody, body, a une fonction/méthode appelée damage en utilisant la fonction has_method. Si le PhysicsBody a une fonction/méthode appelée dommage, alors nous appelons la fonction dommage et nous passons BULLET_DAMAGE pour qu'elle subisse les dommages de la balle qui entre en collision avec elle.

Que le nœud PhysicsBody ait une fonction dommage ou non, nous vérifions ensuite si body est un nœud basé sur RigidBody. Si body est un nœud basé sur RigidBody, alors nous voulons le pousser lorsque la balle entre en collision avec lui.

Pour calculer la quantité de force appliquée, nous prenons simplement COLLISION_FORCE et la divisons par raycast_distance, puis nous multiplions le tout par body.mass. Nous stockons ce calcul dans une variable appelée collision_force. Cela fera que les collisions sur une distance plus courte appliquent plus de force que celles sur de plus longues distances, donnant une réponse de collision légèrement plus réaliste.

Nous poussons ensuite le RigidBody en utilisant la fonction apply_impulse, où la position est un Vector3 zéro de sorte que la force est appliquée à partir du centre, et la force de collision est la variable collision_force que nous avons calculée.


Une fois que toutes les variables Raycasts de la variable raycast ont été itérées, nous jouons alors le son du coup de fusil en appelant la fonction play avec la variable shotgun_fire_sound.

Enfin, nous vérifions si le fusil est tenu par un contrôleur VR en vérifiant si la variable controller n'est pas égale à null. Si elle n'est pas égal à null, nous réglons alors la propriété de rumble du contrôleur VR sur 0.25, de sorte qu'il y ait un léger grondement lorsque le fusil tire.

Fusil de chasse terminé

Tout le reste est exactement le même que le pistolet, avec tout au plus quelques simples changements de nom.

Maintenant, le fusil de chasse est terminé ! Vous pouvez trouver le fusil de chasse dans la scène exemple en regardant derrière l'un des murs (mais pas dans le bâtiment !).

Ajouter une bombe

Ok, ajoutons un autre RigidBody spécial. Au lieu d'ajouter quelque chose qui tire, ajoutons quelque chose que nous pouvons lancer - une bombe !

Ouvrez Bomb.tscn, qui se trouve dans le dossier Scenes.

Le nœud racine est un nœud RigidBody que nous allons étendre pour utiliser VR_Interactable_Rigidbody, qui a un nœud CollisionShape comme les autres nœuds RigidBody spéciaux que nous avons créés jusqu'à présent. De même, il y a un MeshInstance appelé Bomb qui est utilisé pour afficher le maillage de la bombe.

Ensuite, nous avons un nœud Area simplement appelé Area qui a un grand CollisionShape comme enfant. Nous utiliserons ce nœud Area pour affecter tout ce qui s'y trouve lorsque la bombe explosera. Essentiellement, ce nœud Area sera le rayon d'explosion de la bombe.

Il y a aussi quelques nœuds Particules. L'un des nœuds Particules est destiné à la fumée qui sort du détonateur de la bombe, tandis qu'un autre est destiné à l'explosion. Vous pouvez jeter un œil aux ressources ParticlesMaterial, qui définissent le fonctionnement des particules, si vous le souhaitez. Nous ne couvrirons pas le fonctionnement des particules dans ce tutoriel car il ne fait pas partie du sujet.

Il y a une chose avec les nœuds Particles que nous devons noter. Si vous sélectionnez le nœud Explosion_Particles, vous verrez que sa propriété lifetime est fixée à 0.75 et que la case one shot est activée. Cela signifie que les particules ne seront jouer qu'une fois, et que les particules dureront 0,75 seconde. Nous aurons besoin de savoir cela pour pouvoir chronométrer le retrait de la bombe à la fin de l'explosion Particules.

Écrivons le code de la bombe. Sélectionnez le nœud Bomb` RigidBody et créez un nouveau script appelé Bomb.gd. Ajouter le code suivant :

extends VR_Interactable_Rigidbody

var bomb_mesh

const FUSE_TIME = 4
var fuse_timer = 0

var explosion_area
const EXPLOSION_DAMAGE = 100
const EXPLOSION_TIME = 0.75
var explosion_timer = 0
var exploded = false

const COLLISION_FORCE = 8

var fuse_particles
var explosion_particles
var explosion_sound


func _ready():

    bomb_mesh = get_node("Bomb")
    explosion_area = get_node("Area")
    fuse_particles = get_node("Fuse_Particles")
    explosion_particles = get_node("Explosion_Particles")
    explosion_sound = get_node("AudioStreamPlayer3D")

    set_physics_process(false)


func _physics_process(delta):

    if fuse_timer < FUSE_TIME:

        fuse_timer += delta

        if fuse_timer >= FUSE_TIME:

            fuse_particles.emitting = false

            explosion_particles.one_shot = true
            explosion_particles.emitting = true

            bomb_mesh.visible = false

            collision_layer = 0
            collision_mask = 0
            mode = RigidBody.MODE_STATIC

            for body in explosion_area.get_overlapping_bodies():
                if body == self:
                    pass
                else:
                    if body.has_method("damage"):
                        body.damage(EXPLOSION_DAMAGE)

                    if body is RigidBody:
                        var direction_vector = body.global_transform.origin - global_transform.origin
                        var bomb_distance = direction_vector.length()
                        var collision_force = (COLLISION_FORCE / bomb_distance) * body.mass
                        body.apply_impulse(Vector3.ZERO, direction_vector.normalized() * collision_force)

            exploded = true
            explosion_sound.play()


    if exploded:

        explosion_timer += delta

        if explosion_timer >= EXPLOSION_TIME:

            explosion_area.monitoring = false

            if controller != null:
                controller.held_object = null
                controller.hand_mesh.visible = true

                if controller.grab_mode == "RAYCAST":
                    controller.grab_raycast.visible = true

            queue_free()


func interact():
    set_physics_process(true)

    fuse_particles.emitting = true

Voyons comment fonctionne le script.

Explication du code de la bombe

Comme avec les autres nœuds spéciaux RigidBody, la bombe étend VR_Interactable_Rigidbody de sorte que les contrôleurs VR savent qu'ils peuvent interagir avec cet objet et que les fonctions définies dans VR_Interactable_Rigidbody peuvent être appelées lorsque cet objet est tenu par un contrôleur VR.

Ensuite, regardons les variables de classe :

  • bomb_mesh : Une variable pour tenir le nœud MeshInstance qui est utilisé pour la bombe non explosée.

  • FUSE_TIME : Une constante pour définir combien de temps le fusible va 'brûler' avant que la bombe n'explose

  • fuse_timer : Une variable pour contenir le temps qui s'est écoulé depuis que l'amorce de la bombe a commencé à brûler.

  • explosion_area : Une variable pour contenir le nœud Area utilisé pour détecter les objets dans l'explosion de la bombe.

  • EXPLOSION_DAMAGE : Une constante permettant de définir la quantité de dégâts causés par l'explosion de la bombe.

  • EXPLOSION_TIME : Une constante pour définir la durée de vie de la bombe après son explosion. Cette valeur doit être la même que la propriété lifetime du nœud Particles de l'explosion.

  • explosion_timer une variable qui indique le temps écoulé depuis l'explosion de la bombe.

  • exploded : Une variable pour savoir si la bombe a explosé ou non.

  • COLLISION_FORCE : Une constante qui définit la quantité de force qui est appliquée aux nœuds RigidBody lorsque la bombe explose.

  • fuse_particles : Une variable pour contenir une référence au nœud Particles utilisé pour l'amorce de la bombe.

  • explosion_particles : Une variable pour contenir une référence au nœud Particles utilisé pour l'explosion de la bombe.

  • explosion_sound : Une variable pour contenir une référence au nœud AudioStreamPlayer3D utilisé pour le son de l'explosion.

Explication étape par étape de la fonction _ready

La fonction _ready récupère d'abord tous les nœuds de la scène de la bombe et les affecte à leurs variables de classe respectives pour une utilisation ultérieure.

Ensuite, nous appelons set_physics_process et nous passons false pour que _physics_process ne soit pas exécuté. Nous faisons cela parce que le code dans _physics_process va commencer à brûler le fusible et à faire exploser la bombe, ce que nous ne voulons faire que lorsque l'utilisateur interagit avec la bombe. Si nous ne désactivons pas le _physics_process, le fusible de la bombe se déclenchera avant que l'utilisateur n'ait la possibilité d'obtenir la bombe.

Explication étape par étape de la fonction _physics_process

La fonction _physics_process vérifie d'abord si fuse_timer est inférieur à FUSE_TIME. Si c'est le cas, alors le détonateur de la bombe brûle toujours.

Si le fusible de la bombe brûle toujours, nous ajoutons alors le temps, delta, à la variable fuse_timer. Nous vérifions ensuite si fuse_timer est supérieur ou égal à FUSE_TIME, maintenant que nous y avons ajouté delta. Si fuse_timer est supérieur ou égal à FUSE_TIME, alors le fusible vient de se terminer et nous devons faire exploser la bombe.

Pour faire exploser la bombe, nous arrêtons d'abord d'émettre des particules pour le fusible en réglant emitting à false sur fuse_particles. On dit alors au nœud explosion Particules, explosion_particules, d'émettre toutes ses particule en un seul coup en mettant one_shot à true. Après cela, nous réglons emitting à true sur explosion_particles pour que la bombe ait l'air d'avoir explosé. Pour faire croire que la bombe a explosé, nous cachons le nœud MeshInstance en réglant bomb_mesh.visible sur false.

Pour éviter que la bombe n'entre en collision avec d'autres objets du monde de la physique, nous avons réglé les propriétés collision_layer et collision_mask de la bombe sur 0. Nous changeons également le mode RigidBody en MODE_STATIC pour que le RigidBody de la bombe ne bouge pas.

Ensuite, nous devons récupérer tous les nœuds PhysicsBody dans le nœud explosion_area. Pour ce faire, nous utilisons les get_overlapping_bodies dans une boucle for. La fonction get_overlapping_bodies retournera un tableau des nœuds PhysicsBody dans le nœud Area, ce qui est exactement ce que nous recherchons.


Pour chaque nœud PhysicsBody, que nous stockons dans une variable appelée body, nous vérifions s'il est égal à self. Nous faisons cela pour que la bombe ne s'explose elle-même pas accidentellement, car l'explosion_area pourrait potentiellement détecter la Bomb RigidBody comme un corps physique dans la zone d'explosion.

Si le nœud PhysicsBody, body, n'est pas la bombe, alors nous vérifions d'abord si le nœud PhysicsBody a une fonction appelée damage. Si le nœud PhysicsBody a une fonction appelée damage, nous l'appelons et lui passons EXPLOSION_DAMAGE pour qu'il prenne les dommages de l'explosion.

Ensuite, nous vérifions si le nœud PhysicsBody est un nœud RigidBody. Si body est un RigidBody, nous voulons le déplacer quand la bombe explose.

Pour déplacer le nœud RigidBody lorsque la bombe explose, nous devons d'abord calculer la direction de la bombe vers le nœud RigidBody. Pour ce faire, nous soustrayons la position globale de la bombe, global_transform.origin de la position globale du RigidBody. Cela nous donnera un Vecteur3 qui pointe de la bombe vers le nœud RigidBody. Nous stockons ceci Vecteur3 dans une variable appelée direction_vector.

Nous calculons ensuite à quelle distance le RigidBody est de la bombe en utilisant la fonction length sur le direction_vector. Nous stockons la distance dans une variable appelée bomb_distance.

Nous calculons ensuite la force qui sera appliquée au nœud RigidBody lorsque la bombe explosera en divisant COLLISION_FORCE par bomb_distance, et en multipliant le résultat par collision_force. Ainsi, si le nœud RigidBody est plus proche de la bombe, il sera poussé plus loin.

Enfin, nous poussons le nœud RigidBody en utilisant la fonction apply_impulse, avec une position Vector3 de zéro et la force de collision multipliée par le vecteur de direction normalisé comme force. Cela enverra le nœud RigidBody voler lorsque la bombe explosera.


Après avoir parcouru tous les nœuds PhysicsBody dans la explosion_area, nous mettons la variable exploded à true pour que le code sache que la bombe a explosé et nous appelons play avec l'argument explosion_sound pour que le son d'explosion soit joué.


D'accord, la section suivante du code commence par vérifier si exploded est égal à true.

Si exploded est égal à true, alors cela signifie que la bombe attend que les particules de l'explosion se terminent avant de se libérer/se détruire. Nous ajoutons le temps, delta, à explosion_timer afin de pouvoir suivre le temps écoulé depuis l'explosion de la bombe.

Si explosion_timer est supérieur ou égal à EXPLOSION_TIME après avoir ajouté delta, alors le minuteur d'explosion vient de se terminer.

Si le timer d'explosion vient de se terminer, nous mettons explosion_area.monitoring à false. La raison pour laquelle nous faisons cela est qu'il y avait un bug qui affichait une erreur lorsque vous libériez/supprimiez un nœud Area lorsque la propriété monitoring était vraie. Pour s'assurer que cela ne se produise pas, nous avons simplement mis monitoring à faux sur explosion_area.

Ensuite, nous vérifions si la bombe est tenue par un contrôleur VR en vérifiant si la variable controller n'est pas égale à null. Si la bombe est détenue par un contrôleur VR, nous mettons la propriété held_object du contrôleur VR, controller, à null. Comme le contrôleur VR ne tient plus rien, nous rendons visible le maillage de la main du contrôleur VR en réglant controller.hand_mesh.visible à true. Ensuite, nous vérifions si le mode de capture du contrôleur VR est RAYCAST, et si c'est le cas, nous mettons controller.grab_raycast.visible à true pour que le 'laser sight' du raycast soit visible.

Enfin, que la bombe soit tenue par un contrôleur VR ou non, nous appelons queue_free pour que la scène de la bombe soit libérée/supprimée de la scène.

Explication étape par étape de la fonction interact

D'abord, la fonction interact appelle set_physics_process avec l'argument true pour que le code dans _physics_process commence à s'exécuter. Cela allumera l'amorce de la bombe et conduira finalement à l'explosion de la bombe.

Enfin, nous commençons les particules de l'amorce en réglant fuse_particles.visible à true.

Bombe terminée

La bombe est prête ! Vous pouvez trouver les bombes dans le bâtiment orange.

En raison de la façon dont nous calculons la vitesse du contrôleur VR, il est plus facile de lancer les bombes en utilisant un mouvement de poussée plutôt qu'un mouvement de lancer plus naturel. La courbe lisse d'un mouvement de type lancer est plus difficile à suivre avec le code que nous utilisons pour calculer la vitesse des contrôleurs VR, donc ça ne fonctionne pas toujours correctement et peut conduire à des vitesses calculées de manière imprécise.

Ajouter une épée

Ajoutons un dernier nœud spécial basé sur RigidBody de qui peut détruire des cibles. Ajoutons une épée pour pouvoir couper les cibles !

Ouvrez le fichier Sword.tscn, que vous trouverez dans le dossier Scenes.

Il n'y a pas grand-chose qui se passe ici. Tous les nœuds enfants du nœud racine Sword RigidBody sont tournés pour qu'ils soient positionnés correctement quand le contrôleur VR les prend, il y a un MeshInstance nœud pour afficher l'épée, et il y a un nœud AudioStreamPlayer3D qui contient un son pour l'épée entrant en collision avec quelque chose.

Il y a cependant une chose qui est légèrement différente. Il y a un nœud KinematicBody appelé Damage_Body. Si vous le regardez, vous verrez qu'il ne se trouve sur aucune couche de collision, et qu'il ne se trouve au contraire que sur un seul masque de collision. Ainsi, le KinematicBody n'affectera pas les autres nœuds PhysicsBody de la scène, mais il sera toujours affecté par les nœuds PhysicsBody.

Nous allons utiliser le nœud Damage_Body KinematicBody pour détecter le point de collision et la normale lorsque l'épée entre en collision avec quelque chose dans la scène.

Astuce

Bien que ce ne soit peut-être pas la meilleure façon d'obtenir les informations sur les collisions du point de vue des performances, cela nous donne beaucoup d'informations que nous pouvons utiliser pour le post-traitement ! En utilisant un KinematicBody de cette façon, nous pouvons détecter exactement où cette épée est entrée en collision avec d'autres nœuds PhysicsBody.

C'est vraiment la seule chose digne d'intérêt sur la scène de l'épée. Sélectionnez le nœud SwordRigidBody et créez un nouveau script appelé Sword.gd. Ajouter le code suivant :

extends VR_Interactable_Rigidbody

const SWORD_DAMAGE = 2

const COLLISION_FORCE = 0.15

var damage_body = null


func _ready():
    damage_body = get_node("Damage_Body")
    damage_body.add_collision_exception_with(self)
    sword_noise = get_node("AudioStreamPlayer3D")


func _physics_process(_delta):

    var collision_results = damage_body.move_and_collide(Vector3.ZERO, true, true, true);

    if (collision_results != null):
        if collision_results.collider.has_method("damage"):
            collision_results.collider.damage(SWORD_DAMAGE)

        if collision_results.collider is RigidBody:
            if controller == null:
                collision_results.collider.apply_impulse(
                    collision_results.position,
                    collision_results.normal * linear_velocity * COLLISION_FORCE)
            else:
                collision_results.collider.apply_impulse(
                    collision_results.position,
                    collision_results.normal * controller.controller_velocity * COLLISION_FORCE)

        sword_noise.play()

Voyons comment fonctionne ce script !

Explication du code de l'épée

Comme pour les autres nœuds spéciaux RigidBody, l'épée étend VR_Interactable_Rigidbody de sorte que les contrôleurs VR savent qu'ils peuvent interagir avec cet objet et que les fonctions définies dans VR_Interactable_Rigidbody peuvent être appelées lorsque cet objet est tenu par un contrôleur VR.

Ensuite, regardons les variables de classe :

  • SWORD_DAMAGE : Une constante pour définir la quantité des dégâts causés par l'épée. Ce dommage est appliqué à tous les objets de l'épée à chaque appel de _physics_process

  • COLLISION_FORCE : Une constante qui définit la quantité de force appliquée aux nœuds RigidBody lorsque l'épée entre en collision avec un PhysicsBody.

  • damage_body : Une variable pour contenir le nœud KinematicBody utilisé pour détecter si l'épée blesse ou non un nœud PhysicsBody.

  • sword_noise : Une variable pour contenir le nœud AudioStreamPlayer3D utilisé pour jouer un son lorsque l'épée entre en collision avec quelque chose.

Explication étape par étape de la fonction _ready

Tout ce que nous faisons dans la fonction _ready est d'obtenir le nœud Damage_Body KinematicBody et de l'assigner à damage_body. Parce que nous ne voulons pas que l'épée détecte une collision avec la racine RigidBody du nœud de l'épée, nous appelons add_collision_exception_with sur damage_body et passons self pour que l'épée ne soit pas détectée.

Enfin, on obtient le nœud AudioStreamPlayer3D pour le son de collision d'épée et on l'applique à la variable sword_noise.

Explication étape par étape de la fonction _physics_process

Nous devons d'abord déterminer si l'épée entre en collision avec quelque chose ou non. Pour ce faire, nous utilisons la fonction move_and_collide du nœud damage_body. Contrairement à la façon dont on utilise normalement move_and_collide, nous ne passons pas une vitesse et nous passons plutôt un Vecteur3 vide. Parce que nous ne voulons pas que le nœud damage_body se déplace, nous avons défini l'argument test_only (le quatrième argument) comme true donc le KinematicBody génère des informations sur les collisions sans provoquer réellement de collisions dans le monde des collisions.

La fonction move_and_collide renverra un KinematicCollision qui contient toutes les informations dont nous avons besoin pour détecter les collisions sur l'épée. Nous assignons la valeur de retour de move_and_collide à une variable appelée collision_results.

Ensuite, nous vérifions si collision_results n'est pas égal à null. Si collision_results n'est pas égal à null, alors nous savons que l'épée a heurté quelque chose.

Nous vérifions ensuite si le PhysicsBody avec lequel l'épée est entrée en collision a une fonction/méthode appelée dommage en utilisant la fonction has_method. Si le PhysicsBody a une fonction appelée damage_body, nous l'appelons et lui transmettons la quantité de dégâts que l'épée fait, SWORD_DAMAGE.

Ensuite, nous vérifions si le PhysicsBody avec lequel l'épée est entrée en collision est un RigidBody. Si l'épée est entrée en collision avec un nœud RigidBody, nous vérifions alors si l'épée est tenue par un contrôleur VR ou non en vérifiant si contrôleur est égal à null.

Si l'épée n'est pas tenue par un contrôleur VR, controller est égal à null, alors nous déplaçons le nœud RigidBody avec lequel l'épée est entrée en collision en utilisant la fonction apply_impulse. Pour la position de la fonction apply_impulse, nous utilisons la variable collision_position stockée dans la classe KinematicCollision dans collision_results. Pour la velocity de la fonction apply_impulse, nous utilisons la collision_normal multipliée par la linear_velocity du nœud de l'épée RigidBody multipliée par COLLISION_FORCE.

Si l'épée est tenue par un contrôleur VR, controller n'est pas égal à null, alors nous déplaçons le nœud RigidBody avec lequel l'épée est entrée en collision en utilisant la fonction apply_impulse. Pour la position de la fonction apply_impulse, nous utilisons la variable collision_position stockée dans la classe KinematicCollision dans collision_results. Pour la velocity de la fonction apply_impulse, nous utilisons la collision_normal multipliée par la vitesse du contrôleur VR multipliée par la COLLISION_FORCE.

Enfin, que le PhysicsBody soit un RigidBody ou non, nous jouons le son de l'épée qui entre en collision avec quelque chose en appelant play sur sword_noise.

Épée terminée

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

Cela étant fait, vous pouvez maintenant découper les cibles ! Vous pouvez trouver l'épée dans le coin entre le fusil et le pistolet.

Mise à jour de l'UI cible

Mettons à jour l'interface utilisateur au fur et à mesure que les sphères cibles sont détruites.

Ouvrez Main_VR_GUI.tscn, que vous trouverez dans le dossier Scenes. N'hésitez pas à regarder comment la scène est configurée si vous le souhaitez, mais pour éviter que ce tutoriel ne devienne trop long, nous ne couvrirons pas la configuration de la scène dans ce tutoriel.

Développez le nœud GUI Viewport et sélectionnez ensuite le nœud Base_Control. Ajoutez un nouveau script appelé Base_Control.gd, et ajoutez ce qui suit :

extends Control

var sphere_count_label

func _ready():
    sphere_count_label = get_node("Label_Sphere_Count")

    get_tree().root.get_node("Game").sphere_ui = self


func update_ui(sphere_count):
    if sphere_count > 0:
        sphere_count_label.text = str(sphere_count) + " Spheres remaining"
    else:
        sphere_count_label.text = "No spheres remaining! Good job!"

Voyons comment ce script fonctionne très rapidement.

D'abord, dans _ready, nous obtenons le Label qui indique combien de sphères il reste et nous l'attribuons à la variable de classe sphere_count_label. Ensuite, nous obtenons Game.gd en utilisant get_tree().root et nous assignons sphere_ui à ce script.

Dans update_ui, nous changeons le texte du Label sphère. S'il reste au moins une sphère, nous modifions le texte pour montrer combien de sphères il reste dans le monde. S'il ne reste plus de sphères, nous changeons le texte et félicitons le joueur.

Ajout du dernier RigidBody spécial

Enfin, avant de terminer ce tutoriel, ajoutons un moyen de réinitialiser le jeu à l'intérieur de la VR.

Ouvrez Reset_Box.tscn, que vous trouverez dans Scenes. Sélectionnez le nœud Reset_Box RigidBody et créez un nouveau script appelé Reset_Box.gd. Ajouter le code suivant :

extends VR_Interactable_Rigidbody

var start_transform

var reset_timer = 0
const RESET_TIME = 10
const RESET_MIN_DISTANCE = 1


func _ready():
    start_transform = global_transform


func _physics_process(delta):
    if start_transform.origin.distance_to(global_transform.origin) >= RESET_MIN_DISTANCE:
        reset_timer += delta
        if reset_timer >= RESET_TIME:
            global_transform = start_transform
            reset_timer = 0


func interact():
    # (Ignore the unused variable warning)
    # warning-ignore:return_value_discarded
    get_tree().change_scene("res://Game.tscn")


func dropped():
    global_transform = start_transform
    reset_timer = 0

Passons rapidement en revue le fonctionnement de ce script.

Explication du code de la boîte de réinitialisation

Comme pour les autres objets spéciaux RigidBody que nous avons créés, la boîte de réinitialisation étend VR_Interactable_Rigidbody.

La variable de classe start_transform stockera la transformation globale de la boîte de réinitialisation au début du jeu, la variable de classe reset_timer contiendra la durée qui s'est écoulée depuis que la position de la boîte de réinitialisation s'est déplacée, la constante RESET_TIME définit la durée pendant laquelle la boîte de réinitialisation doit attendre avant d'être réinitialisée, et la constante RESET_MIN_DISTANCE définit à quelle distance la boîte de réinitialisation doit se trouver de sa position initiale avant que le minuteur de réinitialisation ne commence.

Dans la fonction _ready, tout ce que nous faisons est de stocker la global_transform de la position de réinitialisation lorsque la scène commence. Nous pouvons ainsi réinitialiser la position, la rotation et l'échelle de l'objet de la boîte de réinitialisation à cette transformation initiale lorsque suffisamment de temps s'est écoulé.

Dans la fonction _physics_process, le code vérifie si la position initiale de la boîte de réinitialisation par rapport à la position actuelle de la boîte de réinitialisation est plus éloignée que la RESET_MIN_DISTANCE. Si elle est plus loin, il commence à ajouter du temps, delta, à reset_timer. Une fois que le reset_timer est supérieur ou égal à RESET_TIME, nous réinitialisons la global_transform à la start_transform pour que la boîte de réinitialisation soit à nouveau dans sa position initiale. Nous mettons ensuite reset_timer à 0.

La fonction interact recharge simplement la scène Game.tscn en utilisant get_tree().change_scene. Cela va recharger la scène de jeu, en remettant tout à zéro.

Enfin, la fonction dropped réinitialise la global_transform à la transformation initiale start_transform de sorte que la boîte de réinitialisation est à sa position/rotation initiale. Ensuite, reset_timer est réglé sur 0, ce qui réinitialise le minuteur.

Boîte de réinitialisation terminée

Une fois cela fait, lorsque vous saisissez et interagissez avec la boîte de réinitialisation, la scène entière se réinitialise/redémarre et vous pouvez à nouveau détruire toutes les cibles !

Note

Rétablir la scène brusquement sans aucune sorte de transition peut entraîner une gêne en VR.

Notes finales

../../../../_images/starter_vr_tutorial_pistol.png

Ouf ! C'était beaucoup de travail.

Vous avez maintenant un projet VR entièrement fonctionnel avec plusieurs types de nœuds spéciaux RigidBody qui peuvent être utilisés et étendus. Nous espérons que cela servira d'introduction à la création de jeux de VR complets dans Godot ! Le code et les concepts détaillés dans ce tutoriel peuvent être développés pour en faire des jeux de puzzle, des jeux d'action, des jeux basés sur l'histoire, et plus encore !

Avertissement

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