Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

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

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

Для цього нам потрібно додати деяку функціональність, яку ми не можемо отримати від вбудованого вузла, тому ми додамо скрипт. Клацніть на вузлі 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.

Using the export keyword on the first variable speed allows us to set its value in the Inspector. This can be handy for values that you want to be able to adjust just like a node's built-in properties. Click on the Player node and you'll see the property now appears in the Inspector in a new section with the name of the script. Remember, if you change the value here, it will override the default value specified in the script (the script won't be modified).

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

Якщо ви використовуєте C#, вам потрібно (пере)будовувати збірки проєктів, щоразу коли ви хочете бачити нові змінні експорту, чи сигнали. Цю збірку можна вручну запустити, натиснувши кнопку Build в правому верхньому кутку редактора.

../../_images/build_dotnet1.webp
../../_images/export_variable.webp

Ваш скрипт player.gd вже повинен містити функції _ready() та _process(). Якщо ви не вибрали шаблон за замовчуванням, показаний вище, створіть ці функції під час виконання уроку.

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

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

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

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

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

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

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

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

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

Нам потрібно призначити клавішу для цієї дії. Натисніть значок «+» праворуч, щоб відкрити вікно менеджера подій.

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

Поле "Очікування введення..." має бути вибране автоматично. Натисніть клавішу "right" на клавіатурі, і меню тепер має виглядати так.

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

Виберіть кнопку "Гаразд". Клавіша "right" тепер пов'язана з дією move_right.

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

  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 у Векторна математика. Це добре знати, але це не буде потрібно для решти цього уроку.

Ми також перевіряємо, чи гравець рухається, щоб ми могли викликати play() або stop () на AnimatedSprite2D.

Порада

$ це скорочення для get_node(). Так що в наведеному вище коді $AnimatedSprite2D.play() це те саме, що і get_node("AnimatedSprite2D").play().

У GDScript $ повертає вузол на відносному шляху від поточного вузла, або повертає null якщо вузол не знайдено. Оскільки AnimatedSprite2D є нащадком поточного вузла, ми можемо використовувати $AnimatedSprite2D.

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

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

Порада

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

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

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

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

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

це, ймовірно, означає, що ви неправильно написали ім'я вузла AnimatedSprite2D. Імена вузлів чутливі до регістру і $НазваВузла повинно відповідати назві, яку ви бачите в дереві сцен.

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

Тепер, коли гравець може рухатися, нам потрібно змінювати анімацію відтворення AnimatedSprite2D, в залежності від напрямку руху. У нас є анімація "walk", де гравець йде вправо. Цю анімацію слід перевернути горизонтально, використовуючи властивість flip_h, для руху ліворуч. У нас також є анімація "up", яку слід перевернути вертикально за допомогою flip_v для руху вниз. Розмістимо цей код в кінці функції _process():

if velocity.x != 0:
    $AnimatedSprite2D.animation = "walk"
    $AnimatedSprite2D.flip_v = false
    # See the note below about the following 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()

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

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

Додайте наступне у верхній частині скрипту. Якщо ви використовуєте GDScript, додайте його після extends Area2D. Якщо ви використовуєте C#, додайте його після public partial class Player : Area2D:

signal hit

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

../../_images/player_signals.webp

Зверніть увагу, що наш користувацький сигнал "hit" там також присутнiй! Оскільки наші противники будуть вузлами RigidBody2D, нам потрібен сигнал body_entered(body: Node2D). Цей сигнал буде викликатися, коли тіло (body) контактує з гравцем. Натисніть "Підключити.." i з'явиться вікно "Підключити сигнал".

Godot створить для вас функцію з такою самою назвою безпосередньо в скрипті. Зараз вам не потрібно змінювати налаштування за замовчуванням.

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

Якщо ви використовуєте зовнішній текстовий редактор (наприклад, Visual Studio Code), помилка наразі заважає Godot зробити це. Ви будете перенаправлені на зовнішній редактор, але нової функції там не буде.

У цьому випадку вам потрібно буде самостійно записати функцію в скрипт файлу Player.

../../_images/player_signal_connection.webp

Зверніть увагу на зелену піктограму, яка вказує на те, що до цієї функції підключено сигнал; це не означає, що функція існує, просто сигнал спробує з'єднатися з функцією з такою назвою, тому ще раз перевірте, чи написання функції точно збігається!

Далі додайте цей код у функцію:

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

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