Продолжаем и дальше создавать свою первую игру на Godot 3.5. В предыдущей статье мы добавили различные механики для оружия, нечто похожее на пользовательский интерфейс, Главное меню и сцену игры. Сегодня добавим больше играбельных персонажей, меню выбора персонажа, для каждого персонажа будем вести свой маленький лидерборд, добавим больше врагов с разными механиками, добавим музыку к нашей игре и переработаем сцену игры.

Персонажи

Для начала нарисуем ещё 3-х персонажей. На данном этапе различие между персонажами может заключаться только в используемом ими оружие и небольшом различии в характеристиках( скорость, хп), поэтому добавим ещё каждому персонажу характеристику урона, это будет показатель на который умножается урон оружия.

Синглтон WeaponsName

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

Создаём новый скрипт обязательно проверьте, чтобы он наследовал Node и помещаем его в папку скриптов. В которой хранятся только синглтоны. После переходим в настройки проекта -> Автозагрузка и указываем путь к нашему синглтону, добавляем и ставим галочку в столбце "Глобальная переменная".

Переходим к редактированию скрипта. На данном этапе нам нужно добавить константы хранящие сцены нашего оружия:

extends Node
#У вас путь к сценам может быть другим, обязательно проверьте что
#указываете ссылку именно на оружие.tscn не на .gd или не на пуля.tscn
const BLASTER = preload("res://scenes/Weapons/Blaster/Blaster.tscn")
const SHOTGUN = preload("res://scenes/Weapons/Shotgun/Shotgun.tscn")
const RIFLE = preload("res://scenes/Weapons/Rifle/Rifle.tscn")
const BAZOOKA = preload("res://scenes/Weapons/Bazooka/Bazooka.tscn")

DefaultCharacter

Начнём с редактирование нашей болванки. В скрипте DefaultCharacter, нужно добавить переменную урона и сделать рюкзак пустым.

export var damage_scale:float = 1#Множитель урона
var backpack_items = [null,null,null,null,null,null]#рюкзак

Так-же мы добавили новый атрибут, урон, нужно увеличивать урон оружия, для этого немного модифицируем функцию equip_item():

#функция прикрепления оружия
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)# у меня стоит масштабировать оружие, возможно у вас нет
		weapon.damage = ceil(weapon.damage*damage_scale)#Перемножаем урон с нашим показателем и округляем в большую сторону

Так-же прикрепляем к Camera2D дочернюю сцену нашего пользовательского интерфейса во время игры. и переносим узел в верхний левый угол камеры. Так-же уберём обработку получения урона с главной сцены и добавим это в скрипт персонажа. Дополнив функцию take_damage(dmg)

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

Создание персонажей

Создаём персонажей так-же просто, как и оружие в предыдущей статье. Создаём новую сцену, главным узлом сцены выбираем болванку персонажа, меняем ему анимации, не забывая сделать SpriteFrame уникальным. Добавляем музыкальную тему и Расширяем скрипт.

В скрипте нам нужно добавить одну или несколько строк добавления оружия персонажу в функции _ready():

func _ready():
	add_equip_item(WeaponsName.BLASTER)
	#Вызываем функцию для добавления предмета в рюкзак и передаём в нею
	# константу из синглтона

Подобное нужно сделать со всеми персонажами. Ниже будет таблица моих настроек.

Персонаж

Оружие

ХП

Урон

Скорость

BrutalHero

1xБластер

5

1

200

Cowboy

1xДробовик

3

1.5

150

Robot

1xВинтовка

10

0.75

300

Soldier

1xБазука

5

2

150

Синглтон CharacterNames

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

extends Node

const BRUTALHERO = preload("res://scenes/Character/BrutalHero/BrutalHero.tscn")
const COWBOY = preload("res://scenes/Character/Cowboy/Cowboy.tscn")
const SOLDIER = preload("res://scenes/Character/Soldier/Soldier.tscn")
const ROBOT = preload("res://scenes/Character/Robot/Robot.tscn")

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

Оружие

В оружие всё просто, нам надо только добавить звуки выстрела.

На сцену болванку для оружия в дерево объектов добавляем AudioStreamPlayer(WeaponSound). Это будет проигрыватель звука выстрела оружия. Чтобы добавить музыку выбираем WeaponSound в дереве объектов. В инспекторе в параметр Stream загружаем свой файл с музыкой, для звуковых эффектов, например выстрела, лучше использовать .wav файлы, для фоновой музыки, лучше подойдёт .ogg формат, но если у вас wav или mp3, ничего страшного, данный проект достаточно маленький и никаких последствий от неправильно выбранного формата не будет.

И переходим к редактированию скрипта, в нём нужно объявить переменную ссылающуюся на WeaponSound:

onready var _weapon_sound = $WeaponSound

Теперь на каждой сцене с оружием добавляем звук выстрела от оружия и в функцию fire() для этого оружия добавляем:

_weapon_sound.play()

Так-же давайте настроим громкость звука, она конечно должна отличаться, но не должна глушить игрока. Уменьшить или Увеличить громкость можно путём изменения параметра Volume dB, у узла AudioPlayer.

Базука

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

Перейдём на сцену ракеты базуки и добавим AudioStreamPlayer(BumSound), как дочерний у Bum

Переходим к редактированию кода:

Нужно объявить ссылку на узел BumSound и воспроизводить звук взрыва в collision_action:

Полный код ракеты
extends "res://scenes/Weapons/DefaultWeapon/DefaultBullet.gd"

onready var _collision_shape = $CollisionShape2D#Фигура столкновений ракеты
onready var _collision_shape_bum = $Bum/CollisionShapeBum#Фигура столкновений взрыва
onready var _bum_live_time = $Bum/BumLiveTime #Таймер жизни взрыва
onready var _bum_sound = $Bum/BumSound #Звук взрыва

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()
		_bum_sound.play()
