Сегодня на Godot 4.1 создадим простой редактор персонажа, как в старых рпг, когда ты выбираешь внешний вид персонажа из уже нарисованных ассетов.
Вступление
Для начала давайте немного обсудим, как это будет работать. У нас есть игровой персонаж, внешний вид которого будет устанавливаться игроком, на данном примере рассмотрим, что будет устанавливаться: цвет кожи, глаза, улыбка. Выбранные игроком спрайты должны куда-то сохраниться и мы в последующем будем загружать их на персонажа. По хорошему нужно хранить это в файле сохранения игрока и при запуске этого сейва заливать в синглтон, который объявлен, как глобальная переменная. Мы же в данном туториале будем хранить только в синглтоне и никуда сохранять не будет.
Графика
Что из себя должна представлять графика.
Для начала нужно создать что-то вроде манекена, на котором мы будем рисовать. То есть это будет наш персонаж, у которого есть отметены под глаза и рот(в нашем случае).

Дальше на нём всё рисуем и сохраняем, как отдельный спрайт.

Размеры изображений у вас должны быть одинаковые в каждой группе элементов. Например для всех пар глаз, размер - 100X30, для всех улыбок - 100X40 и т.д.
Персонаж
С графикой закончили, теперь перейдём к сцене персонажа в Godot.У меня она выглядит следующим образом:

Да, конечно следует ещё добавить узел коллизии, возможно ещё некоторые узлы, но в данном туториале мы просто сделаем персонажа, которого создаёт игрок.
Как правильно расставить местоположение Sprite2D(у вас может быть AnimatedSprite2D, тогда этот способ не подойдёт, если захотите, то рассмотрим это в отдельной статье). Для этого мы опять используем нашего маникена и примеряем всё на нём. То есть выбираем, как текстуру для Body, маникена и выставляем глаза и улыбку.

Сцена редактора
У меня сцена имеет примерно следующий вид

Думаю за что отвечает каждый узел, объяснять смысла нет. Это просто надписи, кнопки и элементы декора. У ColorRect(Preview), как дочерние элементы используются просто 3 спрайта, а не сцена персонажа.
Давайте перейдём к коду:
extends Node2D
#константы с путями к ассетами
const EYE_ROOT = "res://Assets/Eye/eye"
const SKIN_ROOT = "res://Assets/Skin/Skin"
const SMILE_ROOT = "res://Assets/Smile/Smile"
#Массивы которые будут хранить наши ассеты
var eye_array = []
var skin_array = []
var smile_array = []
#номер эллемента в массиве
var eye_number = 0
var skin_number = 0
var smile_number = 0
#элементы дерева
@onready var _eye = $CustomMenu/Preview/Body/Eye
@onready var _body = $CustomMenu/Preview/Body
@onready var _smile = $CustomMenu/Preview/Body/Smile
#Вспомогательные функции для получения полных путей ассетов
func get_eye_path(index):
return EYE_ROOT + str(index) + ".png"
func get_skin_path(index):
return SKIN_ROOT + str(index) + ".png"
func get_smile_path(index):
return SMILE_ROOT + str(index) + ".png"
#Заполним массив ассетами глаз
func get_eye_array():
#Счётчик
var i = 1
while true:
#Если такая картинка есть, то добавляем в массив
if load(get_eye_path(i)) != null:
eye_array.append(load(get_eye_path(i)))
#Иначе заканчиваем while
#У меня все картинки идут по порядку(Eye1,Eye2...)
#Можно сделать чуть иначе, но так проще...
else:
break
i+=1
#тоже самое, но для улыбок
func get_smile_array():
var i = 1
while true:
if load(get_smile_path(i)) != null:
smile_array.append(load(get_smile_path(i)))
else:
break
i+=1
#тоже самое, но для кожи
func get_skin_array():
var i = 1
while true:
if load(get_skin_path(i)) != null:
skin_array.append(load(get_skin_path(i)))
else:
break
i+=1
#наполнили все массивы
func _ready():
get_eye_array()
get_smile_array()
get_skin_array()
#функция получения новых глаз на превью
func get_new_eye():
#Сделали вращалки цикличными
if eye_number == eye_array.size():
eye_number = 0
if eye_number == -1:
eye_number = eye_array.size() - 1
#Залили новую текстурку
_eye.texture = eye_array[eye_number]
#тоже самое для кожи
func get_new_skin():
if skin_number == skin_array.size():
skin_number = 0
if skin_number == -1:
skin_number = skin_array.size() - 1
_body.texture = skin_array[skin_number]
#тоже самое для улыбок
func get_new_smile():
if smile_number == smile_array.size():
smile_number = 0
if smile_number == -1:
smile_number = smile_array.size() - 1
_smile.texture = smile_array[smile_number]
#Обработка сигналов для кнопок Skin
func _on_skin_next_pressed():
skin_number+=1
get_new_skin()
func _on_skin_prew_pressed():
skin_number-=1
get_new_skin()
#Обработка сигналов для кнопок Eye
func _on_eye_next_pressed():
eye_number+=1
get_new_eye()
func _on_eye_prew_pressed():
eye_number-=1
get_new_eye()
#Обработка сигналов для кнопок Smile
func _on_smile_next_pressed():
smile_number+=1
get_new_smile()
func _on_smile_prew_pressed():
smile_number-=1
get_new_smile()
В функции _ready, мы заполняем массивы картинками, и дальше просто заливаем в соответствующий Sprite2D, соответствующую текстурку.
Теперь нам потребуется написать синглтон HeroView и поставить его на автозагрузку и сделать глобальной переменной.
Синглтон HeroView:
extends Node
#Объявляем переменные хранящие картинки(не должны быть пустыми, чтобы не было ошибок)
var skin = load("res://Assets/Skin/Skin1.png")
var eye = load("res://Assets/Eye/eye1.png")
var smile = load("res://Assets/Smile/Smile1.png")
#Обычные сеттеры и геттеры
func set_skin(new_skin):
skin = new_skin
func set_eye(new_eye):
eye = new_eye
func set_smile(new_smile):
smile = new_smile
func get_skin():
return skin
func get_eye():
return eye
func get_smile():
return smile
Он просто хранить картинки и содержит сеттеры и геттеры для картинок.
Как вы могли в скрипте для сцены редактора нет обработки нажатия на кнопку accept, а вот и она:
#Обработка сигнала для кнопки принять
func _on_accept_pressed():
HeroView.set_skin(_body.texture)
HeroView.set_eye(_eye.texture)
HeroView.set_smile(_smile.texture)
Мы просто обновляем переменные в синглтоне HeroView.
Завершающий штрих
Теперь у нас есть где хранятся картинки, но что с ними делать дальше? Дальше мы просто добавляем нашему персонажу следующую функцию:
func get_new_look():
_body.texture = HeroView.get_skin()
_eye.texture = HeroView.get_eye()
_smile.texture = HeroView.get_smile()
И вызываем срабатывание этой функции когда потребуется обновить его внешний вид.
Результат
Я ещё добавил, что герой появляется на сцене, при нажатии кнопки Accept, и теперь мы имеет следующее:

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