Partie 1

Tutoriel d’introduction

../../../_images/FinishedTutorialPicture.png

Cette série de tutoriels vous montrera comment créer un jeu FPS à un seul joueur.

Tout au long de cette série de tutoriels, nous vous expliquerons comment :

  • Faire un personnage à la première personne qui peut se déplacer, sprinter et sauter.
  • Pour faire une machine d’état d’animation simple pour gérer les transitions d’animation.
  • Pour ajouter trois armes au personnage à la première personne, chacune utilisant une façon différente de gérer les collisions de balles :
    • Un couteau (en utilisant un Area)
    • Un pistolet (scènes de balles)
    • Un fusil (utilisant un Raycast)
  • Ajouter deux types différents de grenades au personnage :
    • Une grenade normale
    • Une grenade collante
  • Ajouter la possibilité de saisir et de lancer des nœuds RigidBody
  • Ajouter une entrée joypad pour le joueur
  • Ajouter des munitions et recharger toutes les armes qui consomment des munitions.
  • Ajouter le ramassage des munitions et de santé
    • En deux tailles : grande et petite
  • Ajouter une tourelle automatique
    • Qui peut tirer en utilisant des objets bullet ou un Raycast
  • Ajouter des cibles qui se brisent quand elles ont subi suffisamment de dégâts
  • Ajouter des sons quand les armes à feu tirent.
  • Ajouter un menu principal simple :
    • Avec un menu d’options pour changer le déroulement du jeu
    • Avec un écran de sélection de niveau
  • Ajouter un menu de pause universel, auquel nous pouvons accéder depuis n’importe quel endroit

Note

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

Rappelez-vous: faire des jeux 3D est beaucoup plus difficile que de faire des jeux 2D. Si vous ne savez pas comment faire des jeux 2D, vous aurez probablement du mal à faire des jeux en 3D.

Ce tutoriel suppose que vous avez de l’expérience avec l’éditeur Godot, une expérience de base en programmation GDScript, et une expérience de base en développement de jeux.

Vous pouvez trouver les ressources pour débuter ce tutoriel ici : Godot_FPS_Starter.zip

Les ressources de démarrage fournies contiennent un modèle 3D animé, un ensemble de modèles 3D pour créer des niveaux et quelques scènes déjà configurées pour ce tutoriel.

Toutes les ressources fournies (sauf indication contraire) ont été créés à l’origine par TwistedTwigleg, avec des changements/ajouts par la communauté Godot. Toutes les ressources originales fournies pour ce tutoriel sont publiées sous la licence MIT.

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

Note

La skybox est créée par StumpyStrust sur OpenGameArt. La skybox utilisée est sous licence CC0.

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

Astuce

Vous pouvez trouver le projet fini pour chaque pièce au bas de la page de chaque pièce

Aperçu cette partie

Dans cette partie, nous allons faire un personnage joueur à la première personne qui peut se déplacer dans l’environnement.

../../../_images/PartOneFinished.png

À la fin de cette partie, vous aurez un personnage à la première personne qui peut se déplacer dans l’environnement de jeu, courir, regarder autour de lui avec une caméra à la première personne, sauter dans les airs et allumer et éteindre une lampe flash.

Tout préparer

Lancez Godot et ouvrez le projet inclus dans les ressources de démarrage.

Note

Bien que ces ressources ne soient pas nécessairement nécessaires pour utiliser les scripts fournis dans ce tutoriel, elles rendront le tutoriel beaucoup plus facile à suivre, car il y a plusieurs scènes pré-installées que nous utiliserons tout au long de la série de tutoriels.

Ouvrez d’abord les paramètres du projet et allez dans l’onglet « Input Map ». Vous constaterez que plusieurs actions ont déjà été définies. Nous utiliserons ces actions pour notre joueur. N’hésitez pas à changer les touches liées à ces actions si vous le souhaitez.


Prenons une seconde pour voir ce que nous avons dans les ressources de démarrage.

Plusieurs scènes sont incluses dans les ressources de départ. Par exemple, dans res://, nous avons 14 scènes, dont la plupart seront visitées au cours de cette série de tutoriels.

Pour l’instant, ouvrons Player.tscn.

Note

Il y a un tas de scènes et quelques textures dans le dossier Assets. Vous pouvez y jeter un coup d’œil si vous voulez, mais nous ne parcourrons pas Assets dans cette série de tutoriels. Assets contient tous les modèles utilisés pour chacun des niveaux, ainsi que quelques textures et matériaux.

Faire la logique de mouvement FPS