func _on_Bum_body_entered(body):
	if(body.has_method("hit")):
		body.hit(damage)


func _on_BumLiveTime_timeout():
	queue_free()

Враги

Сейчас в проекте есть просто зомби который идёт на игрока и пытается его ударить. Мы добавим ещё 4 вида врага:

  • Умный зомби(когда игрок смотрит в его сторону, он бежит от игрока, когда нет бежит на игрока)

  • Зомби за щитом(он где-то подобрал кусок железа, если стрелять не точно, то сначала нужно сломать щит, потом только бить зомби)

  • Страшный зомби(быстрый и страшный)

  • Толстый зомби(Большой, толстый и страшный, при смерти создаёт ещё 3-х обычных зомби)

  • Так-же добавим, что после убийства каждого зомби будет появляться лужа крови

ZombieBlood

Создаём новую сцену, выбираем главным узлом сцены Area2D(ZombieBlood), добавляем дочерние элементы:

⦁ AnimatedSprite
⦁ Timer(BloodLive)

В AnimatedSprite создаём новый спрайт фрейм и создаём отдельную анимацию, для каждого вида кровавой лучше( у меня 3), лучше рисовать не 1, чтобы весь пол не был залит одним видом кровавых луж.

Таймер будет служить временем жизни лужи, One Shoot и Autostart - вкл, Wait Time я поставил 30 секунд, то есть через 30 секунд будет удаляться лужа.

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

extends Area2D
#Объявляем переменные из дерева объектов
onready var _animated_sprite = $AnimatedSprite
onready var _blood_live = $BloodLive

func _ready():
	#генерируем случайный вид крови
	var animation_types = _animated_sprite.frames.get_animation_names()
	var animation = animation_types[randi() % animation_types.size()]
	_animated_sprite.play(animation)


func _on_BloodLive_timeout():
	#Вешаем сигнал таймера и по истичению удаляем
	queue_free()

Так-же добавляем кровь в группу all_enemy. Кто забыл, нужно нажать на Area2D(В дереве объектов), в инспекторе нажать "Узел"->Группы, ввести название и нажать добавить. Кровь создали теперь надо немного модифицировать

DefaultEnemy

Нужно объявить переменную содержащую ссылку на сцену крови, написать функцию которая будет создавать кровь на текущем местоположении с случайным поворотом, подключить в _ready() рандомайзер и вызывать функцию создания крови, когда кончились жизни, ещё сделать переменную скорости зомби float.

Попробуйте написать сами, если что под спойлером подсказка:

Полный код болванки врага
extends KinematicBody2D

#подгрузили сцену с кровью
var blood_scene = preload("res://scenes/Enemy/ZombieBlood/ZombieBlood.tscn")

#Добавляем элементы дерева объектов в код
onready var _animated_sprite = $AnimatedSprite
onready var _red_health = $HealthBar/RedHealth

#Добавляем переменную игрока, позже понадобится
export onready var player

#Характеристики врага
export var health = 5
export var speed:float = 2
export var damage = 1
#Ещё чу-чуть переменных
#Длина на которую нужно уменьшить размер RedHealth, в случае получения 1 ед. урона
onready var health_size = _red_health.rect_size.x / health 

var motion = Vector2.ZERO
var dir = Vector2.ZERO

#Функция по выстраиванию пути к заданной точке
func find_position(pos):
	dir = (pos - position).normalized()
	motion = dir.normalized() * speed
	if(dir.x < 0):
		_animated_sprite.set_flip_h(true)
	else:
		_animated_sprite.set_flip_h(false)


func _ready():
	randomize()#подключили рандомайзер 
	_animated_sprite.playing = true #Включили анимацию

#Функция получения урона
func hit(damage):
	health -= damage
	_red_health.rect_size.x -= health_size * damage
	if (health <= 0): #Если <= 0, то удалился
		spawn_blood() #Выызваем функцию спавна крови
		queue_free()

func _physics_process(delta):
	#Если игрока не существует, то некуда идти
	if (player != null):
		find_position(player.position)
		var collision = move_and_collide(motion)
		if collision:#Если столкнулся
			if collision.collider.has_method("take_damage"):#И есть метод take_damage
				collision.collider.take_damage(damage)#нанёс урон

#функция спавна крови
func spawn_blood():
	var b = blood_scene.instance()
		
	b.position = position#задали местоположение
	b.rotation_degrees = randi() % 361#сгенерировали случайный угол поворота

	get_parent().add_child(b)#добавили кровь

func get_health():
	return health

Теперь наконец-то можно создавать наших зомби.

Умный зомби

Наследуем, как главный узел сцену DefaultEnemy, расширяем скрипт и переходим к его редактированию:

extends "res://scenes/Enemy/DefaultEnemy/DefaultEnemy.gd"


#Переопределяем функцию поиска пути
func find_position(pos):
	dir = (pos - position).normalized()
	#Если dir.x < 0, значит игрок слева, если _animated_sprite игрока не повёрнут
	# изначально у меня спрайты смотрят в право, значит игрок смотри на зомби
	# чтобы идти в обратно направлении, умножаем вектор направления на -скорость
	if (dir.x < 0 && !player._animated_sprite.flip_h):
		motion = dir.normalized() * -speed
	#Тоже самое, только если dir.x < 0, значит игрок справа и если он повёрнут
	# то смотрит на зомби
	elif (dir.x > 0 && player._animated_sprite.flip_h):
		motion = dir.normalized() * -speed
	else: # во всех других случаях идём на игрока
		motion = dir.normalized() * speed
	if(dir.x < 0):# поворачиваем зомби, чтобы правильно шёл
		_animated_sprite.set_flip_h(true)
	else:
		_animated_sprite.set_flip_h(false)

