Tutoriel FPS - Partie 4 : Joypads, collectes et cibles

Répondre
Avatar du membre
keltwookie
Admin du site
Messages : 81
Enregistré le : mer. avr. 04, 2018 5:42 pm
Localisation : Kashyyyk
Contact :

Tutoriel FPS - Partie 4 : Joypads, collectes et cibles

Message par keltwookie » ven. mai 04, 2018 4:26 pm

Aperçu de la partie 4


Image


Dans cette partie, nous allons ajouter des collectes de santé et de munitions, des cibles que nous pouvons détruire, ajouter le support pour les joypads, et ajouter la possibilité de changer d'armes avec la molette de défilement de la souris.

Remarque :
Vous êtes supposé avoir terminé Tutoriel FPS - Partie 3 : Les munitions et les sons, avant de passer à cette partie du tutoriel. Le projet terminé de la partie 3 sera le projet de départ de la partie 4.


Commençons !


Ajouter l’entrée joypad


Dans Godot, tout contrôleur de jeu est appelé joypad. Cela comprend les manettes de console, les joysticks (comme pour les simulateurs de vol), les volants (comme pour les simulateurs de conduite), les périphériques de réalité virtuelles, et plus encore.
Nous devons d'abord modifier quelques éléments des contrôles de notre projet. Ouvrez les paramètres du projet et sélectionnez l'onglet Input Map (Contrôles).

Nous devons maintenant ajouter des boutons de joypad à nos différentes actions. Cliquez sur l'icône plus et sélectionnez Joy Button.


Image


N'hésitez pas à utiliser la disposition de boutons que vous voulez. Dans le projet fini, nous utiliserons ce qui suit :
  • movement_sprint: Device 0, Button 4 (L, L1)
  • fire: Device 0, Button 0 (PS Cross, XBox A, Nintendo B)
  • reload: Device 0, Button 0 (PS Square, XBox X, Nintendo Y)
  • flashlight: Device 0, Button 12 (D-Pad Up)
  • shift_weapon_positive: Device 0, Button 15 (D-Pad Right)
  • shift_weapon_negative: Device 0, Button 14 (D-Pad Right)
  • fire_grenade: Device 0, Button 1 (PS Circle, XBox B, Nintendo A).

Remarque :
Celles-ci sont déjà configurées pour vous si vous avez téléchargé les ressources de démarrage.
*Ndt : J’ai volontairement laissé cette liste en Anglais pour que le lecteur puisse s’y retrouver dans le projet* .


Une fois que vous êtes satisfait de votre choix, fermez les paramètres du projet et enregistrez.

Ouvrons maintenant Player.gd et ajoutons le joypad.

Tout d'abord, nous devons définir quelques nouvelles variables globales à Player.gd :

Code : Tout sélectionner

# You may need to adjust depending on the sensitivity of your joypad
var JOYPAD_SENSITIVITY = 2
const JOYPAD_DEADZONE = 0.15
Voyons ce à quoi chacune de ces variables correspond :
  • JOYPAD_SENSITIVITY: C'est la vitesse à laquelle nos joysticks/joypads déplaceront notre caméra.
  • JOYPAD_DEADZONE: La zone morte pour le joypad. Il se peut que vous ayez besoin de vous ajuster en fonction de celui-ci.
Remarque :
Beaucoup de joypads tremblent autour d'un certain point. Pour contrer cela, nous ignorons tout mouvement dans un rayon JOYPAD_DEADZONE. Si nous n'ignorons pas ce mouvement, la caméra va trembler.
Aussi, nous définissons JOYPAD_SENSITIVITY comme une variable au lieu d'une constante parce que nous la changerons plus tard.


Maintenant, nous sommes prêts à commencer à gérer l'entrée des joypads !

Dans process_input ajouter le code suivant, juste avant input_movement_vector = input_movement_vector.normalized() :

Code : Tout sélectionner

# Add joypad input, if there is a joypad
if Input.get_connected_joypads().size() > 0:

    var joypad_vec = Vector2(0, 0)

    if OS.get_name() == "Windows":
        joypad_vec = Vector2(Input.get_joy_axis(0, 0), -Input.get_joy_axis(0, 1))
    elif OS.get_name() == "X11":
        joypad_vec = Vector2(Input.get_joy_axis(0, 1), Input.get_joy_axis(0, 2))
    elif OS.get_name() == "OSX":
        joypad_vec = Vector2(Input.get_joy_axis(0, 1), Input.get_joy_axis(0, 2))

    if joypad_vec.length() < JOYPAD_DEADZONE:
        joypad_vec = Vector2(0, 0)
    else:
        joypad_vec = joypad_vec.normalized() * ((joypad_vec.length() - JOYPAD_DEADZONE) / (1 - JOYPAD_DEADZONE))

    input_movement_vector += joypad_vec
Revoyons ce que nous faisons.

