Godot | Open Dungeon | Часть alpha

Пошаговая инструкция по созданию минималистичной заготовки 3D-игры на движке Godot.

Godot | Open Dungeon | Часть alpha
вот что получится в итоге

Архив с готовыми файлами проекта можно взять на странице: https://thenonsense.itch.io/opendungeon

Open Dungeonthenonsense.itch.io

А мы начнём сборку этого проекта с нуля. Первое, что потребуется сделать — загрузить актуальную версию движка Godot с сайта https://godotengine.org/ в разделе Download.

Скачиваем стандартную версию для своей системы
Скачиваем стандартную версию для своей системы

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

Распакуем скачанный файл и запустим Godot engine. В появившемся окне справа нажмём на кнопку New Project.

Новый проект

Пишем имя проекта, затем открываем Browse чтобы выбрать желаемое место в файловой системе (в данном случае папка Godot_35), в которой будут лежать проекты Godot. Потом нажимаем кнопку Create Folder, чтобы в этой папке создалась папка проекта и переключаем рендер на gles2 (он проще в настройке, требует железа послабее, а при желании проект всегда можно перевести на gles3).

Пишем имя OpenDungeon. Жмём Browse. Выбираем папку для проектов. Нажимаем Select This Folder.
Пишем имя OpenDungeon. Жмём Browse. Выбираем папку для проектов. Нажимаем Select This Folder.
Вернувшись в это окно, сначала нажимаем Create Folder, чтобы в поле Projectt Path к пути добавилась запись /OpenDungeon. Отмечаем OpenGL ES 2.0 и нажимаем Create & Edit
Вернувшись в это окно, сначала нажимаем Create Folder, чтобы в поле Projectt Path к пути добавилась запись /OpenDungeon. Отмечаем OpenGL ES 2.0 и нажимаем Create & Edit

Создание 3d сцены, камера

Получаем стартовый экран. Нажмём на создание 3д-сцены.

Меняем имя узла-пустышки Spatial на Main и сохраняем сцену под именем MainScene.

Как видно внизу — сцена появилась в списке ресурсов. Сразу попробуем запустить игру, нажав на кнопку Play вверху.

Godot напишет, что не выбрана основная сцена и предложит варианты — выбираем сделать текущую сцену основной (Select Current).

Игра запускается, видим пустой экран.

Для того, чтобы увидеть 3д-сцену в игре, нужно как минимум добавить туда камеру. Для этого щёлкаем правой кнопкой на Main, выбираем + Add Child Node, напишем в поиске cam и добавим камеру (Camera). Она прикрепится к узлу Main, в те же координаты.

Если теперь запустить игру, то увидим градиент, имитирующий землю и небо. Для порядка отметим флажком на панели инспектора (справа), что камера является основной текущей (Current).

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

Отключаем Preview, чтобы снова работать с 3д-сценой. Для вращение в 3д-пространстве зажимаем колесо мыши, для приближения удаления — крутим колёсико. Если нужно отцентровать вид, сфокусировавшись на каком-то объекте сцены — выделяем этот объект и нажимаем F.

Объекты на сцене

Теперь добавим какой-нибудь объект. Снова выбираем узел Main, правая кнопка мыши, + Add Child Node. Вбиваем в поиск mesh и выбираем MeshInstance, для создания полигональной сетки. Узел появляется, но ничего не видно — нужно зайти на панель инспектора справа, и выбрать какую-то из предустановленных простых форм, например цилиндр.

Установив для полигональной сетки форму цилиндра снова выделим камеру, переключимся на Mode Move (горячая кнопка W) и потянем за синюю стрелку, перемещая тем самым камеру по оси Z (справа вверху показаны оси и обозначено, какой цвет какой оси соответствует).

Откроем вкладку Transform на панели инспектора камеры, где собраны базовые свойства для любого 3д-объекта — положение по осям, углы поворота, размер. Видно, что в поле Z стоит какое-то ненулевое значение и рядом круг со стрелкой (нажав на этот значок можно сбросить текущее значение на предустановленное). Поставим в отделе Translation число 3 в поле Y, и 5 в поле Z. В отделе Rotation Degrees установим X на минус 30. Если нажать превью камеры или запустить игру, то видно, что камера теперь смотрит на цилиндр.

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