Всё подробно прокомментировал, переходим к следующему зомби.

Толстый зомби

Наследуем, как главный узел сцену DefaultEnemy, расширяем скрипт и переходим к его редактированию:

extends "res://scenes/Enemy/DefaultEnemy/DefaultEnemy.gd"
#Подгружаем сцену с самым обычным зомби, можно использовать других зомби
var zombie_scene = preload("res://scenes/Enemy/Zombie1/Zombie1.tscn")

#Переопределяем функцию получения урона
func hit(damage):
	health -= damage
	_red_health.rect_size.x -= health_size * damage
	if (health <= 0): #Если <= 0, то умер 
		spawn_blood() # создаём кровь
		spawn_zombie(Vector2(position.x,position.y - 50)) #создаём зомби со смещениями
		spawn_zombie(Vector2(position.x,position.y + 50)) #создаём зомби со смещениями
		spawn_zombie(Vector2(position.x + 50,position.y)) #создаём зомби со смещениями
		queue_free()

#Функция спавна зомби в качестве аргумента Vector2
func spawn_zombie(pos):
	var z = zombie_scene.instance()
	#задаём для зомби позицию
	z.position = pos
	#поворот
	z.rotation = rotation
	#игрока
	z.player = player
	get_parent().add_child(z)# добавляем зомби

Всё подробно прокомментировал, переходим к следующему зомби.

Зомби за щитом

Для начала нам нужно создать новую сцену для щита.

Главным узлом выбираем StaticBody2D(Shield), дочерние элементы:

Вместо спрайта можно использовать AnimatedSprite, и сделать что чем меньше у щита хп, тем он больше разрушается. Задавать объект столкновений будем через CollisionPolygon2D, нам важно чтобы размеры щита были точно, как спрайт, потому-что метким выстрелом мы должны наносить урон зомби. И добавляем 2 ColorRect, для полоски жизни, как у зомби.

Как пользоваться CollisionPolygon2D, Нажимаем на CollisionPolygon2D в дереве объектов, выбираем режим выделения(Q) Если не выбран, и нажимаем по контуру спрайта

Так-же в настройках задаём имя новому слою столкновений( у меня 6-й и назвал Enemy_armor). Переходим на сцену DefaultBullet и ставим для неё новые настройки столкновений.

DefaultBullet
DefaultBullet

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

extends StaticBody2D
#объявляем элементы дерева объектов
onready var _red_health = $HealthBar/RedHealth
#Цена одного деления жизней
var health_size
#Размер жизней
export var health = 10

func _ready():
	#Вычисляем цену деления
	health_size = round(_red_health.rect_size.x / health)
	
#Функция получения урона
func hit(damage):
	health -= damage
	_red_health.rect_size.x -= health_size * damage
	if (health <= 0): #Если <= 0, то удалился
		queue_free()

Теперь переходим к созданию самого зомби:

Наследуем, как главный узел сцену DefaultEnemy, добавляем Position2D(ShieldPosition) в дерево объектов. Тут будет стоять щит, выстраиваем его под ваш спрайт. Расширяем скрипт и переходим к редактированию:

extends "res://scenes/Enemy/DefaultEnemy/DefaultEnemy.gd"
#Объявляем элементы дерева объектов
onready var _shield_position = $ShieldPosition
#Сцена с щитом
var shield_scene = preload("res://scenes/Enemy/ZombieShield/Shield.tscn")

#Подключаем рандомайз, для крови
func _ready():
	randomize()
	_animated_sprite.playing = true #Включили анимацию
	spawn_shield()#функция спавна щита


func spawn_shield():
	
	var s = shield_scene.instance()
	#дали местоположения
	s.position = _shield_position.position
	#повернули
	s.rotation = rotation
	add_child(s)# добавляем щит

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

Таблица с характеристиками:

Зомби

хп

скорость

урон

Обычный

5

2

1

Умный

3

2

1

Страшный

3

6

2

Толстый

10

0.5

1

За щитом

5

1

1

Щит

10

-

-

Синглтон EnemyNames

Давайте ещё создадим синглтон, который будет хранить константы со сценами врагов.

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

extends Node
#обычный
const ZOMBIE1 = preload("res://scenes/Enemy/Zombie1/Zombie1.tscn")
#умный
const SMARTZOMBIE = preload("res://scenes/Enemy/SmartZombie/SmartZombie.tscn")
#страшный
const ZOMBIESCARY = preload("res://scenes/Enemy/ZombieScary/ZombieScary.tscn")
#тослтый
const FATZOMBIE = preload("res://scenes/Enemy/FatZombie/FatZombie.tscn")
#за щитом
const ZOMBIESHEILD = preload("res://scenes/Enemy/ZombieShield/ZombieShield.tscn")
#щит
const SHIELD = preload("res://scenes/Enemy/ZombieShield/Shield.tscn")
#кровь
const ZOMBIEBLOOD = preload("res://scenes/Enemy/ZombieBlood/ZombieBlood.tscn")

Теперь давайте везде, где использовали просто preload переопределим на константы, не трогая сцену игры.

Где менять?
#DefaultEnemy 4 строка
var blood_scene = EnemyNames.ZOMBIEBLOOD
#FatZombie 3 строка
var zombie_scene = EnemyNames.ZOMBIE1
#ZombieShield 5 строка
var shield_scene = EnemyNames.SHIELD

На этом с врагами закончили.

Главное меню