Tout d'abord, nous vérifions s'il y a un joypad connecté.

S'il y a un joypad connecté, on obtient alors ses axes de manche gauche pour droite/gauche et haut/bas. Parce qu'un contrôleur Xbox 360 filaire a des axes de joystick différents basés sur l'OS, nous utilisons des axes différents basés sur celui-ci.

Attention :
Ce tutoriel suppose que vous utilisez un contrôleur filaire XBox 360. De plus, je n'ai pas (actuellement) accès à un ordinateur Mac, donc les axes du joystick peuvent avoir besoin d'être changés. Si c'est le cas, veuillez ouvrir un ticket GitHub sur le dépôt de documentation Godot !


Ensuite, nous vérifions si la longueur du vecteur joypad est dans le rayon JOYPAD_DEADZONE. Si c'est le cas, nous réglons joypad_vec sur un Vector2 vide. Si ce n'est pas le cas, nous utilisons une zone morte (deadzone) radiale à l'échelle pour le calcul précis de celle-ci.

Remarque :
Vous trouverez un excellent article expliquant comment gérer les zones mortes des joypads/contrôleur ici : https://www.third-helix.com/2013/04/12/ ... right.html
Nous utilisons une version traduite du code de zone morte radiale mise à l'échelle fourni dans cet article. L'article est une excellente lecture, et je vous suggère fortement de lui jeter un coup d'œil !


Enfin, nous ajoutons joypad_vec à input_movement_vector.

Astuce :
Vous rappelez-vous comment nous normalisons input_movement_vector ? Voilà pourquoi : Si nous n'avions pas normalisé input_movement_vector les joueurs pourraient se déplacer plus rapidement s'ils poussent dans la même direction avec le clavier + le joypad !


Faites une nouvelle fonction appelée process_view_input et ajoutez ce qui suit :

Code : Tout sélectionner

func process_view_input(delta):

    if Input.get_mouse_mode() != Input.MOUSE_MODE_CAPTURED:
        return

    # NOTE: Until some bugs relating to captured mouses are fixed, we cannot put the mouse view
    # rotation code here. Once the bug(s) are fixed, code for mouse view rotation code will go here!

    # ----------------------------------
    # Joypad rotation

    var joypad_vec = Vector2()
    if Input.get_connected_joypads().size() > 0:

        if OS.get_name() == "Windows":
            joypad_vec = Vector2(Input.get_joy_axis(0, 2), Input.get_joy_axis(0, 3))
        elif OS.get_name() == "X11":
            joypad_vec = Vector2(Input.get_joy_axis(0, 3), Input.get_joy_axis(0, 4))
        elif OS.get_name() == "OSX":
            joypad_vec = Vector2(Input.get_joy_axis(0, 3), Input.get_joy_axis(0, 4))

        if joypad_vec.length() < JOYPAD_DEADZONE:
            joypad_vec = Vector2(0, 0)
        else:
            joypad_vec = joypad_vec.normalized() * ((joypad_vec.length() - JOYPAD_DEADZONE) / (1 - JOYPAD_DEADZONE))

        rotation_helper.rotate_x(deg2rad(joypad_vec.y * JOYPAD_SENSITIVITY))

        rotate_y(deg2rad(joypad_vec.x * JOYPAD_SENSITIVITY * -1))

        var camera_rot = rotation_helper.rotation_degrees
        camera_rot.x = clamp(camera_rot.x, -70, 70)
        rotation_helper.rotation_degrees = camera_rot
    # ----------------------------------
Voyons ce qui se passe :
Tout d'abord, nous vérifions le mode de la souris. Si le mode de la souris n'est pas MOUSE_MODE_CAPTURED, nous voulons revenir, ce qui sautera le code sous return :

Ensuite, nous définissons un nouveau Vector2 appelé joypad_vec. Ceci maintiendra la position droite du joystick/joypad. Sur la base de l'OS, nous avons défini ses valeurs pour qu'il soit mappé aux axes appropriés pour le bon joystick/joypad.

Attention :
Comme indiqué précédemment, je n'ai pas (actuellement) accès à un ordinateur Mac, donc les axes du joystick peuvent avoir besoin d'être changés. Si c'est le cas, veuillez ouvrir un ticket GitHub sur le dépôt de documentation Godot !


Nous prenons alors en compte la zone morte du joypad, tout comme dans
process_input.

Ensuite, nous tournons rotation_helper et notre KinematicBody en utilisant joypad_vec.

Vous pouvez remarquer que le code qui gère notre propre rotation et rotation_helper est exactement le même que dans _input. Tout ce que nous avons fait est de changer les valeurs pour utiliser joypad_vec et JOYPAD_SENSITIVITY.

Remarque :
En raison de quelques bogues liés à la souris sur Windows, nous ne pouvons pas mettre la rotation de la souris dans process_view. Une fois ces bogues corrigés, ce sera probablement mis à jour pour placer la rotation de la souris ici aussi.