Une fois que vous avez Player.tscn ouvert, jetons un coup d’œil rapide à la façon dont il est configuré

../../../_images/PlayerSceneTree.png

Tout d’abord, remarquez comment les formes de collision du joueur sont configurées. L’utilisation d’une capsule à pointe verticale comme forme de collision pour le joueur est assez courante dans la plupart des jeux à la première personne.

Nous ajoutons un petit carré aux  » pieds  » du joueur pour qu’il n’ait pas l’impression d’être en équilibre sur un seul point.

Nous voulons que les “pieds” soient légèrement plus hauts que le fond de la capsule afin de pouvoir rouler sur de légers bords. L’endroit où placer les “pieds” dépend de votre niveau et de la façon dont vous voulez que votre joueur se sente.

Note

Souvent, le joueur remarquera que la forme de la collision est circulaire lorsqu’il marche jusqu’à un bord et qu’il glisse. Nous ajoutons le petit carré au bas de la capsule pour réduire le glissement sur et autour des bords.

Une autre chose à remarquer est combien de nœuds sont des enfants de Rotation_Helper. C’est parce que Rotation_Helper contient tous les nœuds que nous voulons faire tourner sur l’axe X (haut et bas). La raison en est que nous pouvons faire pivoter le Joueur'' sur l'axe ``Y'' et ``Rotation_Helper sur l’axe ``X”“.

Note

Si nous n’avions pas utilisé Rotation_helper, nous aurions probablement eu des cas de rotation simultanée sur les axes X et Y, qui auraient pu dégénérer en un état de rotation sur les trois axes dans certains cas.

Voir using transforms pour plus d’informations


Attachez un nouveau script au nœud Player et appelez-le Player.gd.

Programmons notre joueur en ajoutant la possibilité de se déplacer, de regarder autour de lui avec la souris et de sauter. Ajoutez le code suivant à Player.gd :

extends KinematicBody

const GRAVITY = -24.8
var vel = Vector3()
const MAX_SPEED = 20
const JUMP_SPEED = 18
const ACCEL = 4.5

var dir = Vector3()

const DEACCEL= 16
const MAX_SLOPE_ANGLE = 40

var camera
var rotation_helper

var MOUSE_SENSITIVITY = 0.05

func _ready():
    camera = $Rotation_Helper/Camera
    rotation_helper = $Rotation_Helper

    Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)

func _physics_process(delta):
    process_input(delta)
    process_movement(delta)

func process_input(delta):

    # ----------------------------------
    # Walking
    dir = Vector3()
    var cam_xform = camera.get_global_transform()

    var input_movement_vector = Vector2()

    if Input.is_action_pressed("movement_forward"):
        input_movement_vector.y += 1
    if Input.is_action_pressed("movement_backward"):
        input_movement_vector.y -= 1
    if Input.is_action_pressed("movement_left"):
        input_movement_vector.x -= 1
    if Input.is_action_pressed("movement_right"):
        input_movement_vector.x += 1

    input_movement_vector = input_movement_vector.normalized()

    # Basis vectors are already normalized.
    dir += -cam_xform.basis.z * input_movement_vector.y
    dir += cam_xform.basis.x * input_movement_vector.x
    # ----------------------------------

    # ----------------------------------
    # Jumping
    if is_on_floor():
        if Input.is_action_just_pressed("movement_jump"):
            vel.y = JUMP_SPEED
    # ----------------------------------

    # ----------------------------------
    # Capturing/Freeing the cursor
    if Input.is_action_just_pressed("ui_cancel"):
        if Input.get_mouse_mode() == Input.MOUSE_MODE_VISIBLE:
            Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
        else:
            Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
    # ----------------------------------

func process_movement(delta):
    dir.y = 0
    dir = dir.normalized()

    vel.y += delta * GRAVITY

    var hvel = vel
    hvel.y = 0

    var target = dir
    target *= MAX_SPEED

    var accel
    if dir.dot(hvel) > 0:
        accel = ACCEL
    else:
        accel = DEACCEL

    hvel = hvel.linear_interpolate(target, accel * delta)
    vel.x = hvel.x
    vel.z = hvel.z
    vel = move_and_slide(vel, Vector3(0, 1, 0), 0.05, 4, deg2rad(MAX_SLOPE_ANGLE))

func _input(event):
    if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
        rotation_helper.rotate_x(deg2rad(event.relative.y * MOUSE_SENSITIVITY))
        self.rotate_y(deg2rad(event.relative.x * MOUSE_SENSITIVITY * -1))

        var camera_rot = rotation_helper.rotation_degrees
        camera_rot.x = clamp(camera_rot.x, -70, 70)
        rotation_helper.rotation_degrees = camera_rot