Добавляем 2 AudioStreamPlayer2D, в дерево объектов (ClickSound) и (MusicTheme). ClickSound - будет проигрывать звук при нажатии мышкой, MusicTheme - будет проигрывать фоновую музыку. На MusicTheme ставим autoplay - true, и зацикливаем воспроизведение, для этого нажимаем на нашу добавленную аудиозапись, откроются расширенные настройки, там ставим выставляем loop, от выбранного вами формата настройки могут различаться, ниже 3 примера.. В код следует добавить:

#добавляем эллемент из дерева объектов
onready var _click_sound = $ClickSound

func _process(delta):
	if (Input.is_action_just_pressed("fire")):#Если нажата ЛКМ
		_click_sound.play()
Для .mp3
Для .mp3
Для .ogg
Для .ogg
Для .wav
Для .wav

Синглтон SelectedCharacter

Создаём новый синглтон, который будет хранить ссылку на сцену с выбранным персонажем. Создаём новый файл, помещаем его в папку scripts и в настройках ставим автозагрузку и делаем глобальной переменной.

extends Node
#перменаная хранящая текущего героя
var Character

#функция задания переменной героя
func set_character(scene):
	Character = scene

Меню выбора персонажа

Теперь у нас 4 игровых персонажа, значит после нажатия кнопки Start Game, главного меню должна появится сцена выбора персонажа, а только после начаться игра за конкретного персонажа. Значит на сцене выбора, игрок выбирает персонажа, выбранный персонаж записывается в синглтон и в функции _init() сцены игры добавляется на главный экран.

Для начала создадим сцену выбора персонажа, главным узлом выбираем Node2D, добавляем следующие дочерние элементы:

⦁ Sprite
⦁ TextureButton(BrutalHeroBtn)
⦁ TextureButton(CowboyBtn)
⦁ TextureButton(SoldierBtn)
⦁ TextureButton(RobotBtn)
⦁ TextureButton(StartGameBtn)
⦁ AnimatedSprite
⦁ TextureButton(StartGameBtn)
⦁ label(StartGameLbl)-дочерний к StartGameBtn
⦁ TextureButton(ReturnBtn)
⦁ Label(ReturnLbl) - дочерний к ReturnBtn
⦁ AudioStreamPlayer2D(ClickSound)
⦁ Label(CharacterParam)
⦁ AudioStreamPlayer2D(MusicTheme)

Sprite - это наш задний фон меню выбора, первые 4 кнопки отвечают за выбор персонажа, AnimatedSprite - воспроизводит анимацию танца выбранного персонажа, StartGameBtn - запускает игру, ReturnBtn - возвращает в меню. CharacterParam - в него будет записываться информация о персонаже(урон, хп...), ClickSound - звук при нажатии, Musictheme - фоновая музыка.

Расположить элементы следует примерно следующим образом:

Сцена выбора в редакторе
Сцена выбора в редакторе
Сцена выбора, после выбора персонажа.
Сцена выбора, после выбора персонажа.

Персонажи в квадратиках - это кнопки выбора. Для кнопок выстраиваем текстурки, для надписей настраиваем шрифт, кнопку StartGameBtn - по умолчанию делаем disable, станет активной, только после выбора персонажа. В AnimatedSprite заливаем наши анимации танцев и добавляем пустую Default. CharacterParam, располагается в блокноте. Заливаем звуки. Навешиваем скрипт и переходим к его редактированию:

extends Node2D

#Добавляем переменные дерева
onready var _animated_sprite = $AnimatedSprite
onready var _brutalhero_btn = $BrutalHeroBtn
onready var _cowboy_btn = $CowboyBtn
onready var _soldier_btn = $SoldierBtn
onready var _robot_btn = $RobotBtn
onready var _click_sound = $ClickSound
onready var _start_game_btn = $StartGameBtn
onready var _return_btn = $ReturnBtn
onready var _character_param = $CharacterParam

#Считываем щелчёк мыши, для воспроизведения звука
func _process(delta):
	if (Input.is_action_just_pressed("fire")):
		_click_sound.play()

#Если выбран BrutalHero
func _on_BrutalHeroBtn_pressed():
	_animated_sprite.play("Dance1")#Включаем его анимацию
	_start_game_btn.disabled = false#Включаем кнопку старта игры
	_character_param.text = "Character \nBrutalHero \nDamage: 1 \nHP: 5 \nSpeed: 200 \nEquip: Blaster"#Записываем текст
	SelectedCharacter.set_character(CharacterNames.BRUTALHERO)#В синглтон записываем BrutalHero

#Если выбран Cowboy
func _on_CowboyBtn_pressed():
	_animated_sprite.play("Dance2")#Включаем его анимацию
	_start_game_btn.disabled = false#Включаем кнопку старта игры
	_character_param.text = "Character \nCowboy \nDamage: 2.5 \nHP: 3 \nSpeed: 150 \nEquip: Shotgun"#Записываем текст
	SelectedCharacter.set_character(CharacterNames.COWBOY)#В синглтон записываем Cowboy

#Если выбран Soldier
func _on_SoldierBtn_pressed():
	_animated_sprite.play("Dance3")#Включаем его анимацию
	_start_game_btn.disabled = false#Включаем кнопку старта игры
	_character_param.text = "Character \nSoldier \nDamage: 1 \nHP: 5 \nSpeed: 200 \nEquip: Bazooka"#Записываем текст
	SelectedCharacter.set_character(CharacterNames.SOLDIER)#В синглтон записываем Soldier

#Если выбран Robot
func _on_RobotBtn_pressed():
	_animated_sprite.play("Dance4")#Включаем его анимацию
	_start_game_btn.disabled = false#Включаем кнопку старта игры
	_character_param.text = "Character \nRobot \nDamage: 0.75 \nHP: 10 \nSpeed: 300 \nEquip: Rifle"#Записываем текст
	SelectedCharacter.set_character(CharacterNames.ROBOT)#В синглтон записываем Robot

