Tutoriel FPS - Partie 6 : Interfaces, régénération et système de sonorisation

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

Tutoriel FPS - Partie 6 : Interfaces, régénération et système de sonorisation

Message par keltwookie » dim. mai 20, 2018 11:36 pm

Aperçu de la partie 6

Dans cette partie, nous allons ajouter un menu principal et un menu pause, ajouter un système de régénération pour le joueur, et changer/déplacer le système sonore pour que nous puissions l'utiliser à partir de n'importe quel script.

Ceci est la dernière partie du tutoriel FPS, à la fin de ceci vous aurez une base solide pour construire
des jeux FPS étonnants avec Godot !

Image

Remarque :
Vous êtes supposé avoir terminé Tutoriel FPS - Partie 5 : Ajouter des grenades et des tourelles, avant de passer à cette partie du tutoriel. Le projet terminé de la partie 4 sera le projet de départ de la partie 5.


Commençons !


Ajouter le menu principal

Tout d'abord, ouvrez Main_Menu.tscn et regardez comment la scène est configurée.

Le menu principal est divisé en trois panneaux différents, chacun représentant un " écran " différent de notre menu principal.

Remarque :
Le noeud Background_Animation est juste pour que le fond du menu soit un peu plus intéressant qu'une couleur unie. C'est une caméra qui regarde autour de la skybox, rien d'extraordinaire.
N'hésitez pas à développer tous les nœuds et à voir comment ils sont configurés. N'oubliez pas de garder seulement Start_Menu visible lorsque vous avez terminé, car c'est l'écran que nous voulons montrer en premier lorsque nous entrons dans le menu principal.


Sélectionnez Main_Menu (le nœud racine) et créez un nouveau script appelé Main_Menu.gd puis ajouter ce qui suit :

Code : Tout sélectionner

extends Control

var start_menu
var level_select_menu
var options_menu

export (String, FILE) var testing_area_scene
export (String, FILE) var space_level_scene
export (String, FILE) var ruins_level_scene

func _ready():
    start_menu = $Start_Menu
    level_select_menu = $Level_Select_Menu
    options_menu = $Options_Menu

    $Start_Menu/Button_Start.connect("pressed", self, "start_menu_button_pressed", ["start"])
    $Start_Menu/Button_Open_Godot.connect("pressed", self, "start_menu_button_pressed", ["open_godot"])
    $Start_Menu/Button_Options.connect("pressed", self, "start_menu_button_pressed", ["options"])
    $Start_Menu/Button_Quit.connect("pressed", self, "start_menu_button_pressed", ["quit"])

    $Level_Select_Menu/Button_Back.connect("pressed", self, "level_select_menu_button_pressed", ["back"])
    $Level_Select_Menu/Button_Level_Testing_Area.connect("pressed", self, "level_select_menu_button_pressed", ["testing_scene"])
    $Level_Select_Menu/Button_Level_Space.connect("pressed", self, "level_select_menu_button_pressed", ["space_level"])
    $Level_Select_Menu/Button_Level_Ruins.connect("pressed", self, "level_select_menu_button_pressed", ["ruins_level"])

    $Options_Menu/Button_Back.connect("pressed", self, "options_menu_button_pressed", ["back"])
    $Options_Menu/Button_Fullscreen.connect("pressed", self, "options_menu_button_pressed", ["fullscreen"])
    $Options_Menu/Check_Button_VSync.connect("pressed", self, "options_menu_button_pressed", ["vsync"])
    $Options_Menu/Check_Button_Debug.connect("pressed", self, "options_menu_button_pressed", ["debug"])

    Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)

    var globals = get_node("/root/Globals")
    $Options_Menu/HSlider_Mouse_Sensitivity.value = globals.mouse_sensitivity
    $Options_Menu/HSlider_Joypad_Sensitivity.value = globals.joypad_sensitivity


func start_menu_button_pressed(button_name):
    if button_name == "start":
        level_select_menu.visible = true
        start_menu.visible = false
    elif button_name == "open_godot":
        OS.shell_open("https://godotengine.org/")
    elif button_name == "options":
        options_menu.visible = true
        start_menu.visible = false
    elif button_name == "quit":
        get_tree().quit()


func level_select_menu_button_pressed(button_name):
    if button_name == "back":
        start_menu.visible = true
        level_select_menu.visible = false
    elif button_name == "testing_scene":
        set_mouse_and_joypad_sensitivity()
        get_node("/root/Globals").load_new_scene(testing_area_scene)
    elif button_name == "space_level":
        set_mouse_and_joypad_sensitivity()
        get_node("/root/Globals").load_new_scene(space_level_scene)
    elif button_name == "ruins_level":
        set_mouse_and_joypad_sensitivity()
        get_node("/root/Globals").load_new_scene(ruins_level_scene)


func options_menu_button_pressed(button_name):
    if button_name == "back":
        start_menu.visible = true
        options_menu.visible = false
    elif button_name == "fullscreen":
        OS.window_fullscreen = !OS.window_fullscreen
    elif button_name == "vsync":
        OS.vsync_enabled = $Options_Menu/Check_Button_VSync.pressed
    elif button_name == "debug":
        pass


func set_mouse_and_joypad_sensitivity():
    var globals = get_node("/root/Globals")
    globals.mouse_sensitivity = $Options_Menu/HSlider_Mouse_Sensitivity.value
    globals.joypad_sensitivity = $Options_Menu/HSlider_Joypad_Sensitivity.value

La plupart du code ici se rapporte à la création d'UIs, ce qui est en dehors du but de cette série de tutoriels. Nous n'examinerons que brièvement le code relatif à l'interface utilisateur.

Astuce :
Voir GUI et les tutoriels inclus pour de meilleurs moyens de créer des interfaces graphiques et des interfaces utilisateur !


