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

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

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

../../_images/add_script_button.png

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

Примітка

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

../../_images/attach_node_window.png

Примітка

Якщо ви вперше стикаєтеся з 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" ("Змінні скриптів"). Пам'ятайте, що якщо ви зміните тут значення, воно перепише значення, записане в скрипті.

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

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

../../_images/export_variable.png

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

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

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

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

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

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

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

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

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

Нам потрібно призначити ключ до цієї дії. Натисніть значок "+" праворуч, а потім натисніть опцію "Клавіша" в випадаючому меню. У діалоговому вікні буде запропоновано натиснути потрібну клавішу. Натисніть стрілку вправо на клавіатурі і натисніть "Ok".

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

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

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

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

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

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

../../_images/input-mapping-completed.png

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

Примітка

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

Ви можете визначити, чи натиснута кнопка за допомогою функції 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
        $AnimatedSprite.play()
    else:
        $AnimatedSprite.stop()

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

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

Порада

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

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

Порада

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

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

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

position += velocity * delta
position.x = clamp(position.x, 0, screen_size.x)
position.y = clamp(position.y, 0, screen_size.y)

Порада

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

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

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

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

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

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

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

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

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

Примітка

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

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

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

Порада

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

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

hide()

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

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

Додайте наступний код в початок скрипта, після extends Area2d:

signal hit

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

../../_images/player_signals.png

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

../../_images/player_signal_connection.png

Зверніть увагу на зелений значок, який вказує на те, що сигнал підключений до цієї функції. Додайте цей код до функції:

func _on_Player_body_entered(body):
    hide() # Player disappears after being hit.
    emit_signal("hit")
    # 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

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