#Нажата кнопка Return
func _on_ReturnBtn_pressed():
	SceneLoader.build_map_path("MainMenu")#Вернулись в главное меню

#Нажата кнопка StartGame
func _on_StartGameBtn_pressed():
	SceneLoader.build_map_path("GameScene")#Запустили сцену игры.

Возвращаемся к скрипту главного меню и изменяем функцию _on_StartGameBtn_pressed():

#При нажатии кнопки начала игры, вызывается функция нашего Синглтона и переключается на сцену выбора
func _on_StartGameBtn_pressed():
	SceneLoader.build_map_path("PickMenu")

Точка спавна врагов

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

В эту точку нужно сохранять сцену персонажа и сцену зомби.

Главным узлом сцены выбираем StaticBody2D(SpawnPoint) и добавляем AnimationSprite2D(Анимация расширения круга, в моём случае) чтобы было видно, когда появится.

Добавляем скрипт и переходим к его редактированию:

extends StaticBody2D
#переменная хранящая сцену зомби, объявлять её будем на сцене игры
export onready var zombie_enemy
#переменная хранящая сцену игрока, объявлять её будем на сцене игры
export onready var player

#Включаем анимацию
func _ready():
	$AnimatedSprite.play("Idle")	
#Функция спавна зомби
func spawn_zombie():
	var z = zombie_enemy.instance()
	
	z.position = position#Настраиваем позицию 
	z.player = player
	get_parent().add_child(z)# добавляем зомби
	
	queue_free()

#У меня анимация идёт ровно 1 секунду и по её завершению появляется зомби.
func _on_AnimatedSprite_animation_finished():
	spawn_zombie()

Добавляем эту сцену в наш синглтон EnemyNames.

#СпавнПоинт
const SPAWNPOINT = preload("res://scenes/Enemy/SpawnPoint/SpawnPoint.tscn")

И не забываем добавить в группу all_enemy.

С этим разобрались, теперь нужно немного усовершенствовать скрипт DefaultCharacter.

DefaultCharacter

Нам нужно добавить переменную, хранящую количество оружия у персонажа и функцию, которая возвращает true, если ещё есть слоты для оружия и false, если нет слотов.

var weapon_count = 0 #Переменная хранящая количество оружия
func can_add():
	if (weapon_count < 6):
		return true
	else:
		return false

Так-же стоит добавить в функции add_equip_item() и remove_equip_item() увеличение и уменьшение этой переменной.

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

#добавляем оружие
func add_equip_item(item):
	for i in range(6):
		if (backpack_items[i] == null):#Находим первый пустой элемент массива
			backpack_items[i] = item#заливаем в него сцену оружия
			weapon_count +=1 #увеличели на 1 переменную количества оружия
			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
onready var _user_interface = $Camera2D/UserInterface
#Объявляем переменные, которые можно менять извне 
export var health = 5 #Жизни
export var speed = 200 #Скорость 
export var damage_scale:float = 1#Множитель урона
#Объявляем переменные только для этого скрипта
var velocity = Vector2.ZERO #Вектор направления
var direction = Vector2.ZERO #Вектор движения
var backpack_items = [null,null,null,null,null,null]#рюкзак
var weapon_count = 0 #Переменная хранящая количество оружия
#Сигнал о получении урона
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) #Отправляем сигнал о получении урона
		_user_interface.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)# у меня стоит масштабировать оружие, возможно у вас нет
		weapon.damage = ceil(weapon.damage*damage_scale)#Перемножаем урон с нашим показателем и округляем в большую сторону
		
		
#одеваем всё доступное оружие
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()#удаляем объект
		weapon_count -=1 #уменьшили на 1 переменную количества оружия

#добавляем оружие
func add_equip_item(item):
	for i in range(6):
		if (backpack_items[i] == null):#Находим первый пустой элемент массива
			backpack_items[i] = item#заливаем в него сцену оружия
			weapon_count +=1 #увеличели на 1 переменную количества оружия
			equip_all()#Одеваем всё оружие
			return	

func can_add():
	if (weapon_count < 6):
		return true
	else:
		return false

Сцена игры

На сцену игры добавляем Position2D(CharacterSpawnPoint), AudioStreamPlayer2D(MusicTheme), Timer(Difficult), ColorRect(Spawn). CharacterSpawnPoint - будет местоположение персонажа при начале игры.MusicTheme - наша фоновая музыка,Difficult- таймер по истечению которого будет увеличиваться сложность,Spawn - квадрат внутри которого, в любой точке может появится зомби.

Расположение ColorRect(Spawn)
Расположение ColorRect(Spawn)

Теперь в дерево объектов нажимаем на ColorRect(Spawn), в инспекторе color, и показатель A(alfa) - делаем равным 0. Для Timer(Difficult), выставляем autostart = true и Wait Time( у меня 10, то есть каждые 10 секунд игра становится сложнее). Добавляем музыку MusicTheme и зацикливаем её, не забываем про autoplay.

Переходим в скрипт и начинаем его изменять.

Функция спавна героя

#Функция создания героя
func spawn_hero(pos):
	var p
	if(SelectedCharacter.Character != null):#Если герой выбран
		p = SelectedCharacter.Character.instance()
	else:#Если вдруг каким-то образом не выбран
		p = CharacterNames.BRUTALHERO.instance()
	p.name = "Player"#Задаём имя, которое будет в дереве объектов
	p.position = pos
	add_child(p)
	player = p
	p.z_index = 2#Задаём z_index - 2, чтоыб герой ходил сверху крови

Вызываем эту функцию в функции _init():

#Функция инициализации
func _init():
	spawn_hero(Vector2(0,0))#Вызываем функцию создания героя

