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

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

Создайте новую сцену и добавьте 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).

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

The Main node will be spawning new mobs, and we want them to appear at a random location on the edge of the screen. Click the Main node in the Scene dock, then add a child Path2D node named MobPath. When you select Path2D, you will see some new buttons at the top of the editor:

../../_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 в окне Инспектора под "Main.gd".

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

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

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

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

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

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

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

Now we'll connect the timeout() signal of each Timer node (StartTimer, ScoreTimer, and MobTimer) to the main script. For each of the three timers, select the timer in the Scene dock, open the Signals tab of the Node dock, then double-click the timeout() signal in the list. This will open a new signal connection dialog. The default settings in this dialog should be fine, so select Connect to create a new signal connection.

Once all three timers have this set up, you should be able to see each timer have a Signal connection for their respective timeout() signal, showing in green, within their respective Signals tabs:

  • (For MobTimer): _on_mob_timer_timeout()

  • (For ScoreTimer): _on_score_timer_timeout()

  • (For StartTimer): _on_start_timer_timeout()

Now we define how each of these timers operate by adding the code below. Notice that StartTimer will start the other two timers, and that ScoreTimer will increment the score by 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 position to the random location.
    mob.position = mob_spawn_location.position

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

    # 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 в панели «Файловая система» и выбрать «Установить как основную сцену».

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

Когда вы убедитесь, что всё работает, удалите вызов new_game() из _ready() и замените его на pass.

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