using Godot;
using System;

public class Player : KinematicBody
{
    [Export]
    public float Gravity = -24.8f;
    [Export]
    public float MaxSpeed = 20.0f;
    [Export]
    public float JumpSpeed = 18.0f;
    [Export]
    public float Accel = 4.5f;
    [Export]
    public float Deaccel = 16.0f;
    [Export]
    public float MaxSlopeAngle = 40.0f;
    [Export]
    public float MouseSensitivity = 0.05f;

    private Vector3 _vel = new Vector3();
    private Vector3 _dir = new Vector3();

    private Camera _camera;
    private Spatial _rotationHelper;

    // Called when the node enters the scene tree for the first time.
    public override void _Ready()
    {
        _camera = GetNode<Camera>("Rotation_Helper/Camera");
        _rotationHelper = GetNode<Spatial>("Rotation_Helper");

        Input.SetMouseMode(Input.MouseMode.Captured);
    }

    public override void _PhysicsProcess(float delta)
    {
        ProcessInput(delta);
        ProcessMovement(delta);
    }

    private void ProcessInput(float delta)
    {
        //  -------------------------------------------------------------------
        //  Walking
        _dir = new Vector3();
        Transform camXform = _camera.GetGlobalTransform();

        Vector2 inputMovementVector = new Vector2();

        if (Input.IsActionPressed("movement_forward"))
            inputMovementVector.y += 1;
        if (Input.IsActionPressed("movement_backward"))
            inputMovementVector.y -= 1;
        if (Input.IsActionPressed("movement_left"))
            inputMovementVector.x -= 1;
        if (Input.IsActionPressed("movement_right"))
            inputMovementVector.x += 1;

        inputMovementVector = inputMovementVector.Normalized();

        // Basis vectors are already normalized.
        _dir += -camXform.basis.z * inputMovementVector.y;
        _dir += camXform.basis.x * inputMovementVector.x;
        //  -------------------------------------------------------------------

        //  -------------------------------------------------------------------
        //  Jumping
        if (IsOnFloor())
        {
            if (Input.IsActionJustPressed("movement_jump"))
                _vel.y = JumpSpeed;
        }
        //  -------------------------------------------------------------------

        //  -------------------------------------------------------------------
        //  Capturing/Freeing the cursor
        if (Input.IsActionJustPressed("ui_cancel"))
        {
            if (Input.GetMouseMode() == Input.MouseMode.Visible)
                Input.SetMouseMode(Input.MouseMode.Captured);
            else
                Input.SetMouseMode(Input.MouseMode.Visible);
        }
        //  -------------------------------------------------------------------
    }

    private void ProcessMovement(float delta)
    {
        _dir.y = 0;
        _dir = _dir.Normalized();

        _vel.y += delta * Gravity;

        Vector3 hvel = _vel;
        hvel.y = 0;

        Vector3 target = _dir;

        target *= MaxSpeed;

        float accel;
        if (_dir.Dot(hvel) > 0)
            accel = Accel;
        else
            accel = Deaccel;

        hvel = hvel.LinearInterpolate(target, accel * delta);
        _vel.x = hvel.x;
        _vel.z = hvel.z;
        _vel = MoveAndSlide(_vel, new Vector3(0, 1, 0), false, 4, Mathf.Deg2Rad(MaxSlopeAngle));
    }

    public override void _Input(InputEvent @event)
    {
        if (@event is InputEventMouseMotion && Input.GetMouseMode() == Input.MouseMode.Captured)
        {
            InputEventMouseMotion mouseEvent = @event as InputEventMouseMotion;
            _rotationHelper.RotateX(Mathf.Deg2Rad(mouseEvent.Relative.y * MouseSensitivity));
            RotateY(Mathf.Deg2Rad(-mouseEvent.Relative.x * MouseSensitivity));

            Vector3 cameraRot = _rotationHelper.RotationDegrees;
            cameraRot.x = Mathf.Clamp(cameraRot.x, -70, 70);
            _rotationHelper.RotationDegrees = cameraRot;
        }
    }
}

C’est beaucoup de code, alors décomposons-le fonction par fonction :

Astuce

Bien qu’il soit déconseillé de copier et coller du code, car vous pouvez apprendre beaucoup en tapant le code manuellement, vous pouvez copier et coller le code de cette page directement dans l’éditeur de script.

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

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