Enfin, nous fixons la rotation de la caméra pour que nous ne puissions pas regarder à l'envers.

La dernière chose à faire est d'ajouter process_view_input (delta) à _physics_process.
Une fois fait, vous devriez pouvoir jouer en utilisant un joypad !

Remarque :
J'ai décidé de ne pas utiliser les déclencheurs de joypad pour tirer parce que nous devrions alors faire un peu plus de gestion des axes, et parce que je préfère utiliser un déclencheur/gachette (ndt : « Shoulder button » dans le texte original = Gachettes du haut sur une manette Xbox 360) .


Si vous voulez utiliser les déclencheurs pour tirer, vous devrez changer le fonctionnement du tir dans process_input. Vous devez obtenir la bonne valeur d'axe pour le déclencheur, et vérifier si elle est supérieure à une certaine valeur, disons 0,8 par exemple. Si c'est le cas, il suffit d'ajouter le même code que lors de l'action de tir.


Ajouter l'entrée de la molette de la souris

Ajoutons une fonction liée à l'entrée, avant de commencer à travailler sur les collectes et la cible, afin inclure la possibilité de changer d'armes à l'aide de la molette de défilement de la souris.

Ouvrez Player.gd et ajoutez les variables globales suivantes :

Code : Tout sélectionner

var mouse_scroll_value = 0
const MOUSE_SENSITIVITY_SCROLL_WHEEL = 0.08
Passons en revue chacune de ces nouvelles variables :
  • mouse_scroll_value : La valeur de la molette de défilement de la souris.
  • MOUSE_SENSITIVITY_SCROLL_WHEEL : Combien une seule action de défilement augmente la valeur du défilement de la souris.
Ajoutons maintenant ce qui suit à _input :

Code : Tout sélectionner

if event is InputEventMouseButton and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
    if event.button_index == BUTTON_WHEEL_UP or event.button_index == BUTTON_WHEEL_DOWN:
        if event.button_index == BUTTON_WHEEL_UP:
            mouse_scroll_value += MOUSE_SENSITIVITY_SCROLL_WHEEL
        elif event.button_index == BUTTON_WHEEL_DOWN:
            mouse_scroll_value -= MOUSE_SENSITIVITY_SCROLL_WHEEL

        mouse_scroll_value = clamp(mouse_scroll_value, 0, WEAPON_NUMBER_TO_NAME.size()-1)

        if changing_weapon == false:
            if reloading_weapon == false:
                var round_mouse_scroll_value = int(round(mouse_scroll_value))
                if WEAPON_NUMBER_TO_NAME[round_mouse_scroll_value] != current_weapon_name:
                    changing_weapon_name = WEAPON_NUMBER_TO_NAME[round_mouse_scroll_value]
                    changing_weapon = true
                    mouse_scroll_value = round_mouse_scroll_value
Voyons ce qui se passe ici :

Tout d'abord, nous vérifions si l'événement est un événement InputEventMouseButton et que notre mode souris est MOUSE_MODE_CAPTURED. Ensuite, nous vérifions si l'index du bouton est un index BUTTON_WHEEL_UP ou BUTTON_WHEEL_DOWN.

Si l'index de l'événement est bien un index de molette de souris, nous vérifions alors s'il s'agit d'un index BUTTON_WHEEL_UP ou BUTTON_WHEEL_DOWN. Selon qu'elle est vers le haut ou vers le bas, nous ajoutons/supprimons MOUSE_SENSITIVITY_SCROLLL_WHEEL à/depuis la valeur de défilement de la souris.

Nous vérifions ensuite si nous changeons d'armes ou si nous rechargeons. Si nous ne faisons ni l'un ni l'autre, nous arrondissons la valeur de mouse_scroll_value et l’incluons à int.

Remarque :
Nous intégrons mouse_scroll_value à int pour que nous puissions l'utiliser comme touche dans notre dictionnaire. Si nous la laissions comme une flottante, nous aurions une erreur lorsque nous essayerions d'exécuter le projet.


Ensuite, nous vérifions si le nom de l'arme à round_mouse_scroll_value n'est pas égal au nom de l'arme courante en utilisant WEAPON_NUMBER_TO_NAME. Si l'arme est différente de notre arme actuelle, nous assignons change_weapon_name, le réglons à true afin de changer les armes dans process_changing_weapons (voir la fonction _physics_process) , et réglons mouse_scroll_value à round_mouse_scroll_value.

Astuce :
La raison pour laquelle nous réglons la valeur de défilement de la souris à une valeur arrondie est que nous ne voulons pas que le joueur garde la molette de la souris juste entre les valeurs, ce qui lui donne la possibilité de basculer extrêmement rapidement. En assignant la valeur de défilement de la souris à la valeur de défilement de la souris à round_mouse_scroll_value, nous nous assurons que chaque arme prend exactement la même valeur de défilement pour changer.