Regardons les variables globales en premier.
  • start_menu : Une variable pour gérer un Pannel Start_Menu.
  • level_select_menu : Une variable pour gérer le panneau Level_Select_Menu.
  • options_menu : Une variable pour gérer le panneau Options_Menu.
  • testing_area_scene : le chemin vers le fichier Testing_Area.tscn, gràce auquel nous pourrons accéder depuis la scène .
  • space_level_scene : le chemin vers le fichier Space_Level.tscn file, gràce auquel nous pourrons accéder depuis la scène .
  • ruins_level_scene : le chemin vers le fichier Ruins_Level.tscn file, gràce auquel nous pourrons accéder depuis la scène .
Attention :
Vous devrez définir les chemins d'accès corrects aux fichiers dans l'éditeur avant de tester ce script ! Sinon, ça ne marchera pas !


Maintenant, passons à _ready.

Tout d'abord, nous obtenons tous les noeuds Pannel et nous les assignons aux variables appropriées.

Ensuite, nous connectons tous les signaux des boutons pressés à leurs fonctions respectives [nom_du_panel_ici]_button_pressed.

Nous réglons ensuite le mode de la souris sur MOUSE_MODE_VISIBLE pour nous assurer que chaque fois que nous retournons à cette scène, notre souris sera visible.

Puis nous obtenons un singleton, appelé Globals. Nous définissons ensuite les valeurs pour les nœuds HSlider afin que leurs valeurs s'alignent avec la sensibilité de la souris et du joypad dans le singleton.

Remarque :
Nous n'avons pas encore fait le singleton Globals, alors ne vous inquiétez pas ! On va bientôt y arriver !


Dans start_menu_pressed, nous vérifions quel bouton est appuyé.

En fonction du bouton pressé, soit nous changeons le panneau actuellement visible, soit nous quittons l'application, soit nous ouvrons le site web du moteur Godot.

Dans level_select_menu_button_pressed, nous vérifions quel bouton est appuyé.

Si le bouton « back » a été pressé, nous changeons les panneaux actuellement visibles afin de revenir au menu principal.

Si l'un des boutons de changement de scène est pressé, nous appelons tout d'abord set_mouse_and_joypad_sensitivity pour que notre singleton ait les valeurs des nœuds Hslider. Ensuite, nous disons au singleton de changer les noeuds en utilisant sa fonction load_new_scene, pour passer au chemin du fichier de la scène vers laquelle nous voulons changer.

Remarque :
Ne vous inquiétez pas pour le singleton, nous y serons bientôt !


Dans options_menu_button_pressed, nous vérifions quel bouton est appuyé.

Si le bouton « back » a été pressé, nous changeons les panneaux actuellement visibles afin de revenir au menu principal.

Si le bouton fullscreen est pressé, nous basculons le mode plein écran du système d'exploitation (voir OS) en le réglant sur une version différente de sa valeur actuelle.

Si le bouton vsync est pressé, nous réglons le Vsync de l'OS en fonction de l'état du bouton de contrôle Vsync.

Enfin, jetons un coup d'oeil à set_mouse_and_joypad_sensitivity.

Nous obtenons d'abord le singleton Globals et nous l'assignons à une variable locale.

Nous réglons ensuite les variables mouse_sensitivity and joypad_sensitvity aux valeurs de leurs homologues des nœuds HSlider respectifs.


Concevoir le singleton Globals

Maintenant, pour que tout cela fonctionne, nous devons créer le singleton Globals. Créez un nouveau script dans l'onglet Script et appelez-le Globals.gd.

Ajoutez-y:

Code : Tout sélectionner

extends Node

var mouse_sensitivity = 0.08
var joypad_sensitivity = 2

func _ready():
    pass

func load_new_scene(new_scene_path):
    get_tree().change_scene(new_scene_path)

Comme vous pouvez le constater, c'est tout à fait simple et concis. Au fur et à mesure de cette partie, nous continuerons à ajouter des complexités à Global.gd, mais pour l'instant, tout ce qu'il fait est de tenir deux variables pour nous, et le fait d'abstraire la façon dont nous changeons les scènes.
  • mouse_sensitivity : La sensibilité actuelle de notre souris pour que nous puissions la charger dans Player.gd.
  • joypad_sensitivity : La sensibilité actuelle de notre joypad, pour que nous puissions la charger dans Player.gd.
Pour l’instant, tout ce à quoi nous sert Globals.gd est un moyen de transporter des variables d’une scène à l’autre. Parce que la sensibilité de notre souris et de notre joypad sont stockées dans Globals.gd, tout changement que nous faisons dans une scène (comme Main_Menu) affecte la sensibilité de notre joueur.

Tout ce que nous faisons dans load_new_scene est d'appeler la fonction change_scene du SceneTree, en passant dans le chemin de scène donné dans load_new_scene.

C'est tout le code dont a besoin Globals.gd pour l’instant ! Avant de pouvoir tester le menu principal, nous devons d'abord définir Globals.gd comme script de chargement automatique.

Ouvrez les paramètres du projet et cliquez sur l'onglet AutoLoad.

Image

Sélectionnez ensuite le chemin d'accès vers Globals.gd dans le champ « Path » en cliquant sur le bouton situé à côté. Assurez-vous que le nom dans le champ « Node Name » est Globals. Si vous avez quelque chose d’identique à l'image ci-dessus, appuyez sur « Add » !

Cela fera de Globals.gd un script singleton/autoload, ce qui nous permettra d'y accéder depuis n'importe où dans n'importe quelle scène.

Astuce :
Pour plus d’informations sur les scripts singleton/autoload, voir Singletons (AutoLoad).


Maintenant que Globals.gd est un script singleton/autoload, vous pouvez tester le menu principal !

