Как стать автором
Поиск
Написать публикацию
Обновить

Продолжаем создавать свою первую игру на Godot 3.5 (часть 4) Конец близок…

Уровень сложностиПростой
Время на прочтение40 мин
Количество просмотров4.8K

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

Дерево талантов

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

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

Ветвь дерева

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

  • Sprite

  • Node2D(DescriptionNode)

  • ColorRect(Description), дочерний у DescriptionNode

  • Label(DescriptionLbl), дочерний у Description

  • TextureButton(DescriptionAccept), дочерний у Description

  • TextureButton(DescriptionCancel), дочерний у Description

У TreeBranch добавляем Texture, и ставим Disabled - true. В Sprite заливаем иконку по умолчанию. У DescriptionNode, выставляем Z Index = 10. В Description, устанавливаем текстурку так-же нужно поставить точку вращения объекта в центр текстуры, для этого выбираем и нажимаем в примерный центр текстуры. В DescriptionLbl добавляем шрифт. В DescriptionAccept и DescriptionCancel, добавляем текстурки. Пока на этом всё, добавляем скрипт на корень и переходим к его редактированию. После всех настроек ставим у DescriptionLbl Visible = false.

Как ветвь дерева выглядит у меня:

DescriptionLbl Visible - false
DescriptionLbl Visible - false
DescriptionLbl Visible - true
DescriptionLbl Visible - true

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

#Элементы дерева
onready var _description = $DescriptionNode
onready var _description_Lbl = $DescriptionNode/Description/DescriptionLbl
#Описание ветви
export(String, MULTILINE) var description_text
#Предыдущая ветвь
export (NodePath) var previous_branch
#Какой параметр улучшаем
export(int, "Atack", "Speed", "Health", "WeaponUp") var param_up
#на сколько % улучшаем
export var param_scale:float
#какое оружие делаем "Супер", если выбран WeaponUp
export (int, "Blaster", "Shotgun", "Rifle", "Bazooka") var param_weapon_name
#массив ветвей на этом слое
export (Array, NodePath) var this_layer_branch
#картинка, которая заливается в img
export (Texture) var img
#сигнал о прокачке ветви
signal branch_pick
#сигнал о открытии описание этой ветви
signal close_description
#сигнал улучшения навыка(урон, скорость, хп)
signal skill_up(param_name,value)

Теперь напишем функцию _ready(), в ней буду заливаться выбранная нами картинка таланта, и привязываться сигналы, от элементов, которые мы записали в export - переменные

func _ready():
	#Установили режим паузы для дерева
	pause_mode = Node.PAUSE_MODE_PROCESS
	#Залили картинку
	$Sprite.texture = img
	#Развернули картинку
	$Sprite.rotation_degrees -= rect_rotation
	#Развернули описание
	$DescriptionNode/Description.rect_rotation -=rect_rotation
	#Объявили предыдущую ветвь
	var branch = get_node(previous_branch)
	#Если не Null, то добавили сигнал
	if (branch != null):
		branch.connect("branch_pick",self,"_on_branch_pick")
	else:
	#Если у дерева нет предыдущих ветвей, то это корень дерева
		disabled = false
	#Пробегаемся по массиву ветвей на этом слое
	for i in range(this_layer_branch.size()):
		#Обхявили ветвь
		var layer_branch = get_node(this_layer_branch[i])
		#Если не null
		if (layer_branch != null):
			#То привязали 2 сигнала
			layer_branch.connect("branch_pick",self,"_on_branch_pick_this_layer")
			layer_branch.connect("close_description",self,"_on_close_description_this_layer")

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

#Функция обработки выбора ветви
func skill_up():
	match param_up:
		0,1,2:
			#Если это атака, хп или скорость
			emit_signal("skill_up",param_up,param_scale)
		3:
			#Если прокачали оружие, то вызвали функцию синглтона
			WeaponsName.weapon_level_up(param_weapon_name)

Ещё нам нужно дописать в Синглтон WeaponsName, объявление переменных, которые становятся true, если оружие стало супер и функцию, которая определяет, какое оружие сделать супер и функцию очистку, которая делает все переменные false:

var blaster_up = false
var shotgun_up = false
var rifle_up = false
var bazooka_up = false

func weapon_level_up(weapon):
	match weapon:
		0:
			blaster_up = true
		1:
			shotgun_up = true
		2:
			rifle_up = true
		3:
			bazooka_up = true

func clear_all():
	blaster_up = false
	shotgun_up = false
	rifle_up = false
	bazooka_up = false

Теперь добавим в код сигналы от TreeBranch, DescriptionAccept, DescriptionCancel, и напишем функции обработки сигналов, которые привязали в _ready()

#Обработка сигнала предыдущей ветви,
#тоесть если выбрали предыдущую ветвь,
#То снять disabled
func _on_branch_pick():
	disabled = false
	
#Обработка сигналов, ветви с этого слоя,
#если выбрали с этого слоя,
#то блокируем эту ветвь	
func _on_branch_pick_this_layer():
	disabled = true
	
#Обрабатываем сигнал от других ветвей этого слоя, 
#тоесть если открыли другое описание, то закрыли это
func _on_close_description_this_layer():
	_description_Lbl.text = ""
	_description.visible = false
	
