Продолжаем создавать свою первую игру на Godot 3.5(часть 2)

—

от автора

Ссылка на первую часть


В предыдущей части мы создали болванки сцен: Персонажа, Оружия, Снаряда и врага, сегодня немного доработаем эти болванки, добавим разных пушек, сцену игры и главное меню. Давайте начнём с доработки сцены оружия.

Оружие

Открываем нашу сцену DefaultWeapons, это та, которая болванка для оружия.
Для начала поработаем немного с анимацией стрельбы, сейчас мы имеем, что при удержании кнопки выстрела воспроизводится анимация выстрела, при отпускании она прекращается, то-есть у оружия с долгой перезарядкой анимация выстрела будет проигрываться, даже когда оружие на перезарядке. Или если быстро нажать кнопку выстрела, то пуля полетит, а анимация не воспроизведётся. Поэтому будем воспроизводить анимацию выстрела по условию, если нажата кнопка выстрела и оружие не на перезарядке.

func get_input():  if ((global_position - get_global_mouse_position()).x < 0): _animated_sprite.flip_v = false else: _animated_sprite.flip_v = true look_at(get_global_mouse_position())# направляем взгляд оружия на курсор мыши  if (Input.is_action_pressed('fire') && _fire_couldown_timer.is_stopped()):#нажат выстрел и нет перезарядки _animated_sprite.play("Fire")#анимация выстрела fire()#вызываем функцию выстрела 

Добавляем сигнал от AnimatedSprite о том, что анимация проигралась до конца(animation_finished())

func _on_AnimatedSprite_animation_finished(): if (_animated_sprite.animation == "Fire"):#если анимация выстрела, то меняем на обычную _animated_sprite.play("Default") 

Дальше давайте разберёмся с функцией выстрела. Наше стандартное оружие не обязательно учить стрелять, поэтому можно просто прописать в функции выстрела pass и для каждого отдельного оружия писать механику выстрела именно для этого оружия.

Полный листинг Оружия
extends KinematicBody2D #Добавляем элементы дерева объектов в код onready var _animated_sprite = $AnimatedSprite onready var _fire_couldown_timer = $FireCouldownTimer #Объявляем переменные, которые можно менять извне  export (PackedScene) var bullet_scene # это будет сцена нашей пули export var fire_rate = 0.2 # скорость атаки export var damage = 1 # урон   #функция обработки нажатий func get_input():  if ((global_position - get_global_mouse_position()).x < 0): _animated_sprite.flip_v = false else: _animated_sprite.flip_v = true look_at(get_global_mouse_position())# направляем взгляд оружия на курсор мыши  if (Input.is_action_pressed('fire') && _fire_couldown_timer.is_stopped()):#нажат выстрел и нет перезарядки _animated_sprite.play("Fire")#анимация выстрела fire()#вызываем функцию выстрела   func _ready(): _fire_couldown_timer.wait_time = fire_rate # выставляем скорость атаки   func spawn_bullet(rot):# передаём параметр дополнительного поворота пули, позже пригодится  var b = bullet_scene.instance()  var new_position = position var direction = rotation - rot  get_parent().add_child(b)# добавляем пулю, как потомка оружия  b.start(new_position,direction) b.damage = damage# задаём пуле урон   func _on_AnimatedSprite_animation_finished(): if (_animated_sprite.animation == "Fire"):#если анимация выстрела, то меняем на обычную _animated_sprite.play("Default")  # функция выстрела func fire(): pass #Это дефолтный класс, ему нет нужны стрелять, для каждого другого оружия  #будем определять класс стрельбы заного.  func _physics_process(delta): get_input() 

Пуля

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

#Функция обработки поведения при столкновении  func collision_action(_collision_object):#_collision_object - объект столкновения pass 
Полный листинг Пули
onready var _animated_sprite = $AnimatedSprite  var velocity = Vector2()  export var damage = 1 export var speed = 750  #функция для задания стартового положения func start(pos, dir): rotation = dir position = pos velocity = Vector2(speed, 0).rotated(rotation)  func _physics_process(delta): var collision = move_and_collide(velocity * delta) if collision:  collision_action(collision.colider)    #Функция обработки сигнала от VisibilityNotifier, Сигнал screen_exited func _on_VisibilityNotifier2D_screen_exited(): queue_free()  #Функция обработки поведения при столкновении  func collision_action(_collision_object):#_collision_object - объект столкновения pass 