Vous pouvez aussi changer la scène principale de Testing_Area.tscn à Main_Menu.tscn, de sorte que lorsque nous exporterons le jeu, nous commencerons par le menu principal. Vous pouvez le faire à travers les paramètres du projet, sous l'onglet « General ». Ensuite, dans la catégorie « Application », cliquez sur la sous-catégorie « Run » et vous pouvez changer la scène principale en changeant la valeur dans scène principale.

Attention :
Vous devrez définir les chemins d'accès aux fichiers corrects dans Main_Menu dans l'éditeur avant de tester le menu principal ! Sinon, vous ne pourriez pas modifier les scènes à partir du menu/écran de sélection de niveau.



Ajouter un menu débogage

Ajoutons maintenant une simple scène de débogage pour que nous puissions suivre les choses comme dans le jeu. Ouvrez Debug_Display.tscn.

Vous pouvez voir qu'il s'agit d'un Panel situé dans le coin supérieur droit de l'écran. Il a trois Labels, un pour afficher le FPS où le jeu est en cours d'exécution, un pour montrer sur quel OS le jeu est en cours d'exécution, et une étiquette pour montrer la version Godot avec laquelle le jeu est en cours d’exécution.

Ajoutons le code nécessaire pour remplir ces Labels. Sélectionnez Debug_Display et créez un nouveau script appelé Debug_Display.gd. Ajouter ce qui suit :

Code : Tout sélectionner

extends Control

func _ready():
    $OS_Label.text = "OS:" + OS.get_name()
    $Engine_Label.text = "Godot version:" + Engine.get_version_info()["string"]

func _process(delta):
    $FPS_Label.text = "FPS:" + str(Engine.get_frames_per_second())

Revoyons ce que fait ce script.

Dans _ready, nous réglons le texte de OS_Label au nom fourni dans l'OS en utilisant la fonction get_name. Ceci retournera le nom de l'OS (ou du système d'exploitation) pour lequel Godot a été compilé. Par exemple, lorsque vous exécutez Windows, il renvoie Windows, alors que lorsque vous exécutez Linux, il renvoie X11.

Ensuite, nous réglons le texte de Engine_Label à l’info de version fournie par Engine.get_version_info, qui renvoie un dictionnaire plein d'informations utiles sur la version avec laquelle Godot fonctionne actuellement. Nous ne nous soucions que de la version de la chaîne de caractères pour les besoins de cet affichage, donc nous obtenons la chaîne de caractères et l'assignons comme text dans Engine_Label. Voir Engine pour plus d'informations sur les valeurs get_version_info.

Dans _process nous avons défini le texte du FPS_Label à Engine.get_frames_per_seconde, mais parce que get_frames_per_seconde retourne un integer (=nombre entier), nous devons l’associer à une chaîne de caractères en utilisant str avant de pouvoir l'ajouter à notre label.

Revenons maintenant à Main_Menu.gd et changeons ce qui suit dans options_menu_button_pressed :

Code : Tout sélectionner

elif button_name == "debug":
    pass
to this instead:
elif button_name == "debug":
 get_node("/root/Globals").set_debug_display($Options_Menu/Check_Button_Debug.pressed)

Ceci appellera une nouvelle fonction dans notre singleton appelée set_debug_display, alors ajoutons cela !

Ouvrez Globals.gd et ajoutez les variables globales suivantes :

Code : Tout sélectionner

# ------------------------------------
# All of the GUI/UI related variables

var canvas_layer = null

const DEBUG_DISPLAY_SCENE = preload("res://Debug_Display.tscn")
var debug_display = null

# ------------------------------------
  • canvas_layer : Un calque de canevas pour que notre interface graphique soit toujours au premier plan.
  • DEBUG_DISPLAY : La scène de débogage sur laquelle nous avons travaillé plus tôt.
  • debug_display : Une variable pour gérer l'affichage de débogage lorsqu'il y a un bogue.
Maintenant que nous avons défini nos variables globales, nous avons besoin d'ajouter quelques lignes pour que nous ayons une couche canvas à utiliser dans canvas_layer. Modifier _ready comme suit :

Code : Tout sélectionner

func _ready():
    canvas_layer = CanvasLayer.new()
    add_child(canvas_layer)
Maintenant, dans _ready, nous sommes en train de créer un nouveau calque de canevas et de l'ajouter en tant qu'enfant du script de chargement automatique.

La raison pour laquelle nous ajoutons un CanvasLayer est pour que tous nos nœuds d'interface graphique et d'interface utilisateur que nous instancions/générons dans Globals.gd soient toujours au premier plan.

Lorsque vous ajoutez des nœuds à un singleton/autoload, vous devez faire attention à ne pas perdre la référence vers aucun des nœuds enfants. C'est parce que les nœuds ne seront pas libérés/détruits lorsque vous changez de scène, ce qui signifie que vous pouvez rencontrer des problèmes de mémoire si vous instanciez/générez beaucoup de nœuds et que vous ne les libérez pas (_queue_free).

Nous devons maintenant ajouter set_debug_display à Globals.gd :

Code : Tout sélectionner

func set_debug_display(display_on):
    if display_on == false:
        if debug_display != null:
            debug_display.queue_free()
            debug_display = null
    else:
        if debug_display == null:
            debug_display = DEBUG_DISPLAY_SCENE.instance()
            canvas_layer.add_child(debug_display)

Voyons ce qui se passe.

Tout d'abord, nous vérifions si nous essayons d'activer ou de désactiver l'affichage de débogage.

Si nous désactivons l'affichage, nous vérifions alors si debug_display n'est pas égal à null. Si ce n’est pas le cas, alors nous avons le plus souvent un affichage de débogage actuellement actif. Si nous avons un affichage de débogage actif, nous le libérons en utilisant queue_free et ensuite assignons debug_display à null.
Si nous activons l'affichage, nous vérifions alors pour nous assurer que nous n'avons pas déjà un affichage de débogage actif. Pour ce faire, nous nous assurons que debug_display est égal à null. Si c’est le cas, nous instancions un nouveau DEBUG_DISPLAY_SCENE, et nous l'ajoutons en tant qu'enfant de canvas_layer.

Une fois fait, nous pouvons maintenant activer et désactiver l'affichage de débogage en activant le bouton CheckButton dans le panneau Options_Menu. Essayez !

Remarquez comment l'affichage de débogage reste affiché même lorsque vous changez de scène depuis Main_Menu.tscn à une autre scène (comme Testing_Area.tscn). C’est toute la magie d’instancier/générer des nœuds dans un singleton/autolad et de les y ajouter. Tous les nœuds ajoutés en tant qu'enfants du singleton/autoload resteront aussi longtemps que le jeu est en cours d'exécution, sans aucun travail supplémentaire de notre part !


Ajouter un menu pause

Ajoutons un menu pause pour revenir au menu principal lorsque nous appuyons sur l'action ui_cancel.

Ouvrez Pause_Popup.tscn.

Remarquez comment le nœud racine dans Pause_Popup est un WindowDialog. Il hérite de Popup, ce qui signifie que WindowDialog peut agir comme un popup.

Sélectionnez Pause_Popup et faites défiler vers le bas jusqu'au menu Pause dans l'inspecteur. Remarquez comment le mode pause est réglé pour process au lieu d'inherit comme il l'est normalement par défaut. Cela permet de continuer à traiter l’information même lorsque le jeu est en pause, ce dont nous avons besoin pour interagir avec les éléments de l'interface utilisateur.

Maintenant que nous avons regardé comment Pause_Popupup.tscn est configuré, écrivons le code de manière fonctionnelle. Normalement, nous attachons un script au nœud racine de la scène, Pause_Popup dans ce cas, mais comme nous en aurons besoin pour recevoir quelques signaux dans Globals.gd, nous écrirons tout le code pour le popup dedans.

Ouvrez Globals.gd et ajoutez-y les variables globales suivantes :

Code : Tout sélectionner

const MAIN_MENU_PATH = "res://Main_Menu.tscn"
const POPUP_SCENE = preload("res://Pause_Popup.tscn")
var popup = null
  • MAIN_MENU_PATH : Le chemin vers la scène du menu principal.
  • POPUP_SCENE : La scène popup que nous avons regardé plus tôt.
  • popup: Une variable pour gérer la scène popup.
Maintenant nous devons ajouter à Globals.gd pour pouvoir répondre quand l'action ui_cancel est pressée. Ajoutez à _process ce qui suit :

Code : Tout sélectionner

func _process(delta):
    if Input.is_action_just_pressed("ui_cancel"):
        if popup == null:
            popup = POPUP_SCENE.instance()

            popup.get_node("Button_quit").connect("pressed", self, "popup_quit")
            popup.connect("popup_hide", self, "popup_closed")
            popup.get_node("Button_resume").connect("pressed", self, "popup_closed")

            canvas_layer.add_child(popup)
            popup.popup_centered()

            Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)

            get_tree().paused = true