#Прикрепляем сигнал от TreeBranch и обрабатываем
func _on_TreeBranch_pressed():
	#Если у родительской сцены, переменная 
	#level_up_count > 0, показываем описание
	if(get_parent().level_up_count > 0):
		emit_signal("close_description")
		_description_Lbl.text = description_text
		_description.visible = true

#Прикрепляем сигнал от DescriptionCancel и обрабатываем
func _on_DescriptionCancel_pressed():
	_description_Lbl.text = ""
	_description.visible = false

#Прикрепляем сигнал от DescriptionAccept и обрабатываем
func _on_DescriptionAccept_pressed():
	#у меня в текстурке texture_pressed хранится текстура, 
	#как выглядила бы ветвь, после выбора
	texture_disabled = texture_pressed
	_description_Lbl.text = ""
	_description.visible = false
	#Отправили сигнал
	emit_signal("branch_pick")
	#Вызвали функцию
	skill_up()
	#Уменьшили родительский счётчик
	get_parent().level_up_count -=1
	disabled = true

Код прокомментирован подробно, и теперь у нас есть универсальная сцена ветви дерева, которую мы будем добавлять на сцену дерева, настраивать все export переменные и она будет работать.

Сцена дерева талантов

Главным узлом сцены выбираем Node2d, добавляем sprite и дальше собираем наше дерево. Все его ветви можно спокойно вращать.

Примерный вид сцены
Примерный вид сцены
вид в игре
вид в игре

Как расположить элементы в дереве объектов и назвать. Я называл так: Layer№Branch№.

Примеры настроек ветвей:

Эта ветвь будет увеличивать параметр скорости на 10%, является корнем дерева и больше других ветвей нет.

Эта ветвь будет улучшать оружие(винтовку), Так-же на этом-же слое есть 4 ветви и указана предыдущая.

С настройками сцены разобрались, добавляем скрипт и переходим к редактированию:

extends Node2D
#Переменная игрока, будем передавать со сцены игры
var player
#Переменная хранящая количество прокачек
var level_up_count = 0
#Сигнал о прокачке
signal branch_skill_up(param, scale)


func _ready():
	init_tree()
	
#Считываем нажатия, если нажата кнопак меню, то ставим игру на паузу,
#и показываем, выставляем масштаб, и местоположение
func _process(delta):
	if (Input.is_action_just_pressed("menu")):
		get_tree().paused = !get_tree().paused
		visible = !visible
		scale = Vector2(1,1)
		position = player.position
#Привязываем сигнал от всех потомком и прокачке
func init_tree():
	for i in get_child_count():
		get_child(i).connect("skill_up",self,"_on_branch_skill_up_")

#Обрабатываем этот сигнал(отправляя свой) 		
func _on_branch_skill_up_(param, scale):
	emit_signal("branch_skill_up",param,scale)

Так-же не забываем в настройках добавить кнопку "menu", у меня это пробел.

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

Теперь нужно добавить, обработку сигналов о повышении характеристик и изменить оружие.

Оружие

Начнём с доработки оружия. Бластер будет выстреливать по 2 пули, дробовик выстреливать 11 пулями, у винтовки перезарядка уменьшится на 50%, радиус взрыва у базуки увеличится на 50%. Значит нам надо будет отредактировать следующие скрипты:

  • Скрипт бластера

  • Скрипт дробовика

  • Скрипт винтовки

  • Скрипт ракеты для базуки

Скрипт бластера

В дерево объектов нужно добавить Timer(SecondFire), выставить OneShot = true, Wait Timer = 0,1

#Добавили таймер
onready var _second_fire = $SecondFire

func fire():
	if (_fire_couldown_timer.is_stopped()): # не на перезарядке
		#Если оружие Суперское, то запускаем таймер
		if (WeaponsName.blaster_up):
			_second_fire.start()
		spawn_bullet(0) # создаём пулю с 0-м дополнительным поворотом
		_fire_couldown_timer.start()
		_weapon_sound.play()

#Таймер сработал, стреляем
func _on_SecondFire_timeout():
	spawn_bullet(0)

Скрипт дробовика

func fire():
	if (_fire_couldown_timer.is_stopped()):
		if (WeaponsName.shotgun_up):
			spawn_bullet(5*PI/12)# Поворачиваем пулю на ~37,5 градусов
			spawn_bullet(4*PI/12)# Поворачиваем пулю на ~ 30 градусов
			spawn_bullet(3*PI/12)# Поворачиваем пулю на ~ 22,5 градусов
			spawn_bullet(2*PI/12)# Поворачиваем пулю на ~ 15 градусов
			spawn_bullet(PI/12)# Поворачиваем пулю на ~ 7,5 градусов
			spawn_bullet(0)# выпускаем пулю прямо
			spawn_bullet(-PI/12)# Поворачиваем пулю на ~ -7,5 градусов
			spawn_bullet(-2*PI/12)# Поворачиваем пулю на ~ -15 градусов
			spawn_bullet(-3*PI/12)# Поворачиваем пулю на ~ -22,5 градусов
			spawn_bullet(-4*PI/12)# Поворачиваем пулю на ~ -30 градусов
			spawn_bullet(-5*PI/12)# Поворачиваем пулю на ~ -37,5 градусов
		else:
			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()# включаем перезарядку	
		_weapon_sound.play()