Функция _ready

Для начала объявляем наши новые переменные из дерева объектов

onready var _character_spawn_point = $CharacterSpawnPoint
onready var spawn = $Spawn

Изменённая функция _ready():

#Функция старта(срабатывает после _init)
func _ready():
	_mob_spawn_timer.start()#Включили таймер спавна
	randomize()# подключаем генератор случайных чисел
	player.position = _character_spawn_point.global_position
	#Передали игроку установленное в редакторе местоположение
	player._user_interface.init_health(player.health)# инициализируем наш UI
	player.connect("dead",self,"_on_Player_dead")#Привязываем сигнал о смерти игрока

Функция создания точки спавна врага

Теперь удалим функцию spawn_zombie() и создадим функцию spawn_point():

#Функция призыва точки спавна в качестве аргумента используется сцена с врагом
func spawn_point(enemy):
	var z = EnemyNames.SPAWNPOINT.instance()
	var rect_pos = spawn.rect_global_position
	var rect_size = spawn.rect_size
	#генерируем случайный вектор с местоположение зомби по следующему алгорится
	#для местоположению по х выбираем случайное значение из диапазона:
	#берём глобальное расположение квадрата оп х, как миннимум
	#и глобал местоположения по х + размер по х, как максимум
	#для y тоже самое, только вместо х-y
	z.position = Vector2(rand_range(rect_pos.x,rect_pos.x+rect_size.x),rand_range(rect_pos.y,rect_pos.y+rect_size.y))
	z.z_index = 100#Ставим z_index большим, чтобы точка спавна всегда распологалась поверх других объектов
	z.player = player#Задаём игрока
	z.zombie_enemy = enemy#Задаём врага
	get_parent().add_child(z)# добавляем точку спавна

Усложнение игры с течением времени

Объявляем переменные для усложнения игры:

#Массив всего оружия
var weapon_massiv = [WeaponsName.BLASTER,WeaponsName.RIFLE, WeaponsName.BAZOOKA, WeaponsName.SHOTGUN]
#Сложность игры
var spawn_time = 5 #Время частоты спавна врагов
var zombie1_chance = 40#вероятность для обычного зомби
var smart_chance = 40#вероятность для умного зомби
var shield_chance = 12#вероятность для зомби с щитом
var scary_chance = 4#вероятность для страшного зомби
var fat_chance = 4#вероятность для толстого зомби
var spawn_count = 3#кол-во призываемых зомби
var difficult_tick = 0#кол-во раз, которое увеличилась сложность
var weapon_add_chance = 0#шанс добавления предметов

Сама функция усложнения игры, будет срабатывать по сигналу таймера Difficult:

#Усложняем игру
func _on_Difficult_timeout():
	#генерируем шанс на получение оружия
	var weapon_chance = randi() % 100
	#Если чисто меньше, нашего шанса и можно добавить
	if (weapon_chance <= weapon_add_chance && player.can_add()):
		#Добавляем случайное оружия из массива с оружие
		player.add_equip_item(weapon_massiv[randi() % weapon_massiv.size()])
		#Обнуляем шанс на получение
		weapon_add_chance = 0
	else:
		#Если не получили, то увеличиваем шанс
		weapon_add_chance+=5
	#Увеличиваем счётчик усложнения
	difficult_tick += 1
	#Когда счётчик кратен 3, то
	if (difficult_tick % 3 == 0):
		#Добавляем ещё одного зомби
		spawn_count+=1
		#и меняем вероятности
		shield_chance += 4
		if (shield_chance > 20): #ограничиваем вероятность спавна в 20%
			shield_chance = 20
		fat_chance += 2
		if (fat_chance > 20): #ограничиваем вероятность спавна в 20%
			fat_chance = 20
		scary_chance += 2
		if (scary_chance > 20):#ограничиваем вероятность спавна в 20%
			scary_chance = 20
		zombie1_chance -= 4
		if (zombie1_chance < 20):#ограничиваем вероятность спавна в 20%
			zombie1_chance = 20
		smart_chance -= 4	
		if (smart_chance < 20):#ограничиваем вероятность спавна в 20%
			smart_chance = 20
#zombie1 и smart крайте просты, поэтому их вероятность уменьшаем, за счёт этого
#увеличиваем вероятность на появление других зомби
#сумма шанса призыва всех зомби должна быть равна 100.

Обработка сигнала от MobSpawnTimer

#Функция срабатывания таймера MobSpawnTimer
func _on_MobSpawnTimer_timeout():
	#Задаём цикл для призыва зомби
	for i in range(spawn_count):
		#Генерируем шанс, делаем остаток от деления на 101 - будут числа в радиусе от (0 до 100)
		var chance = randi() % 101
		if (chance <= zombie1_chance):
			spawn_point(EnemyNames.ZOMBIE1)
		elif (chance <= zombie1_chance + smart_chance):
			spawn_point(EnemyNames.SMARTZOMBIE)
		elif (chance <= zombie1_chance + smart_chance + shield_chance):
			spawn_point(EnemyNames.ZOMBIESHEILD)
		elif (chance <= zombie1_chance + smart_chance + shield_chance + scary_chance):
			spawn_point(EnemyNames.ZOMBIESCARY)
		elif (chance <= zombie1_chance + smart_chance + + shield_chance + scary_chance + fat_chance):
			spawn_point(EnemyNames.FATZOMBIE)
		#Если вдруг что-то пошло не так, то спавним Zombie1
		else:
			spawn_point(EnemyNames.ZOMBIE1)
		#рассмотрим генерацию на примере след. данных
		#zombie1_chance = 40
		#smart_chance = 40
		#shield_chance = 12
		#scary_chance = 4
		#fat_chance = 4
		#Если от 0 до 40, то Zombie1, если от 41 до 80, то Smart_zombie,
		#Если то 81 до 92, то Zombie_shield, если от 93 до 96, то Zombie_scary
		#Если от 97 до 100, то Fat_zombie
	_mob_spawn_timer.wait_time = spawn_time#задали время срабатывания
	_mob_spawn_timer.start()#включили