С настройками оружия разобрались, давайте перейдём к созданию нового и доработке старого оружия.

Бластер

Сцены оружия и пули для бластера остаются без изменений. Для начала настроим пулю. Нам нужно добавить для пули обработчик столкновения. Пкм на корень сцены -> Расширить скрипт

В созданном скрипте, нужно переопределить функцию collision_action().

func collision_action(_collision_object): if _collision_object.has_method("hit"): #Вызвали метод, если он есть _collision_object.hit(damage) queue_free()#Удалили пулю

Перейдём к настройке оружия, выставляем его характеристики, у меня (урон = 1, перезарядка = 0.2) и не забываем прицепить сцену нашей пули.
Дальше расширяем скрипт бластера и переопределяем функцию fire()

func fire(): if (_fire_couldown_timer.is_stopped()): # не на перезарядке spawn_bullet(0) # создаём пулю с 0-м дополнительным поворотом _fire_couldown_timer.start()
Демонстрация бластера

Демонстрация бластера

Наш бластер готов идём дальше

Дробовик

Для дробовика сцены оставляем стандартными(конечно ставим новые спрайтики и анимации). Расширяем скрипт для пули и переопределяем функцию collision_action(), она будет такая-же, как для бластера.
Расширяем скрипт для дробовика и переопределяем функцию fire()

func fire(): if (_fire_couldown_timer.is_stopped()): spawn_bullet(PI/12)# Поворачиваем пулю на ~15 градусов spawn_bullet(PI/24)# Поворачиваем пулю на ~7,5 градусов spawn_bullet(0)# выпускаем пулю прямо spawn_bullet(-PI/24)# Поворачиваем пулю на ~-7,5 градусов spawn_bullet(-PI/12)# Поворачиваем пулю на ~-15 градусов _fire_couldown_timer.start()# включаем перезарядку 

Для дробовика я выставил урон = 1 и скорость атаки = 0.5

Демонстрация дробовика

Демонстрация дробовика

Винтовка

Сцены для винтовки никак изменять не надо, только конечно заменить анимации. Скрипт выстрела будет такой-же, как для бластера.

Пуля из винтовки должна будет способна пробить несколько врагов, если враг после попадания умер. Перейдём сразу к расширению скрипта пули. Функция collision_action() примет следующий вид:

export var fly_count = 3 #сколько врагов пробьёт на сквозь var previous_enemy #враг который уже получил урон  func collision_action(_collision_object): if (_collision_object.has_method("hit") && previous_enemy != _collision_object):        #Вызвали метод, если он есть      #и враг не равен предыдущему _collision_object.hit(damage) fly_count -=1 # уменьшили счётчик возможных пробиваний previous_enemy = _collision_object # запомнили врага if (fly_count > 0): # если счётчик не 0, продолжаем движение move_and_slide(velocity) else: queue_free() else: queue_free() 

И обязательно не забудьте объявить новые переменные.
Для винтовки я выставил урон 5 и перезарядку 2, функция fire(), ничем не отличается от бластера.

Демонстрация винтовки

Демонстрация винтовки

БАЗУУУУКААААА

Какой экшен-шутер может обойтись без красивых взрывов от выстрела из ракетницы, правильно никакой. Тут уже нужно будет немного повозиться с сценой ракеты, поэтому начнём с сцены самой базуки.

Тут ничего добавлять не нужно, только изменить характеристики и добавить спрайты базуки. У меня урон = 3, а перезарядка = 5.

Теперь переходим к интересному, к сцене ракеты.

Тут есть как говорится 2 стула. Можно либо создать отдельную сцену для взрыва, и когда снаряд касается любого игрового объекта через код создавать взрыв и удалять снаряд, либо создать взрыв, как вторую анимацию у ракеты и добавить ещё пару элементов на сцену. Если в вашем проекте предусмотрено несколько объектов, которые должны взрываться( различные гранаты, ракетницы ) , то скорее всего стоит выбрать первый вариант, я же выбрал второй вариант, он как-то попроще, на мой взгляд.