Скрипт винтовки

func fire():
	if(_fire_couldown_timer.is_stopped()):
		spawn_bullet(0)
		_fire_couldown_timer.start()
		if (WeaponsName.rifle_up):
			fire_rate *= 0.5 #Если оружие улучшено, умножаем на 0.5
		_fire_couldown_timer.wait_time = fire_rate
		_weapon_sound.play()

Скрипт ракеты для базуки

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 # включаем фигуру столкновения взрыва
		if(WeaponsName.bazooka_up):#если оружие супер
			scale = Vector2(15,15)
		else:
			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()

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

Болванка персонаж

Для начала нам нужно объявить переменную, в которую будет передавать сцену дерева объектов.

export (NodePath) var skill_tree# дерево навыков

в функции _ready(), нужно привязать сигнал от нашего дерева.

#если есть дерево, то привязываем его сигнал
	var tree = get_node(skill_tree)
	if (tree != null):
		tree.connect("branch_skill_up",self,"_on_SkillTree_branch_skill_up")

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

#обработка сигнала от дерева
func _on_SkillTree_branch_skill_up(param, scale):
	#выбираем параметр
	match param:
		0:#урон
			damage_scale += damage_scale * scale/100# увеличиваем урон
		1:#скорость
			speed += speed * scale/100# увеличиваем скорость
		2:#хп
			health += round(health * scale/100)# увеличивам хп
			_user_interface.init_health(health)#переинициализируем ui
		_:
			pass
Полный скрипт болванки персонажа
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#Множитель урона
export (NodePath) var skill_tree# дерево навыков
#Объявляем переменные только для этого скрипта
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()
	#если есть дерево, то привязываем его сигнал
	var tree = get_node(skill_tree)
	if (tree != null):
		tree.connect("branch_skill_up",self,"_on_SkillTree_branch_skill_up")


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
#обработка сигнала от дерева
func _on_SkillTree_branch_skill_up(param, scale):
	#выбираем параметр
	match param:
		0:#урон
			damage_scale += damage_scale * scale/100# увеличиваем урон
		1:#скорость
			speed += speed * scale/100# увеличиваем скорость
		2:#хп
			health += round(health * scale/100)# увеличивам хп
			_user_interface.init_health(health)#переинициализируем ui
		_:
			pass

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

Сцена игры

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

Для начала объявим переменную дерева в коде.

onready var _skill_tree = $SkillTree

В функции _ready(), добавим передачу нашей переменой игрока в дерево талантов

_skill_tree.player = player#привязываем игрока к дереву

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

func clear_level():
	WeaponsName.clear_all()#убираем улучшения с оружия
	get_tree().call_group("all_enemy", "queue_free")#Удаляем всех врагов со сцены

#Добавляем сигнал, от нашего персонажа о смерти
func _on_Player_dead():
	save_record(player._user_interface.get_score())#записали результаты
	clear_level()
	SceneLoader.build_map_path("MainMenu")#Переходим в главное меню
Полный скрипт сцены игры
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
onready var _skill_tree = $SkillTree
#Массив всего оружия
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()#Включили таймер спавна
	_skill_tree.player = player#привязываем игрока к дереву
	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():
	save_record(player._user_interface.get_score())#записали результаты
	clear_level()
	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)
	p.skill_tree="../SkillTree"
	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.

#функция сохранения в качестве аргумента берёт текущий счёт
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 clear_level():
	WeaponsName.clear_all()#убираем улучшения с оружия
	get_tree().call_group("all_enemy", "queue_free")#Удаляем всех врагов со сцены

Уровень персонажа

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

Сцена очка опыта

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

⦁ Sprite
⦁ CollisionShape

Заливаем спрайт, подгоняем CollisionShape. В настройках добавляем название для нового слоя столкновений( у меня это 7, назвал Pick_up), настройки для CollisionObject2D:

столкновения для очка опыта
столкновения для очка опыта

Добавляем в группу all_enemy, чтобы удалялось после окончания игры.

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

extends StaticBody2D

#объявляем переменную, кол-во даваемого опыта
export var exp_param = 1

#функция, которая сработает, когда игрок подберёт опыт
func pick_up_exp():
	queue_free()

#функция, которая вернут кол-во даваемого опыта
func get_exp_param():
	return exp_param

Сцена врага

Сам опыт создали, теперь нужно научить зомби, оставлять после смерти этот опыт. Для этого:

Добавляем в наш синглтон EnemyNames, константу с сценой опыта

#очко опыта
const EXPPOINT = preload("res://scenes/Game/ExpPoint/ExpPoint.tscn")

В скрипте болванке для врагов, объявляем переменную с сценой, переименовываем функцию спавна крови spawn_blood, в функцию dead(), и расширяем её, потом вызываем функцию dead() в обработчике получения урона, если хп кончились:

