Część 1

Przedstawienie samouczka

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

Ten samouczek pokaże jak stworzyć pierwszoosobową grę FPS.

Podczas tego kursu dowiesz się jak:

  • Stworzyć pierwszoosbową postać, która może się poruszać, biegać i skakać.
  • Aby utworzyć prostą maszynę stanu animacji do obsługi przejść animacji.
  • Dodać trzy bronie do postaci, każda inaczej obsługująca kolizję pocisków:
    • Nóż (używając Area)
    • A pistol (Bullet scenes)
    • Karabin (używając Raycast)
  • Dodanie dwóch różnych rodzajów granatów do postaci:
    • Zwykły granat
    • Granat przylepny
  • To add the ability to grab and throw RigidBody nodes
  • Aby dodać wejścia pada dla gracza
  • Dodawanie amunicji i przeładowywania dla wszystkich rodzajów broni używającej amunicję.
  • Aby dodać amunicję i zdrowie do podnoszenia
    • W dwóch rozmiarach: dużym i małym
  • By dodać automatyczna wieżyczkę
    • Może strzelać używając pocisków lub Raycast
  • By dodać cele które niszczą się gdy otrzymają wystarczająco dużo obrażeń
  • By dodać dźwięk, odtwarzający się gdy broń wystrzeli.
  • By dodać proste menu główne:
    • Menu z opcją zmiany sposobu uruchomienia gry
    • Z ekranem do wyboru poziomu
  • By dodać uniwersalne menu pauzy do którego zawsze możemy się odwołać

Informacja

Chociaż ten samouczek może być ukończony przez początkujących, zaleca się, aby ukończyć przed nim :ref:`doc_your_first_game.

Remember: Making 3D games is much harder than making 2D games. If you do not know how to make 2D games, you will likely struggle making 3D games.

This tutorial assumes you have experience working with the Godot editor, basic programming experience in GDScript, and basic experience in game development.

Możesz znaleźć zasoby z tego poradnika tutaj: Godot_FPS_Starter.zip

Dostarczone zasoby startowe zawierają animowany model 3D, kilka modeli 3D do tworzenia poziomów oraz kilka scen już skonfigurowanych dla tego samouczka.

Wszystkie zasoby (o ile nie zaznaczono inaczej) zostały pierwotnie stworzone przez TwistedTwigleg, ze zmianami społeczności Godota. Wszystkie zasoby zawarte tym samouczku są dostępne na licencji MIT.

Zapraszamy do korzystania z tych zasobów, w dowolny sposób! Wszystkie zasoby należą do społeczności Godota, a pozostałe do wymienionych poniżej:

Informacja

Skybox jest tworzony przez StumpyStrust na OpenGameArt. Skybox jest licencjonowany na licencji CC0.

Używana czcionka to Titillium-Regular, i jest licencjonowana jako SIL Open Font License, Version 1.1.

Wskazówka

Możesz znaleźć ukończony projekt dla każdej części, na dole strony

Part overview

W tej części stworzymy postać z widokiem pierwszoosobowym który będzie mógł poruszać się po środowisku.

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

By the end of this part, you will have a working first-person character who can move around the game environment, sprint, look around with a mouse based first person camera, jump into the air, and turn a flash light on and off.

Przygotowywanie wszystkiego

Otwieranie Godota i projektu z zawartymi w nim zasobami startowymi.

Informacja

While these assets are not necessarily required to use the scripts provided in this tutorial, they will make the tutorial much easier to follow, as there are several pre-setup scenes we will be using throughout the tutorial series.

First, open the project settings and go to the „Input Map” tab. You’ll find several actions have already been defined. We will be using these actions for our player. Feel free to change the keys bound to these actions if you want.


Zobaczmy, jakie są nasze początkowe zasoby.

W początkowych zasobach znajduje się kilka scen. Na przykład, w res:// mamy 14 scen, z których większość będziemy korzystać, przechodząc przez ten samouczek.

Otwórzmy Player.tscn`.

Informacja

There are a bunch of scenes and a few textures in the Assets folder. You can look at these if you want, but we will not be exploring through Assets in this tutorial series. Assets contains all the models used for each of the levels, as well as some textures and materials.

Tworzenie poruszania FPS

Po otwarciu ``Player.tscn``przyjrzyjmy się jak jest on skonfigurowany

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

Najpierw zauważ, jak ustawione są kształty kolizji gracza. Użycie pionowej kapsuły jako kształtu kolizji dla gracza jest dość powszechne w większości gier z pierwszoosobowym widokiem.

Dodajemy mały kwadrat do «stóp» gracza, ponieważ gracz nie ma raczej ochoty balansować na jednym punkcie.

We do want the «feet» slightly higher than the bottom of the capsule so we can roll over slight edges. Where to place the «feet» is dependent on your levels and how you want your player to feel.

Informacja

Many times the player will notice the collision shape being circular when they walk to an edge and slide off. We are adding the small square at the bottom of the capsule to reduce sliding on, and around, edges.

Another thing to notice is how many nodes are children of Rotation_Helper. This is because Rotation_Helper contains all the nodes we want to rotate on the X axis (up and down). The reason behind this is so we can rotate Player on the Y axis, and Rotation_helper on the X axis.

Informacja

Had we not used Rotation_helper, we would’ve likely had cases of rotating on both the X and Y axes simultaneously, potentially further degenerating into a state of rotation on all three axes in some cases.

Więcej informacji można znaleźć w temacie używanie przekształceń


Podłącz nowy skrypt do węzła Player i nazwij go Player.gd.

Zaprogramujmy gracza dodając możliwość poruszania się, rozglądania się myszą i skakania. Dodaj następujący kod do 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;
        }
    }
}

Jest to dużo kodu, więc rozbijmy go według funkcji:

Wskazówka

Podczas gdy kopiowanie i wklejanie kodu nie jest zalecane, ponieważ można się wiele nauczyć, ręcznie przepisując kod, ale można skopiować i wkleić kod z tej strony bezpośrednio do edytora skryptów.

Jeśli to zrobisz, cały skopiowany kod będzie używał spacji zamiast tabulatorów.

To convert the spaces to tabs in the script editor, click the „edit” menu and select „Convert Indent To Tabs”. This will convert all the spaces into tabs. You can select „Convert Indent To Spaces” to convert tabs back into spaces.


Po pierwsze, definiujemy pewne zmienne w klasie, aby zdecydować, jak nasz gracz będzie poruszał się po świecie.

Informacja

Throughout this tutorial, variables defined outside functions will be referred to as „class variables”. This is because we can access any of these variables from any place in the script.

Let’s go through each of the class variables:

  • GRAVITY: How strong gravity pulls us down.
  • vel: Nasza prędkość KinematicBody.
  • MAX_SPEED: Największa prędkość z jaką możemy się poruszać.
  • JUMP_SPEED: Jak wysoko można skoczyć.
  • ACCEL: How quickly we accelerate. The higher the value, the sooner we get to max speed.
  • DEACCEL: How quickly we are going to decelerate. The higher the value, the sooner we will come to a complete stop.
  • MAX_SLOPE_ANGLE: Największy kąt jaki przez KinematicBody będzie uważany za «podłogę».
  • camera: Węzeł Camera.
  • rotation_helper: A Spatial node holding everything we want to rotate on the X axis (up and down).
  • MOUSE_SENSITIVITY: Jak bardzo czuła jest mysz. Wartość 0.05 jest odpowiednia dla mojej myszy, lecz prawdopodobnie będziesz musiał dostosować tą wartość do siebie.

You can tweak many of these variables to get different results. For example, by lowering GRAVITY and/or increasing JUMP_SPEED you can get a more «floaty» feeling character. Feel free to experiment!

Informacja

You may have noticed that MOUSE_SENSITIVITY is written in all caps like the other constants, but MOUSE_SENSITIVITY is not a constant.

Powodem tego jest to, że chcemy traktować ją jako stałą zmienną (zmienną, która nie może się zmieniać) w całym skrypcie, ale chcemy mieć możliwość późniejszej zmiany wartości po dodaniu ustawień konfigurowalnych. Więc, aby przypomnieć sobie, aby traktować go jak stałą, to nazywa jak zwykłe stałe.


Spójrzmy teraz na funkcję ready:

Najpierw wczytujemy węzły(node) camera i rotation_helper i zapisujemy je do zmiennych.

Then we need to set the mouse mode to captured, so the mouse cannot leave the game window.

This will hide the mouse and keep it at the center of the screen. We do this for two reasons: The first reason being we do not want the player to see their mouse cursor as they play.

The second reason is because we do not want the cursor to leave the game window. If the cursor leaves the game window there could be instances where the player clicks outside the window, and then the game would lose focus. To assure neither of these issues happens, we capture the mouse cursor.

Informacja

Zobacz Input documentation dla różnych trybów myszy. Będziemy używali jedynie MOUSE_MODE_CAPTURED i MOUSE_MODE_VISIBLE w tym poradniku.


Następnie przyjrzyjmy się _physics_process:

Wszystko co robimy w _physics_process to wywoływanie dwóch funkcji: process_input i process_movement.

process_input will be where we store all the code relating to player input. We want to call it first, before anything else, so we have fresh player input to work with.

process_movement is where we’ll send all the data necessary to the KinematicBody so it can move through the game world.


Let’s look at process_input next:

Najpierw ustawiamy dir na pusty wektor Vector3.

dir będzie używany do zapisywania kierunku, w którym gracz zamierza podążać. Ponieważ nie chcemy, aby poprzednie wejście odtwarzacza powodowało następstwa dla kolejnych process_movement, więc będziemy resetować dir.

Next we get the camera’s global transform and store it as well, into the cam_xform variable.

The reason we need the camera’s global transform is so we can use its directional vectors. Many have found directional vectors confusing, so let’s take a second to explain how they work:


World space can be defined as: The space in which all objects are placed in, relative to a constant origin point. Every object, no matter if it is 2D or 3D, has a position in world space.

To put it another way: world space is the space in a universe where every object’s position, rotation, and scale can be measured by a single, known, fixed point called the origin.

W Godocie, środek jest na pozycji (0, 0, 0) z rotacją (0, 0, 0) i skalą ``(1, 1, 1) ``.

Informacja

When you open up the Godot editor and select a Spatial based node, a gizmo pops up. Each of the arrows points using world space directions by default.

If you want to move using the world space directional vectors, you’d do something like this:

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));

Informacja

Notice how we do not need to do any calculations to get world space directional vectors. We can define a few Vector3 variables and input the values pointing in each direction.

Oto jak wygląda przestrzeń w 2D:

Informacja

Poniższe ilustracje są tylko przykładami. Każda strzałka/prostokąt reprezentuje wektor kierunkowy

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

I tak to wygląda w przypadku 3D:

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

Notice how in both examples, the rotation of the node does not change the directional arrows. This is because world space is a constant. No matter how you translate, rotate, or scale an object, world space will always point in the same direction.

Przestrzeń lokalna jest inna, ponieważ uwzględnia rotację obiektu.

Local space can be defined as follows: The space in which an object’s position is the origin of the universe. Because the position of the origin can be at N many locations, the values derived from local space change with the position of the origin.

Informacja

This stack overflow question has a much better explanation of world space and local space.

https://gamedev.stackexchange.com/questions/65783/what-are-world-space-and-eye-space-in-game-development (Local space and eye space are essentially the same thing in this context)

To get a Spatial node’s local space, we need to get its Transform, so then we can get the Basis from the Transform.

Each Basis has three vectors: X, Y, and Z. Each of those vectors point towards each of the local space vectors coming from that object.

To use the Spatial node’s local directional vectors, we use this 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());

Oto jak wygląda lokalna przestrzeń w 2D:

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

I tak to wygląda w przypadku 3D:

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

Here is what the Spatial gizmo shows when you are using local space mode. Notice how the arrows follow the rotation of the object on the left, which looks exactly the same as the 3D example for local space.

Informacja

You can change between local and world space modes by pressing T or the little cube button when you have a Spatial based node selected.

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

Local vectors are confusing even for more experienced game developers, so do not worry if this all doesn’t make a lot of sense. The key thing to remember about local vectors is that we are using local coordinates to get direction from the object’s point of view, as opposed to using world vectors, which give direction from the world’s point of view.


Dobrze, wróć do process_input:

Next we make a new variable called input_movement_vector and assign it to an empty Vector2. We will use this to make a virtual axis of sorts, to map the player’s input to movement.

Informacja

This may seem overkill for just the keyboard, but this will make sense later when we add joypad input.

Based on which directional movement action is pressed, we add to or subtract from input_movement_vector.

After we’ve checked each of the directional movement actions, we normalize input_movement_vector. This makes it where input_movement_vector’s values are within a 1 radius unit circle.

Next we add the camera’s local Z vector times input_movement_vector.y to dir. This is so when the player presses forward or backwards, we add the camera’s local Z axis so the player moves forward or backwards in relation to the camera.

Informacja

Because the camera is rotated by -180 degrees, we have to flip the Z directional vector. Normally forward would be the positive Z axis, so using basis.z.normalized() would work, but we are using -basis.z.normalized() because our camera’s Z axis faces backwards in relation to the rest of the player.

We do the same thing for the camera’s local X vector, and instead of using input_movement_vector.y we instead use input_movement_vector.x. This makes it where the player moves left/right in relation to the camera when the player presses left/right.

Next we check if the player is on the floor using KinematicBody’s is_on_floor function. If it is, then we check to see if the „movement_jump” action has just been pressed. If it has, then we set the player’s Y velocity to JUMP_SPEED.

Because we’re setting the Y velocity, the player will jump into the air.

Then we check for the ui_cancel action. This is so we can free/capture the mouse cursor when the escape button is pressed. We do this because otherwise we’d have no way to free the cursor, meaning it would be stuck until you terminate the runtime.

To free/capture the cursor, we check to see if the mouse is visible (freed) or not. If it is, we capture it, and if it’s not, we make it visible (free it).

That’s all we’re doing right now for process_input. We’ll come back several times to this function as we add more complexities to our player.


Now let’s look at process_movement:

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

Next we normalize dir to ensure we’re within a 1 radius unit circle. This makes it where we’re moving at a constant speed regardless of whether the player is moving straight or diagonally. If we did not normalize, the player would move faster on the diagonal than when going straight.

Next we add gravity to the player by adding GRAVITY * delta to the player’s Y velocity.

After that we assign the player’s velocity to a new variable (called hvel) and remove any movement on the Y axis.

Next we set a new variable (target) to the player’s direction vector. Then we multiply that by the player’s max speed so we know how far the player will move in the direction provided by dir.

After that we make a new variable for acceleration, named accel.

We then take the dot product of hvel to see if the player is moving according to hvel. Remember, hvel does not have any Y velocity, meaning we are only checking if the player is moving forwards, backwards, left, or right.

If the player is moving according to hvel, then we set accel to the ACCEL constant so the player will accelerate, otherwise we set accel to our DEACCEL constant so the player will decelerate.

Then we interpolate the horizontal velocity, set the player’s X and Z velocity to the interpolated horizontal velocity, and call move_and_slide to let the KinematicBody handle moving the player through the physics world.

Wskazówka

All of the code in process_movement is exactly the same as the movement code from the Kinematic Character demo!


The final function we have is the _input function, and thankfully it’s fairly short:

First we make sure that the event we are dealing with is an InputEventMouseMotion event. We also want to check if the cursor is captured, as we do not want to rotate if it is not.

Informacja

See Mouse and input coordinates for a list of possible input events.

If the event is indeed a mouse motion event and the cursor is captured, we rotate based on the relative mouse motion provided by InputEventMouseMotion.

First we rotate the rotation_helper node on the X axis, using the relative mouse motion’s Y value, provided by InputEventMouseMotion.

Następnie obracamy KinematicBody <class_KinematicBody>`na osi ``Y` o względną wartość ruchu myszy X.

Wskazówka

Godot zamienia względny ruch myszy na Vector2 gdzie ruch myszy w górę i w dół to 1 i -1. Ruch w prawo i w lewo wynosi odpowiednio 1 i -1.

Ze względu na sposób, w jaki obracamy gracza, mnożymy względny ruch myszy wartość X przez -1 tak, że ruch myszy w lewo i w prawo obraca gracza w lewo i w prawo w tym samym kierunku.

Na koniec, w rotation_helper wartość obrotu osi X zawieramy pomiędzy -70 i``70`` stopni tak, aby gracz nie mógł obracać się do góry nogami.

Wskazówka

See using transforms for more information on rotating transforms.


To test the code, open up the scene named Testing_Area.tscn, if it’s not already opened up. We will be using this scene as we go through the next few tutorial parts, so be sure to keep it open in one of your scene tabs.

Go ahead and test your code either by pressing F6 with Testing_Area.tscn as the open tab, by pressing the play button in the top right corner, or by pressing F5. You should now be able to walk around, jump in the air, and look around using the mouse.

Danie graczowi latarki oraz opcji sprintu

Before we get to making the weapons work, there are a couple more things we should add.

Wiele gier FPS ma możliwość sprintu i latarkę. Możemy je łatwo dodać do naszego gracza, więc zróbmy to!

Najpierw w naszym skrypcie gracza potrzebujemy kilku dodatkowych zmiennych klasy:

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;

All the sprinting variables work exactly the same as the non sprinting variables with similar names.

is_sprinting jest logiczną zmienną do śledzenia, czy gracz biega w tej chwili, a flashlight jest zmienną, której będziemy używać do trzymania węzła światła.

Teraz musimy dodać kilka wierszy kodu, zaczynając od _ready. Do _ready dodaj następujący tekst:

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

This gets the Flashlight node and assigns it to the flashlight variable.


Teraz musimy zmienić część kodu w process_input. dodaj go gdzieś w 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();
}

Przejdźmy do dodatków:

Ustawiamy is_sprinting jako true, gdy gracz trzyma wciśnięty przycisk movement_sprint i false, gdy akcja movement_sprint jest zwalniana. Do process_movement dodamy kod, który przyspiesza bieg gracza podczas sprintu. Tutaj w process_input zmienimy tylko zmienną is_sprinting.

We do something similar to freeing/capturing the cursor for handling the flashlight. We first check to see if the flashlight action was just pressed. If it was, we then check to see if flashlight is visible in the scene tree. If it is, then we hide it, and if it’s not, we show it.


Teraz musimy zmienić kilka rzeczy w process_movement. Po pierwsze, zastąp target *= MAX_SPEED przez:

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

Teraz zamiast zawsze mnożyć target przez MAX_SPEED`, najpierw sprawdzamy, czy gracz biega czy nie. Jeśli gracz biega, mnożymy target przez MAX_SPRINT_SPEED`.

Now all that’s left is to change the acceleration when sprinting. Change accel = ACCEL to the following:

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

Now, when the player is sprinting, we’ll use SPRINT_ACCEL instead of ACCEL, which will accelerate the player faster.


Należy teraz być w stanie zacząć bieg po naciśnięciu przycisku shift i móc włączyć lub wyłączyć lampę błyskową, naciskając przycisk F!

Go try it out! You can change the sprint-related class variables to make the player faster or slower when sprinting!

Uwagi końcowe

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

Whew! That was a lot of work. Now you have a fully working first person character!

W Część 2 dodamy kilka broni do naszego gracza.

Informacja

At this point we’ve recreated the Kinematic character demo from first person perspective with sprinting and a flash light!

Wskazówka

Aktualnie skrypt gracza byłby niemal w idealny dla wszelkiego rodzaju gier pierwszoosobowych. Na przykład: horrorów, gier platformowych, gier przygodowych i wiele innych!

Ostrzeżenie

Jeśli kiedykolwiek się zagubisz, przeczytaj kod ponownie!

Gotowy projekt dla tej części można pobrać tutaj: Godot_FPS_Part_1.zip