*Вы можете найти ссылки на предыдущие части в нижней части этого руководства.

Теперь у нас в игре есть бомба, но нам все еще нужен спавнер для нее. Спаунер создаст бомбу на определенном пути — и она будет возрождать новую бомбу каждый раз, когда бомба сталкивается с игроком или со стеной/тайловой картой. Вместо того, чтобы самим размещать бомбы на картах, спаунер позаботится об этом за нас.

ЧТО ВЫ УЗНАЕТЕ В ЭТОЙ ЧАСТИ:

  • Как работать с узлами Path2D.
  • Как работать с узлами PathFollow2D.
  • Как работать с узлами AnimationPlayer.

Мы создадим предопределенный путь для нашей бомбы, используя узел Path2D. Этот узел Path2D позволяет нам рисовать точки на карте, которые будут служить траекторией нашей бомбы. После того, как мы создали наши точки пути, мы будем использовать узел PathFollow2D, чтобы переместить нашу созданную сцену с бомбой по этому пути. Нам нужно будет использовать узел AnimationPlayer, чтобы анимировать движение нашей бомбы по этому пути.

Давайте создадим новую сцену для нашего генератора бомб! Наш корневой узел должен быть узлом Node2D, который вы можете переименовать в «BombSpawner». Сохраните эту сцену в папке «Сцены».

Для этой сцены BombSpawner потребуется узел Timer, который будет создавать бомбу каждый раз, когда количество бомб падает ниже нуля (если бомб нет, таймер создаст новую).

В панели Inspector узла Timer нам нужно включить опцию «Autostart». Это запустит таймер автоматически, когда BombSpawner войдет в дерево основной сцены. Другой вариант «одноразовый» запускает таймер один раз, и таймер останавливается при достижении 0.

Наконец, этой сцене понадобится узел AnimatedSprite2D. Этот узел AnimatedSprite2D будет содержать анимацию для нашей пушки, которая будет стрелять бомбой. Мы добавим обработчик пушки в следующей части!

Давайте создадим две новые анимации для нашей пушки под названием «cannon_idle» и «cannon_fired».

Для нашей анимации cannon_idle перейдите к «res://Assets/Kings and Pigs/Sprites/10-Cannon/Idle.png» и добавьте в анимацию единственный кадр. Пока оставьте значения зацикливания и FPS значениями по умолчанию.

Для нашей анимации cannon_fired перейдите к «res://Assets/Kings and Pigs/Sprites/10-Cannon/Shoot (44x28).png» и добавьте в анимацию все 4 кадра. Дважды добавьте первый кадр, чтобы анимация пушки начиналась и заканчивалась одним и тем же кадром. Выключите зацикливание и оставьте значение FPS равным 4.

Мы также хотим, чтобы наша пушка была обращена в другую сторону (справа), поэтому на панели Inspector узла AnimatedSprite2D в разделе Offset включите свойство «Flip H».

Через минуту мы вернемся к нашей сцене BombSpawner, а пока давайте вернемся к нашей основной сцене и добавим в нее новый узел Node2D. Переименуйте его в «BombPath». Кроме того, удалите любую сцену с бомбой, которую вы ранее создали из своей сцены.

Добавьте узел Path2D в качестве дочернего узла узла BombPath. Этот узел позволит нам рисовать точки пути для движения нашей бомбы.

Если вы выберете свой узел Path2D, вы увидите на панели инспектора, что есть возможность добавить точки. Если вы добавите элемент в свойство Points, вы увидите, что он создал ромбовидную форму в окне игры в точке (x: 0, y: 0).

Вы можете перетащить эту точку в любое место на карте, где вы хотите, чтобы ваша бомба появилась. Это начальная точка вашего пути к бомбе. Вы можете переместить его позже.

Если вы добавите еще один элемент, он создаст еще одну точку.

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

Давайте создадим путь сверху вниз для нашей бомбы. Бомба будет следовать по этому пути каждый раз, когда она появляется, и если она достигнет конца этого пути или столкнется с нашим игроком, она возродится сверху и снова спустится до конца. Убедитесь, что вы оставили достаточно места, чтобы ваш игрок мог перепрыгнуть через бомбу, и если вы заставили свою бомбу столкнуться с вашей Tilemap (вариант № 1) — убедитесь, что у вас достаточно места, чтобы ваша бомба могла течь без взрыва.