var exp_scene = EnemyNames.EXPPOINT

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

	get_parent().add_child(b)#добавили кровь
	
	var e = exp_scene.instance()#объявии сцену с опытом
	
	e.position = position#задали местоположение
	e.rotation_degrees = randi() % 361#сгенерировали случайный угол поворота
	e.z_index = 50#увеличиваем z-index
	
	get_parent().add_child(e)#добавили опыт
	
	queue_free()#удалили зомби

#Функция получения урона
func hit(damage):
	health -= damage
	_red_health.rect_size.x -= health_size * damage
	if (health <= 0): #Если <= 0, то удалился
		dead()
Полный скрипт болванки врага
extends KinematicBody2D

#подгрузили сцену с кровью и опытом
var blood_scene = EnemyNames.ZOMBIEBLOOD
var exp_scene = EnemyNames.EXPPOINT
#Добавляем элементы дерева объектов в код
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, то удалился
		dead()

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 dead():
	var b = blood_scene.instance()
		
	b.position = position#задали местоположение
	b.rotation_degrees = randi() % 361#сгенерировали случайный угол поворота

	get_parent().add_child(b)#добавили кровь
	
	var e = exp_scene.instance()#объявии сцену с опытом
	
	e.position = position#задали местоположение
	e.rotation_degrees = randi() % 361#сгенерировали случайный угол поворота
	e.z_index = 50#увеличиваем z-index
	
	get_parent().add_child(e)#добавили опыт
	
	queue_free()#удалили зомби

func get_health():
	return health

Сцена пользовательского интерфейса

На сцену нужно добавить ещё одну полоску, которая будет отображать опыт, как мы это делали во второй статье

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

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

#Элементы дерева
onready var _score_add_timer = $Score/ScoreAddTimer
onready var _score = $Score
onready var _current_health_bar = $CurrentHealthBar

var max_exp #Максимальное кол-во опыта
var current_exp #Текущее кол-во опыта
var exp_bar_size #Размер на который нужно уменьшать жёлтый квадрат

# Функция задания всех переменных опыта
func init_exp(add_exp):
	max_exp = add_exp 
	current_exp = 0 
	_exp.text = String(current_exp) + "/" + String(max_exp)#записываем в лэйбэл наш опыт
	exp_bar_size = _current_exp_bar.rect_size.x / max_exp# определяем длинну деления
	_current_exp_bar.rect_size.x = 0 #обнуляем полоску опыта до 0

# Функция вызываемая при получении опыта
func take_exp(add_exp):
	current_exp += add_exp #добавили опыт
	_exp.text = String(current_exp) + "/" + String(max_exp)#записываем в лэйбэл наш опыт
	_current_exp_bar.rect_size.x += exp_bar_size * add_exp#увеличиваем длину квадрата
Полный скрипт пользовательского интерфейса
extends Node2D

#Элементы дерева
onready var _score_add_timer = $Score/ScoreAddTimer
onready var _score = $Score
onready var _current_health_bar = $CurrentHealthBar
onready var _health = $CurrentHealthBar/Health
onready var _current_exp_bar = $CurrentExpBar
onready var _exp = $CurrentExpBar/Exp


var max_health #Максимальное кол-во хп
var current_health #Текущее кол-во хп
var health_bar_size #Размер на который нужно уменьшать зелёный квадрат

var max_exp #Максимальное кол-во опыта
var current_exp #Текущее кол-во опыта
var exp_bar_size #Размер на который нужно уменьшать жёлтый квадрат

# Функция задания всех переменных опыта
func init_exp(add_exp):
	max_exp = add_exp 
	current_exp = 0 
	_exp.text = String(current_exp) + "/" + String(max_exp)#записываем в лэйбэл наш опыт
	exp_bar_size = _current_exp_bar.rect_size.x / max_exp# определяем длинну деления
	_current_exp_bar.rect_size.x = 0 #обнуляем полоску опыта до 0

# Функция задания всех переменных хп
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 take_exp(add_exp):
	current_exp += add_exp #добавили опыт
	_exp.text = String(current_exp) + "/" + String(max_exp)#записываем в лэйбэл наш опыт
	_current_exp_bar.rect_size.x += exp_bar_size * add_exp#увеличиваем длину квадрата

# Сигнал от таймера
func _on_ScoreAddTimer_timeout():
	_score.text = String(int(_score.text) + 1)

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

Сцена персонажа

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

export var max_exp = 20#сколько опыта надо для повышения уровня
export var scale_exp = 2# во сколько раз увеличится требуемы опыт после повышения
var current_exp = 0 #Переменная хранящая кол-во опыта

#Сигна о повышении уроня
signal lvl_up

func _ready():
	_animated_sprite.animation = "Stand" # При старте персонаж должен просто стоять
	equip_all()
	_user_interface.init_exp(max_exp)# инициализая опыта, для интерфейса
	_user_interface.init_health(health)# инициализация опыта, для интерфейса
	#если есть дерево, то привязываем его сигнал
	var tree = get_node(skill_tree)
	if (tree != null):
		tree.connect("branch_skill_up",self,"_on_SkillTree_branch_skill_up")