Voyons ce qui se passe ici.

Tout d'abord, nous vérifions si l'action ui_cancel est activée. Ensuite, nous vérifions pour nous assurer que nous n'avons pas déjà un popup ouvert en vérifiant si le popup est égal à null.

Si nous n'avons pas de pop up ouvert, nous instancions POPUP_SCENE et l'assignions à popup.

Nous obtenons alors le bouton quit et assignons son signal pressé à popup_quit, que nous ajouterons sous peu.

Ensuite, nous assignons le signal popup_hide du WindowDialog et le signal pressé du bouton de reprise à popup_closed, que nous allons bientôt ajouter.

Ensuite, nous ajoutons un popup en tant qu'enfant de canvas_layer pour qu'il apparaisse au premier plan. Nous disons ensuite au popup d'apparaître au centre de l'écran en utilisant popup_centered.

Ensuite, nous nous assurons que le mode de la souris est MOUSE_MODE_VISIBLE pour que nous puissions interagir avec le pop up. Si nous ne le faisions pas, nous ne pourrions pas interagir avec la fenêtre pop up dans toute scène où le mode souris est MOUSE_MODE_MODE_CAPTURED.

Enfin, mettez en pause tout le SceneTree.

Remarque :
Pour plus d'informations sur la pause dans Godot, voir Pausing games.


Nous devons maintenant ajouter les fonctions auxquelles nous avons connecté les signaux. Ajoutons d'abord popup_closed .

Ajoutez ce qui suit à Globals.gd :

Code : Tout sélectionner

func popup_closed():
    get_tree().paused = false

    if popup != null:
        popup.queue_free()
        popup = null

popup_closed reprendra le jeu et détruira la pop up s'il y en a une.

popup_quit est similaire, mais nous nous assurons aussi que la souris est visible et que les scènes changent pour l'écran de titre.
Ajoutez ce qui suit à Globals.gd:

Code : Tout sélectionner

func popup_quit():
    get_tree().paused = false

    Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)

    if popup != null:
        popup.queue_free()
        popup = null

    load_new_scene(MAIN_MENU_PATH)

popup_quit reprendra le jeu, réglera le mode de la souris sur MOUSE_MODE_VISIBLE pour s'assurer que la souris est visible dans le menu principal, détruira la fenêtre popup s'il y en a une, et changera les scènes dans le menu principal.

Avant que nous soyons prêts à tester la popup, nous devrions changer quelque chose dans Player.gd.

Ouvrez Player.gd et dans process_input, changez le code pour capturer/libérer le curseur vers ce qui suit :

