Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Кодування гравця

У цьому уроці ми додамо гравцю рух, анімацію та налаштуємо йому виявлення зіткнень.

Для цього нам потрібно додати деяку функціональність, яку ми не можемо отримати від вбудованого вузла, тому ми додамо скрипт. Клацніть на вузлі Player і натисніть кнопку "Долучити скрипт":

../../_images/add_script_button.webp

У вікні налаштувань скрипту ви можете залишити налаштування за замовчуванням. Просто натисніть "Створити":

Примітка

Якщо ви створюєте скрипт на C# , або іншій мові, виберіть мову зі спадного меню Мова, перед тим, як створити скрипт.

../../_images/attach_node_window.webp

Примітка

Якщо ви вперше стикаєтеся з GDScript, будь ласка, прочитайте Мови сценаріїв, перш ніж продовжити.

Почніть з оголошення змінних-членів, які будуть потрібні цьому об'єкту:

extends Area2D

@export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.

Використання ключового слова export на першій змінній speed (швидкість) дозволяє нам встановлювати її значення в Інспекторі. Це може бути зручно для значень, які ви хочете мати можливість регулювати так само, як і вбудовані властивості вузла. Натисніть на вузол Player, і ви побачите властивість, яка появилася в розділі інспектора "Script Variables" ("Змінні скриптів"). Пам'ятайте, що якщо ви зміните тут значення, воно перепише значення, записане в скрипті.

Попередження

If you're using C#, you need to (re)build the project assemblies whenever you want to see new export variables or signals. This build can be manually triggered by clicking the Build button at the top right of the editor.

../../_images/build_dotnet.webp
../../_images/export_variable.webp

Your player.gd script should already contain a _ready() and a _process() function. If you didn't select the default template shown above, create these functions while following the lesson.

Функція _ready() викликається , коли вузол входить в дерево сцени, чудовий момент , щоб задати розмір вікна гри:

func _ready():
    screen_size = get_viewport_rect().size

Тепер ми можемо використовувати функцію _process(), щоб визначити, що робитиме гравець. _process() викликається кожен кадр, тому ми будемо використовувати її для оновлення тих елементів нашої гри, які будуть часто змінюватися. Для гравця нам потрібно зробити наступне:

  • Перевірити ввід.

  • Рухати в заданому напрямі.

  • Відтворити відповідну анімацію.

Спочатку нам потрібно перевірити ввід - чи гравець натискає клавішу? Для цієї гри у нас є 4 напрямки для перевірки. Дії введення визначені в Параметрах проекту в розділі "Карта введення". Тут ви можете визначити власні події та призначити їм різні клавіші, події миші чи інші входи. Для цієї гри ми зіставимо клавіші зі стрілками до чотирьох напрямків.

Натисніть Проект -> Параметри проекту, щоб відкрити вікно налаштувань проекту, і натисніть на вкладку "Карта введення" вгорі. Введіть "move_right" у верхній графі та натисніть кнопку "Додати", щоб додати дію move_right.

../../_images/input-mapping-add-action.webp

We need to assign a key to this action. Click the "+" icon on the right, to open the event manager window.

../../_images/input-mapping-add-key.webp

The "Listening for Input..." field should automatically be selected. Press the "right" key on your keyboard, and the menu should look like this now.

../../_images/input-mapping-event-configuration.webp

Select the "ok" button. The "right" key is now associated with the move_right action.

Повторіть ці дії, щоб додати ще три зіставлення:

  1. move_left з клавішею зі стрілкою вліво.

  2. move_up з клавішею зі стрілкою вгору.

  3. Та move_down з клавішею зі стрілкою вниз.

Вкладка карти введення має мати такий вигляд:

../../_images/input-mapping-completed.webp

Натисніть кнопку "Закрити", щоб закрити параметри проекту.

Примітка

Ми зіставили лише одну клавішу з кожною вхідною дією, але ви можете зіставити кілька клавіш, кнопок джойстика або кнопок миші з однією і тією ж дією введення.

Ви можете визначити, чи натиснута кнопка за допомогою функції Input.is_action_pressed(), яка повертає true, якщо клавіша натиснута або false, якщо ні.

func _process(delta):
    var velocity = Vector2.ZERO # The player's movement vector.
    if Input.is_action_pressed("move_right"):
        velocity.x += 1
    if Input.is_action_pressed("move_left"):
        velocity.x -= 1
    if Input.is_action_pressed("move_down"):
        velocity.y += 1
    if Input.is_action_pressed("move_up"):
        velocity.y -= 1

    if velocity.length() > 0:
        velocity = velocity.normalized() * speed
        $AnimatedSprite2D.play()
    else:
        $AnimatedSprite2D.stop()

Почнемо з того, що встановимо значення velocity на (0, 0) - за замовчуванням гравець рухатися не повинен. Потім, ми перевіряємо кожне введення і додаємо/віднімаємо значення з velocity, щоб отримати загальний напрямок. Наприклад, якщо ви одночасно утримуєте right і down, отриманий вектор velocity буде (1, 1). В цьому випадку, оскільки ми додаємо одночасно горизонтальний і вертикальний рух, гравець буде рухатися швидше, ніж якби він переміщувався просто по горизонталі.

Ми зможемо уникнути цього, якщо нормалізуємо швидкість, що означає, що ми встановлюємо її довжину на 1 і будемо множити на бажану швидкість. Це означає відсутність швидшого руху по діагоналі.

Порада

Якщо ви ніколи раніше не використовували векторну математику, або вам потрібно освіжити знання, ви можете побачити пояснення по використанню векторів у Godot у Векторна математика. Це добре знати, але це не буде потрібно для решти цього уроку.