func _physics_process(delta):
	get_input()
	get_anim()
	var collision= move_and_collide(direction * delta) 
	#записываем в переменную collision для дальнейшей обработки столкновения
	if collision:#если столкновение
		if collision.collider.has_method("pick_up_exp"):#И есть метод take_damage
				collision.collider.pick_up_exp()#поднимаем опыт
				current_exp += collision.collider.get_exp_param()#увеличиваем значение
				level_up()#вызываем функцию повышение опыта
				_user_interface.take_exp(collision.collider.get_exp_param())#увеличиваем опыт

#функция поднятия опыта
func level_up():
	if (current_exp == max_exp):#Если достигли опыта для лвлапа
		emit_signal("lvl_up")#отправляем сигнал
		max_exp *= scale_exp#увеличиваем требуемый опыт для след уровня
		current_exp = 0#обнуляем текущий опыт
		_user_interface.init_exp()#обновляем интерфейс
Полный скрипт болванки персонажа
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#Множитель урона
export var max_exp = 20#сколько опыта надо для повышения уровня
export var scale_exp = 2# во сколько раз увеличится требуемы опыт после повышения
export (NodePath) var skill_tree# дерево навыков
#Объявляем переменные только для этого скрипта
var velocity = Vector2.ZERO #Вектор направления
var direction = Vector2.ZERO #Вектор движения
var backpack_items = [null,null,null,null,null,null]#рюкзак
var weapon_count = 0 #Переменная хранящая кол-во оружия
var current_exp = 0 #Переменная хранящая кол-во опыта
#Сигнал о получении урона
signal take_damage(damage)
#Сигнал о смерти
signal dead
#Сигна о повышении уроня
signal lvl_up
#Функция считывания нажатий
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()
	_user_interface.init_exp(max_exp)# инициализая опыта, для интерфейса
	_user_interface.init_health(health)# инициализация опыта, для интерфейса
	#если есть дерево, то привязываем его сигнал
	var tree = get_node(skill_tree)
	if (tree != null):
		tree.connect("branch_skill_up",self,"_on_SkillTree_branch_skill_up")


func _physics_process(delta):
	get_input()
	get_anim()
	var collision= move_and_collide(direction * delta) 
	#записываем в переменную collision для дальнейшей обработки столкновения
	if collision:#если столкновение
		if collision.collider.has_method("pick_up_exp"):#И есть метод take_damage
				collision.collider.pick_up_exp()#поднимаем опыт
				current_exp += collision.collider.get_exp_param()#увеличиваем значение
				level_up()#вызываем функцию повышение опыта
				_user_interface.take_exp(collision.collider.get_exp_param())#увеличиваем опыт
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
#обработка сигнала от дерева
func _on_SkillTree_branch_skill_up(param, scale):
	#выбираем параметр
	match param:
		0:#урон
			damage_scale += damage_scale * scale/100# увеличиваем урон
		1:#скорость
			speed += speed * scale/100# увеличиваем скорость
		2:#хп
			health += round(health * scale/100)# увеличивам хп
			_user_interface.init_health(health)#переинициализируем ui
		_:
			pass

#функция поднятия опыта
func level_up():
	if (current_exp == max_exp):#Если достигли опыта для лвлапа
		emit_signal("lvl_up")#отправляем сигнал
		max_exp *= scale_exp#увеличиваем требуемый опыт для след уровня
		current_exp = 0#обнуляем текущий опыт
		_user_interface.init_exp()#обновляем интерфейс

Добавление построек

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

Болванка для всех построек

Создаём новую сцену, главным узлом выбираем KinematicBody2D(DefaultBuilding),дочерние к нему:

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

Болванка защитной постройки

Выбираем главным узлом сцены Болванку для постройки и добавляем ещё следующие элементы:

Выстраиваем 2 ColorRect, которые будут полоской жизни, так-же, как делали это для болванки врага. Навешиваем скрипт и переходим к его редактированию:

extends KinematicBody2D

#Объявляем переменные дерева
onready var _health_bar = $HealthBar
onready var _red_health_bar = $HealthBar/RedHealthBar
onready var _immortal_timer = $ImmortalTimer

#Объявляем переменную хп
export var health = 5

#Объявляем переменную длины красной полоски
var health_bar_size

#Присваиваем размер красной полоски
func _ready():
	health_bar_size = round(_red_health_bar.rect_size.x / health)

#Функция получения урона	
func take_damage(dmg):
	#Если можно ударить
	if(_immortal_timer.is_stopped()):
		health -= dmg#наносим урон
		_immortal_timer.start()#запустили таймер
		#Уменьшаем красную полоску
		_red_health_bar.rect_size.x -= health_bar_size * dmg
		#Если хп кончились, то вызываем функцию смерти
		if(health <= 0):
			dead()
#Функция смерти
func dead():
	#Будет переопределять для каждой постройки
	pass

Теперь нужно задать слои столкновений, в настройках именуем новый слой столкновений( у меня 8, назвал Protect_building) присваиваем защитной постройке этот уровень и в маске указываем, что сталкивается только с врагами

Обычная баррикада

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

extends "res://scenes/Buildings/ProtectBuilding/DefaultProtectBuilding.gd"

#Функция смерти
func dead():
	#Это обычная барикада, она просто будет удаляться
	queue_free()

У меня стоит 10 жизней

Взрывающаяся баррикада

Главным узлом сцены выбираем болванку защитной постройки. Добавляем следующие элементы:

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

extends "res://scenes/Buildings/ProtectBuilding/DefaultProtectBuilding.gd"

onready var _animated_sprite = $AnimatedSprite
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 #Звук взрыва

export var damage = 3#Урон взрыва

#Переопределяем функцию смерти
func dead():
	_animated_sprite.play("Bum")# превращаем в взрыв
	_collision_shape.disabled = true # выключаем обычную фигуру столкновения
	_collision_shape_bum.disabled = false # включаем фигуру столкновения взрыва
	_bum_live_time.start()#Вклюаем таймер
	_bum_sound.play()#проигрываем звук
		
#Если кто-то в взрыве
func _on_Bum_body_entered(body):
	#и есть метод hit
	if(body.has_method("hit")):
		#наносим урон
		body.hit(damage)
		
#таймер кончился, удаляем взрыв
func _on_BumLiveTime_timeout():
	queue_free()

У меня стоит 5 жизней и урон 2

Болванка атакующей постройки

Выбираем главным узлом сцены, болванку постройки и добавляем в дерево объектов Timer(CouldownTimer):

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

extends KinematicBody2D
#объявляем элементы дерева
onready var _couldown_timer = $CouldownTimer
#задаём переменные
export (PackedScene) var bullet_scene#пуля
export var damage = 1#урон
export var fire_rate = 2#скорость атаки
#присвоили скорость атаки таймеру
func _ready():
	_couldown_timer.wait_time = fire_rate
#когда таймер сработал, стреляем
func _on_CouldownTimer_timeout():
	fire()
#функцию выстрела будет переопределять
func fire():
	pass
#функция создания пули
func spawn_bullet(rot):
# передаём параметр дополнительного поворота пули, позже пригодится 
	var b = bullet_scene.instance()
	#задали местоположение и поворот
	var new_position = position
	var direction = rotation - rot
	get_parent().add_child(b)# добавляем пулю, как потомка оружия
	b.scale = Vector2(0.5,0.5)
	b.start(new_position,direction)
	b.damage = damage# задаём пуле урон

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

Турель стреляющая во круг

Главным узлом сцены выбираем болванку атакующей постройки. Расширяем скрипт и переходим к редактированию:

extends "res://scenes/Buildings/AtackBuilding/DefaultAtackBuilding.gd"


func fire():
	#создаём пули с поворотами
	spawn_bullet(0)
	spawn_bullet(PI/4)
	spawn_bullet(2*PI/4)
	spawn_bullet(3*PI/4)
	spawn_bullet(4*PI/4)
	spawn_bullet(5*PI/4)
	spawn_bullet(6*PI/4)
	spawn_bullet(7*PI/4)
	#запускаем перезарядку
	_couldown_timer.start()

У меня стоит 1 урона ,перезарядка 2, сцена пули дробовика

Турель стреляющая ракетами

Главным узлом сцены выбираем болванку атакующей постройки. Турель будет стрелять ракетами в случайном направлении. Расширяем скрипт и переходим к редактированию:

func fire():
	spawn_bullet(randi() % 361)
	_couldown_timer.start()

Главное в сцену пули, залейте сцену снаряда базуки. У меня стоит 2 урона и перезарядка 5.

Хорошо, какие-то постройки создали, теперь нужно создать для них меню.

Сцена болванки кнопки постройки

Создаём новую сцены. Главным узлом сцены выбираем TextureButton(BuildingsButton), дочерние элементы:

В BuildingsButton заливаем спрайт, в label будет писаться цена постройки, в спрайте рисоваться картинка постройки. Навешиваем на корень скрипт и переходим к его редактированию:

extends TextureButton

#объявляем переменные
#Картинка отображаемая в Sprite
export (Texture) var img
#Какую постройку нужно построить при покупке
export (PackedScene) var building_scene
#Цена
export var building_price: int
#Сигнал о покупке
signal building_buy(building_scene, building_price)

# В функции _ready() отрисовываем иконку кнопки, текст стоимости 
#и присоединяем сигнал нажатия кнопки
func _ready():
	$Sprite.texture = img
	$Label.text = String(building_price) + " Exp"
	connect("pressed",self,"_on_BuildingsButton_pressed")

func _on_BuildingsButton_pressed():
	emit_signal("building_buy",building_scene,building_price)

Меню выбора постройки

Главным узлом сцены выбираем Node2D(BuildingMenu), дочерними выбираем Sprite и добавляем наши созданные ранее кнопки, не забывая их настроить.

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

extends Node2D
#Сигнал о постройке
signal building_stand(building,building_price)
#Переменная можно-ли показать меню
var can_show = true
#Переменная игрока
var player

#присоединяем кнопки
func _ready():
	connect_button()
	
#функция присоединения сигнала кнопок
func connect_button():
	#Пробегаемся по все элементам дерева
	for i in range(get_child_count()):
		#Если есть этот метод
		if (get_child(i).has_method("_on_BuildingsButton_pressed")):
			#То присоединяем от этого элемента сигнал
			var btn = get_child(i).connect("building_buy",self,"_on_building_buy")
