Привет ,Habr. Сегодня мы начнём разработку своего карточного рогалика про приготовление... рогаликов! Именно эта тема победила в обсуждении под предыдущим туториалом. Работать будет на свежем Godot 4.1. В этой статье добавим карты, "Руку (Копыто) карт" и реализуем функцию Drag & Drop.

Знакомство
Для начала давайте познакомимся с Эдгаром, отважным поросёнком, который мечтает стать пекарем и ради своей мечты отправится в подземелье, чтобы накормить тамошних обитателей свежей выпечкой.

В последующих статьях он будет для нас:
Замешивать тесто

Формировать рогалики

Печь

Сервировать стол

С Эдгаром познакомились, вернёмся к нему в следующей части. Пока, Эдгар...
Создание проекта
Создаём проект примерно так:

После создание проекта, заходим в настройки проекта -> Список действий и добавляем действие "click", щелчок ЛКМ
Сцена болванка для карт
Начнём как всегда с создания сцены, которую позже будем наследовать для разных карт.
Главным узлом выбираем Node2D(CardDefault), дочерний к нему Sprite2D(CardSprite) - текстурка нашей карты, дочерние к CardSprite:
Sprite2D(CardTitul) - Картинка рисуемая на карте
Label(CardName) - Заголовок карты
Label(CardDescription) - Описание карты
Дерево объектов:

Выглядеть это должно примерно так:

Давайте загрузим шрифт в Label, на 4 версии, это делается в пару кликов. Найдите где-то шрифт, файл .ttf. Выберите в дереве объектов Label, В инспекторе на вкладке "Control", последний пункт "Theme Overrides", выбираем Fonts -> загрузить и загружаем свой шрифт, поменяйте ему цвет, размер, сделайте всё под себя. Также в инс��екторе, найдите свойство Autowrap Mode и установите его в Word (Smart), для обоих Label. Создайте несколько карт. Изменяя текстуру в CardTitul и напишите для них название в CardName и описание в CardDescription. Должно получится примерно следующее:

Чтобы создать карту:
Создаём новую сцену
Главным узлом сцены выбираем "Инстанцировать дочернею сцену"
(Ctrl+Shift+A) и выбираем нашу сцену, которую только-что создали.CardTitul, В свойстве Texture, нажимаем на стрелочку "Сделать уникальным" и добавляем свою картинку.
В оба Label пишем текст.
К скрипту вернёмся чуть позже.
Сцена "Руки карт"
Главным узлом сцены выбираем Node2D(CardArm), и дочерние к нему элементы:
Sprite2D
4 Marker2D(Card1...Card4)

Каждая метка будет указывать местоположение и угол поворота для карты. У вас может быть больше 4-х, просто у Эдгара влезает только 4 карты. Заливаем текстуру нашей руки и выставляем местоположение для Marker2D. Не большое отступление, так-же выставляйте угол поворота. Карты обычно держат под углом. Для Sprite2D выставляем Z_index = 2