Une autre chose que nous devons changer est dans process_input. Dans le code de changement d'armes, ajoutez ce qui suit juste après la ligne changing_weapon = true :

Code : Tout sélectionner

mouse_scroll_value = weapon_change_number
Maintenant notre valeur de défilement sera changée en fonction de l’action sur le clavier. Si nous n'avions pas changé cela, notre valeur de défilement serait désynchronisée. Si la molette de défilement n'est pas synchronisée, le défilement vers l'avant ou vers l'arrière ne permet pas de passer à l'arme suivante/précédente, mais plutôt à l'arme suivante/ précédente sur laquelle la molette de défilement est placée.

Vous pouvez maintenant changer d'armes à l'aide de la molette de la souris ! Faites un essai !


Ajouter des collectes de santé

Maintenant que notre joueur a la santé et des munitions, nous avons idéalement besoin d'un moyen de reconstituer ces ressources.
Ouvrez Health_Pickup.tscn.

Développez le nœud Holder si ce n'est déjà fait. Remarquez comment nous avons deux nœuds Spatial, l'un appelé Health_Kit et un autre appelé Health_Kit_Small.

C'est parce que nous allons en fait faire deux tailles de collecte santé, l'une petite et l'autre grande/normale. Health_Kit et Health_Kit_Small n'ont qu'un seul MeshInstance comme nœud enfant.

Ensuite, développez Health_Pickup_Trigger. C'est un nœud Area que nous allons utiliser pour vérifier si le joueur a marché assez près pour ramasser le kit de santé. Si vous l'agrandissez, vous trouverez deux formes de collision, une pour chaque taille. Nous utiliserons une taille de forme de collision différente en fonction de la taille du pick-up santé, de sorte que la collecte de santé plus petite a une forme de déclenchement de collision plus proche de sa taille.

La dernière chose à noter est que nous avons un nœud AnimationPlayer pour que le kit de santé tourne lentement tout en montant et descendant.

Sélectionnez Health_Pickup et ajoutez un nouveau script appelé Health_Pickup.gd. Ajouter ce qui suit :

Code : Tout sélectionner

extends Spatial

export (int, "full size", "small") var kit_size = 0 setget kit_size_change

# 0 = full size pickup, 1 = small pickup
const HEALTH_AMOUNTS = [70, 30]

const RESPAWN_TIME = 20
var respawn_timer = 0

var is_ready = false

func _ready():

    $Holder/Health_Pickup_Trigger.connect("body_entered", self, "trigger_body_entered")

    is_ready = true

    kit_size_change_values(0, false)
    kit_size_change_values(1, false)
    kit_size_change_values(kit_size, true)


func _physics_process(delta):
    if respawn_timer > 0:
        respawn_timer -= delta

        if respawn_timer <= 0:
            kit_size_change_values(kit_size, true)


func kit_size_change(value):
    if is_ready:
        kit_size_change_values(kit_size, false)
        kit_size = value
        kit_size_change_values(kit_size, true)
    else:
        kit_size = value


func kit_size_change_values(size, enable):
    if size == 0:
        $Holder/Health_Pickup_Trigger/Shape_Kit.disabled = !enable
        $Holder/Health_Kit.visible = enable
    elif size == 1:
        $Holder/Health_Pickup_Trigger/Shape_Kit_Small.disabled = !enable
        $Holder/Health_Kit_Small.visible = enable


func trigger_body_entered(body):
    if body.has_method("add_health"):
        body.add_health(HEALTH_AMOUNTS[kit_size])
        respawn_timer = RESPAWN_TIME
        kit_size_change_values(kit_size, false)
Passons en revue ce que fait ce script, en commençant par ses variables globales :
  • kit_size : La taille du kit de santé. Remarquez comment nous utilisons une fonction setget pour dire si elle a changé.
  • HEALTH_AMMOUNTS : La quantité de santé que chaque kit de chaque taille contient.
  • RESPAWN_TIME : Le temps, en secondes, qu'il faut pour que le kit se régénère dans le jeu.
  • respawn_timer : Une variable utilisée pour suivre combien de temps l'amélioration de la santé a attendu pour se reproduire.
  • is_ready : Une variable pour savoir si la fonction _ready a été appelée ou non.
Nous utilisons is_ready parce que les fonctions setget sont appelées avant _ready, nous devons ignorer le premier appel kit_size_change, car nous ne pouvons pas accéder aux nœuds enfants jusqu'à ce que _ready soit appelé. Si nous n'ignorions pas le premier appel setget, nous aurions plusieurs erreurs dans le débogueur.