#Обработка сигнала
func _on_building_buy(scene,price):
	emit_signal("building_stand",scene,price)#Отправляем сигнал дальше

#Считываем нажатие на открытие меню
func _process(delta):
	#Если нажата кнопка, у меня это Z
	if(Input.is_action_just_pressed("Open_build_menu")):
		#Если нужно показать
		if(can_show):
			show()#Показываем
			can_show = false#меняем переменную
			position = player.position#Выствляем местоположение
			position.x += 225
			get_tree().paused = true#включаем паузу
		else:#Иначе
			hide()#Прячем
			can_show = true#меняем переменную
			get_tree().paused = false#выключаем паузу

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

Пример как это выглядит:

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

В пользовательском интерфейсе, нужно добавить функцию уменьшающую опыт:

# функция списывания опыта
func remove_exp(minus_exp):
	current_exp -= minus_exp#Вычли опыт
	_current_exp_bar.rect_size.x -= exp_bar_size * minus_exp#уменьшили длину квадрата
	_exp.text = String(current_exp) + "/" + String(max_exp)#записываем в лэйбэл наш опыт
Полный скрипт пользовательского интерфейса
extends Node2D

#Элементы дерева
onready var _score_add_timer = $Score/ScoreAddTimer
onready var _score = $Score
onready var _current_health_bar = $CurrentHealthBar
onready var _health = $CurrentHealthBar/Health
onready var _current_exp_bar = $CurrentExpBar
onready var _exp = $CurrentExpBar/Exp
onready var _buildings_menu = $BuildingsMenu

var max_health #Максимальное кол-во хп
var current_health #Текущее кол-во хп
var health_bar_size #Размер на который нужно уменьшать зелёный квадрат

var max_exp #Максимальное кол-во опыта
var current_exp #Текущее кол-во опыта
var exp_bar_size #Размер на который нужно уменьшать жёлтый квадрат

# Функция задания всех переменных опыта
func init_exp(add_exp):
	max_exp = add_exp 
	current_exp = 0 
	_exp.text = String(current_exp) + "/" + String(max_exp)#записываем в лэйбэл наш опыт
	exp_bar_size = _current_exp_bar.rect_size.x / max_exp# определяем длинну деления
	_current_exp_bar.rect_size.x = 0 #обнуляем полоску опыта до 0

# Функция задания всех переменных хп
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 take_exp(add_exp):
	current_exp += add_exp #добавили опыт
	_exp.text = String(current_exp) + "/" + String(max_exp)#записываем в лэйбэл наш опыт
	_current_exp_bar.rect_size.x += exp_bar_size * add_exp#увеличиваем длину квадрата
	
# функция списывания опыта
func remove_exp(minus_exp):
	current_exp -= minus_exp#Вычли опыт
	_current_exp_bar.rect_size.x -= exp_bar_size * minus_exp#уменьшили длину квадрата
	_exp.text = String(current_exp) + "/" + String(max_exp)#записываем в лэйбэл наш опыт

# Сигнал от таймера
func _on_ScoreAddTimer_timeout():
	_score.text = String(int(_score.text) + 1)

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

Болванка персонажа

К персонажу нам нужно добавить подключение сигнала от меню и его обработку.

Добавляем переменную нашего меню:

export (NodePath) var build_menu# дерево постройки

В функции _ready(), привязываем сигнал:

#если есть меню, то привязываем его сигнал
var menu = get_node(build_menu)
if (menu != null):
	menu.connect("building_stand",self,"build")

Функция обработки сигнала:

#Функция обработки сигнала постройки
func build(build_scene, build_price):
	#Если опыта >=, чем стоит постройка
	if (current_exp >= build_price):
		#вычитаем стоимость
		current_exp -= build_price
		#обновляем UI
		_user_interface.remove_exp(build_price)
		#добавляем постройку
		var b = build_scene.instance()
		
		b.position = position
		
		get_parent().add_child(b)
Полный скрипт персонажа
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#Множитель урона
export var max_exp = 20#сколько опыта надо для повышения уровня
export var scale_exp = 2# во сколько раз увеличится требуемы опыт после повышения
export (NodePath) var skill_tree# дерево навыков
export (NodePath) var build_menu# дерево постройки
#Объявляем переменные только для этого скрипта
var velocity = Vector2.ZERO #Вектор направления
var direction = Vector2.ZERO #Вектор движения
var backpack_items = [null,null,null,null,null,null]#рюкзак
var weapon_count = 0 #Переменная хранящая кол-во оружия
var current_exp = 0 #Переменная хранящая кол-во опыта
#Сигнал о получении урона
signal take_damage(damage)
#Сигнал о смерти
signal dead
#Сигна о повышении уроня
signal lvl_up
#Функция считывания нажатий
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()
	_user_interface.init_exp(max_exp)# инициализая опыта, для интерфейса
	_user_interface.init_health(health)# инициализация опыта, для интерфейса
	#если есть дерево, то привязываем его сигнал
	var tree = get_node(skill_tree)
	if (tree != null):
		tree.connect("branch_skill_up",self,"_on_SkillTree_branch_skill_up")
	#если есть меню, то привязываем его сигнал
	var menu = get_node(build_menu)
	if (menu != null):
		menu.connect("building_stand",self,"build")