Code : Tout sélectionner

if Input.get_mouse_mode() == Input.MOUSE_MODE_VISIBLE:
    Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)

Maintenant, au lieu de capturer/libérer la souris, nous vérifions si le mode souris actuel est MOUSE_MODE_MODE_VISIBLE. Si c'est le cas, nous le réinitialisons à MOUSE_MODE_CAPTURED.

Comme la fenêtre popup active le mode souris MOUSE_MODE_VISIBLE à chaque pause, nous n'avons plus à nous soucier de libérer le curseur dans Player.gd.

Le menu pause est maintenant terminé. Vous pouvez maintenant faire une pause à n'importe quel moment du jeu et revenir au menu principal !


Commencer le système de régénération

Puisque notre joueur peut perdre toute sa santé, ce serait idéal si nos joueurs mouraient et se régénérait aussi, alors ajoutons cela !

Tout d'abord, ouvrez Player.tscn et développez HUD. Remarquez comment il y a un ColorRect appelé Death_Screen. Quand le joueur meurt, nous allons rendre Death_Screen visible, et leur montrer combien de temps ils doivent attendre avant de pouvoir se reproduire.

Ouvrez Player.gd et ajoutez les variables globales suivantes :

Code : Tout sélectionner

const RESPAWN_TIME = 4
var dead_time = 0
var is_dead = false

var globals
  • RESPAWN_TIME : Le temps (en secondes) qu'il faut pour la régénération.
  • dead_time : Une variable pour savoir depuis combien de temps le joueur est mort.
  • is_dead: Une variable pour savoir si le joueur est actuellement mort ou non.
  • Globals : Une variable pour contenir le singleton Globals.gd.
Nous devons maintenant ajouter quelques lignes à _ready, afin de pouvoir utiliser Globals.gd. Ajoutez-y ce qui suit :

Code : Tout sélectionner

globals = get_node("/root/Globals")
global_transform.origin = globals.get_respawn_position().

Maintenant, nous obtenons le singleton Globals.gd et nous l'assignons à globals. Nous avons également défini notre position globale en utilisant l'origine de notre Transform global à la position retournée par globals.get_respawn_position.

Remarque :
Ne vous inquiétez pas, nous allons parler plus avant de globals.get_respawn_position ci-dessous.


Ensuite, nous devons apporter quelques changements à physics_process. Changez pour à ce qui suit :

Code : Tout sélectionner

func _physics_process(delta):

    if !is_dead:
        process_input(delta)
        process_view_input(delta)
        process_movement(delta)

    if (grabbed_object == null):
        process_changing_weapons(delta)
        process_reloading(delta)

    process_UI(delta)
    process_respawn(delta)

Maintenant, nous ne traitons pas les entrées ou les entrées de mouvement quand nous sommes morts. Nous appelons aussi process_respawn, mais nous ne l'avons pas encore écrit, alors changeons cela.

Ajoutez ce qui suit à Player.gd :

Code : Tout sélectionner

func process_respawn(delta):

    # If we just died
    if health <= 0 and !is_dead:
        $Body_CollisionShape.disabled = true
        $Feet_CollisionShape.disabled = true

        changing_weapon = true
        changing_weapon_name = "UNARMED"

        $HUD/Death_Screen.visible = true

        $HUD/Panel.visible = false
        $HUD/Crosshair.visible = false

        dead_time = RESPAWN_TIME
        is_dead = true

        if grabbed_object != null:
            grabbed_object.mode = RigidBody.MODE_RIGID
            grabbed_object.apply_impulse(Vector3(0,0,0), -camera.global_transform.basis.z.normalized() * OBJECT_THROW_FORCE / 2)

            grabbed_object.collision_layer = 1
            grabbed_object.collision_mask = 1

            grabbed_object = null

    if is_dead:
        dead_time -= delta

        var dead_time_pretty = str(dead_time).left(3)
        $HUD/Death_Screen/Label.text = "You died\n" + dead_time_pretty + " seconds till respawn"

        if dead_time <= 0:
            global_transform.origin = globals.get_respawn_position()

            $Body_CollisionShape.disabled = false
            $Feet_CollisionShape.disabled = false

            $HUD/Death_Screen.visible = false

            $HUD/Panel.visible = true
            $HUD/Crosshair.visible = true

            for weapon in weapons:
                var weapon_node = weapons[weapon]
                if weapon_node != null:
                    weapon_node.reset_weapon()

            health = 100
            grenade_amounts = {"Grenade":2, "Sticky Grenade":2}
            current_grenade = "Grenade"

            is_dead = false

Passons en revue ce que fait cette fonction.

Tout d'abord, nous vérifions si nous venons de mourir en vérifiant si la santé est égale ou inférieure à 0 et si is_dead est à false.

Si nous venons de mourir, nous désactivons nos formes de collision pour le joueur. On fait ça pour s'assurer qu'on ne bloque rien avec notre cadavre.

Nous réglons ensuite changing_weapon à true et changing_weapon_name à UNARMED. C'est ainsi que si nous utilisons une arme, nous la rangeons quand nous mourons.

Nous rendons alors visible le Death_Screen ColorRect afin d'obtenir une belle superposition grise sur la totalité. Nous rendons alors invisibles le reste de l'interface utilisateur, les nœuds Panel et Crosshair.

Ensuite, nous réglons dead_time à RESPAWN_TIME pour pouvoir commencer à compter la durée de la mort du joueur. Nous avons aussi mis is_dead à true pour que nous sachions que celui-ci est mort.

Si nous tenons un objet quand nous sommes morts, nous devons le jeter. Nous vérifions d'abord si nous tenons un objet ou non. Si c'est le cas, nous le lançons en utilisant le même code que celui que nous avons ajouté dans la partie 5.