Notez aussi comment nous utilisons une variable exportée. C'est ainsi que nous pouvons changer la taille du kit de santé dans l'éditeur, pour chaque collecte. Cela fait que nous n'avons pas besoin de faire deux scènes pour les deux tailles, puisque nous pouvons facilement changer les tailles dans l'éditeur en utilisant la variable exportée.

Voir GDScript et faites défiler vers le bas jusqu'à la section Exports pour obtenir une liste d'indices d'exportation que vous pouvez utiliser.

Jetons un coup d'oeil à _ready :

Tout d'abord, nous connectons le signal body_entered de notre Health_Pickup_Trigger à la fonction trigger_body_entered. C'est ici que n'importe quel corps qui entre dans le Area déclenche la fonction trigger_body_body_entered.

Ensuite, nous mettons is_ready à true pour que nous puissions utiliser notre fonction setget.

Ensuite, nous cachons tous les kits possibles et leurs formes de collision à l'aide des valeurs kit_size_change_values. Le premier argument est la taille du kit, tandis que le second argument est de savoir s'il faut activer ou désactiver la forme de collision et le maillage pour cette taille.

Ensuite, nous ne rendons visible que la taille du kit que nous avons sélectionné, en appelant kit_size_change_values et en passant dans kit_size à true, de sorte que la taille kit_size est activée.

Regardons maintenant Kit_size_changed.

La première chose que nous faisons est de vérifier si is_ready est à true.

Si is_ready a la valeur true, nous faisons en sorte que le kit actuellement assigné à kit_size soit désactivé en utilisant kit_size_change_values, en passant à kit_size et false.

Ensuite, nous assignons kit_size à la nouvelle valeur transmise, value. Ensuite, nous appelons kit_size_change_values en passant à nouveau dans kit_size, mais cette fois avec le second argument comme true, donc nous l'activons. Parce que nous avons changé kit_size à la valeur passée, cela rendra visible n'importe quelle taille de kit.

Si is_ready n'est pas sur true, nous assignons simplement kit_size à la valeur passée.

Regardons maintenant les valeurs de kit_size_change_values .

La première chose que nous faisons est de vérifier quelle taille nous utilisons. En fonction de la taille que nous voulons activer/désactiver, nous voulons obtenir différents nœuds.

Nous obtenons la forme de collision pour le nœud correspondant à la taille et nous la désactivons sur la base du paramètre activé passé dans les arguments/variables.

Remarque :
Pourquoi utilisons-nous !enable au lieu de enable ? C'est ainsi quand nous disons que nous voulons activer le nœud, nous pouvons passer à true mais puisque CollisionShape utilise désactivé au lieu d'activé (enable), nous devons le retourner. En le retournant, nous pouvons activer la forme de collision et rendre le maillage visible lorsque true est passé.
Nous obtenons alors le noeud Spatial correspondant au maillage et réglons sa visibilité à enable.
Cette fonction peut être un peu confuse, essayez d'y penser ainsi : Nous activons ou désactivons les nœuds appropriés pour size à l'aide de l'option enable. C'est ainsi que nous ne pouvons pas prendre la collecte de santé pour une taille qui n'est pas visible, et donc, seul le maillage pour la bonne taille sera visible.


Enfin, regardons trigger_body_entered.

La première chose que nous faisons est de voir si le corps qui vient d'entrer a une méthode/fonction appelée add_health. Si c'est le cas, nous appelons alors add_health et passons dans la santé fournie par la taille actuelle du kit.

Ensuite, nous réglons respawn_timer à RESPAWN_TIME pour que nous devions attendre avant de pouvoir ré-obtenir un kit de santé. Enfin, appelez kit_size_change_values, en passant dans kit_size et false pour que le kit dans kit_size soit invisible jusqu'à ce que nous ayons attendu assez longtemps pour qu’il soir régénéré.

La dernière chose que nous avons besoin de faire avant de pouvoir utiliser cette collecte de santé est d'ajouter quelques choses à notre joueur.
Ouvrez Player.gd et ajoutez la variable globale suivante :

Code : Tout sélectionner

const MAX_HEALTH = 150
  • MAX_HEALTH : La quantité maximale de santé qu'un joueur peut avoir.
Nous devons maintenant ajouter la fonction add_health à notre joueur. Ajoutez ce qui suit à Player.gd :

Code : Tout sélectionner

func add_health(additional_health):
    health += additional_health
    health = clamp(health, 0, MAX_HEALTH)
Revoyons rapidement ce que cela fait.

Nous ajoutons d'abord additional_health à notre santé actuelle. Nous clampons alors la santé pour qu'elle ne puisse pas dépasser une valeur supérieure à MAX_HEALTH, ni une valeur inférieure à 0.

Cela fait, nous pouvons maintenant collecter les kits de santé ! Allez placer quelques scènes Health_Pickup autour et faites un essai. Vous pouvez changer la taille du kit de santé dans l'éditeur lorsqu'une scène instanciée Health_Pickup est sélectionnée, à partir d'un menu déroulant approprié.