func _physics_process(delta):
	get_input()
	get_anim()
	var collision= move_and_collide(direction * delta) 
	#записываем в переменную collision для дальнейшей обработки столкновения
	if collision:#если столкновение
		if collision.collider.has_method("pick_up_exp"):#И есть метод take_damage
				collision.collider.pick_up_exp()#поднимаем опыт
				current_exp += collision.collider.get_exp_param()#увеличиваем значение
				level_up()#вызываем функцию повышение опыта
				_user_interface.take_exp(collision.collider.get_exp_param())#увеличиваем опыт
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
#обработка сигнала от дерева
func _on_SkillTree_branch_skill_up(param, scale):
	#выбираем параметр
	match param:
		0:#урон
			damage_scale += damage_scale * scale/100# увеличиваем урон
		1:#скорость
			speed += speed * scale/100# увеличиваем скорость
		2:#хп
			health += round(health * scale/100)# увеличивам хп
			_user_interface.init_health(health)#переинициализируем ui
		_:
			pass

#функция поднятия опыта
func level_up():
	if (current_exp == max_exp):#Если достигли опыта для лвлапа
		emit_signal("lvl_up")#отправляем сигнал
		max_exp *= scale_exp#увеличиваем требуемый опыт для след уровня
		current_exp = 0#обнуляем текущий опыт
		_user_interface.init_exp(max_exp)#обновляем интерфейс

#Функция обработки сигнала постройки
func build(build_scene, build_price):
	#Если опыта >=, чем стоит постройка
	if (current_exp >= build_price):
		#вычитаем стоимость
		current_exp -= build_price
		#обновляем UI
		_user_interface.remove_exp(build_price)
		#добавляем постройку
		var b = build_scene.instance()
		
		b.position = position
		
		get_parent().add_child(b)

Сцена игры

В дерево сцены игры, нужно добавить наше меню и скрыть его.

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

#Функция создания героя
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
	p._user_interface
	add_child(p)
	#Передаём путь к дереву
	p.skill_tree="../SkillTree"
	#Передаём путь к меню
	p.build_menu ="../BuildingsMenu"
	player = p
	p.z_index = 2#Задаём z_index - 2, чтоыб герой ходил сверху крови

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

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

и в функцию clear_all(), добавляем удаление группы построек:

func clear_level():
	WeaponsName.clear_all()#убираем улучшения с оружия
	get_tree().call_group("all_enemy", "queue_free")#Удаляем всех врагов со сцены
	get_tree().call_group("all_buildings", "queue_free")#Удаляем все постройки со сцены
Полный код сцены игры
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
onready var _skill_tree = $SkillTree
#Массив всего оружия
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()#Включили таймер спавна
	_skill_tree.player = player#привязываем игрока к дереву
	_skill_tree.add_connect()
	$BuildingsMenu.player = player#привязываем игрока к меню построек
	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():
	save_record(player._user_interface.get_score())#записали результаты
	clear_level()
	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
	p._user_interface
	add_child(p)
	#Передаём путь к дереву
	p.skill_tree="../SkillTree"
	#Передаём путь к меню
	p.build_menu ="../BuildingsMenu"
	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.

#функция сохранения в качестве аргумента берёт текущий счёт
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 clear_level():
	WeaponsName.clear_all()#убираем улучшения с оружия
	get_tree().call_group("all_enemy", "queue_free")#Удаляем всех врагов со сцены
	get_tree().call_group("all_buildings", "queue_free")#Удаляем все постройки со сцены

Подведение итогов

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

Игру уже можно куда-нибудь залить на всеобщее обозрение, возможно сделаем это позже.

По этому проекту позже должна будет выйти ещё одна статья с локальным мультиплеером, но пока нет возможности его реализовать, поэтому придётся подождать. Ниже будет несколько голосований, проголосуйте во всех, чтобы я понял, каким проектом заняться дальше. Голосования закончатся 10.07.2023 в 8 по МСК, успевайте проголосовать

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
За какой проект взяться дальше
27.27% Свой PvZ с горохом и такос3
9.09% Создадим свой PlateUp!, где наконец-то накормим всем ненасытных монстров1
9.09% Придуманная игра туалетный ниндзя( Простите, что?!)1
18.18% Большая статья про кликеры «От клика до СУПЕР-НЕРЕАЛЬНО-ВОСХИТИТЕЛЬНОЙ-СПАРЖИ»2
0% Создадим современный Sokoban, про грустного песика в космосе.0
36.36% Карточный рогалик про приготовление… Рогаликов4
Проголосовали 11 пользователей. Воздержался 1 пользователь.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Продолжать писать статьи так подробно?
90% Да9
10% Нет1
Проголосовали 10 пользователей. Воздержавшихся нет.
Теги:
Хабы:
Всего голосов 2: ↑2 и ↓0+2
Комментарии4

Публикации

Ближайшие события