Чтобы наша бомба следовала по этому пути после того, как на нем была создана, нам нужно добавить узел PathFollow2D в качестве дочернего элемента узла Path2D.

Нам также нужно добавить узел AnimationPlayer в качестве дочернего элемента узла Path2D. Анимационный проигрыватель используется для универсального воспроизведения ресурсов Анимация. Он содержит словарь ресурсов AnimationLibrary и настраиваемое время наложения между переходами анимации. Вы будете использовать этот узел AnimationPlayer всякий раз, когда вам нужно анимировать элементы, не являющиеся спрайтами, такие как метки или цвета.

Мы добавляем анимацию в узел AnimationPlayer на панели «Анимация» ниже.

Чтобы добавить новую анимацию, щелкните свойство «Анимация» и выберите параметр «Новая». Назовите эту анимацию «bomb_movement».

Это создаст анимацию, состоящую из четырех частей:

  • Элементы управления (т. е. добавление, загрузка, сохранение и удаление анимации)
  • Список треков.
  • Временная шкала с ключевыми кадрами, определяющая значение свойства в определенный момент времени.
  • Временная шкала и элементы управления дорожкой, где вы можете, например, масштабировать временную шкалу и редактировать дорожки. Вы также можете увеличивать и уменьшать масштаб временной шкалы, удерживая нажатой клавишу CRTL и прокручивая колесико мыши.

Давайте изменим длину нашей анимации на 5. Это будет время, необходимое нашей бомбе, чтобы переместиться из первой точки нашего узла Path2D в последнюю точку. Мы можем изменить это позже, чтобы сделать его медленнее или быстрее.

Давайте создадим новый трек. Каждая дорожка хранит путь к узлу и затронутое им свойство. Нажмите «Добавить дорожку» и выберите «Дорожка собственности». Мы выбираем Property Track, потому что хотим изменить значение свойства определенного узла (свойства, которые мы можем найти на панели Inspector).

Мы хотим анимировать свойство коэффициента прогресса нашего узла PathFollow2D, чтобы мы могли перемещать бомбу по пути в соответствии с этим коэффициентом прогресса. Поскольку мы хотим анимировать узел PathFollow2D, нам нужно выбрать его в появившемся всплывающем окне.

Теперь мы можем выбрать свойство узла PathFollow2D, которое мы хотим анимировать. Выберите «Коэффициент прогресса». Этот коэффициент прогресса относится к расстоянию вдоль пути в виде числа в диапазоне от 0,0 (для первой вершины) до 1,0 (для последней). Это просто еще один способ выражения прогресса в пути, поскольку указанное смещение умножается внутри на длину пути.

Давайте добавим два ключевых кадра: один в начале и один в конце временной шкалы анимации. Чтобы добавить новый ключевой кадр, щелкните правой кнопкой мыши и выберите «Вставить ключ». Добавьте ключ в точке 0 и точке 5 на временной шкале. 0 — это наша начальная точка, где бомба появится и начнет двигаться, а 5 — конечная точка, где бомба будет уничтожена и перестанет двигаться.

Прямо сейчас анимация не будет двигаться, потому что коэффициент прогресса изменяется от значения 0 до значения 0. Если вы выберете свой ключевой кадр, вы увидите это значение на панели «Инспектор». Нам нужно изменить свойство Value нашего ключевого кадра «5» на 1. Это означает, что наша анимация будет воспроизводиться от 0 до 1, поэтому наша бомба будет двигаться по пути, а не просто оставаться на месте. Выберите ключевой кадр, который вы поместили на время 5, и измените его значение на 1. Мы пока не можем протестировать эту анимацию, но она нам нужна, иначе наша Бомба не будет двигаться по нашей траектории!

Повторите те же шаги, что и выше (для нашего BombPath) для вашего второго уровня в вашей сцене Main_2.

Теперь, вернувшись к нашей сцене BombSpawner, нам нужно добавить в нашу сцену новый скрипт. Сохраните его в папке «Скрипты».