Ajouter des collectes de munition

Bien que l'ajout de collectes de santé soit implémenté, nous ne pouvons pas vraiment en récolter les fruits puisque rien ne peut (actuellement) nous nuire. Ajoutons maintenant des collectes de munitions !

Ouvrez Ammo_Pickup.tscn. Remarquez comment il est structuré exactement de la même façon que Health_Pickup.tscn, juste avec les maillages et les formes de collision de déclenchement légèrement modifiées pour s'ajuster à la différence de taille des maillages.

Sélectionnez Ammo_Pickup et ajoutez un nouveau script appelé Ammo_Pickup.gd. Ajouter ce qui suit :

Code : Tout sélectionner

extends Spatial

export (int, "full size", "small") var kit_size = 0 setget kit_size_change

# 0 = full size pickup, 1 = small pickup
const AMMO_AMOUNTS = [4, 1]

const RESPAWN_TIME = 20
var respawn_timer = 0

var is_ready = false

func _ready():

    $Holder/Ammo_Pickup_Trigger.connect("body_entered", self, "trigger_body_entered")

    is_ready = true

    kit_size_change_values(0, false)
    kit_size_change_values(1, false)

    kit_size_change_values(kit_size, true)


func _physics_process(delta):
    if respawn_timer > 0:
        respawn_timer -= delta

        if respawn_timer <= 0:
            kit_size_change_values(kit_size, true)


func kit_size_change(value):
    if is_ready:
        kit_size_change_values(kit_size, false)
        kit_size = value

        kit_size_change_values(kit_size, true)
    else:
        kit_size = value


func kit_size_change_values(size, enable):
    if size == 0:
        $Holder/Ammo_Pickup_Trigger/Shape_Kit.disabled = !enable
        $Holder/Ammo_Kit.visible = enable
    elif size == 1:
        $Holder/Ammo_Pickup_Trigger/Shape_Kit_Small.disabled = !enable
        $Holder/Ammo_Kit_Small.visible = enable


func trigger_body_entered(body):
    if body.has_method("add_ammo"):
        body.add_ammo(AMMO_AMOUNTS[kit_size])
        respawn_timer = RESPAWN_TIME
        kit_size_change_values(kit_size, false)
Vous avez peut-être remarqué que ce code est presque exactement le même que celui de la prise en charge de la santé. C'est parce que c'est en grande partie la même chose ! Il n'y a que peu d’éléments qui ont changés, et c'est ce sur quoi nous allons nous pencher.