Tout d’abord, nous définissons quelques variables de classe pour dicter comment notre joueur va se déplacer dans le monde.

Note

Tout au long de ce tutoriel, les variables définies à l’extérieur de fonctions seront appelées « variables de classe ». C’est parce que nous avons accès à ces variables à partir de n’importe quel endroit dans le script.

Passons en revue chacune des variables de classe :

  • GRAVITY : Comme la gravité nous tire vers le bas.
  • vel : la vélocité de notre CinematicBody.
  • MAX_SPEED : La vitesse la plus rapide que nous puissions atteindre. Une fois que nous aurons atteint cette vitesse, nous n’irons pas plus vite.
  • JUMP_SPEED : À quelle hauteur nous pouvons sauter.
  • ACCEL : L’accélération. Plus la valeur est élevée, plus vite nous atteignons la vitesse maximale.
  • DEACCEL : La décéléreration. Plus la valeur est élevée, plus vite nous nous arrêterons complètement.
  • MAX_SLOPE_ANGLE: L’angle le plus raide que notre KinematicBody considère comme un “plancher”.
  • camera : Le nœud Camera.
  • rotation_helper : Un nœud Spatial contenant tout ce que nous voulons faire tourner sur l’axe X (haut et bas).
  • MOUSE_SENSITIVITY : La sensibilité de la souris. Je trouve qu’une valeur de 0.05 fonctionne bien pour ma souris, mais vous devrez peut-être la modifier en fonction de la sensibilité de votre souris.

Vous pouvez ajuster plusieurs de ces variables pour obtenir des résultats différents. Par exemple, en diminuant GRAVITY et/ou en augmentant ``JUMP_SPEED``vous pouvez obtenir un personnage plus “flottant”. N’hésitez pas à expérimenter !

Note

Vous avez peut-être remarqué que MOUSE_SENSITIVITY est écrit en majuscules comme les autres constantes, mais MOUSE_SENSITIVITY n’est pas une constante.

La raison est que nous voulons le traiter comme une variable constante (une variable qui ne peut pas changer) tout au long de notre script, mais nous voulons être en mesure de changer la valeur plus tard lorsque nous ajoutons des paramètres personnalisables. Donc, dans un effort pour nous rappeler de la traiter comme une constante, elle est nommée en majuscules.


Voyons maintenant la fonction _ready :

On obtient d’abord les nœuds camera et rotation_helper et on les stocke dans leurs variables.

Ensuite, nous devons régler le mode de capture de la souris, de sorte que la souris ne puisse pas quitter la fenêtre de jeu.

Ceci cachera la souris et la gardera au centre de l’écran. Nous le faisons pour deux raisons : La première raison étant que nous ne voulons pas que le joueur voit le curseur de sa souris en jouant.

La deuxième raison est que nous ne voulons pas que le curseur quitte la fenêtre de jeu. Si le curseur quitte la fenêtre de jeu, il peut y avoir des cas où le joueur clique à l’extérieur de la fenêtre, et alors le jeu perdrait le focus. Pour s’assurer qu’aucun de ces problèmes ne se produise, nous capturons le curseur de la souris.

Note

voir Documentation d’entrée pour les différents modes de souris. Nous utiliserons seulement MOUSE_MODE_CAPTURED``et ``MOUSE_MODE_VISIBLE dans cette série de tutoriels.


Voyons maintenant _processus_physique :

Tout ce que nous faisons dans _physics_process` est d’appeler deux fonctions : process_input et process_movement.

process_input sera l’endroit où nous stockerons tout le code relatif aux entrées joueur. Nous voulons l’appeler d’abord, avant toutes choses, pour que nous puissions travailler avec les dernière entrées joueur.

process_movement est l’endroit où nous enverrons toutes les données nécessaires au KinematicBody pour lui permettre de se déplacer dans le monde du jeu.


Regardons process_input suivant :

Tout d’abord, nous réglons dir à une Vector3 vide.

dir` sera utilisé pour mémoriser la direction vers laquelle le joueur a l’intention de se déplacer. Parce que nous ne voulons pas que l’entrée précédente du joueur affecte le joueur au-delà d’un seul appel de process_movement, nous réinitialisons dir.

Ensuite, nous récupérons la transformation globale de la caméra et la stockons également, dans la variable cam_xform.

La raison pour laquelle nous avons besoin de la transformation globale de la caméra est que nous puissions utiliser ses vecteurs directionnels. Beaucoup ont trouvé les vecteurs directifs déroutants, alors prenons une seconde pour expliquer comment ils fonctionnent :