В нашем только что созданном скрипте нам нужно определить несколько переменных. Во-первых, нам нужно создать ссылку на нашу сцену с бомбой. Мы можем сделать это с помощью глобального метода preload, который загружает наш ресурс сцены как можно раньше, чтобы заранее загрузить операции загрузки и избежать загрузки ресурсов в середине кода, чувствительного к производительности. Предварительная загрузка гарантирует, что сцена будет загружена в память до начала игры, что может помочь избежать задержек позже.

### BombSpawner.gd

extends Node2D

#bomb scene reference
var bomb = preload("res://Scenes/Bomb.tscn")

Поскольку у нас более одного уровня, нам нужно найти способ динамически изменить имя текущей сцены, чтобы мы могли соответствующим образом получить ее путь. Другими словами, если мы находимся в нашей основной сцене, мы хотим, чтобы наша бомба появлялась на нашем пути «/root/Main/BombPath/Path2D/PathFollow2D», а если мы находимся в нашей сцене Main_2, мы хотим, чтобы наша бомба появлялась на нашем пути «/root/Main_2/BombPath/Path2D/PathFollow2D».

Мы можем использовать наш глобальный скрипт, чтобы отслеживать, какая основная сцена активна в данный момент. Мы будем использовать нашу функцию ready(), чтобы получить имя текущей сцены, в которой находится наш игрок (Main или Main_2). Мы сделаем это через объект current_scene().

### Global.gd

extends Node

#movement states
var is_attacking = false
var is_climbing = false
var is_jumping = false

#current scene
var current_scene_name

func _ready():
    # Sets the current scene's name
    current_scene_name = get_tree().get_current_scene().name

Теперь, вернувшись в наш скрипт BombSpawner, давайте определим некоторые переменные, в которых будет храниться наше значение current_scene_path (Main или Main_2), путь нашей бомбы (current_scene_path + /BombPath/Path2D/PathFollow2D) и путь анимации бомбы ( current_scene_path + /BombPath/Path2D/AnimationPlayer).

### Bombspawner.gd

extends Node2D

#Bomb scene reference
var bomb = preload("res://Scenes/Bomb.tscn")

#references to our scene, PathFollow2D path, and AnimationPlayer path
var current_scene_path
var bomb_path
var bomb_animation

Мы инициируем значения этих переменных в нашей функции ready(). Нам также нужно установить анимацию загрузки нашей пушки по умолчанию на «cannon_idle».

### Bombspawner.gd

#older code

#when it's loaded into the scene
func _ready():
    #default animation on load
    $AnimatedSprite2D.play("cannon_idle")   
    #initiates paths
    current_scene_path = "/root/" + Global.current_scene_name + "/" #current scene
    bomb_path = get_node(current_scene_path + "/BombPath/Path2D/PathFollow2D") #PathFollow2D
    bomb_animation = get_node(current_scene_path + "/BombPath/Path2D/AnimationPlayer") #AnimationPlayer

Чтобы создать нашу бомбу, нам нужно создать новую функцию, которая создаст экземпляр сцены с бомбой и вернет ее. Чтобы создать экземпляр нашей ссылки на сцену, мы используем метод instantiate(). Нам также нужно воспроизвести нашу анимацию cannon_fired, которая будет воспроизводиться каждый раз, когда наша функция вызывается для создания новой бомбы.

### Bombspawner.gd

#older code

#spawns bomb instance    
func shoot():
    #play cannon shoot animation each time the function is fired off
    $AnimatedSprite2D.play("cannon_fired")
    #returns spawned bomb
    var spawned_bomb = bomb.instantiate()
    return spawned_bomb

Чтобы создать бомбу с помощью нашей функции стрельбы, нам нужно подключить сигнал timeout() нашего узла Timer к нашему сценарию.

В функции _on_timer_timeout() нам нужно проверить, не появляются ли бомбы в данный момент на нашей сцене, и если это правда, мы вызовем нашу функцию shoot() и создать бомбу на нашем bomb_path. Мы можем проверить дочерние элементы нашего узла /BombPath/Path2D/PathFollow2D/ с помощью методов get_child или get_child_count. Вы можете добавить узел к определенному пути (например, к нашему bomb_path), используя метод add_child. Это создаст бомбу под нашим путем, поэтому наш путь в конечном итоге будет выглядеть как /BombPath/Path2D/PathFollow2D/Bomb.