Tout d'abord, remarquez comment nous avons AMMO_AMOUNTS au lieu de HEALTH_AMMOUNTS. AMMO_AMOUNTS sera le nombre de chargeurs que nous ajouterons à l'arme actuelle. (Contrairement à HEALTH_AMMOUNTS qui était le nombre de points de vie, nous ajoutons simplement un chargeur entier pour l'arme courante, au lieu de la quantité de munitions brutes).

La seule autre chose à remarquer est que dans trigger_body_entered nous vérifions et appelons une fonction appelée add_ammo, pas add_health.

Mis à part ces deux petits changements, tout le reste est exactement le même que la collecte de santé !

Tout ce que nous avons à faire pour que le ramassage de munitions fonctionne est d'ajouter une nouvelle fonction à notre joueur. Ouvrez Player.gd et ajoutez :

Code : Tout sélectionner

func add_ammo(additional_ammo):
    if (current_weapon_name != "UNARMED"):
        if (weapons[current_weapon_name].CAN_REFILL == true):
            weapons[current_weapon_name].spare_ammo += weapons[current_weapon_name].AMMO_IN_MAG * additional_ammo
Voyons ce que fait cette fonction.

La première chose que nous vérifions est de voir si nous utilisons UNARMED ou non. Comme UNARMED n'a pas de nœud/script, nous voulons nous assurer que nous n'utilisons pas UNARMED avant d'essayer d'obtenir le nœud/script attaché à current_weapon_name.

Ensuite, nous vérifions si l'arme actuelle peut être rechargée. Si l'arme actuelle le peut, nous ajoutons un chargeur complet d'une valeur de munitions à l'arme en multipliant les temps variables AMMO_IN_MAG de l'arme actuelle, quel que soit le nombre de chargeurs de munitions que nous ajoutons (additional_ammo).

Cela fait, vous devriez maintenant être en mesure d'obtenir des munitions supplémentaires ! Allez placer des ramasseurs de munitions dans une des deux scènes (ou les deux) et faites-en l'essai !

Remarque :
Remarquez que nous ne limitons pas la quantité de munitions que vous pouvez transporter. Pour limiter la quantité de munitions que chaque arme peut transporter, il suffit d'ajouter une variable supplémentaire au script de chaque arme, puis de clamper la variable spare_ammo de l'arme après avoir ajouté des munitions dans add_ammo.



Ajouter des cibles destructibles

Avant de terminer cette partie, ajoutons quelques cibles.
Ouvrez Target.tscn et regardez les scènes dans l'arbre des scènes.

Tout d'abord, remarquez que nous n'utilisons pas un noeud RigidBody, mais plutôt un nœud StaticBody à la place. La raison en est que nos cibles non détruites ne se déplaceront nulle part, utiliser un RigidBody serait plus de tracas que nécessaire, puisque tout ce qu'il a à faire est de rester immobile.

Astuce :
Nous sauvegardons aussi un tout petit peu de performance en utilisant un StaticBody plutôt qu’un RigidBody.


L'autre chose à noter est que nous avons un noeud appelé Broken_Target_Holder. Ce nœud va tenir une scène générée/instanciée appelée Broken_Target.tscn. Ouvrez Broken_Target.tscn.

Remarquez comment la cible est divisée en cinq morceaux, chacun avec un nœud RigidBody. Nous allons générer/instancier cette scène lorsque la cible subit trop de dégâts et doit être détruite. Ensuite, nous allons cacher la cible non détruite, de sorte qu'il semblera que la cible soit détruite plutôt qu'une cible détruite ait été régénérée/instanciée.

Tant que vous avez encore Broken_Target.tscn ouvert, attachez RigidBody_hit_test.gd à tous les nœuds RigidBody. Cela nous permettra de tirer sur les morceaux brisés, et ceux-ci réagiront aux balles.
Très bien, maintenant retournez sur Target.tscn, sélectionnez le noeud StaticBody Target et créez un nouveau script appelé Target.gd et ajoutez-y le code suivant :

Code : Tout sélectionner

extends StaticBody

const TARGET_HEALTH = 40
var current_health = 40

var broken_target_holder

# The collision shape for the target.
# NOTE: this is for the whole target, not the pieces of the target
var target_collision_shape

const TARGET_RESPAWN_TIME = 14
var target_respawn_timer = 0

export (PackedScene) var destroyed_target

func _ready():
    broken_target_holder = get_parent().get_node("Broken_Target_Holder")
    target_collision_shape = $Collision_Shape


func _physics_process(delta):
    if target_respawn_timer > 0:
        target_respawn_timer -= delta

        if target_respawn_timer <= 0:

            for child in broken_target_holder.get_children():
                child.queue_free()

            target_collision_shape.disabled = false
            visible = true
            current_health = TARGET_HEALTH


func bullet_hit(damage, bullet_hit_pos):
    current_health -= damage

    if current_health <= 0:
        var clone = destroyed_target.instance()
        broken_target_holder.add_child(clone)

        for rigid in clone.get_children():
            if rigid is RigidBody:
                var center_in_rigid_space = broken_target_holder.global_transform.origin - rigid.global_transform.origin
                var direction = (rigid.transform.origin - center_in_rigid_space).normalized()
                # Apply the impulse with some additional force (I find 12 works nicely)
                rigid.apply_impulse(center_in_rigid_space, direction * 12 * damage)

        target_respawn_timer = TARGET_RESPAWN_TIME

        target_collision_shape.disabled = true
        visible = false
Passons en revue ce que fait ce script, en commençant par les variables globales :
  • TARGET_HEALTH : Le montant des dégâts nécessaires pour briser une cible complètement guérie.
  • current_health : L'état de santé actuel de cette cible.
  • broken_target_holder : Une variable pour tenir le nœud Broken_Target_Holder afin que nous puissions l'utiliser facilement.
  • target_collision_shape : Une variable pour maintenir le CollisionShape pour la cible non détruite.
  • TARGET_RESPAWN_TIME : Le temps, en secondes, qu'il faut pour qu'une cible réapparaisse.
  • target_respawn_timer : Variable permettant de savoir depuis combien de temps un objectif a été détruit.
  • destroyed_target : Un PackedScene pour contenir la scène de la cible détruite.
Remarquez comment nous utilisons une variable exportée (un PackedScene) pour obtenir la scène cible détruite au lieu d'utiliser le préchargement. En utilisant une variable exportée, nous pouvons choisir la scène à partir de l'éditeur, et quand (et si) nous avons besoin d'utiliser une scène différente, c'est aussi simple que de sélectionner une scène différente dans l'éditeur, nous n'avons pas besoin d'aller dans le code pour changer la scène que nous utilisons.

Jetons un coup d'oeil à _ready.

La première chose que nous faisons est d'obtenir le détenteur de la cible détruite et de l'assigner à broken_target_holder. Remarquez comment nous utilisons get_parent().get_node() ici, au lieu de $. Si vous voulez utiliser $, alors vous devez changer get_parent().get_node() en $".../Broken_Target_Holder".

Remarque :
Au moment où ce tutoriel a été écrit, je n'avais pas réalisé que vous pouviez utiliser $".../Nom_du_Noeud" pour obtenir les nœuds parents en utilisant $, ce qui explique pourquoi get_parent().get_node().get_node() est utilisé à la place.


Ensuite, nous obtenons la forme de collision et nous l'assignons à target_collision_shape. La raison pour laquelle nous avons besoin d'une forme de collision est que même lorsque le maillage est invisible, la forme de collision existera toujours dans le monde physique. Cela fait que le joueur peut interagir avec une cible non détruite même si elle est invisible, ce qui n'est pas ce que nous voulons. Pour contourner ce problème, nous allons désactiver/activer la forme de collision en rendant le maillage visible/invisible.

Regardons maintenant _physics_process.

Nous allons seulement utiliser _physics_process pour la régénération, et donc la première chose que nous faisons est de vérifier si target_respawn_timer est supérieur à 0.

Si c'est le cas, nous enlevons le delta.

Ensuite, nous vérifions si target_respawn_timer est 0 ou moins. La raison en est que nous venons de supprimer delta de target_respawn_timer, si c'est 0 ou moins alors nous y sommes parvenu, ce qui nous permet de faire tout ce que nous avons besoin de faire quand le chronomètre est terminé.

Dans ce cas, nous voulons que notre cible réapparaisse.

La première chose que nous faisons est de retirer tous les nœuds enfants du détenteur de la cible brisée. Nous le faisons en itérant sur tous les nœuds enfants dans broken_target_holder et en les libérant.


Ensuite, nous activons notre forme de collision en réglant son booléen désactivé sur false.
Ensuite, nous nous rendons visibles, ainsi que tous les nœuds enfants.
Enfin, nous réinitialisons current_health à TARGET_HEALTH.

Enfin, regardons bullet_hit.

La première chose que nous faisons, c'est d'enlever les dommages que la balle fait à notre santé.

Ensuite, nous vérifions si nous sommes à 0 ou moins. Si c'est le cas, nous venons de mourir et nous avons besoin de générer une cible détruite.

Nous commençons par une nouvelle scène cible détruite, et l'assignons à une nouvelle variable : clone.
Ensuite, nous ajoutons clone en tant que nœud enfant de notre support de cible brisée.

Pour un bonus supplémentaire, nous voulons que toutes les pièces cibles explosent vers l'extérieur. Pour ce faire, on itère sur tous les nœuds enfants dans clone.

Pour chaque nœud enfant, nous vérifions d'abord s'il s'agit d'un nœud RigidBody. Si c'est le cas, nous calculons alors la position centrale de la cible par rapport au nœud enfant. Ensuite, nous déterminons dans quelle direction nous sommes par rapport au centre. En utilisant ces variables calculées, nous poussons l'enfant depuis le centre calculé, dans la direction opposée au centre, en utilisant les dommages de la balle comme force.

Remarque :
Nous multiplions les dégâts par 12, ce qui a un effet plus spectaculaire. Vous pouvez changer cette valeur à une valeur supérieure ou inférieure en fonction de l'explosivité à laquelle vous voulez que vos cibles se brisent.


Ensuite, nous réglons notre chronomètre de réapparition pour notre cible non détruite. Nous l'avons réglé sur TARGET_RESPAWN_TIME, de sorte qu'il faut plusieurs secondes à TARGET_RESPAWN_TIME pour se reproduire.

Ensuite, nous désactivons la forme de collision de la cible non détruite et réglons notre visibilité sur false.

Attention :
Assurez-vous de définir la valeur destroyed_target exportée pour Target.tscn dans l'éditeur ! Sinon, les cibles ne seront pas détruites et vous obtiendrez une erreur !


Une fois fait, allez placer des instances Target.tscn dans un, les deux ou tous les niveaux. Vous devriez voir qu'ils explosent en cinq morceaux une fois qu'elles ont subi assez de dégâts. Au bout d'un certain temps, ils vont se reconstituer en une cible complète.


Remarques finales

Image

Vous pouvez maintenant utiliser un joypad, changer d'armes avec la molette de la souris, réapprovisionner votre santé et vos munitions, et briser les cibles avec vos armes.

Dans la partie suivante, nous allons ajouter des grenades à notre joueur, donner à notre joueur la possibilité de saisir et de lancer des objets, et ajouter des tourelles !

Attention :
Si jamais vous vous perdez, n'oubliez pas de relire le code !


Vous pouvez télécharger le projet fini pour cette partie ici : Godot_FPS_Part_4.zip

Tutoriel FPS - Partie 4 - Version PDF téléchargeable
- Le projet"XPlore"
- Tutos Blender
- Tutos Godot Game Engine

“ L'artiste est un malade qui essaie de se soigner en créant, mais plus il se soigne, plus il est malade. Et plus il est malade, plus il est content, vu qu'il n'a aucune envie de guérir." Philippe Geluck