Небольшой лайфхак. Вы можете добавить на сцену, 4 карты, инстанцировав дочернею сцену. Расположить их как вам нравится. Потом записать в Marker2D положения по X и Y и угол поворота
Добавляем скрипт на CardArm и переходим к его редактированию:
extends Node2D #Объявляем 4 переменных на экспорт, это и будут наши карты @export var card1:PackedScene @export var card2:PackedScene @export var card3:PackedScene @export var card4:PackedScene #Записываем наши паркеры и переменные @onready var _card1_marker = $Card1 @onready var _card2_marker = $Card2 @onready var _card3_marker = $Card3 @onready var _card4_marker = $Card4 #Спрайтик тоже запишем, вдруг пригодится потом @onready var _sprite = $Sprite2D #Каждой карте мы будем присваивать ID, просто пропишите эту строчку var ID = 0 #В функции _ready, создаём наши карты func _ready(): place_card(card1,_card1_marker) place_card(card2,_card2_marker) place_card(card3,_card3_marker) place_card(card4,_card4_marker) #Функция создания карты, в качестве аргумента, #передаём сцену, которую ставим и маркер - куда ставим. func place_card(scene,marker): #Установили сцену карты var s = scene.instantiate() #Выставили карте поворот s.rotation_degrees = marker.rotation_degrees #Выставили карт местоположение s.position = marker.position #Увеличили счётчик, перед присваиванием, т.к. ID начинается с 1 ID += 1 #Присвоили ID s.ID = ID #Добавили сцену, как потомка add_child(s)
Переменная ID, потребуется в дальнейшем, чтобы управлять картами.
Синглтон CardMover
Создаём новый скрипт, обязательно проверяем, чтобы он наследовал Node. После создания скрипта, это делается на вкладке "Script" -> Файл -> Новый скрипт. Обязательно назовите его CardMover. Переходим в настройки -> Автозагрузка -> выбираем путь до нашего скрипта и нажимаем добавить. Мы будем использовать Глобальный синглтон, для управления рукой карт. Переходим к редактированию этого скрипта:
extends Node #Выбранная карта var selected_card_ID = null #Установить ID func set_ID(ID): selected_card_ID = ID #Обнулить ID func unset_ID(): selected_card_ID = null #Получить ID func get_ID(): return selected_card_ID #Проверить ID #Мы можем взаимодействовать только с выбранной картой #Или с пустой func check_ID(ID): if((selected_card_ID == null) or (selected_card_ID == ID)): return true else: return false
Тут никаких сложных функций нет, только почти обычные гетеры и сетеры.
Скрипт болванки для карт
Навешиваем скрипт на CardDefault и переходим к его редактированию:
extends Node2D #Объявили переменные дерева @onready var _card_sprite = $CardSprite @onready var _card_titul = $CardSprite/CardTitul @onready var _card_description = $CardSprite/CardDescription #Объявили переменную ID @export var ID = "0" #Объявили локальные переменные var start_rotation:int#Переменная стартовой позиции var start_position:Vector2#Переменная стартового поворота var dragable = false#Выделен-ли объект, для перетаскивания var start_z_index#Стартовый Z-index #Объявляем все стартовые переменные func _ready(): start_z_index = z_index start_rotation = rotation_degrees start_position = position #Функция обработки ввода игроком func _input(event): #Если нажата ЛКМ if event is InputEventMouseButton and event.is_action_pressed("click"): #Если клик произошёл в области спрайта карты и проверили ID if (_card_sprite.get_rect().has_point(to_local(event.position)) and CardMover.check_ID(ID)): #Можно перетаскивать dragable = true #Установили ID CardMover.set_ID(ID) #Если отпущена ЛКМ if event is InputEventMouseButton and event.is_action_released("click"): #Если отпустили ЛКМ в области спрайта карты и проверили ID if (_card_sprite.get_rect().has_point(to_local(event.position)) and CardMover.check_ID(ID)): #Отменили перетаскивание dragable = false #Обнулили ID CardMover.unset_ID() #Если переместили карту, то if(position != start_position): #Убира�� стартовый поворот start_rotation = 0 #Меняем стартовое положение start_position = position #Поменяли угол rotation_degrees = start_rotation #Если курсор мыши двигается if event is InputEventMouseMotion: #Если при движении курсор мыши задел спрайт нашей карты и проверили ID if (_card_sprite.get_rect().has_point(to_local(event.position)) and CardMover.check_ID(ID)): #Убрали поворот rotation_degrees = 0 #Установили поверх других карт z_index = 2 #Задали ID CardMover.set_ID(ID) else: #Проверили ID if (CardMover.check_ID(ID)): #Вернули z_index z_index = start_z_index #Вернули угол поворота rotation_degrees = start_rotation #Обнулили ID CardMover.unset_ID() func _process(delta): #Если состояние карты перетягивание, то следуем за курсором if dragable: set_global_position(get_global_mouse_position())
При создании карт в "Руке карт" мы добавляли каждой карте ID, чтобы была возможность отслеживать каждую карту в руке и взаимодействовать только с ней, если такого не было-бы, то в местах наслаивания карт друг на друга, мы бы брали две карты сразу или поворачивали две карты сразу при наведении.
Код в целом прокомментирован очень подробно, думаю вопросов не должно возникнуть.
Заключение

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