Ensuite, nous vérifions si nous sommes morts. Si c'est le cas, nous enlevons delta de dead_time.

Nous créons alors une nouvelle variable appelée dead_time_pretty, où nous convertissons dead_time en chaîne de caractères, en utilisant seulement les trois premiers caractères à partir de la gauche. Cela nous donne une belle chaîne de caractères qui montre combien de temps il nous reste à attendre avant de nous régénérer.

Nous changeons ensuite l'étiquette dans Death_Screen pour montrer combien de temps il nous reste.

Ensuite, nous vérifions si nous avons attendu assez longtemps et si nous pouvons renaître. Pour ce faire, nous vérifions si dead_time est égal ou inférieur à 0.

Si nous avons attendu assez longtemps pour rejouer, nous plaçons la position du joueur à une nouvelle position de départ fournie par get_respawn_position.

Nous activons ensuite nos deux formes de collision pour que le joueur puisse entrer en collision avec l'environnement.

Ensuite, nous rendons l'écran Death_Screen invisible et rendons le reste de l'interface utilisateur, les nœuds Panel et Crosshair à nouveau visibles.

Nous passons ensuite en revue chaque arme et appelons la fonction reset_weapon. Nous l’ajouterons bientôt.

Ensuite, nous réinitialisons la santé à 100, les grenades à leurs valeurs par défaut, et changeons current_grenade à Grenade.

Enfin, nous avons mis is_dead à false.

Avant de quitter Player.gd, nous devons ajouter une chose rapidement à _input. Au début, ajoutez-y ce qui suit :

Code : Tout sélectionner

if is_dead:
    return

Maintenant que nous sommes morts, nous ne pouvons pas regarder autour de nous avec la souris.


Finir le système de régénération

Tout d’abord, ouvrons Weapon_Pistol.gd et ajoutons la fonction reset_weapon :

Code : Tout sélectionner

func reset_weapon():
    ammo_in_weapon = 10
    spare_ammo = 20
Maintenant, quand nous appelons reset_weapon, les munitions de notre arme et celles des collectes seront remises à leur valeur par défaut.
Ajoutons reset_weapon dans Weapon_Rifle.gd:

Code : Tout sélectionner

func reset_weapon():
    ammo_in_weapon = 50
    spare_ammo = 100

Et ajoutez à Weapon_Knife.gd:
func reset_weapon():
    ammo_in_weapon = 1
    spare_ammo = 1

Maintenant, nos armes se réinitialiseront quand nous mourrons.

Nous devons maintenant ajouter quelques choses à Globals.gd. Premièrement, ajoutez la variables globales suivantes :

Code : Tout sélectionner

var respawn_points = null

respawn_points est une variable pour gérer tous les points de régénération dans un niveau.

Parce que nous obtenons un point de régénération aléatoire à chaque fois, nous devons randomiser le générateur de nombres. Ajoutez ce qui suit à
_ready :

Code : Tout sélectionner

randomize()

randomize nous donnera une nouvelle graine aléatoire de sorte que nous obtenons une chaîne (relativement) aléatoire de nombres lorsque nous utilisons l'une des fonctions aléatoires.

Maintenant, ajoutons get_respawn_position à Globals.gd :

Code : Tout sélectionner

func get_respawn_position():
    if respawn_points == null:
        return Vector3(0, 0, 0)
    else:
        var respawn_point = rand_range(0, respawn_points.size()-1)
        return respawn_points[respawn_point].global_transform.origin
Voyons ce que fait cette fonction.

Nous vérifions d'abord si nous avons des respawn_points en vérifiant s’ils sont nuls ou non.Si c’est le cas, on retourne une position de Vector3 vide avec la position (0, 0, 0).

Si respawn_points n'est pas égal à null, nous obtenons alors un nombre aléatoire entre 0 et le nombre d'éléments que nous avons dans respawn_points, moins 1 puisque la plupart des langages de programmation (y compris le GDScript) commencent à compter à partir de 0 lorsque vous accédez aux éléments d'une liste.

Nous retournons ensuite la position du nœud Spatial à la position respawn_point.

Avant d'en avoir fini avec Globals.gd. Nous devons ajouter ce qui suit à load_new_scene :

Code : Tout sélectionner

respawn_points = null

Nous réglons respawn_point à null de sorte que quand/si nous arrivons à un niveau sans points de régénération, nous ne nous régénérerons pas aux respawn_point dans le niveau précédent.

Maintenant, tout ce qu'il nous faut, c'est un moyen d'établir les points de régénération. Ouvrez Ruins_Level.tscn et sélectionnez Spawn_Points. Ajoutez un nouveau script appelé Respawn_Point_Setter.gd et attachez-le aux Spawn_Points. Ajoutez ce qui suit à Respawn_Point_Setter.gd

Code : Tout sélectionner

extends Spatial

func _ready():
    var globals = get_node("/root/Globals")
    globals.respawn_points = get_children()
Maintenant, quand un nœud avec Respawn_Point_Setter.gd a sa fonction _ready appelée, tous les nœuds enfants du nœud avec Respawn_Point_Setter.gd, Spawn_Points dans le cas de Ruins_Level.tscn, seront ajoutés aux respawn_points dans Globals.gd.

Attention :
Tout noeud avec Respawn_Point_Setter.gd doit être au-dessus du joueur dans l'arbre de scène afin que les points de régénération soient définis avant que le joueur en ait besoin dans sa fonction _ready.


Maintenant, quand vous mourrez, vous renaîtrez après 4 secondes d'attente !

Remarque :
Aucun point de régénération n'est déjà établi pour aucun des niveaux à part Ruins_Level.tscn ! L'ajout de ces points à Space_Level.tscn est laissé comme exercice pour le lecteur.