### Bombspawner.gd

#older code

#shoot and spawn bomb onto path
func _on_timer_timeout():
    #reset animation before shooting    
    $AnimatedSprite2D.play("cannon_idle")
    #spawns a bomb onto our path if there are no bombs available
    if bomb_path.get_child_count() <= 0:
        bomb_path.add_child(shoot())

Наконец, нам нужно проверить путь на наличие изменений. Если бомба достигла конца пути (когда анимация достигает значения 1), нам нужно сбросить значение пути обратно на 0, чтобы анимация могла перезапуститься, а также создать новую бомбу.

Чтобы это работало правильно, нам нужно создать глобальную переменную, которая изменит состояние движения нашей бомбы. Если наша бомба создана, она должна двигаться, а если она сталкивается с нашим игроком, она не должна двигаться.

### Global.gd

extends Node

#movement states
var is_attacking = false
var is_climbing = false
var is_jumping = false

#current scene
var current_scene_name

#bomb movement state
var is_bomb_moving = false

func _ready():
    # Sets the current scene's name
    current_scene_name = get_tree().get_current_scene().name
### Bomb.gd

#older code

func _on_body_entered(body):
    #if the bomb collides with the player, play the explosion animation and start the timer
    if body.name == "Player":
        $AnimatedSprite2D.play("explode")
        $Timer.start()
        Global.is_bomb_moving = false
    #OPTION 1   
    #if the bomb collides with our Level Tilemap (floor and walls). 
    if body.name == "Level" and !body.name.begins_with("Platform"):
        $AnimatedSprite2D.play("explode")
        $Timer.start()
        Global.is_bomb_moving = false
        
    #OPTION 2   
    #if the bomb collides with our Wall scene, explode and remove
    if body.name.begins_with("Wall"):
        $AnimatedSprite2D.play("explode")
        $Timer.start()
        Global.is_bomb_moving = false
### BombSpawner.gd

#older code

#spawns bomb instance    
func shoot():
    #play cannon shoot animation each time the function is fired off
    $AnimatedSprite2D.play("cannon_fired")
    #sets the bomb to moving and plays bomb animation
    Global.is_bomb_moving = true
    bomb_animation.play("bomb_movement")
    #returns spawned bomb
    var spawned_bomb = bomb.instantiate()
    return spawned_bomb

Теперь в нашей функции _on_timer_timeout() мы можем проверить, не движется ли наша бомба, и если это правда, нам нужно удалить все наши бомбы и остановить воспроизведение нашей анимации, чтобы наш коэффициент прогресса мог сбросить обратно на 0. Мы можем сделать это, запустив Цикл по элементам, который подсчитает дочерние элементы внутри нашего bomb_path, а затем удалит их всех, если они существуют.

### BombSpawner.gd

#older code

#shoot and spawn bomb onto path
func _on_timer_timeout():
    #reset animation before shooting    
    $AnimatedSprite2D.play("cannon_idle")
    #spawns a bomb onto our path if there are no bombs available
    if bomb_path.get_child_count() <= 0:
        bomb_path.add_child(shoot())
    # Clear all existing bombs  
    if Global.is_bomb_moving == false:
        for bombs in bomb_path.get_children():
            bomb_path.remove_child(bombs)
            bombs.queue_free()
            bomb_animation.stop()

Наконец, нам нужен AnimationPlayer из сцен уровня, чтобы воспроизвести анимацию bomb_movement в начале. Это позволит бомбе двигаться, когда наш BombSpawner появится на сцене.

### Bombspawner.gd

#older code

#when it's loaded into the scene
func _ready():
    #default animation on load
    $AnimatedSprite2D.play("cannon_idle")   
    #initiates paths
    current_scene_path = "/root/" + Global.current_scene_name + "/" #current scene
    bomb_path = get_node(current_scene_path + "/BombPath/Path2D/PathFollow2D") #PathFollow2D
    bomb_animation = get_node(current_scene_path + "/BombPath/Path2D/AnimationPlayer") #AnimationPlayer
    
    #starts bomb movement
    bomb_animation.play("bomb_movement")