Полный код сцены игры
extends Node2D


#Сцена Врага
export (PackedScene) var zombie_enemy

#Элементы дерева
onready var _mob_spawn_timer = $MobSpawnTimer
onready var player = $Player
onready var _character_spawn_point = $CharacterSpawnPoint
onready var spawn = $Spawn
#Массив всего оружия
var weapon_massiv = [WeaponsName.BLASTER,WeaponsName.RIFLE, WeaponsName.BAZOOKA, WeaponsName.SHOTGUN]
#Сложность игры
var spawn_time = 5 #Время частоты спавна врагов
var zombie1_chance = 40#вероятность для обычного зомби
var smart_chance = 40#вероятность для умного зомби
var shield_chance = 12#вероятность для зомби с щитом
var scary_chance = 4#вероятность для страшного зомби
var fat_chance = 4#вероятность для толстого зомби
var spawn_count = 3#кол-во призываемых зомби
var difficult_tick = 0#кол-во раз, которое увеличилась сложность
var weapon_add_chance = 0#шанс добавления предметов

#Функция призыва точки спавна в качестве аргумента используется сцена с врагом
func spawn_point(enemy):
	var z = EnemyNames.SPAWNPOINT.instance()
	var rect_pos = spawn.rect_global_position
	var rect_size = spawn.rect_size
	#генерируем случайный вектор с местоположение зомби по следующему алгорится
	#для местоположению по х выбираем случайное значение из диапазона:
	#берём глобальное расположение квадрата оп х, как миннимум
	#и глобал местоположения по х + размер по х, как максимум
	#для y тоже самое, только вместо х-y
	z.position = Vector2(rand_range(rect_pos.x,rect_pos.x+rect_size.x),rand_range(rect_pos.y,rect_pos.y+rect_size.y))
	z.z_index = 100#Ставим z_index большим, чтобы точка спавна всегда распологалась поверх других объектов
	z.player = player#Задаём игрока
	z.zombie_enemy = enemy#Задаём врага
	get_parent().add_child(z)# добавляем точку спавна
	
#Функция инициализации
func _init():
	spawn_hero(Vector2(0,0))#Вызываем функцию создания героя
	
#Функция старта(срабатывает после _init)
func _ready():
	_mob_spawn_timer.start()#Включили таймер спавна
	randomize()# подключаем генератор случайных чисел
	player.position = _character_spawn_point.global_position
	#Передали игроку установленное в редакторе местоположение
	player._user_interface.init_health(player.health)# инициализируем наш UI
	player.connect("dead",self,"_on_Player_dead")#Привязываем сигнал о смерти игрока

#Функция срабатывания таймера MobSpawnTimer
func _on_MobSpawnTimer_timeout():
	#Задаём цикл для призыва зомби
	for i in range(spawn_count):
		#Генерируем шанс, делаем остаток от деления на 101 - будут числа в радиусе от (0 до 100)
		var chance = randi() % 101
		if (chance <= zombie1_chance):
			spawn_point(EnemyNames.ZOMBIE1)
		elif (chance <= zombie1_chance + smart_chance):
			spawn_point(EnemyNames.SMARTZOMBIE)
		elif (chance <= zombie1_chance + smart_chance + shield_chance):
			spawn_point(EnemyNames.ZOMBIESHEILD)
		elif (chance <= zombie1_chance + smart_chance + shield_chance + scary_chance):
			spawn_point(EnemyNames.ZOMBIESCARY)
		elif (chance <= zombie1_chance + smart_chance + + shield_chance + scary_chance + fat_chance):
			spawn_point(EnemyNames.FATZOMBIE)
		#Если вдруг что-то пошло не так, то спавним Zombie1
		else:
			spawn_point(EnemyNames.ZOMBIE1)
		#рассмотрим генерацию на примере след. данных
		#zombie1_chance = 40
		#smart_chance = 40
		#shield_chance = 12
		#scary_chance = 4
		#fat_chance = 4
		#Если от 0 до 40, то Zombie1, если от 41 до 80, то Smart_zombie,
		#Если то 81 до 92, то Zombie_shield, если от 93 до 96, то Zombie_scary
		#Если от 97 до 100, то Fat_zombie
	_mob_spawn_timer.wait_time = spawn_time#задали время срабатывания
	_mob_spawn_timer.start()#включили
	


#Добавляем сигнал, от нашего персонажа о смерти
func _on_Player_dead():
	get_tree().call_group("all_enemy", "queue_free")#Удаляем всех врагов со сцены
	SceneLoader.build_map_path("MainMenu")#Переходим в главное меню
	
#Функция создания героя
func spawn_hero(pos):
	var p
	if(SelectedCharacter.Character != null):#Если герой выбран
		p = SelectedCharacter.Character.instance()
	else:#Если вдруг каким-то образом не выбран
		p = CharacterNames.BRUTALHERO.instance()
	p.name = "Player"#Задаём имя, которое будет в дереве объектов
	p.position = pos
	add_child(p)
	player = p
	p.z_index = 2#Задаём z_index - 2, чтоыб герой ходил сверху крови
	