Теперь сделаем копию цилиндра, и переделаем его в куб. Для этого выделяем цилиндр и нажимаем сочетание Ctrl + D (клонирование). Появляется объект MeshInstance2 (Godot автоматически переименовал новый объект с тем же именем). Передвинем его по оси Z на -3 и в поле Mesh выберем NewCubeMesh, чтобы второй цилиндр стал кубом.

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

Сделаем заготовку персонажа. Для этого добавляем на сцену (выделив Main и нажав Ctrl + A или правую кнопку мыши, с последующим выбором опции + Add Child Node) новый узел-пустышку Spatial. Перетаскиваем внутрь неё цилиндр и камеру, после чего переименовываем узел Spatial в Hero.

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

Выделим корневой узел Main и нажмём на свиток с зелёным плюсом сверху, чтобы добавить новый скрипт. Появится окошко, где предлагается название скрипта MainScene.gd. Нажмём на папку сбоку и в открывшемся окне нажимаем на кнопку сверху Create Folder, называем новую папку Scripts. Теперь меняем имя скрипта, пусть будет theMain, после чего нажимаем на кнопку Open справа. Видим, что путь и имя файла изменились (строчка path), затем нажимаем кнопку Create.

Оказываемся в окне скриптов, где видим строки свежесозданного скрипта theMain.gd.

Удаляем верхние строчки, начинающиеся с символа # (с этого символа начинаются строки с комментариями к коду), и заводим ссылку на нашего героя. Пишем выше строки func _ready(): строчку onready var main_hero = $Hero Ниже, заменим строку pass на такую запись main_hero.translation.x += 10.0 (символ табуляции >| в начале этой строки не трогаем).

Рядом со скриптом горит звёздочка, так как мы вносили изменения, для того чтобы сохранить их нажмём на строчку File и выберем Save.

Теперь запустим игру и видим, что цилиндр сместился влево относительно куба. Это дело рук скрипта theMain, где мы до инициализации сцены получили ссылку на цилиндр с камерой, а затем, при первом появлении в сцене сдвинули цилиндр на 10 по оси X.

Переключимся обратно в сцену, щелкнув на кнопке 3d сверху экрана. Создадим новый дочерний к Main MeshInstance, выбрав в качестве формы New PlaneMesh. Переименуем его в Pol_A и установим его размеры Scale на 5 по X, 1 по Y и 5 по Z.

Заходим в поле Material и выбираем там пункт New SpatialMaterial. Появится изображение белого шара. Щелкаем на него, открываются настройки нового материала. Меняем Color во вкладке Albedo на какой-то другой цвет.

Теперь сохраним этот материал, нажав на иконку дискетки. Создадим папку Materials и переименуем материал в ground_mat.tres

Добавляем интерактив

Вернёмся в скрипты, нажав на свиток справа от Main или щёлкнув на Script вверху экрана.