Для начала в дерево объектов следует добавить:
⦁ Area2D(Bum)
⦁ CollisionShape2D(CollisionShapeBum), как дочерний к Area2D
⦁ Timer(BumLiveTime),как дочерний к Area2D

И у CollisionShapeBum, в инспекторе параметр Disabled, включить. Подогнать все элементы. У меня это выглядит следующим образом:

⦁ красным обведено — CollisionShapeBum
⦁ зелёным — CollisionShape2D

Таймер будем задавать маленьким, у меня 0.2 секунды, это время жизни взрыва, он всё-таки не моментально взорвался и всё, у него обязательно сделать Oneshot = true.

Логика проста, когда объект с чем-то коснулся, он становится взрывом CollisionShape2D, выключается, а CollisionShapeBum включается. Добавляем в скрипт сигнал от Area2D body_entered, на вхождение тела в область коллизии и в нём уже наносим урон, если это возможно

Расширяем скрипт и переходим к его редактированию:

onready var _collision_shape = $CollisionShape2D#Фигура столкновений ракеты onready var _collision_shape_bum = $Bum/CollisionShapeBum#Фигура столкновений взрыва onready var _bum_live_time = $Bum/BumLiveTime #Таймер жизни взрыва  func collision_action(_collision_object):# обработка столкновения снаряда if(_animated_sprite.animation == "Fly"):# если он был снарядом _animated_sprite.play("Bum")# превращаем в взрыв _collision_shape.disabled = true # выключаем обычную фигуру столкновения _collision_shape_bum.disabled = false # включаем фигуру столкновения взрыва scale = Vector2(10,10)  # увеличиваем размер в 10 раз,  #у вас может быть в другое количество раз, для моего проекта это в самый раз velocity=Vector2(position.x,position.y) _bum_live_time.start() func _on_Bum_body_entered(body): if(body.has_method("hit")): body.hit(damage)  func _on_BumLiveTime_timeout(): queue_free() 
Демонстрация базуки

Демонстрация базуки

Перейдём к модификации сцены персонажа.

Персонаж

  • К болванке сцены персонажа, нужно привязать камеру.

  • Сделать что-то вроде рюкзака, в котором будет хранится используемое оружие.

  • Добавить сигналы о получении персонажем урона и смерти

Начнём с простого. На узел DefaultCharacter добавляем Camera2D и в инспекторе выставляем current = true. На этом всё, камеру добавили.

Что я подразумеваю под рюкзаком, на сцене будет узел содержащий 6 position2D, на этих позициях будет отображаться оружие которое сейчас есть у персонажа. У самого персонажа будет массив с ссылками на сцены оружия, которое он использует. Начнём с добавления узла рюкзак. К персонажу добавляем Node2D(Backpack) и 6 Position2D(Slot1…Slot6), как дочерние у Node2D:

Дерево объектов персонажа

Дерево объектов персонажа
Пример расположения Backpack

Пример расположения Backpack

Навешиваем на Backpack скрипт и переходим к его редактированию. Нам нужно добавить простенькую функцию, которая возвращает Вектор позиции данного слота.

extends Node2D  #Массив Слотов onready var backpack = [$Slot1,$Slot2,$Slot3,$Slot4,$Slot5,$Slot6]  #функция получения текущего положения func get_slot_position(elem): return backpack[elem].position 

Дальше переходим к редактированию скрипта персонажа:

В начале скрипта нам нужно объявить новую переменную, backpack_items, на 6 эллементов, в нём будет хранится либо сцена, либо null. Так-же объявить сигнал take_damage(damage) и dead

#Рюкзак персонажа var backpack_items = [preload("res://scenes/Weapons/Shotgun/Shotgun.tscn"),null,null,null,null,null] #Сигнал о получении урона signal take_damage(damage) #Сигнал о смерти signal dead 

В функцию получения урона нужно добавить отправку сигналов:

#Функция получения урона func take_damage(dmg): if(_immortal_timer.is_stopped()): #Проверяем не бессмертен ли наш персонаж health -= dmg _animated_sprite.play("TakeDamage") emit_signal("take_damage",dmg) #Отправляем сигнал о получении урона _immortal_timer.start() #Запускаем таймер после получения урона if(health == 0): emit_signal("dead")#Отправляем сигнал о смерти 