#Усложняем игру
func _on_Difficult_timeout():
	#генерируем шанс на получение оружия
	var weapon_chance = randi() % 100
	#Если чисто меньше, нашего шанса и можно добавить
	if (weapon_chance <= weapon_add_chance && player.can_add()):
		#Добавляем случайное оружия из массива с оружие
		player.add_equip_item(weapon_massiv[randi() % weapon_massiv.size()])
		#Обнуляем шанс на получение
		weapon_add_chance = 0
	else:
		#Если не получили, то увеличиваем шанс
		weapon_add_chance+=5
	#Увеличиваем счётчик усложнения
	difficult_tick += 1
	#Когда счётчик кратен 3, то
	if (difficult_tick % 3 == 0):
		#Добавляем ещё одного зомби
		spawn_count+=1
		#и меняем вероятности
		shield_chance += 4
		if (shield_chance > 20): #ограничиваем вероятность спавна в 20%
			shield_chance = 20
		fat_chance += 2
		if (fat_chance > 20): #ограничиваем вероятность спавна в 20%
			fat_chance = 20
		scary_chance += 2
		if (scary_chance > 20):#ограничиваем вероятность спавна в 20%
			scary_chance = 20
		zombie1_chance -= 4
		if (zombie1_chance < 20):#ограничиваем вероятность спавна в 20%
			zombie1_chance = 20
		smart_chance -= 4	
		if (smart_chance < 20):#ограничиваем вероятность спавна в 20%
			smart_chance = 20
#zombie1 и smart крайте просты, поэтому их вероятность уменьшаем, за счёт этого
#увеличиваем вероятность на появление других зомби
#сумма шанса призыва всех зомби должна быть равна 100.

Сохранение лучшего счёта

Под конец статьи добавим сохранение лучшего счёта. Будем сохранять в файл Save.Save, в директорию пользователя (C:\Users\имя_пользователя\AppData\Roaming\Godot\app_userdata\Название проекта). Предварительно создавать там файл не нужно.

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

#Функция передачи счёта
func get_score():
	return int(_score.text)

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

#Переменные с лучшими счетами
var brutalhero_score
var cowboy_score
var soldier_score
var robot_score

#Получаем лучший счёт
func set_score():
	var file = File.new()#новый файл
	file.open("user://Save.Save", File.READ)#Открыли
	var content = file.get_as_text()#Записали контент файла
	var dir = parse_json(content)#Перезаписали контент, как "Словарь"
	#Если словать не null, то записываем значения из него 
	if(dir!=null):
		brutalhero_score = dir["BrutalHero"]
		cowboy_score = dir["Cowboy"]
		soldier_score = dir["Soldier"]
		robot_score = dir["Robot"]
	#Иначе нолики
	else:
		brutalhero_score = 0
		cowboy_score = 0
		soldier_score = 0
		robot_score = 0

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

Функция сохранения:

#функция сохранения в качестве аргумента берёт текущий счёт
func save_record(score):
	#Объявили нвоый файл
	var save_file = File.new()
	#Создали новый "словарь" записали в него лучший показатели на текущий момент
	var save_dict ={
		"BrutalHero": CharacterNames.brutalhero_score,
		"Cowboy":CharacterNames.cowboy_score,
		"Robot":CharacterNames.robot_score,
		"Soldier":CharacterNames.soldier_score,
	}
	#Если данный герой и текущий счёт больше лучшего, то записываем другой
	if(SelectedCharacter.Character == CharacterNames.BRUTALHERO && score > save_dict["BrutalHero"]):
		save_dict["BrutalHero"] = score
	if(SelectedCharacter.Character == CharacterNames.COWBOY && score > save_dict["Cowboy"]):
		save_dict["Cowboy"] = score
	if(SelectedCharacter.Character == CharacterNames.ROBOT && score > save_dict["Robot"]):
		save_dict["Robot"] = score
	if(SelectedCharacter.Character == CharacterNames.SOLDIER && score > save_dict["Soldier"]):
		save_dict["Soldier"] = score
	#Открываем фаил с сохранением
	save_file.open("user://save.save", File.WRITE)
	#Сохраняем
	save_file.store_line(to_json(save_dict))

Обработка сигнала о смерти игрока:

#Добавляем сигнал, от нашего персонажа о смерти
func _on_Player_dead():
	get_tree().call_group("all_enemy", "queue_free")#Удаляем всех врагов со сцены
	save_record(player._user_interface.get_score())#записали результаты
	SceneLoader.build_map_path("MainMenu")#Переходим в главное меню

Осталось немного доработать сцену с выбором персонажа. в функции _ready() будем вызывать метод set_score, синглтона CharacterNames. и при нажатии на кнопку персонажа в надпись будем дополнительно выводить рекорд.

Функция _ready():

func _ready():
	CharacterNames.set_score()#Взяли записи из файла сохранения

Один из обработчиков нажатия кнопки персонажа:

#Если выбран BrutalHero
func _on_BrutalHeroBtn_pressed():
	_animated_sprite.play("Dance1")#Включаем его анимацию
	_start_game_btn.disabled = false#Включаем кнопку старта игры
	_character_param.text = "Character \nBrutalHero \nDamage: 1 \nHP: 5 \nSpeed: 200 \nEquip: Blaster \nMax score: " + String(CharacterNames.brutalhero_score)#Записываем текст
	SelectedCharacter.set_character(CharacterNames.BRUTALHERO)#В синглтон записываем BrutalHero
Полный код меню выбора персонажа


На этом эта статья подошла к концу. В принципе на этом можно считать игру законченной в неё уже можно играть и пытаться ставить всё большие и большие рекорды. Ещё точно выйдет одна статья по этому проекту, в которой мы добавим: локальный мультиплеер, возможность создания различных построек, механику поднятия уровня персонажа с чем-то вроде дерева талантов, вы выбрали именно эти 3 пункта в опросе под предыдущей статьей(все набрали по 7, получается 777, на удачу так сказать).

Комментарии (0)