We also check whether the player is moving so we can call play() or stop() on the AnimatedSprite2D.

Порада

$ is shorthand for get_node(). So in the code above, $AnimatedSprite2D.play() is the same as get_node("AnimatedSprite2D").play().

In GDScript, $ returns the node at the relative path from the current node, or returns null if the node is not found. Since AnimatedSprite2D is a child of the current node, we can use $AnimatedSprite2D.

Тепер, коли у нас є напрямок руху, ми можемо оновити позицію гравця. Ми також можемо використовувати clamp(), щоб він не покинув екран. Clamping означає обмеження руху діапазоном. Додайте наступне в кінець функції _process (переконайтеся, що під else нема відступу):

position += velocity * delta
position = position.clamp(Vector2.ZERO, screen_size)

Порада

Параметр delta у функції _process () означає тривалість кадру - тобто часу, необхідного для завершення попереднього кадру. Використання цього значення гарантує, що ваш рух залишатиметься послідовним, навіть якщо частота кадрів змінюється.

Натисніть "Відтворити сцену" (F6, Cmd + R на macOS) і переконайтеся, що можете рухати гравця по екрані у всіх напрямках.

Попередження

Якщо ви отримуєте помилку на панелі "Зневаджувач", яка говорить

Спроба виклику функції 'play' в основі 'null instance' на нульовому зразкові

this likely means you spelled the name of the AnimatedSprite2D node wrong. Node names are case-sensitive and $NodeName must match the name you see in the scene tree.

Вибір анімації

Now that the player can move, we need to change which animation the AnimatedSprite2D is playing based on its direction. We have the "walk" animation, which shows the player walking to the right. This animation should be flipped horizontally using the flip_h property for left movement. We also have the "up" animation, which should be flipped vertically with flip_v for downward movement. Let's place this code at the end of the _process() function:

if velocity.x != 0:
    $AnimatedSprite2D.animation = "walk"
    $AnimatedSprite2D.flip_v = false
    # See the note below about boolean assignment.
    $AnimatedSprite2D.flip_h = velocity.x < 0
elif velocity.y != 0:
    $AnimatedSprite2D.animation = "up"
    $AnimatedSprite2D.flip_v = velocity.y > 0

Примітка

Булеві призначення в наведеному вище коді - це звичайна стенограма для програмістів. Оскільки ми робимо тест порівняння (булевий), а також присвоюємо булеве значення, ми можемо робити і те й інше. Розглянемо цей код порівняно з однорядним булевим призначенням вище:

if velocity.x < 0:
    $AnimatedSprite2D.flip_h = true
else:
    $AnimatedSprite2D.flip_h = false

Відтворіть сцену ще раз і перевірте правильність анімації в кожному з напрямків.

Порада

Загальною помилкою є неправильне іменування анімацій. Імена анімацій в панелі SpriteFrames повинні збігатися з іменами анімацій в вашому коді. Якщо ви назвали анімацію "Walk", ви повинні також використовувати велику літеру "W" в коді.

Коли ви впевнені, що рух працює правильно, додайте цей рядок до _ready (), щоб гравець був прихований, коли гра починається:

hide()

Підготовка до зіткнень

Ми хочемо виявляти зіткнення гравця з ворогами, але ворогів ми ще не створили! Це добре, тому що ми будемо використовувати сигнали Godot.

Add the following at the top of the script. If you're using GDScript, add it after extends Area2D. If you're using C#, add it after public partial class Player : Area2D:

signal hit

Цей рядок визначає власний сигнал під назвою "hit" ("удар"), який буде випромінюватися з нашого гравця, коли він буде стикатися з ворогом. Ми будемо використовувати Area2D для виявлення зіткнення. Виберіть вузол Player і натисніть на вкладку "Вузол" поруч із вкладкою Інспектор, щоб побачити список сигналів, які гравець може випромінювати:

../../_images/player_signals.webp

Notice our custom "hit" signal is there as well! Since our enemies are going to be RigidBody2D nodes, we want the body_entered(body: Node2D) signal. This signal will be emitted when a body contacts the player. Click "Connect.." and the "Connect a Signal" window appears.

Godot will create a function with that exact name directly in script for you. You don't need to change the default settings right now.

Попередження

If you're using an external text editor (for example, Visual Studio Code), a bug currently prevents Godot from doing so. You'll be sent to your external editor, but the new function won't be there.

In this case, you'll need to write the function yourself into the Player's script file.

../../_images/player_signal_connection.webp

Note the green icon indicating that a signal is connected to this function; this does not mean the function exists, only that the signal will attempt to connect to a function with that name, so double-check that the spelling of the function matches exactly!

Next, add this code to the function:

func _on_body_entered(body):
    hide() # Player disappears after being hit.
    hit.emit()
    # Must be deferred as we can't change physics properties on a physics callback.
    $CollisionShape2D.set_deferred("disabled", true)

Кожен раз, коли ворог б’є гравця, буде випромінюватися сигнал. Нам потрібно відключити зіткнення гравця, щоб ми не запускали сигнал hit більше одного разу.

Примітка

Відключення області зіткнення може призвести до помилки, якщо це станеться під час опрацювання редактором зіткнень. Використання set_deferred() говорить Godot почекати, і вимкнути область, коли це буде безпечно.

Остання деталь, яку треба додати це функція, яку ми можемо викликати для появи гравця при запуску нової гри.

func start(pos):
    position = pos
    show()
    $CollisionShape2D.disabled = false

З гравцем покінчено, в наступному уроці ми будемо працювати над ворогом.