L’espace mondial peut être défini comme il suit : L’espace dans lequel tous les objets sont placés par rapport à un point d’origine constant. Chaque objet, qu’il soit en 2D ou en 3D, a une position dans l’espace mondial.

En d’autres termes, l’espace mondial est l’espace dans un univers où la position, la rotation et l’échelle de chaque objet peuvent être mesurées par un seul point fixe connu, appelé origine.

Dans Godot, l’origine est à la position (0, 0, 0) avec une rotation de (0, 0, 0) et une échelle de``(1, 1, 1)``.

Note

Lorsque vous ouvrez l’éditeur Godot et sélectionnez un nœud basé sur Spatial, un gadget apparaît. Chacune des flèches pointe en utilisant les directions de l’espace mondial par défaut.

Si vous voulez vous déplacer en utilisant les vecteurs directionnels de l’espace mondial, vous feriez quelque chose comme ceci :

if Input.is_action_pressed("movement_forward"):
    node.translate(Vector3(0, 0, 1))
if Input.is_action_pressed("movement_backward"):
    node.translate(Vector3(0, 0, -1))
if Input.is_action_pressed("movement_left"):
    node.translate(Vector3(1, 0, 0))
if Input.is_action_pressed("movement_right"):
    node.translate(Vector3(-1, 0, 0))
if (Input.IsActionPressed("movement_forward"))
    node.Translate(new Vector3(0, 0, 1));
if (Input.IsActionPressed("movement_backward"))
    node.Translate(new Vector3(0, 0, -1));
if (Input.IsActionPressed("movement_left"))
    node.Translate(new Vector3(1, 0, 0));
if (Input.IsActionPressed("movement_right"))
    node.Translate(new Vector3(-1, 0, 0));

Note

Remarquez que nous n’avons pas besoin de faire de calculs pour obtenir des vecteurs directionnels de l’espace mondial. On peut définir quelques variables Vector3 et entrer les valeurs pointant dans chaque direction.

Voici à quoi ressemble l’espace mondial en 2D :

Note

Les images suivantes ne sont que des exemples. Chaque flèche/rectangle représente un vecteur directionnel

../../../_images/WorldSpaceExample.png

Et voici à quoi cela ressemble pour la 3D :

../../../_images/WorldSpaceExample_3D.png

Notez que dans les deux exemples, la rotation du nœud ne change pas les flèches directionnelles. C’est parce que l’espace mondial est une constante. Quelle que soit la façon dont vous bougez, faites pivoter ou mettez à l’échelle un objet, l’espace du monde indiquera toujours la même direction.

L’espace local est différent, car il prend en compte la rotation de l’objet.

L’espace local peut être défini comme il suit : L’espace dans lequel la position d’un objet est à l’origine de l’univers. Comme la position de l’origine peut se trouver à de nombreux endroits, les valeurs dérivées de l’espace local changent en fonction de la position de l’origine.

Note

Cette question sur stack overflow a une bien meilleure explication de l’espace mondial et de l’espace local.

https://gamedev.stackexchange.com/questions/65783/what-are-world-space-and-eye-space-in-game-development (L’espace local et l’espace oculaire sont essentiellement la même chose dans ce contexte)

Pour obtenir l’espace local d’un nœud Spatial, nous devons obtenir son Transform, afin de pouvoir obtenir le Basis depuis le Transform.

Chaque Base a trois vecteurs: X, Y et Z. Chacun de ces vecteurs pointent vers chacun des vecteurs de l’espace local venant de cet objet.

Pour utiliser les vecteurs directionnels locaux du nœud Spatial, nous utilisons ce code :

if Input.is_action_pressed("movement_forward"):
    node.translate(node.global_transform.basis.z.normalized())
if Input.is_action_pressed("movement_backward"):
    node.translate(-node.global_transform.basis.z.normalized())
if Input.is_action_pressed("movement_left"):
    node.translate(node.global_transform.basis.x.normalized())
if Input.is_action_pressed("movement_right"):
    node.translate(-node.global_transform.basis.x.normalized())
if (Input.IsActionPressed("movement_forward"))
    node.Translate(node.GlobalTransform.basis.z.Normalized());
if (Input.IsActionPressed("movement_backward"))
    node.Translate(-node.GlobalTransform.basis.z.Normalized());
if (Input.IsActionPressed("movement_left"))
    node.Translate(node.GlobalTransform.basis.x.Normalized());