Écrire un système de sonorisation que l'on peut utiliser depuis n'importe où.

Enfin, faisons un système de sonorisation pour que nous puissions jouer des sons de n'importe où, sans avoir à utiliser le joueur.

Tout d'abord, ouvrez SimpleAudioPlayer.gd et changez-le comme suit :

Code : Tout sélectionner

extends Spatial

var audio_node = null
var should_loop = false
var globals = null

func _ready():
    audio_node = $Audio_Stream_Player
    audio_node.connect("finished", self, "sound_finished")
    audio_node.stop()

    globals = get_node("/root/Globals")


func play_sound(audio_stream, position=null):
    if audio_stream == null:
        print ("No audio stream passed, cannot play sound")
        globals.created_audio.remove(globals.created_audio.find(self))
        queue_free()
        return

    audio_node.stream = audio_stream

    # If you are using a AudioPlayer3D, then uncomment these lines to set the position.
    # if position != null:
    #       audio_node.global_transform.origin = position

    audio_node.play(0.0)


func sound_finished():
    if should_loop:
        audio_node.play(0.0)
    else:
        globals.created_audio.remove(globals.created_audio.find(self))
        audio_node.stop()
        queue_free()
Il y a plusieurs changements par rapport à l'ancienne version, d'abord et avant tout parce que nous ne stockons plus les fichiers son dans SimpleAudioPlayer.gd. C'est beaucoup mieux pour la performance puisque nous ne chargeons plus chaque clip audio lorsque nous créons un son, mais nous forçons un flux audio à être transmis pour play_sound.

Un autre changement est que nous avons une nouvelle variable globale appelée should_loop. Au lieu de détruire le lecteur audio chaque fois qu'il est terminé, nous voulons plutôt vérifier si nous sommes en boucle ou non. Cela nous permet d'avoir de l'audio avec une musique de fond en boucle sans avoir à créer un nouveau lecteur pour la musique lorsque l'ancien lecteur est terminé.

Enfin, au lieu d'être instancié/générée dans Player.gd, nous allons plutôt être généré dans Globals.gd afin de pouvoir créer des sons à partir de n'importe quelle scène. Nous devons maintenant stocker le fichier singleton Globals.gd de sorte que lorsque nous détruisons le lecteur audio, nous le supprimons également d'une liste dans Globals.gd.

Passons en revue les changements.

Pour les variables globales, nous avons supprimé toutes les variables audio_[insérer_le_nom_ici] puisque nous les aurons transmises à la place. Nous avons également ajouté deux nouvelles variables globales, should_loop et globals. Nous utiliserons should_loop pour dire si nous voulons faire une boucle quand le son sera terminé, et globals gérera le singleton Globals.gd.

Le seul changement dans _ready est maintenant que nous obtenons le singleton Globals.gd et que nous l'assignons aux globals.

Dans play_sound, on s'attend maintenant à ce qu'un flux audio, nommé audio_stream, soit passé en lieu et place de sound_name. Au lieu de vérifier le nom du son et de définir le flux pour le lecteur audio, nous vérifions plutôt pour nous assurer qu'un flux audio a été transmis. Si un flux audio n'est pas transmis, nous imprimons un message d'erreur, supprimons le lecteur audio d'une liste dans le fichier Globals.gd singleton appelé created_audio, puis libérons le lecteur audio.

Enfin, dans sound_finished, nous vérifions d'abord si nous sommes supposés boucler ou non en utilisant should_loop. Si nous sommes supposés faire une boucle, nous rejouons le son à partir du début de l'audio, à la position 0.0. Si nous ne sommes pas censés faire une boucle, nous supprimons le lecteur audio d'une liste dans le fichier Globals.gd singleton appelé created_audio, puis nous libérons le lecteur audio.

Maintenant que nous avons terminé nos changements à SimpleAudioPlayer.gd, nous devons maintenant porter notre attention sur Globals.gd. Tout d'abord, ajoutez les variables globales suivantes :

Code : Tout sélectionner

# ------------------------------------
# All of the audio files.

# You will need to provide your own sound files.
var audio_clips = {
    "pistol_shot":null, #preload("res://path_to_your_audio_here!")
    "rifle_shot":null, #preload("res://path_to_your_audio_here!")
    "gun_cock":null, #preload("res://path_to_your_audio_here!")
}

const SIMPLE_AUDIO_PLAYER_SCENE = preload("res://Simple_Audio_Player.tscn")
var created_audio = []
# ------------------------------------

Survolons ces variables globales :
  • audio_clips: Un dictionnaire gérant tous les clips audio que nous pouvons jouer.
  • SIMPLE_AUDIO_PLAYER_SCENE : La scène du lecteur audio simple.
  • created_audio : Une liste pour gérer tous les lecteurs audio simples que nous créons.
Remarque :
Si vous voulez ajouter de l'audio supplémentaire, vous devez l'ajouter à audio_clips. Aucun fichier audio n'est fourni dans ce tutoriel, vous devrez donc fournir le vôtre.
Un site que je recommande est GameSounds.xyz. J'utilise "Gamemaster audio gun sound pack" inclus dans "Sonniss’ GDC Game Audio bundle for 2017". Les pistes que j'ai utilisées (avec quelques retouches mineures) sont les suivantes :

  • gun_revolver_pistol_shot_04,
  • gun_semi_auto_rifle_cock_02,
  • gun_submachine_auto_shot_00_automatic_preview_01



Nous devons maintenant ajouter une nouvelle fonction appelée play_sound à Globals.gd :

Code : Tout sélectionner