Теперь перейдём к созданию функций по работе с рюкзаком.

Сначала напишем функцию, которая принимает в качестве аргумента номер слота от 0 до 5, в котором нужно отрисовать оружие:

#функция прикрепления оружия func equip_item(slot):# передаём номер слота в котором нужно отрисовать оружие if (backpack_items[slot] != null):#Если слот объявлен var weapon = backpack_items[slot].instance() weapon.position = _backpack.get_slot_position(slot)#получаем позицую данного слота weapon.name = "WeaponSlot" + String(slot)#Именя оружия WeaponSlot0..5 add_child(weapon) weapon.scale = Vector2(0.5,0.5)# у меня стоит масштабировать оружие, возможно у вас нет 

Дальше нам понадобится функция которая будет создавать все элементы рюкзака на сцене и если какой-то на сцене уже есть, то удалять его и рисовать заново:

#одеваем всё доступное оружие func equip_all(): for i in range(6):#Пробегаем по всему массиву backpack_item if(get_node("WeaponSlot"+String(i)) != null): var item =get_node("WeaponSlot"+String(i)) #Ищем узел if (item != null): #Если есть то удаляем его со сцены item.queue_free() equip_item(i)# и рисуем новый 

Так-же понадобится функция для удаления оружия у персонажа, которая в качестве аргумента получает номер слота из которого нужно удалить объект:

#удаляем оружие func remove_equip_item(slot):#Передаём номер слота if (slot >= 0 && slot <=5):#Проверяем номер слота var item = get_node("WeaponSlot" + slot) backpack_items[slot] = null#обнуляем значение в рюкзаке item.queue_free()#удаляем объект

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

#добавляем оружие func add_equip_item(item): for i in range(6): if (backpack_items[i] == null):#Находим первый пустой элемент массива backpack_items[i] = item#заливаем в него сцену оружия equip_all()#Одеваем всё оружие return 
Полный скрипт персонажа
extends KinematicBody2D   #Добавляем элементы дерева объектов в код onready var _animated_sprite = $AnimatedSprite onready var _idle_animation_timer = $IdleAnimationTimer onready var _immortal_timer = $ImmortalTimer onready var _backpack = $Backpack #Объявляем переменные, которые можно менять извне  export var health = 5 #Жизни export var speed = 200 #Скорость  #Объявляем переменные только для этого скрипта var velocity = Vector2.ZERO #Вектор направления var direction = Vector2.ZERO #Вектор движения var backpack_items = [preload("res://scenes/Weapons/Blaster/Blaster.tscn"),preload("res://scenes/Weapons/Blaster/Blaster.tscn"),null,null,null,null] #Сигнал о получении урона signal take_damage(damage) #Сигнал о смерти signal dead #Функция считывания нажатий func get_input(): velocity = Vector2.ZERO if Input.is_action_pressed("left"): velocity.x -= 1 if Input.is_action_pressed("right"): velocity.x += 1 if Input.is_action_pressed("up"): velocity.y -= 1 if Input.is_action_pressed("down"): velocity.y += 1 direction = velocity.normalized() * speed  #Функция воспроизведения анимаций func get_anim(): if (_immortal_timer.is_stopped()): #Проверяем не воспроизводится-ли анимация бессмертия if (velocity != Vector2.ZERO): #Если есть направление движения, то идём  _animated_sprite.play("Walk") else: if (_animated_sprite.animation != "IdleAnimation"): #Иначе если не брутальная анимация, то просто стоим _animated_sprite.play("Stand") if (_idle_animation_timer.is_stopped()): #Запускаем отчёт до брутальной анимации _idle_animation_timer.start() if (velocity.x > 0): # поворачиваем нашего персонажа в сторону движения _animated_sprite.flip_h = false  if (velocity.x < 0): _animated_sprite.flip_h = true  #Функция получения урона func take_damage(dmg): if(_immortal_timer.is_stopped()): #Проверяем не бессмертен ли наш персонаж health -= dmg _animated_sprite.play("TakeDamage") emit_signal("take_damage",dmg) #Отправляем сигнал о получении урона _immortal_timer.start() #Запускаем таймер после получения урона if(health == 0): emit_signal("dead")#Отправляем сигнал о смерти   func _ready(): _animated_sprite.animation = "Stand" # При старте персонаж должен просто стоять equip_all()   func _physics_process(delta): get_input() get_anim() var collider = move_and_collide(direction * delta) # записываем в переменную collider для дальнейшей обработки столкновения   func _on_IdleAnimationTimer_timeout(): _animated_sprite.play("IdleAnimation") # Включаем БРУТАЛЬНУЮ анимацию по истечении таймера  #функция прикрепления оружия func equip_item(slot):# передаём номер слота в котором нужно отрисовать оружие if (backpack_items[slot] != null):#Если слот объявлен var weapon = backpack_items[slot].instance() weapon.position = _backpack.get_slot_position(slot)#получаем позицую данного слота weapon.name = "WeaponSlot" + String(slot)#Именя оружия WeaponSlot0..5 add_child(weapon) weapon.scale = Vector2(0.5,0.5)# у меня стоит масштабировать оружие, возможно у вас нет   #одеваем всё доступное оружие func equip_all(): for i in range(6):#Пробегаем по всему массиву backpack_item if(get_node("WeaponSlot"+String(i)) != null): var item =get_node("WeaponSlot"+String(i)) #Ищем узел if (item != null): #Если есть то удаляем его со сцены item.queue_free() equip_item(i)# и рисуем новый  #удаляем оружие func remove_equip_item(slot):#Передаём номер слота if (slot >= 0 && slot <=5):#Проверяем номер слота var item = get_node("WeaponSlot" + slot) backpack_items[slot] = null#обнуляем значение в рюкзаке item.queue_free()#удаляем объект  #добавляем оружие func add_equip_item(item): for i in range(6): if (backpack_items[i] == null):#Находим первый пустой элемент массива backpack_items[i] = item#заливаем в него сцену оружия equip_all()#Одеваем всё оружие return 