Расскоментируем строку func _process(delta) (удалив символ # в начале строки) и уберём прочие серые строчки. Пишем ниже func _process() строку main_hero.translation.x -= delta

Таким образом мы создали функцию, которая будет делать что-то в цикле. В данном случае потихоньку сдвигать цилиндр в направлении противоположном начальному сдвигу со скоростью привязанной с скорости выполнения программы. Если убрать deltа и поставить некое число, то движение будет происходить с разной скорость на разных системах. Для того чтобы правильным образом ускорить или замедлить скорость движения нужно в этой строчке умножать delta на нужное число (например, чтобы движение происходило в 2 раза быстрее строчка выглядела бы как main_hero.translation.x -= 2 * delta ). Думаю принцип понятен, а теперь запустим игру и убедимся, что цилиндр движется влево.

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

Теперь внутри Hero (выделяем его, Ctrl + A) создадим узел Spatial. Назовём его Visual и перетащим туда оба цилиндра.

Перепишем код в Main следующим образом:

extends Spatial

onready var main_hero = $Hero
onready var hero_visual = $Hero/Visual

var horizontal_joy = 0
var vertikal_joy = 0

func _ready():
	main_hero.translation.x += 10.0

func _process(delta):
	main_hero.translation.x -= delta
	
	horizontal_joy = 0
	vertikal_joy = 0
	
	if Input.is_action_pressed("ui_up"):
		vertikal_joy = 1
	if Input.is_action_pressed("ui_left"):
		horizontal_joy = -1
	if Input.is_action_pressed("ui_right"):
		horizontal_joy = 1
	if Input.is_action_pressed("ui_down"):
		vertikal_joy = -1
		
	match vertikal_joy:
		0:
			match horizontal_joy:
				1: 
					hero_visual.rotation_degrees = Vector3(0.0,-90.0,0.0)
				-1: 
					hero_visual.rotation_degrees = Vector3(0.0,90.0,0.0)
		1:
			match horizontal_joy:
				0: 
					hero_visual.rotation_degrees = Vector3(0.0,0.0,0.0)
				1: 
					hero_visual.rotation_degrees = Vector3(0.0,-45.0,0.0)
				-1: 
					hero_visual.rotation_degrees = Vector3(0.0,45.0,0.0)
		-1:
			match horizontal_joy:
				0: 
					hero_visual.rotation_degrees = Vector3(0.0,180.0,0.0)
				1: 
					hero_visual.rotation_degrees = Vector3(0.0,-135.0,0.0)
				-1: 
					hero_visual.rotation_degrees = Vector3(0.0,135.0,0.0)

Обратите внимание, что некоторые строки имеют отступы и начинаются с символов табуляции (ставятся кнопкой Tab), а не пробелов. В языке GDScript эти отступы важны — каждая следующая степень вложенности логики требует добавления ещё одного отступа. Если в определённой области отступов становится слишком много, то есть смысл задуматься о том, чтобы вынести из того места какую-то логику в отдельные функции.
Итак, что мы добавили? Создали ссылку hero_visual на визуальное представление героя (узел с цилиндрами), чтобы поворачивать его отдельно от перемещения контейнера с героем и камерой. Также завели две переменные horizontal_joy и vertikal_joy, чтобы отслеживать, какие оси направления зажаты игроком. Далее в цикле func _process(delta) мы каждый раз обнуляем переменные направления, затем опрашиваем клавиатуру на предмет того, нажал ли пользователь одну из кнопок соотнесённых с ключами ui_up, ui_left, ui_right, ui_down (по умолчанию это стрелки на клавиатуре). Если да, то отмечаем направление по каждой оси в виде отклонений от центра (0) в положительную или отрицательную строну. Завершается всё блоком проверки значений осей, с поворотом визуала героя в соответствующую сторону.

Если запустить игру, то цилиндр будет как обычно ехать влево, но теперь уже «крутить головой» при нажатии стрелок.

Теперь удалим первую строчку в функции_process(delta), в которой цилиндр постояннно едет влево (main_hero.translation.x -= delta на 13-й строке), а выше, до функции _ready() напишем строчку var hero_speed = 2.2 для того, чтобы у персонажа была скорость.

Далее добавим пару строк в самом конце (следим за правильным количеством отступов, чтобы эти строки не оказались внутри проверок по осям):

main_hero.translation.x += horizontal_joy * hero_speed * delta

main_hero.translation.z -= vertikal_joy * hero_speed * delta

Теперь наш герой научился полноценно перемещаться.

Вернёмся на 3д сцену и уменьшим размеры цилиндров, изображающих персонажа, подняв основной так, чтобы он находился немного над полом. Сам узел Visual не трогаем и его размеры не меняем, он должен оставаться единичного размера и с нулевыми координатами по осям. Уже больше похоже на движущегося персонажа.

Для перемещения и изменения размеров вручную пользуемся режимами Move и Scale, включающиеся на панели значков вверху.

Колонны и столкновения

Добавим в Main (через Ctrl+A) новый узел Spatial, назовём его Pillar_A и закинем туда модельку с кубом.

Теперь выделим узел Pillar_A и сделаем из него префаб (отдельную мини-сцену). Для этого щелкаем правой кнопкой мыши и выбираем опцию Save Brunch as Scene.

В открывшемся окне заводим папку для префабов Prefabs и сохраняем сцену под предложенным именем туда.

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

Выделим куб и нажмём на сброс координат во вкладке Transform у поля Translation.

Куб перепрыгнет в начало локальных координат этой конкретной сцены.

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

Сохраним изменения в сцене. Переключаемся на вкладку основной сцены MainScene. Видим, что получившаяся колонна заслоняет персонажа.

Это получилось потому, что пустышку для колонны мы создавали в начале координат и когда сбросили смещение куба, находящегося внутри пустышки, то он оказался на том месте, где она расположена в 3д пространстве. Сдвинем Pillar_A по оси Z, расположив перед персонажем.

Теперь сделаем клон узла Pillar_A, выделив его и нажав сочетание Ctrl+D (либо щёлкнув правой кнопкой и выбрав опцию Duplicate). Появится узел Pillar_A2, смещаем его вправо. Затем дублируем его таким же способом, получив Pillar_A3 и смещаем его ещё правее.

Запустим игру, теперь персонаж может побегать между колонн. Естественно, просачиваясь сквозь них, так как мы не сделали колонны «твёрдыми».

Немного подправим колонны. Заходим в любую их них, например Pillar_A3, оказавшись внутри выделяем вытянутый куб, клонируем и делаем два маленьких, приплюснутых, располагая их внизу и вверху.

После чего переключаемся на самый верхний узел сцены (Pillar_A), и добавляем к нему новый узел. Набираем в поиске area, выбираем узел Area и добавляем его.

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

Для этого щёлкаем на Area правой кнопкой и выбираем добавить новый узел. На этот раз ищем CollisionShape.

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

Щелкнем на панели инспектора справа, в поле Shape, чтобы выбрать одну из предустановленных форм — New BoxShape.

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

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

Ниже, во вкладке CollisionObject, в поле Collision снимаем отметки с единичек и отмечаем только 2 ячейку layer.

Таким образом эта зона столкновения будет «видима» только для объектов, которые мониторят второй слой коллизий. Сохраняем сцену, переключаемся на MainScene.

Выделяем узел Visual и добавляем ему новый узел RayCast — это луч, отслеживающий столкновения.

В инспекторе справа ставим галочку напротив Enabled, чтобы луч был активным. Выставляем параметры Cast To в 0 (по x) 0.2 (по y) и -1 (по z). Теперь можно заметить, что луч выглядит как синий отрезок, выступающий из персонажа.

Ниже Cast To, в ячейках Colision Mask убираем 1 и отмечаем 2, чтобы луч мониторил второй слой коллизий. Ещё ниже во вкладке Collide With отмечаем пункт Areas, чтобы луч взаимодействовал с зонами (Area).

Откроем скрипт theMain и допишем выше _ready() получение ссылки на наш визуальный рейкаст — onready var rcast = $Hero/Visual/RayCast
В самом низу скрипта добавим следующую пару строк (не забываем про отступы-табуляции):

	if rcast.is_colliding():
		print("столкновение")

Первая строка это условие «если рейкаст с чем-то столкнулся», а ниже идёт инструкция, что делать, когда это произошло. В данном случае это вывод сообщения. Запускаем игру и видим, что когда персонаж подходит к колонне, то вне игры, в окошке редактора выводится сообщение «столкновение», пока рейкаст соприкасается с препятствием (колонной).

Вырежем две строчки выше этой последней записи и перенесём их ниже строки print. Нужно добавить по одному Tab в начало этих строк, чтобы они находились со строкой print на одном уровне. Таим образом движение персонажа теперь будет происходить только при условии столкновения. Если запустить игру сейчас, то персонаж окажется словно приклеенным к месту.

Немного изменим условие, переписав строку следующим образом:

	if rcast.is_colliding() == false:
		main_hero.translation.x += horizontal_joy * hero_speed * delta
		main_hero.translation.z -= vertikal_joy * hero_speed * delta
	else:
		print("столкновение")

Теперь персонаж будет двигаться тогда, когда его рейкаст ни с чем не сталкивается, а в противном случае (else) будет выводится сообщение, а движения не будет. Запустим игру и убедимся в том, что теперь действительно колонны стали непреодолимым препятствием для героя.

Вернёмся в MainScene. Выделим узел Pol_A, жмём правой кнопкой и превращаем его в префаб (через опцию Save Brunch as Scene). Сохраняем под этим же именем в ранее созданную папку Prefabs, где уже должен лежать префаб Pillar_A.

Дублируем получившийся узел Pol_A (Ctrl+D), получив копию Pol_A2. Сдвинем копию куска пола ровно вправо, введя 10 в поле X у параметра Translation в инспекторе. Теперь пол расширился и герой будет появляться не на пустом месте.

В процессе написания:

Стартовый экран

Здоровье

Игровой цикл

Сбор запускаемого билда

 

Источник

Читайте также