if (Input.IsActionPressed("movement_right"))
    node.Translate(-node.GlobalTransform.basis.x.Normalized());

Voici à quoi ressemble l’espace local en 2D :

../../../_images/LocalSpaceExample.png

Et voici à quoi cela ressemble pour la 3D :

../../../_images/LocalSpaceExample_3D.png

Voici ce que le gadget Spatial affiche lorsque vous utilisez le mode espace local. Remarquez comment les flèches suivent la rotation de l’objet à gauche, qui ressemble exactement à l’exemple 3D pour l’espace local.

Note

Vous pouvez passer du mode espace local au mode espace mondial en appuyant sur T ou sur le petit bouton cube lorsque vous avez sélectionné un nœud basé sur Spatial.

../../../_images/LocalSpaceExampleGizmo.png

Les vecteurs locaux sont déroutants même pour les développeurs de jeux plus expérimentés, donc ne vous inquiétez pas si tout cela n’a pas beaucoup de sens. La chose clé à retenir au sujet des vecteurs locaux est que nous utilisons des coordonnées locales pour obtenir la direction du point de vue de l’objet, par opposition à l’utilisation de vecteurs mondiaux, qui donnent la direction du point de vue du monde.


Bon, revenons à process_input :

Ensuite, nous créons une nouvelle variable appelée input_movement_vector et l’assignons à un Vector2. Nous l’utiliserons pour faire une sorte d’axe virtuel, pour représenter l’entrée joueur pour le mouvement.

Note

Cela peut sembler exagéré pour un simple clavier, mais cela aura un sens plus tard, lorsque nous ajouterons des entrées de joypad.

En fonction de l’action directionnelle de mouvement pressée, on ajoute ou on soustrait à input_movement_vector.

Après avoir vérifié chacune des actions de mouvement directionnel, nous normalisons input_movement_vector. Cela fait que les valeurs de input_inmovement_vector sont à l’intérieur d’un cercle d’unité de rayon 1.

Ensuite, nous ajoutons le vecteur local de la caméra Z multiplié par input_movement_vector.y à dir. C’est ainsi que lorsque le joueur avance ou recule, nous ajoutons l’axe local ``Z”” de la caméra pour que le lecteur avance ou recule par rapport à la caméra.

Note

Parce que la caméra est tournée de -180 degrés, nous devons retourner le vecteur directionnel Z. Normalement en avant serait l’axe Z positif, donc utiliser basis.z.normalisé() fonctionnerait, mais nous utilisons -basis.z.normalisé() parce que l’axe Z de notre caméra est orienté vers l’arrière par rapport au reste du joueur.

Nous faisons la même chose pour le vecteur local X de la caméra, et au lieu d’utiliser input_movement_vector.y nous utilisons plutôt input_movement_vector.x. Cela signifie que le lecteur se déplace vers la gauche/droite par rapport à la caméra lorsque le joueur appuie vers la gauche/droite.

Ensuite, nous vérifions si le joueur est sur le sol en utilisant la fonction KinematicBody <class_KinematicBody>`s ``is_on_floor`. Si c’est le cas, alors on vérifie si l’action « movement_jump » vient d’être appuyée. Si c’est le cas, alors nous réglons la vélocité Y du joueur sur JUMP_SPEED.

Parce que nous réglons la vélocité Y, le joueur va sauter dans les airs.

Ensuite, nous vérifions l’action ui_cancel. C’est ainsi que nous pouvons libérer/capturer le curseur de la souris quand le bouton escape est pressé. Nous faisons cela parce que sinon nous n’aurions aucun moyen de libérer le curseur, ce qui signifie qu’il serait bloqué jusqu’à ce que vous terminiez le runtime.

Pour libérer/capturer le curseur, nous vérifions si la souris est visible (libérée) ou non. Si c’est le cas, nous le capturons, et si ce n’est pas le cas, nous le rendons visible (nous le libérons).

C’est tout ce que nous faisons pour l’instant pour « process_input ». Nous reviendrons plusieurs fois sur cette fonction au fur et à mesure que nous ajouterons de la complexité à notre joueur.


Regardons maintenant le process_movement :

First we ensure that dir does not have any movement on the Y axis by setting its Y value to zero.

Ensuite, nous normalisons dir pour nous assurer que nous sommes à l’intérieur d’un cercle d’unité de rayon de 1. C’est ce qui fait que nous nous déplaçons à une vitesse constante, que le joueur se déplace en ligne droite ou en diagonale. Si nous ne nous normalisions pas, le joueur se déplacerait plus rapidement sur la diagonale qu’en allant tout droit.

Ensuite, nous ajoutons la gravité au joueur en ajoutant GRAVITY * delta à la vitesse Y du joueur.

Ensuite, nous assignons la vitesse du joueur à une nouvelle variable (appelée hvel) et supprimons tout mouvement sur l’axe Y.

Ensuite, nous définissons une nouvelle variable (cible) dans le vecteur de direction du joueur. Ensuite, nous multiplions cela par la vitesse maximale du joueur pour savoir jusqu’où le joueur se déplacera dans la direction fournie par dir.

Ensuite, nous créons une nouvelle variable pour l’accélération, appelée accel.

Nous prenons ensuite le produit scalaire du hvel pour voir si le joueur se déplace selon hvel. Rappelez-vous que le hvel n’a pas de vitesse Y, ce qui signifie que nous vérifions seulement si le joueur avance, recule, va à gauche ou va à droite.

Si le joueur se déplace selon hvel, alors nous réglons accel sur la constante ACCEL pour que le joueur accélère, sinon nous réglons accel sur notre constante DEACCEL pour que le joueur ralentisse.

Ensuite, nous interpolons la vitesse horizontale, réglons la vitesse X et Z du joueur sur la vitesse horizontale interpolée, et appelons move_and_slide pour laisser le KinematicBody gérer le déplacement du lecteur dans le monde physique.

Astuce

Tout le code dans process_movement est exactement le même que le code de mouvement de la démo de Kinematic Character !


La dernière fonction que nous avons est la fonction _input, et heureusement elle est assez courte :

Tout d’abord, nous nous assurons que l’événement auquel nous avons affaire est un événement InputEventMouseMotion. Nous voulons aussi vérifier si le curseur est capturé, car nous ne voulons pas tourner s’il ne l’est pas.

Note

Voir Les coordonnées de la souris et de l’entrée pour une liste des événements d’entrée possibles.

Si l’événement est bien un événement de mouvement de souris et que le curseur est capturé, nous tournons en fonction du mouvement relatif de la souris fourni par InputEventMouseMotion.

Tout d’abord, nous tournons le nœud rotation_helper sur l’axe X, en utilisant la valeur Y du mouvement relatif de la souris, fournie par InputEventMouseMotion.