Давайте ещё изменим стандартный курсор, на нарисованный нами

Курсор

Переходим в настройки проекта -> Дисплей -> Курсор мыши. В пользовательском изображении, указываем путь к нашему курсору.

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

Синглтон SceneLoader

Для начала перейдите на вкладку скрипт и создайте скрипт файл. Обязательно проверьте чтобы он наследовал узел Node. Я сохраним его в отдельную папку scripts

Теперь переходим в настройки проекта, вкладка автозагрузка нажимаем на иконку папки и указываем путь до нашего скрипта, нажимаем добавить, в столбце глобальная переменная должно быть включить — true

Переходим к редактированию этого скрипта:

extends Node # Константа хранящая путь к папке со сценами const MAP_ROOT = "res://scenes/Game/"  var current_scene = null # текущая сцена  #При первом запуске объявляем текущую сцену func _ready(): var root = get_tree().root current_scene = root.get_child(root.get_child_count() - 1)  #подготавливаем полный путь сцены, тоесть сюда будет передаваться только название func build_map_path(map_name): var path = MAP_ROOT.plus_file(map_name + "Scene/"+ map_name + ".tscn") #у меня сцены карт хранятся в папке scenes/Game/Папка с названием сцены/файл с названием сцены.tscn _change_map(path)  #функция смены карты func _change_map(path): #безопасно удаляем текущую сцены current_scene.free() # загружаем в в переменную текущей сцены, требуемую сцену current_scene = ResourceLoader.load(path).instance() # добавляем требуемую сцену, как потомка корня дерева get_tree().root.add_child(current_scene) # передаём параметр установленной сцены через API get_tree().current_scene = current_scene 

Тут закончили теперь давайте накидаем простенькое главное меню

Главное меню

Будем рассуждать логически у главного меню должно быть что-то похожее на задний фон и кнопочки. В нашем проекте я ещё добавлю анимацию танцующего главного героя на задний фон. Главным узлом сцены выбираем Node2D(MainMenu). Дальше добавляем элементы в дерево объектов:

⦁Sprite(Background)
⦁AnimatedSprite
⦁TextureButton(StartGameBtn)
⦁Label(StartGameLbl) — подчинённый TextureButton
⦁TextureButton(SettingBtn)
⦁Label(SettingLbl) — подчинённый SettingBtn
⦁TextureButton(ExitGameBtn)
⦁Label(ExitGameLbl) — подчинённый ExitGameBtn

В инспекторе у надписей нужно написать сам текст кнопки, дальше в инспекторе переходим во вкладку Control-> Theme Overrides -> Fonts и в свойстве Font, выбираем новый DynamicFont, дважды нажимаем на него, открываем свойство Font и указываем путь к нашему шрифту. Дальше из меню Fonts->Font выбираем Settings и указываем желаемый размер шрифта( у меня 36). Шрифт настроили, теперь давайте настроим расположение. В верхней панели нажимаем «Макет» и выбираем в нём расположение «По центру».

Меню примерно должно выглядеть так:

Персонаж на экране телефона как раз таки и есть AnimatedSprite, он будет танцевать. Сцену настроек сделаем в следующей части статьи.
Перейдём к скрипту:

extends Node2D  #Переменные onready var _animated_sprite = $AnimatedSprite onready var _start_btn = $StartGameBtn onready var _settings_btn = $SettingsBtn onready var _exit_btn = $ExitGameBtn #При запуске включается анимация func _ready(): _animated_sprite.play("Dance")  #Когда анимация закончилась, персонаж поворачивается func _on_AnimatedSprite_animation_finished(): _animated_sprite.flip_h = !_animated_sprite.flip_h   #При нажатии кнопки начала игры, вызывается функция нашего Синглтона и переключается на сцену игры func _on_StartGameBtn_pressed(): SceneLoader.build_map_path("GameScene")  #При нажатии кнопки выхода, игра закрывается func _on_ExitGameBtn_pressed(): get_tree().quit() 

Скрипт короткий и вполне понятный, получаем сигналы с кнопок и обрабатываем их.

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

Для начала создадим сцену с пользовательским интерфейсом:

Пользовательский интерфейс

Главным узлом сцены выбираем Node2D, дочерние к нему элементы:

⦁ ColorRect(CurrentHealthBar)
⦁ TextureRect(HealthBar) — дочерний у ColorRect
⦁ Label(Health) — дочерний у ColorRect
⦁ Label(Score)
⦁ Timer(ScoreAddTimer) — дочерний у Label

Label(Health) — будет отображать текущий и максимальный запас жизней, через слэш.
ColorRect — это просто зелёная полоска жизней, которая будет уменьшаться, как у врагов
TextureRect — это текстура HealthBar
Label(Score) — отображает заработанные очки
Timer — отчёт до начисления очков( 1 сек)

Расположить элементов на экране

Расположить элементов на экране

Теперь навешиваем скрипт на Node2D и переходим к его редактированию:

extends Node2D  #Элементы дерева onready var _score_add_timer = $Score/ScoreAddTimer onready var _score = $Score onready var _current_health_bar = $CurrentHealthBar onready var health = $CurrentHealthBar/Health  var max_health #Максимальное кол-во хп var current_health #Текущее кол-во хп var health_bar_size #Размер на который нужно уменьшать зелёный квадрат  # Функция заадния всех переменных func init_health(hp): max_health = hp current_health = hp health.text = String(current_health) + "/" + String(max_health)#записываем в лэйбэл наши хп health_bar_size = _current_health_bar.rect_size.x / max_health# определяем длинну деления  # При старте запускаем таймер func _ready(): _score_add_timer.start() # Функция вызываемая при получении урона func take_damage(damage): current_health -= damage  health.text = String(current_health) + "/" + String(max_health) _current_health_bar.rect_size.x -= health_bar_size * damage  # Сигнал от таймера func _on_ScoreAddTimer_timeout(): _score.text = String(int(_score.text) + 1) 

Прокомментировал всё достаточно подробно, не вижу смысла что-то дополнять

Теперь создаём сцену игры и переходим к её редактированию.

Сцена игры

Главным элементом сцены выбираем Node2D, дочерние элементы:

⦁ StaticBody2D, дочерние к нему
⦁ Sprite(background), дочерний к StaticBody2D
⦁ 4 CollisionShape2D, дочерний к StaticBody2D
⦁ Timer(MobSpawnTimer)
⦁ Path2D(MobPath)
⦁ PathFollow2D(MobFollowPath), дочерний к Path2D
⦁ Сцена нашего персонажа
⦁ Сцена UserInterface, дочерняя к нашему персонажу

Для чего-же нам StaticBody с 4-мя CollisionShape, Это будут граница экрана за которые персонаж не может выйти, как примерно стоит всё это расположить:

Для StaticBody2D, Нужно настроить CollisionObject2D, как мы это делали в предыдущей части, теперь нужно дать название 5-му слою в списке(у меня это Map) и присвоить нашему StaticBody2D, Layer 5. Дальше отредактируем столкновение для DefaultCharacter и DefaultEnemy:

DefaultCharacter

DefaultCharacter
DefaultEnemy

DefaultEnemy

Спавнить зомби, будем так-же, как и в первой статье на тестовой сцене, в следующей части это переработаем.
Навешиваем на Node2D скрипт и переходим к его редактированию:

#Сцена Врага export (PackedScene) var zombie_enemy  #Элементы дерева onready var _mob_spawn_timer = $MobSpawnTimer onready var player = $BrutalHero onready var _user_interface = $BrutalHero/UserInterface #Функция призыва Зомби func spawn_zombie(): var z = zombie_enemy.instance()  var zombie_spawn_location = $MobPath/MobPathFollow  zombie_spawn_location.unit_offset = rand_range(0.0,1.0)#Генерируем случайную точку спавна  z.position = zombie_spawn_location.position#Настраиваем позицию   z.player = player get_parent().add_child(z)# добавляем зомби  z.speed = 2# присваеваем ему статы z.health = 5  func _ready(): _mob_spawn_timer.start() randomize()# подключаем генератор случайных чисел _user_interface.init_health(player.health)# инициализируем наш UI   func _on_MobSpawnTimer_timeout(): spawn_zombie() spawn_zombie() spawn_zombie() _mob_spawn_timer.start()  #Добавляем сигнал, от нашего персонажа на получение урона func _on_BrutalHero_take_damage(damage): _user_interface.take_damage(damage)  #Добавляем сигнал, от нашего персонажа о смерти func _on_BrutalHero_dead(): get_tree().call_group("all_enemy", "queue_free")#Удаляем всех врагов со сцены SceneLoader.build_map_path("MainMenu")#Переходим в главное меню

Всё подробно прокомментировал, стоит остановится только на одном моменте, в строке get_tree().call_group(«all_enemy», «queue_free») (строка 42), вызываем какую-то группу, что-то с ней делаем, как это так?

Для этого нам нужно перейти на сцену DefaultEnemy, Нажать на главный узел сцены и перейти на вкладку «Узел». На вкладке Узел перейти к Группам, ввести название(all_enemy) и нажать добавить. Теперь когда будет появляться экземпляр сцены DefaultEnemy, он будет добавляться в группу «all_enemy», и при вызове функции queue_free(), мы удаляем всех, кто находится в этой группе.


Вот в принципе и всё. На этом мы можно сказать сделали первую ULTRA-alfa версию нашей игры. В следующей части добавим музыку и звуковые эффекты, а то играть можно, но в тишине скучно. Так-же добавим цель для игры, а то на данном этапе набранные нами очки даже никуда не сохраняются и не отображаются. Добавим меню выбора персонажа и различных персонажей, переработаем сцену игры и добавим большое количество врагов.

Голосуйте в опросе, 3 самых популярных варианта добавим в следующей части.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Что добавляем в следующих частях?
14.29% Оружие ближнего боя 1
42.86% Локальный мультиплеер 3
14.29% Больше оружия дальнего боя 1
28.57% Больше характеристик у персонажа 2
57.14% Возможность создания построек(Различные баррикады, турели) 4
28.57% Создание разных игровых режимов 2
14.29% Создание механики уровня персонажа, с чем-то вроде дерева талантов 1
Проголосовали 7 пользователей. Воздержавшихся нет.

ссылка на оригинал статьи https://habr.com/ru/articles/745956/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *