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.
Checking the stable version of the documentation...
Кодування гравця
У цьому уроці ми додамо гравцю рух, анімацію та налаштуємо йому виявлення зіткнень.
Для цього нам потрібно додати деяку функціональність, яку ми не можемо отримати від вбудованого вузла, тому ми додамо скрипт. Клацніть на вузлі Player і натисніть кнопку "Долучити скрипт":
У вікні налаштувань скрипту ви можете залишити налаштування за замовчуванням. Просто натисніть "Створити":
Примітка
Якщо ви створюєте скрипт на C# , або іншій мові, виберіть мову зі спадного меню Мова, перед тим, як створити скрипт.
Примітка
Якщо ви вперше стикаєтеся з GDScript, будь ласка, прочитайте Мови сценаріїв, перш ніж продовжити.
Почніть з оголошення змінних-членів, які будуть потрібні цьому об'єкту:
extends Area2D
@export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.
using Godot;
public partial class Player : Area2D
{
[Export]
public int Speed { get; set; } = 400; // How fast the player will move (pixels/sec).
public Vector2 ScreenSize; // 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 в правому верхньому кутку редактора.
Ваш скрипт player.gd вже повинен містити функції _ready() та _process(). Якщо ви не вибрали шаблон за замовчуванням, показаний вище, створіть ці функції під час виконання уроку.
Функція _ready() викликається , коли вузол входить в дерево сцени, чудовий момент , щоб задати розмір вікна гри:
func _ready():
screen_size = get_viewport_rect().size
public override void _Ready()
{
ScreenSize = GetViewportRect().Size;
}
Тепер ми можемо використовувати функцію _process(), щоб визначити, що робитиме гравець. _process() викликається кожен кадр, тому ми будемо використовувати її для оновлення тих елементів нашої гри, які будуть часто змінюватися. Для гравця нам потрібно зробити наступне:
Перевірити ввід.
Рухати в заданому напрямі.
Відтворити відповідну анімацію.
Спочатку нам потрібно перевірити ввід - чи гравець натискає клавішу? Для цієї гри у нас є 4 напрямки для перевірки. Дії введення визначені в Параметрах проекту в розділі "Карта введення". Тут ви можете визначити власні події та призначити їм різні клавіші, події миші чи інші входи. Для цієї гри ми зіставимо клавіші зі стрілками до чотирьох напрямків.
Натисніть Проект -> Параметри проекту, щоб відкрити вікно налаштувань проекту, і натисніть на вкладку Карта введення вгорі. Введіть "move_right" у верхній графі та натисніть кнопку "Додати", щоб додати дію move_right.
Нам потрібно призначити клавішу для цієї дії. Натисніть значок «+» праворуч, щоб відкрити вікно менеджера подій.
Поле "Очікування введення..." має бути вибране автоматично. Натисніть клавішу "right" на клавіатурі, і меню тепер має виглядати так.
Виберіть кнопку "Гаразд". Клавіша "right" тепер пов'язана з дією move_right.
Повторіть ці дії, щоб додати ще три зіставлення:
move_leftз клавішею зі стрілкою вліво.move_upз клавішею зі стрілкою вгору.Та
move_downз клавішею зі стрілкою вниз.
Вкладка карти введення має мати такий вигляд:
Натисніть кнопку "Закрити", щоб закрити параметри проекту.
Примітка
Ми зіставили лише одну клавішу з кожною вхідною дією, але ви можете зіставити кілька клавіш, кнопок джойстика або кнопок миші з однією і тією ж дією введення.
Ви можете визначити, чи натиснута кнопка за допомогою функції 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()
public override void _Process(double delta)
{
var velocity = Vector2.Zero; // The player's movement vector.
if (Input.IsActionPressed("move_right"))
{
velocity.X += 1;
}
if (Input.IsActionPressed("move_left"))
{
velocity.X -= 1;
}
if (Input.IsActionPressed("move_down"))
{
velocity.Y += 1;
}
if (Input.IsActionPressed("move_up"))
{
velocity.Y -= 1;
}
var animatedSprite2D = GetNode<AnimatedSprite2D>("AnimatedSprite2D");
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)
Position += velocity * (float)delta;
Position = new Vector2(
x: Mathf.Clamp(Position.X, 0, ScreenSize.X),
y: Mathf.Clamp(Position.Y, 0, ScreenSize.Y)
);
Порада
Параметр 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.Animation = "walk";
animatedSprite2D.FlipV = false;
// See the note below about the following boolean assignment.
animatedSprite2D.FlipH = velocity.X < 0;
}
else if (velocity.Y != 0)
{
animatedSprite2D.Animation = "up";
animatedSprite2D.FlipV = velocity.Y > 0;
}
Примітка
Булеві призначення в наведеному вище коді - це звичайна стенограма для програмістів. Оскільки ми робимо тест порівняння (булевий), а також присвоюємо булеве значення, ми можемо робити і те й інше. Розглянемо цей код порівняно з однорядним булевим призначенням вище:
if velocity.x < 0:
$AnimatedSprite2D.flip_h = true
else:
$AnimatedSprite2D.flip_h = false
if (velocity.X < 0)
{
animatedSprite2D.FlipH = true;
}
else
{
animatedSprite2D.FlipH = false;
}
Відтворіть сцену ще раз і перевірте правильність анімації в кожному з напрямків.
Порада
Загальною помилкою є неправильне іменування анімацій. Імена анімацій в панелі SpriteFrames повинні збігатися з іменами анімацій в вашому коді. Якщо ви назвали анімацію "Walk", ви повинні також використовувати велику літеру "W" в коді.
Коли ви впевнені, що рух працює правильно, додайте цей рядок до _ready (), щоб гравець був прихований, коли гра починається:
hide()
Hide();
Підготовка до зіткнень
Ми хочемо, щоб Player визначав, коли його вражає ворог, але ми ще не створили жодних ворогів! Це нормально, бо ми збираємося використовувати функціональність сигналу від Godot, щоб це запрацювало.
Додайте наступне у верхній частині скрипту. Якщо ви використовуєте GDScript, додайте його після extends Area2D. Якщо ви використовуєте C#, додайте його після public partial class Player : Area2D:
signal hit
// Don't forget to rebuild the project so the editor knows about the new signal.
[Signal]
public delegate void HitEventHandler();
Це визначає спеціальний сигнал під назвою "удар", який наш гравець видаватиме (надсилатиме) при зіткненні з ворогом. Ми використовуватимемо Area2D для виявлення зіткнення. Виберіть вузол Player та клацніть вкладку Signals поруч із вкладкою Inspector, щоб переглянути список сигналів, які гравець може видавати:
Зверніть увагу, що наш користувацький сигнал "hit" там також присутнiй! Оскільки наші противники будуть вузлами RigidBody2D, нам потрібен сигнал body_entered(body: Node2D). Цей сигнал буде викликатися, коли тіло (body) контактує з гравцем. Натисніть "Підключити.." i з'явиться вікно "Підключити сигнал".
Godot створить для вас функцію з такою самою назвою безпосередньо в скрипті. Зараз вам не потрібно змінювати налаштування за замовчуванням.
Попередження
Якщо ви використовуєте зовнішній текстовий редактор (наприклад, Visual Studio Code), помилка наразі заважає Godot зробити це. Ви будете перенаправлені на зовнішній редактор, але нової функції там не буде.
У цьому випадку вам потрібно буде самостійно записати функцію в скрипт файлу Player.
Зверніть увагу на зелену піктограму, яка вказує на те, що до цієї функції підключено сигнал; це не означає, що функція існує, просто сигнал спробує з'єднатися з функцією з такою назвою, тому ще раз перевірте, чи написання функції точно збігається!
Далі додайте цей код у функцію:
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)
// We also specified this function name in PascalCase in the editor's connection window.
private void OnBodyEntered(Node2D body)
{
Hide(); // Player disappears after being hit.
EmitSignal(SignalName.Hit);
// Must be deferred as we can't change physics properties on a physics callback.
GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred(CollisionShape2D.PropertyName.Disabled, true);
}
Кожен раз, коли ворог б’є гравця, буде випромінюватися сигнал. Нам потрібно відключити зіткнення гравця, щоб ми не запускали сигнал hit більше одного разу.
Примітка
Відключення області зіткнення може призвести до помилки, якщо це станеться під час опрацювання редактором зіткнень. Використання set_deferred() говорить Godot почекати, і вимкнути область, коли це буде безпечно.
Остання деталь, яку треба додати це функція, яку ми можемо викликати для появи гравця при запуску нової гри.
func start(pos):
position = pos
show()
$CollisionShape2D.disabled = false
public void Start(Vector2 position)
{
Position = position;
Show();
GetNode<CollisionShape2D>("CollisionShape2D").Disabled = false;
}
З гравцем покінчено, в наступному уроці ми будемо працювати над ворогом.