Теперь в ваших основных сценах создайте экземпляр сцены BombSpawner. Вы хотите разместить свою пушку перед траекторией бомбы.

Main.tscn:

Main_2.tscn:

Теперь, если вы запустите свою сцену, ваша бомба должна появиться, и если она взорвется по пути, она должна появиться снова. Бомба движется слишком быстро, поэтому давайте изменим анимацию bomb_movement в нашей основной сцене, чтобы она была немного выше. Вам придется проверить это значение в зависимости от длины вашего пути.

Main.tscn:

Main_2.tscn:

Если вы заметили, что ваш игрок продолжает сталкиваться с бомбой (т. е. не может перепрыгнуть через нее), рассмотрите возможность изменения переменной jump_height вашего игрока на немного меньшее значение, например -110. Вы также можете изменить форму столкновения узла Bomb, чтобы она была немного меньше. Вы также можете сделать то же самое для формы столкновения вашего игрока.

Давайте сделаем еще одну вещь для этой части. Давайте заставим Бомбу катиться, если она движется. Мы можем сделать это, добавив к его значению rotate в функции physics_process(). В сценарии Bomb создайте новую переменную и задайте для ее скорости скорость, с которой вы хотите, чтобы бомба вращалась.

### Bomb.gd 

extends Area2D

var rotation_speed = 10

Затем в вашей функции physics_process() давайте повернем бомбу, если она движется.

### Bomb.gd 

#older code

#rolls the bomb
func _physics_process(delta):
    # Rotate the bomb if it hasn't exploded
    if Global.is_bomb_moving == true:
        $AnimatedSprite2D.rotation += rotation_speed * delta

Ваш код должен выглядеть как это.

Теперь, если вы запустите свои сцены, ваша бомба должна дойти до конечной точки. Если он сталкивается с коллайдерами, которые вы определили в своем скрипте Bomb, он должен удалить себя со сцены, а создатель должен возродить новую бомбу. Ваш игрок также должен уметь перепрыгивать через бомбу. Существует небольшая задержка появления, когда пушка впервые стреляет в начале игры, но это не нарушает условия сделки.

Поздравляем с созданием генератора бомб! В следующей части мы создадим обработчик пушки, у которого будет речевой пузырь, в котором он будет насмехаться над нами — кроме того, что в нашей игре он предназначен исключительно для визуальных эффектов!

Сейчас самое время сохранить ваш проект и сделать резервную копию вашего проекта, чтобы вы могли вернуться к этой части, если возникнут какие-либо ошибки, нарушающие работу игры. Вернитесь и повторите то, что вы узнали, прежде чем продолжить серию, и как только вы будете готовы, увидимся в следующей части!

Следующая часть серии руководств

Учебная серия состоит из 24 глав. Я буду публиковать все главы в отдельных ежедневных частях в течение следующих нескольких недель. Вы можете найти обновленный список ссылок на учебники для всех 24 частей этой серии на моем GitBook. Если вы еще не видите ссылку, добавленную к части, это означает, что она еще не опубликована. Кроме того, если будут какие-либо будущие обновления серии, мой GitBook будет местом, где вы сможете быть в курсе всего!

Поддержите серию и получите ранний доступ!

Если вам нравится эта серия и вы хотите поддержать меня, вы можете пожертвовать любую сумму моему магазину KoFi или купить оффлайн PDF, в котором вся серия собрана в одном буклете, который можно взять с собой!

Брошюра дает вам пожизненный доступ к полной автономной версии брошюры «Изучайте Godot 4, создавая двухмерный платформер» в формате PDF. Это 451-страничный документ, содержащий все учебные пособия из этой серии в последовательном формате, а также вы получите от меня специальную помощь, если вы когда-нибудь застрянете или вам понадобится совет. Это означает, что вам не нужно ждать, пока я опубликую следующую часть серии руководств на Dev.to или Medium. Вы можете просто двигаться дальше и продолжать обучение в своем собственном темпе — в любое время и в любом месте!

Эта книга будет постоянно обновляться для исправления недавно обнаруженных ошибок или устранения проблем совместимости с более новыми версиями Godot 4.