Ensuite, nous faisons pivoter le :ref:KinematicBody <class_KinematicBody>` sur l'axe ``Y par la valeur X du mouvement relatif de la souris.

Astuce

Godot convertit le mouvement relatif de la souris en un Vector2 où le mouvement de la souris qui monte et descend est 1 et -1 respectivement. Le mouvement vers la droite et vers la gauche est 1 et -1 respectivement.

En raison de la façon dont nous faisons tourner le lecteur, nous multiplions la valeur X du mouvement relatif de la souris par -1 pour que le mouvement de la souris tournant à gauche et à droite fasse tourner le lecteur à gauche et à droite dans la même direction.

Finalement, nous bloquons la rotation X de rotation_helper pour qu’elle soit entre -70 et 70 degrés afin que le joueur ne puisse pas se tourner la tête en bas.

Astuce

Voir using transforms pour plus d’informations sur les transformations en rotation.


Pour tester le code, ouvrez la scène nommée Testing_Area.tscn, si elle n’est pas déjà ouverte. Nous utiliserons cette scène au cours des prochaines parties du tutoriel, assurez-vous donc de la garder ouverte dans l’un des onglets de votre scène.

Allez-y et testez votre code soit en appuyant sur F6 avec Testing_Area.tscn comme onglet ouvert, soit en appuyant sur le bouton play dans le coin supérieur droit, soit en appuyant sur F5. Vous devriez maintenant être capable de marcher, de sauter dans les airs et de regarder autour de vous à l’aide de la souris.

Donner au joueur une lampe flash et la possibilité de sprinter

Avant de commencer à faire fonctionner les armes, il y a quelques autres choses que nous devrions ajouter.

De nombreux jeux FPS ont une option pour le sprint et une lampe de poche. Nous pouvons les ajouter facilement à notre joueur, alors faisons-le !

Tout d’abord, nous avons besoin de quelques variables de classe supplémentaires dans notre script de joueur :

const MAX_SPRINT_SPEED = 30
const SPRINT_ACCEL = 18
var is_sprinting = false

var flashlight
[Export]
public float MaxSprintSpeed = 30.0f;
[Export]
public float SprintAccel = 18.0f;
private bool _isSprinting = false;

private SpotLight _flashlight;

Toutes les variables de sprint fonctionnent exactement de la même manière que les variables non sprint avec des noms similaires.

is_sprinting est un booléen pour savoir si le joueur est en train de sprinter, et flashlight est une variable que nous allons utiliser pour tenir le nœud de lumière flash du joueur.

Il nous faut maintenant ajouter quelques lignes de code, en commençant par _ready. Ajouter ce qui suit à _ready :

flashlight = $Rotation_Helper/Flashlight
_flashlight = GetNode<SpotLight>("Rotation_Helper/Flashlight");

Ceci obtient le nœud Flashlight et l’affecte à la variable flashlight.


Nous devons maintenant changer une partie du code dans process_input. Ajoutez ce qui suit quelque part dans process_input :

# ----------------------------------
# Sprinting
if Input.is_action_pressed("movement_sprint"):
    is_sprinting = true
else:
    is_sprinting = false
# ----------------------------------

# ----------------------------------
# Turning the flashlight on/off
if Input.is_action_just_pressed("flashlight"):
    if flashlight.is_visible_in_tree():
        flashlight.hide()
    else:
        flashlight.show()
# ----------------------------------
//  -------------------------------------------------------------------
//  Sprinting
if (Input.IsActionPressed("movement_sprint"))
    _isSprinting = true;
else
    _isSprinting = false;
//  -------------------------------------------------------------------

//  -------------------------------------------------------------------
//  Turning the flashlight on/off
if (Input.IsActionJustPressed("flashlight"))
{
    if (_flashlight.IsVisibleInTree())
        _flashlight.Hide();
    else
        _flashlight.Show();
}

Revoyons les ajouts :

Nous réglons is_sprinting sur true lorsque le joueur maintient l’action movement_sprint, et false lorsque l’action movement_sprint est relâchée. Dans process_movement nous ajouterons le code qui rend le joueur plus rapide lorsqu’il sprinte. Ici, dans process_input, nous allons simplement changer la variable is_sprinting.

Nous faisons quelque chose de similaire pour libérer/capturer le curseur afin de manipuler la lampe flash. Nous vérifions d’abord si l’action flashlight vient d’être pressée. Si c’était le cas, nous vérifions alors si ``flashlight``est visible dans l’arbre de scène. Si c’est le cas, on le cache, et si ce n’est pas le cas, on le montre.


Maintenant nous avons besoin de changer quelques choses dans le process_movement. Tout d’abord, remplacer target *= MAX_SPEED par ce qui suit :

if is_sprinting:
    target *= MAX_SPRINT_SPEED
else:
    target *= MAX_SPEED
if (_isSprinting)
    target *= MaxSprintSpeed;
else
    target *= MaxSpeed;

Maintenant, au lieu de toujours multiplier target``par ``MAX_SPEED, nous vérifions d’abord si le joueur sprinte ou non. Si le joueur sprinte, on multiplie target par MAX_SPRINT_SPEED.

Il ne reste plus qu’à modifier l’accélération au sprint. Modifier accel = ACCEL par ce qui suit :

if is_sprinting:
    accel = SPRINT_ACCEL
else:
    accel = ACCEL
if (_isSprinting)
    accel = SprintAccel;
else
    accel = Accel;

Maintenant, lorsque le joueur sprinte, nous utiliserons SPRINT_ACCEL au lieu de ACCEL, ce qui accélérera le joueur plus rapidement.


Vous devriez maintenant être capable de sprinter si vous appuyez sur le bouton shift, et vous pouvez allumer et éteindre la lampe flash en appuyant sur le bouton F !

Allez l’essayer ! Vous pouvez changer les variables de classe liées au sprint pour rendre le joueur plus rapide ou plus lent lors du sprint !

Notes finales

../../../_images/PartOneFinished.png

Whew ! C’était beaucoup de travail. Maintenant, vous avez un personnage à la première personne qui fonctionne parfaitement !

Dans doc_fps_tutorial_part_tutorial_part_two nous allons ajouter des armes à feu à notre personnage joueur.

Note

A ce stade, nous avons recréé la démo du personnage Kinematic du point de vue de la première personne avec un sprint et une lumière flash !

Astuce

Actuellement, le script du joueur serait dans un état idéal pour faire toutes sortes de jeux à la première personne. Par exemple : Jeux d’horreur, jeux de plates-formes, jeux d’aventure, et plus encore !

Avertissement

Si jamais vous vous perdez, n’oubliez pas de relire le code !

Vous pouvez télécharger le projet terminé pour cette partie ici : Godot_FPS_Part_1.zip