func play_sound(sound_name, loop_sound=false, sound_position=null):
    if audio_clips.has(sound_name):
        var new_audio = SIMPLE_AUDIO_PLAYER_SCENE.instance()
        new_audio.should_loop = loop_sound

        add_child(new_audio)
        created_audio.append(new_audio)

        new_audio.play_sound(audio_clips[sound_name], sound_position)

    else:
        print ("ERROR: cannot play sound that does not exist in audio_clips!")

Revoyons ce que fait ce script.

Tout d'abord, nous vérifions si nous avons un clip audio avec le nom sound_name dans audio_clips. Si nous ne le faisons pas, nous imprimons un message d'erreur.

Si nous avons un clip audio avec le nom sound_name, nous créons un nouveau SIMPLE_AUDIO_AUDIO_PLAYER_SCENE et nous l'assignons à new_audio.

Nous définissons ensuite should_loop, et ajoutons new_audio en tant qu'enfant de Globals.gd.

Remarque :
Rappelez-vous, nous devons faire attention en ajoutant des nœuds à un singleton, car ces nœuds ne seront pas détruits lors du changement de scènes.


Nous appelons alors play_sound, en passant dans le clip audio associé au sound_name, et la position du son.

Avant de quitter Globals.gd, nous devons ajouter quelques lignes de code à load_new_scene pour que lorsque nous changeons de scène, nous détruisions tout l'audio.

Ajoutez ce qui suit à load_new_scene :

Code : Tout sélectionner

for sound in created_audio:
    if (sound != null):
        sound.queue_free()
created_audio.clear()

Maintenant, avant de changer de scènes, nous passons en revue chaque lecteur audio simple dans les created_sounds et nous les détruisons. Une fois que nous avons parcouru tous les sons dans created_audio, nous effaçons created_audio afin qu'il ne contienne plus aucune référence aux lecteurs audio simples créés précédemment.

Changeons create_sound dans Player.gd pour utiliser ce nouveau système. Tout d'abord, supprimez simple_audio_player des variables globales de Player.gd, puisque nous n’ instancions/générons plus directement de sons dans Player.gd.

Maintenant, changez create_sound pour ce qui suit :

Code : Tout sélectionner

func create_sound(sound_name, position=null):
    globals.play_sound(sound_name, false, position)

Maintenant, chaque fois que create_sound est appelé, nous appelons simplement play_sound dans Globals.gd, en passant tous les arguments que nous avons relancés.

De plus, tous les sons de notre FPS peuvent être joués de n'importe où. Tout ce que nous avons à faire est d'obtenir le Globals.gd singleton, et d'appeler play_sound, en passant le nom du son que nous voulons jouer, que nous voulions le boucler ou non, et la position à partir de laquelle jouer le son.

Par exemple, si vous voulez jouer un son d'explosion lorsque les grenades explosent, vous devrez ajouter un nouveau son à audio_clips dans Globals.gd, obtenir le singleton Globals.gd, et puis ajouter quelque chose comme :

Code : Tout sélectionner

globals.play_sound("explosion", false, global_transform.origin) 

Dans la fonction grenades _process, juste après que la grenade endommage tous les corps à l'intérieur de son rayon de souffle.


Remarques Finales

Image

Maintenant, vous disposez d'un FPS solo entièrement fonctionnel !

À ce stade, vous avez une bonne base pour construire des jeux FPS plus compliqués.

Attention :
Si jamais vous vous perdez, n'oubliez pas de relire le code !
Vous pouvez télécharger le projet terminé pour l'ensemble du tutoriel ici : Godot_FPS_Part_6.zip


Remarque :
Les fichiers source du projet fini contiennent exactement le même code, juste écrit dans un ordre différent. C'est parce que les fichiers sources du projet fini sont à la base du tutoriel.
Le code du projet fini a été écrit dans l'ordre dans lequel les caractéristiques ont été créées, pas nécessairement dans un ordre idéal pour l'apprentissage.
En dehors de cela, la source est exactement la même, juste avec des commentaires utiles expliquant ce que chaque partie fait.


Astuce :
Le code source du projet fini est également hébergé sur Github : https://github.com/TwistedTwigleg/Godot ... S_Tutorial
Veuillez noter que le code de Github peut ou non être synchronisé avec le tutoriel de la documentation.
Le code dans la documentation est probablement mieux géré et/ou plus à jour. Si vous n'êtes pas sûr sur lequel utiliser, utilisez le(s) projet(s) fourni(s) dans la documentation tels qu'ils sont maintenus par la communauté Godot.


Vous pouvez télécharger tous les fichiers .blend dans ce tutoriel ici: Godot_FPS_BlenderFiles.zip

Toutes les ressources fournies dans Starter (sauf indication contraire) ont été créés à l'origine par TwistedTwigleg, avec des changements/ajouts par la communauté Godot. Tous les actifs originaux fournis pour ce tutoriel sont diffusés sous licence MIT.

N'hésitez pas à utiliser ces ressources comme vous le souhaitez ! Toutes les ressources originales appartiennent à la communauté Godot, les autres appartenant aux personnes énumérés ci-dessous :

La skybox a été créée par StumpyStrust et peut être trouvée sur OpenGameArt.org. https://opengameart.org/content/space-skyboxes-0 . La skybox est sous licence CC0.

La police utilisée est Titillium-Regular, sous licence SIL Open Font License, Version 1.1.

La skybox a été converti en une image équirectangulaire de 360 degrés en utilisant cet outil : https://www.360toolkit.co/convert-cubem ... gular.html

Bien qu'aucun son n’ait été fourni, vous pourrez en trouvez beaucoup de prêt pour les jeux sur https://gamesounds.xyz/

Remarque :
OpenGameArt.org, 360toolkit.co, le(s) créateur(s) de Titillium-Regular, StumpyStrust, et GameSounds.xyz ne sont en aucun cas impliqués dans ce tutoriel.



Tutoriel FPS - Partie 6 - 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