Up to date

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

Главная сцена игры

Итак, настало время перенести всё, что мы сделали вместе, на играбельную игровую сцену.

Создайте новую сцену и добавьте Node с именем Main. (Причиной, по которой мы используем Node, а не Node2D, является то, что узел будет контейнером для обработки игровой логики. Это не требует именно двумерного функционала.)

Нажмите на кнопку Instance (Экземпляр, выглядящий как значок звена цепи) и выберите ваш сохраненный player.tscn.

../../_images/instance_scene.webp

Теперь добавьте следующие узлы в виде дочерних элементов Main и назовите их как показано ниже (значения указаны в секундах):

  • Timer (названный MobTimer) - чтобы контролировать частоту появления мобов

  • Timer (названный ScoreTimer) - чтобы каждую секунду увеличивать счет

  • Timer (названный StartTimer) - чтобы дать задержку перед стартом игры

  • Marker2D (названный StartPosition) - чтобы указать начальную позицию игрока

Задайте значение Wait Time для каждого из узлов Timer следующим образом:

  • MobTimer: 0.5

  • ScoreTimer: 1

  • StartTimer: 2

Кроме того, установите для свойства One Shot узла StartTimer значение "Вкл" и для свойства Position узла StartPosition установите значение (240, 450).

Добавление мобов

Узел Main будет порождать новых мобов, и мы хотим, чтобы они появлялись в случайном месте на краю экрана. Добавьте узел Path2D с именем MobPath как дочерний элемент узла Main. Когда вы выберете Path2D, вы увидите несколько новых кнопок в верхней части редактора:

../../_images/path2d_buttons.webp

Выберите среднюю ("Добавить точку") и нарисуйте путь щелчками мыши, чтобы добавить точки в показанных углах. Чтобы точки привязывались к сетке, убедитесь, что выбраны "Использовать привязку к сетке" и "Использовать умную привязку". Эти опции можно найти слева от кнопки "Заблокировать узел", они отображаются в виде магнита с точками и магнита с пересекающимися линиями.

../../_images/grid_snap_button.webp

Важно

Нарисуйте путь в порядке по часовой стрелке, иначе ваши мобы будут появляться с направлением наружу, а не внутрь!

../../_images/draw_path2d.gif

Поместив точку "4" на изображение, нажмите кнопку "Сомкнуть кривую", и она будет завершена.

Теперь, когда путь определен, добавьте узел PathFollow2D как дочерний элемент MobPath и назовите его MobSpawnLocation. Этот узел будет автоматически вращаться и следовать по пути при его перемещении, поэтому мы можем использовать его для выбора случайной позиции и направления вдоль пути.

Ваша сцена должна выглядеть так:

../../_images/main_scene_nodes.webp

Главный скрипт

Добавьте скрипт к Main. В верхней части скрипта мы пишем @export var mob_scene: PackedScene, что позволяет нам выбрать сцену Mob, экземпляр которой мы хотим сделать.

extends Node

@export var mob_scene: PackedScene
var score

Выберите узел Main и вы увидите свойство MobScene в окне Инспектора под "Script Variables".

Значение этого свойства можно присвоить двумя способами:

  • Перетащите Mob.tscn из панели "Файловая система" в свойство Mob Scene.

  • Нажмите стрелочку вниз рядом с "[пусто]" и выберите "Загрузить" ("Load"). Затем выберите mob.tscn.

Затем выберите экземпляр сцены «Player» в узле «Main» в панели «Сцена» и откройте панель Узла на боковой панели. Убедитесь, что в панели Узла выбрана вкладка Сигналы(Signals).

Вы должны увидеть список сигналов для узла Player. В списке найдите и дважды щелкните по сигналу hit (или щелкните по нему правой кнопкой мыши и выберите "Присоединить..."). Это откроет диалоговое окно подключения сигнала. Мы хотим создать новую функцию с именем game_over, которая будет обрабатывать то, что должно произойти, когда игра заканчивается. Введите "game_over" в поле "Receiver Method"("Метод получения") в нижней части диалогового окна подключения сигнала и нажмите "Присоединить". Добавьте следующий код в новую функцию, а также функцию new_game, которая настроит всё для новой игры:

func game_over():
    $ScoreTimer.stop()
    $MobTimer.stop()

func new_game():
    score = 0
    $Player.start($StartPosition.position)
    $StartTimer.start()

Теперь присоедините сигнал timeout() каждого из узлов Timer (StartTimer, ScoreTimer и MobTimer) к главному скрипту. StartTimer запустит два других таймера. ScoreTimer будет увеличивать счет на 1.

func _on_score_timer_timeout():
    score += 1

func _on_start_timer_timeout():
    $MobTimer.start()
    $ScoreTimer.start()

В функции _on_mob_timer_timeout() мы создадим экземпляр моба, выберем случайное начальное местоположение вдоль Path2D и приведем его в движение. Узел PathFollow2D будет автоматически поворачивать его по направлению пути, поэтому мы воспользуемся этим, чтобы выбрать направление моба и его позицию. Когда мы создаем моба, получаем случайное значение от 150.0 до 250.0 - это определяет скорость движения моба (было бы скучно, если бы они все двигались с одинаковой скоростью).

Обратите внимание, что новый экземпляр должен быть добавлен в сцену с помощью функции add_child().

func _on_mob_timer_timeout():
    # Create a new instance of the Mob scene.
    var mob = mob_scene.instantiate()

    # Choose a random location on Path2D.
    var mob_spawn_location = $MobPath/MobSpawnLocation
    mob_spawn_location.progress_ratio = randf()

    # Set the mob's direction perpendicular to the path direction.
    var direction = mob_spawn_location.rotation + PI / 2

    # Set the mob's position to a random location.
    mob.position = mob_spawn_location.position

    # Add some randomness to the direction.
    direction += randf_range(-PI / 4, PI / 4)
    mob.rotation = direction

    # Choose the velocity for the mob.
    var velocity = Vector2(randf_range(150.0, 250.0), 0.0)
    mob.linear_velocity = velocity.rotated(direction)

    # Spawn the mob by adding it to the Main scene.
    add_child(mob)

Важно

Почему PI? В функциях, требующих углы, Godot использует радианы, а не градусы. Число Пи представляет собой пол-оборота в радианах, примерно 3.1415 (также есть переменная TAU, которая равна 2 * PI) Если вам удобнее работать с градусами, вам нужно использовать функции deg2rad() и rad2deg().

Тестирование сцены

Давайте протестируем сцену, чтобы убедиться, что все работает. Добавьте в _ready() вызов new_game:

func _ready():
    new_game()

Также давайте назначим сцену Main в качестве нашей "Главной сцены", которая запускается автоматически при запуске игры. Нажмите кнопку "Запустить проект" и выберите main.tscn при появлении запроса.

Совет

Если вы уже установили другую сцену в качестве «Основной сцены», вы можете щелкнуть правой кнопкой мыши по main.tscn в панели «Файловая система» и выбрать «Установить как основную сцену».

У вас должна быть возможность перемещать игрока, видеть, как появляются мобы, и видеть, как игрок исчезает, когда его бьет моб.

When you're sure everything is working, remove the call to new_game() from _ready() and replace it with pass.

Чего не хватает нашей игре? Какого-нибудь пользовательского интерфейса. В следующем уроке мы добавим заглавный экран и отобразим очки игрока.