Больше функционала для минималистичного прототипа игры: объекты уровня, враги, апгрейд управления, глобальный скрипт и статичные выстрелы.
Архив с готовыми файлами проекта можно взять на странице: https://thenonsense.itch.io/opendungeon
В этой статье мы пошагово обновим прототип alpha до состояния beta.
Запускаем Godot. Открываем проект Open Dungeon, который мы создали ранее в части alpha . Открывается сцена MainScene.
Уровень
Добавим к Main (корневой узел сцены) новый узел Spatial. Перетащим в него все копии Pol и Pillar, затем переименуем этот узел в Level_A1.
![далее выбрать выбрать в списке узел Spatial далее выбрать выбрать в списке узел Spatial](https://habrastorage.org/getpro/habr/upload_files/b54/be4/7aa/b54be47aacfd2b4a2e57c39b5c4479d5.jpg)
![Перетащить выделенные объекты в Spatial Перетащить выделенные объекты в Spatial](https://habrastorage.org/getpro/habr/upload_files/c6b/330/c34/c6b330c341bfc3bcd29bc11485d903eb.jpg)
![Переименовать Spatial в Level_A1 Переименовать Spatial в Level_A1](https://habrastorage.org/getpro/habr/upload_files/19f/e27/6b3/19fe276b39a3f016fd940f0a93d101d1.jpg)
Щёлкаем по узлу правой кнопкой, выбирая Save Branch as Scene. Делаем двойной щелчок на папке Prefabs (входя в неё) затем нажимаем сверху справа Create Folder. Пишем имя папки - Levels. Ок. Сохраняем префаб уровня.
![](https://habrastorage.org/getpro/habr/upload_files/31a/f29/f2d/31af29f2d328b95b1a937b1d407c0aa3.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/68f/3d4/95f/68f3d495f8feff18ed874237741a3d1f.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/95c/798/627/95c7986278b1b452f0940315983e469b.jpg)
Теперь на основной сцене остался только узел level_A1, все вложенные элементы сжались внутрь него. В Godot можно включить возможность редактирования содержания сцены-префаба, не открывая его полностью (в меня по правой кнопке нужно отметить пункт Editable Children), но мы не будем этого делать и для редактирования содержимого добавленных сцен-префабов будем открывать их в отдельном окне.
Откроем Level_A1 отдельной сценой, щёлкнув по значку рядом с глазом.
![](https://habrastorage.org/getpro/habr/upload_files/c8e/f90/41e/c8ef9041ec130a7bc2ce3cee50f0f765.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/17a/6a3/fb8/17a6a3fb8a0c4982ddf3c765663f54ea.jpg)
Выделим узлы Pol_A и Pol_A2, скопируем (Ctrl + D). Справа на панели инспектора в разделе Transform в поле Translation допишем к 0 в ячейке Z ещё минус 10 и нажмём ввод (Enter). Каждый скопированный кусочек сдвинется на минус 10 пунктов, и мы получим уже 4 примыкающих друг к другу куска пола.
![](https://habrastorage.org/getpro/habr/upload_files/dbe/6c6/f90/dbe6c6f901c57060579c5071f65ff2bd.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/28c/3a9/c59/28c3a9c590fead3eb1bfe28a46720756.jpg)
Когда нужно изменить числовые параметры в инспекторе (прибавить отнять определённое число), то вместо рассчёта и написания нового результата можно дописывать в поле к текущему параметру + число или - число, чтобы Godot вычислил новый результат автоматически. Если выделены несколько объектов, то изменение параметров полей в инспекторе будет касаться сразу их всех (если, конечно, объекты однотипные, или когда конкретный редактируемый параметр есть у всех выделенных объектов).
Также скопируем три узла Pillar и отодвинем их дальше за синюю стрелку по оси Z.
![](https://habrastorage.org/getpro/habr/upload_files/838/483/9d8/8384839d8e438c9c51f107b7addc5352.jpg)
Сундуки
Выделим отдельно последний узел Pillar_A6 и разберём его обратно, на составляющее, достав из связанного состояния. Для этого нажмём на узел правой кнопкой и выберем пункт Make Loсal.
![](https://habrastorage.org/getpro/habr/upload_files/0cc/159/105/0cc159105b5c7979d6f234c0f8165466.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/515/5a4/073/5155a4073141a8b122e314e4ea9430ad.jpg)
Растребушив префаб удаляем нижний приплюснутый кубик (MeshInstance3), колонну уменьшаем до куба и сдвигаем вниз, верxний сплющенный куб опускаем сверху, сохраняя на небольшом расстоянии.
![](https://habrastorage.org/getpro/habr/upload_files/378/eb8/8ad/378eb88ad9c2917a8c348ea906553270.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/9ad/225/5cb/9ad2255cb3fd99724d8cafd6c67d07ab.jpg)
Зайдём в поле Mesh в каждом из этих двух MeshInstance, выбрав там опцию Make Unique. Визуально ничего не изменится, однако таким образом мы переназначили сеткам форму и она теперь не привязана к форме, оставшейся у кубиков внутри префабов с колоннами.
![](https://habrastorage.org/getpro/habr/upload_files/437/c50/5fe/437c505fef2aa1741f596d50e7237ba8.jpg)
Также нужно нажать на узел CollisionShape и тоже выбрать опцию Make Unique в выпадающем списке для его Shape. Если этого не сделать, то вы сами можете заметить, что коллизия связанна с коллизиями колонн - потянув за точку куба и увидев, что полупрозрачные кубики колонн тоже тянутся. После применения Make Unique эта коллизия станет уникальной, не связанной с прочими.
![](https://habrastorage.org/getpro/habr/upload_files/121/c1b/034/121c1b0343b7bd2729b349578d98e83d.jpg)
![Вариант, когда Make Unique не был выполнен - коллайдеры в колоннах тянутся вслед за редактируемым коллайдером объекта. Вариант, когда Make Unique не был выполнен - коллайдеры в колоннах тянутся вслед за редактируемым коллайдером объекта.](https://habrastorage.org/getpro/habr/upload_files/5c3/646/26d/5c364626d479ca5303e7984529053905.jpg)
Переименовываем узел Pillar_A6 в Chest_A. В поле Translation в инспекторе нажимаем значок сброса кооординат, чтобы Chest_A оказался в начале координат (0 по всем трём осям).
![](https://habrastorage.org/getpro/habr/upload_files/0f7/df8/cb6/0f7df8cb61b035fd8242ed0bc5b93281.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/351/22b/8e2/35122b8e29445bae027d3504c3bffaea.jpg)
Находим его. И превратим в префаб (правая кнопка, Save Branch as Scene).
![](https://habrastorage.org/getpro/habr/upload_files/2ab/f37/3b3/2abf373b3f37b085e50265f3cf0cacea.jpg)
Сейчас у нас открыта папка Levels внутри Prefabs, поднимемся выше и сохраняем Chest_A сюда. Также сохраним текущую сцену Level_A1 (Верхняя панель редактора - Scene - Save Scene).
![](https://habrastorage.org/getpro/habr/upload_files/f0d/eb4/21d/f0deb421da8b3a19f70f2581a1735bf7.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/8bd/f5b/fa8/8bdf5bfa8e5b9a8f328278e43d3a9d16.jpg)
Зайдём теперь внутрь Chest_A, щелкнув по соответствующему значку.
![](https://habrastorage.org/getpro/habr/upload_files/180/ac6/b5c/180ac6b5c865e16e8b3f8606334c6bea.jpg)
Выделим узел Area, и в инспекторе в ячейках Layer у Collision оставим включенной только ячейку 3, таким образом коллизия сундуков будет отличаться от коллизии колонн, располагаясь в другом слое.
![](https://habrastorage.org/getpro/habr/upload_files/794/b97/718/794b97718f67d32ce1eefbad245cf709.jpg)
Выделим корневой узел Chest_A и добавим новый узел Spatial. Переместим его немного вверх и вправо по оси Z, чтобы он оказался на боковой стороне нашего квадратного сундука, в щели между основанием и "крышкой".
![](https://habrastorage.org/getpro/habr/upload_files/a19/144/74b/a1914474b8604438f0c6a09c450891b4.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/6b7/1d7/c3f/6b71d7c3f2fc4b8657d1e4478399d05f.jpg)
Теперь перетащим "крышку" (MeshInstance4) внутрь Spatial.
![](https://habrastorage.org/getpro/habr/upload_files/6eb/b70/b82/6ebb70b82d4e54136e4f4c3991723013.jpg)
Выделим узел Spatial и повернём его по оси X потянув за красный круг (как бы приоткрывая "крышку"). Или же можно вбить угол поворота в редакторе (набираем в инспекторе для Rotation Degrees -25 в ячейке X).
![](https://habrastorage.org/getpro/habr/upload_files/62d/82d/dce/62d82ddce57792e697fedeb3c8d1749d.jpg)
Сохраним текущую сцену. Закроем её вкладку, нажав на крестик рядом с названием.
![](https://habrastorage.org/getpro/habr/upload_files/3a9/7ce/76d/3a97ce76dac182b70bae39bbe2bc1377.jpg)
Оказываемся снова в сцене уровня. Сделаем пару копий Chest_A (Сtrl + D) сдвигая каждую немного по Z. Затем выделим все и переместим правее по оси X, в сторону от колонн.
![](https://habrastorage.org/getpro/habr/upload_files/2a4/be4/729/2a4be47296bbc96fab527b6d0e2675f9.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/7a3/77b/f6e/7a377bf6ef43550b9b2d19bd6b524fec.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/d33/e2e/873/d33e2e8732e382250c24d04f40bf8ccb.jpg)
Запустим игру. Персонаж проходит сквозь сундуки, так как мы перевели их в слой, на который не реагирует рейкаст персонажа. Так пока и оставим.
![](https://habrastorage.org/getpro/habr/upload_files/416/529/c2e/416529c2ed2b3b91ee9bf9a964df03ac.jpg)
Заготовка для врага
Сделаем копию последнего сундука Chest_A3. Выделим получившийся Chest_A4 и сбросим его Translation в инспекторе.
![](https://habrastorage.org/getpro/habr/upload_files/a54/d5f/32f/a54d5f32fd9272c6a0f678ad6f0d7dfd.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/317/3d9/ff8/3173d9ff803d202a14c87961865da3ff.jpg)
Щелкаем по Chest_A4 правой кнопкой и разбираем его на составляющие (Make Local).
![](https://habrastorage.org/getpro/habr/upload_files/91f/7f4/d25/91f7f4d25c3cb2c4963ff3f6f99e2b64.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/d67/405/e35/d67405e3552c6ddcd4ee7e9c40a71159.jpg)
Переименуем узел в Enemy_red.
![](https://habrastorage.org/getpro/habr/upload_files/470/a57/810/470a578108ec4d41b16bfdb3f9a35ffc.jpg)
У обоих кубиков (MeshInstance2 и MeshInstance4) в поле Shape инспектора применим Make Unique.
![](https://habrastorage.org/getpro/habr/upload_files/593/3c9/991/5933c999180ff7dcd7d19e9c41eaf662.jpg)
А вот в узле CollisionShape выберем другую форму коллизии вместо текущей кубической - SphereShape. Радиус сферы установим в 1.5.
![](https://habrastorage.org/getpro/habr/upload_files/1e2/007/2d2/1e20072d229aa526f5880fe965d6f47b.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/fba/df3/954/fbadf395407aa234968b1f17a587f9ce.jpg)
Теперь нажимаем правой кнопкой на узле Enemy_red и сжимаем его в сцену-префаб (правая кнопка, Save Branch as Scene). Сохраняем в ту же текущую папку (Prerfabs) под предлагаемым именем.
Сохраняем текущую сцену уровня. Заходим внутрь сцены Enemy_red. Выделим первый кубик (MeshInstance2), откроем вкладку Material в инспекторе. Щелкнув на пустом поле создадим новый материал, выбрав New SpatialMaterial.
![](https://habrastorage.org/getpro/habr/upload_files/536/297/7ed/5362977edaaa0da38836e220468a36dd.jpg)
Щёлкаем по белой сфере и в открывшихся подробных настройках нового материала открываем вкладку Albelo. Устанавливаем красный цвет в поле Color.
![](https://habrastorage.org/getpro/habr/upload_files/950/71e/6ce/95071e6ce90cd3f9d7865146e07fca1c.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/9fd/3c7/e4e/9fd3c7e4e83a59b27a87adda052b2540.jpg)
Сохраняем материал, нажав на иконку дискетки и выбрав Save As...
![](https://habrastorage.org/getpro/habr/upload_files/673/065/dd3/673065dd3da8f62edb43e359b1108e36.jpg)
Щёлкаем два раза по папке Materials, меняем имя на emeny_red_mat и жмём сохранить.
![](https://habrastorage.org/getpro/habr/upload_files/9de/02e/355/9de02e3554def6a0b12e85ecb5baf567.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/a22/4ed/c2b/a224edc2bb8414ddcb2b840300d295c2.jpg)
Выделим ещё раз узел MeshInstance2, чтобы появились его настройки в инспекторе. Кликаем по выпадающему списку на красной сфере в его поле material и выбираем опцию Copy.
![](https://habrastorage.org/getpro/habr/upload_files/d1f/7c4/f1a/d1f7c4f1a0cf1d106a39b7b5e246f495.jpg)
Теперь выбираем следующий кубик (MeshInstance4), открываем его вкладку Material и в выпадающем списке у поля empty (либо по правой кнопке) выбираем самый нижний вариант - Paste. Красный материал копируется, и теперь оба кубика красные. Сохраним пока сцену.
![](https://habrastorage.org/getpro/habr/upload_files/53e/0a5/e59/53e0a5e591fb8c5028987c6c7bc243c8.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/9ce/c12/174/9cec12174ef54025cfb34b7a27feec28.jpg)
Настало время настроить слои коллизий, чтобы они стали более информативными. Для этого идём в Project - Project Settings...
![](https://habrastorage.org/getpro/habr/upload_files/0c7/7b2/363/0c77b2363756187a457ccde98fd86e20.jpg)
Откроется окошко с кучей настроек.
![](https://habrastorage.org/getpro/habr/upload_files/669/af8/a92/669af8a929a8536fb09450a69177c7bd.jpg)
Проматываем левую колонку вниз и в Layer Names кликаем на строку 3D Physics. В полях справа дописываем свои названия для некоторых слоёв. Layer 2 - solid object, Layer 3 - chest, Layer 4 - enemy, Layer 5 - player. Закрываем настройки, нажав внизу кнопку close.
![](https://habrastorage.org/getpro/habr/upload_files/447/574/208/4475742080a417971760e8a1a10af2f7.jpg)
Выделим теперь узел Area, откроем в инспекторе вкладку Collision, наведём на отмеченную ячейку - там горит подсказка, что слой 3 это chest (сундук). Но в данном случае мы делаем префаб врага, поэтому выключим слой 3 и включим 4 - enemy.
![](https://habrastorage.org/getpro/habr/upload_files/1e1/9d2/f74/1e19d2f741078ee03f9508d5fdce3097.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/556/60e/02f/55660e02f2ac76344f5a9ac2320fc27e.jpg)
Теперь добавим врагу управляющий скрипт. Выделим корневой узел Enemy_red и нажмём на свиток с зелёным плюсиком правее и выше.
![](https://habrastorage.org/getpro/habr/upload_files/3a7/fd7/097/3a7fd7097d0f8379cc8106690ef9a471.jpg)
Жмём на иконку папки, поднимаемся из папки Prefabs на уровень выше и заходим в папку scripts, где уже лежит скрипт основной сцены - Main.gd.
![](https://habrastorage.org/getpro/habr/upload_files/6ca/0d9/791/6ca0d979127c595f3deac17ccf545b19.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/a20/2bb/642/a202bb6424b7a4676d1b7a4e7d5013da.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/72b/f11/7cf/72bf117cf1a02e623e2a249dd1fbf985.jpg)
Правим название на theAI_red и нажимаем Open. Теперь можно нажать Create.
![](https://habrastorage.org/getpro/habr/upload_files/660/9cb/db8/6609cbdb831e7535416bccf324b3af44.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/2b9/07c/62b/2b907c62bd883eb2d621ac0c23abf8c5.jpg)
Попадаем в новый созданный скрипт. Внизу можно нажать на стрелочку, чтобы показать/скрыть панельку с перечнем текущих используемых скриптов.
![](https://habrastorage.org/getpro/habr/upload_files/624/b01/18c/624b0118c29f3383491d29eecbaa5b77.jpg)
Видим там, что у нас сейчас выбран скрипт theAI_red.gd. Удаляем всё, что ниже первой строчки и пишем строки:
func _physics_process(delta):
self.translation.x += delta
![](https://habrastorage.org/getpro/habr/upload_files/12f/8a4/d13/12f8a4d132726a0c8fbc92522f00fd8b.jpg)
Сохраняем текущий выбранный скрипт, щёлкнув по File - Save. Запускаем игру (Кнопка Play, горячая клавиша F5).
![](https://habrastorage.org/getpro/habr/upload_files/745/48f/17c/74548f17cb0f0a980b91973539a39c4a.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/303/88b/f72/30388bf72f2b8c3f4069320b93c9758f.jpg)
Видим, что враг появляется в той же точке, что и игрок, и всё время убегает вправо.
![](https://habrastorage.org/getpro/habr/upload_files/54b/55f/eea/54b55feea6c6c11a8a87337d34078f06.jpg)
Глобальный скрипт и враг-преследователь
Теперь заведём глобальный одиночный скрипт, который будет загружаться вне сцен и хранить какие-то данные, которые можно будет увидеть из прочих скриптов, где бы они не находились. Для этого щёлкнем на строке res:// в небольшом левом окошке с иерархией ресурсов. Нажимаем кнопку New Script.
![](https://habrastorage.org/getpro/habr/upload_files/c0a/335/cef/c0a335cefded2062e9ff53e368a75a56.jpg)
Далее щёлкаем на значок папки. Меняем предложенное название скрипта на букву G (G.gd) и нажимаем Open. Создаём скрипт, нажав на Create.
![](https://habrastorage.org/getpro/habr/upload_files/f54/a71/7c5/f54a717c544777ba4b331f6dc4320f02.jpg)
Теперь откроем его, сделав двойной клик по G.gd в списке ресурсов.
![](https://habrastorage.org/getpro/habr/upload_files/d86/ee6/be2/d86ee6be29ac5c2f772ee0ea35f62a27.jpg)
Уберём все закомментированные строки и выше _ready() напишем строку var player_place = Vector3.ZERO . Сохраним скрипт. Таким образом сейчас у нас есть переменная player_place для хранения координат, доступная в остальных скриптах по имени G.player_place.
![](https://habrastorage.org/getpro/habr/upload_files/028/97e/ce6/02897ece61924c18527e69abdc4e9448.jpg)
Заходим в Project - Project Settings...
![](https://habrastorage.org/getpro/habr/upload_files/713/065/785/7130657852c3430e9aa619e415437e29.jpg)
Переключаемся там на вкладку AutoLoad. Нажимаем на значок папки. Выбираем G.gd. Open.
![](https://habrastorage.org/getpro/habr/upload_files/3fc/762/b39/3fc762b394cdda03264299fa88eee567.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/809/b53/815/809b53815fca39596fc816eea5c565ce.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/65c/a1f/f3e/65ca1ff3e455d98088e383cc0e540702.jpg)
Далее нажимаем самую правую кнопку Add. Появляется строчка с параметрами. Закрываем окно, нажав внизу на Close. Теперь скрипт G стал доступен из других скриптов по своему имени.
![](https://habrastorage.org/getpro/habr/upload_files/f88/bf3/053/f88bf3053a9ca8013b097e8cf0bc84ec.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/f79/6ce/b7f/f796ceb7f76f67620834d8de9835f519.jpg)
Переключимся в скрипт основной сцены theMain.gd.
![](https://habrastorage.org/getpro/habr/upload_files/4bf/54c/78f/4bf54c78f4799ed906435c7356d52b35.jpg)
Допишем ниже строки func _process() пару строк с новой функцией, реагирующей на события ввода:
func _unhandled_input(event):
G.player_place = main_hero.global_translation
![](https://habrastorage.org/getpro/habr/upload_files/a40/eff/c3d/a40effc3d48961f4f2abdc8943d1ce9b.jpg)
Если пользователь что-то нажимает, то мы будем отправлять координаты героя (main_hero.global_translation) в переменную глобального скрипта (player_place).
Сохраняем скрипт. Переключаемся в theAI_red.gd.
![](https://habrastorage.org/getpro/habr/upload_files/d0e/d9c/4f0/d0ed9c4f0839aeeb45e092e48fb0368f.jpg)
Заменим весь его код следующим:
extends Spatial
var speed = 1.0
func _physics_process(delta):
self.look_at(Vector3(G.player_place.x,self.translation.y,G.player_place.z), Vector3.UP)
self.translate(-Vector3.BACK * speed * delta)
![](https://habrastorage.org/getpro/habr/upload_files/a53/72a/8fa/a5372a8fa5e483642de82921aebc3566.jpg)
Запускаем игру. Сейчас враг стал преследовать игрока по его координатам, вращаясь вокруг своей оси, если подошёл совсем близко.
![](https://habrastorage.org/getpro/habr/upload_files/81e/a56/74b/81ea5674b8bcb1e0bebbc0c755ccf6aa.jpg)
Движется враг "спиной", поэтому немного подправим его сцену. Переключаемся в режим 3D, оказавшись на сцене Enemy_red. Выделим корневой узел (Enemy_red) и создадим новый Spatial (автоматически переименуется в Spatial2, так как выше есть узел с таким именем)
![](https://habrastorage.org/getpro/habr/upload_files/4d5/5de/303/4d55de303602f08536594e607293926d.jpg)
Переименуем созданный узел в Visual и сбросим в него все прочие узлы, которые были внутри корневого.
![](https://habrastorage.org/getpro/habr/upload_files/722/39a/076/72239a076ab4587559c6c6b437fe13ad.jpg)
Теперь выделим узел Visual и во вкладке Rotation Degrees инспектора поставим поворот на 180 по Y.
![](https://habrastorage.org/getpro/habr/upload_files/340/5c6/f8f/3405c6f8fa7222590b772aa41368fcb7.jpg)
Запускаем игру, враг теперь поворачивается правильной стороной.
![](https://habrastorage.org/getpro/habr/upload_files/619/e07/487/619e0748793c8528c1d17c6a8f1c4b07.jpg)
Немного упростим запись предустановленных векторов. Переключимся на скрипты и допишем в G.gd после первой строки упрощённые записи единичных векторов по основным осям:
const _X = Vector3.RIGHT
const _Y = Vector3.UP
const _Z = Vector3.BACK
![](https://habrastorage.org/getpro/habr/upload_files/fd3/19a/162/fd319a162c9d556254c2f0c2848210a9.jpg)
Сохраним скрипт и вернёмся в theAI_red.gd, переписав его следующим образом (ничего не меняя в логике, просто немного упростив запись):
extends Spatial
var speed = 1.0
func _physics_process(delta):
self.look_at(Vector3(G.player_place.x,self.translation.y,G.player_place.z), G._Y)
self.translate(-G._Z * speed * delta)
![](https://habrastorage.org/getpro/habr/upload_files/67a/e72/5c4/67ae725c4f00ce691535fa929645020d.jpg)
То есть, например, вместо записи Vector3.RIGHT мы теперь используем G._Y
Сохраняем скрипт. Закрываем сцены Enemy_red и Level_A1 (щелкая по крестикам). Оказываемся в главной сцене, переключаемся на 3D.
![](https://habrastorage.org/getpro/habr/upload_files/c83/2d8/168/c832d8168ad824cd5bdf7fbbd1f0794d.jpg)
Добавим источник света, чтобы картина стала повеселее. Щёлкаем на узел Hero, добавляем ему новый узел OmniLight.
![](https://habrastorage.org/getpro/habr/upload_files/b72/dfa/681/b72dfa68195ccf5cd1658fc68ab71daf.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/7ae/249/83d/7ae24983db3dcb743b9e7bca0465f0cf.jpg)
Поднимем источник света выше (примерно на 3 по y). В настройках источника света в инспекторе увеличим Range до 7 и включим галочку Enabled во вкладке Shadows (чтобы источник света отбрасывал тени, а не только добавлял освещённости).
![](https://habrastorage.org/getpro/habr/upload_files/695/fa4/409/695fa4409f34c2141791949d451df7e7.jpg)
Запускаем игру. Теперь всё смотрится бодрее.
![](https://habrastorage.org/getpro/habr/upload_files/822/ff7/849/822ff7849118073282816e8666ef8232.jpg)
Улучшаем управление
Донастроим управление. Перемещение стрелками хотелось бы дублировать кнопками WASD. Откроем Project - Project Settings... Переключимся на вкладку Input Map.
![](https://habrastorage.org/getpro/habr/upload_files/111/af4/65a/111af465a33075b240b86c45f53ecf23.jpg)
Находим здесь строчку ui_left. Этот ключ уже используется в нашем управлении персонажем, теперь осталось добавить в него дополнительную кнопку. Нажимаем на плюсик сбоку, выбирая Key.
![](https://habrastorage.org/getpro/habr/upload_files/5ad/e91/c05/5ade91c05a0162e2e929fd3e9caabd1e.jpg)
Появится окошко с предложением нажать кнопку. Нажимаем A и жмём ок.
![](https://habrastorage.org/getpro/habr/upload_files/12d/19c/abd/12d19cabdc6fa092571a018ef0f62066.jpg)
Внутри группы записей под ui_left появилась новая строчка.
![](https://habrastorage.org/getpro/habr/upload_files/fde/be2/f01/fdebe2f01c4b67e33d50757ad201eca9.jpg)
Аналогичным образом добавляем D в ui_right, W в ui_up и S в ui_down.
![](https://habrastorage.org/getpro/habr/upload_files/cb3/566/ae7/cb3566ae775c9ca159c333ffcda7e090.jpg)
Добавим также пару своих новых ключей, связанных с кнопками мыши. Для этого в верхней строчке Action пишем имя нового ключа mouse_L и нажимаем справа кнопку Add.
![](https://habrastorage.org/getpro/habr/upload_files/7e0/428/2c8/7e04282c8502e01487a1ee69cde3bdbd.jpg)
Находим строчку mouse_L, нажимаем на плюс справа и на этот раз выбираем пункт Mouse Button.
![](https://habrastorage.org/getpro/habr/upload_files/e29/0a3/872/e290a3872056aa642511a8ff765ac79a.jpg)
Здесь сразу выставлена левая кнопка, поэтому сразу подтверждаем нажав на Add.
![](https://habrastorage.org/getpro/habr/upload_files/662/467/352/662467352ef2c45229b423b035018e5d.jpg)
Аналогичным образом заведём ключ для правой кнопки мыши - mouse_R.
![](https://habrastorage.org/getpro/habr/upload_files/d83/405/f88/d83405f8853fe0897085a040c48f73f3.jpg)
На этом закрываем окошко, нажав на Close. Если запустить игру сейчас, то персонаж будет управляться и клавиатурными стрелками и кнопками WASD.
Повесим теперь на кнопки мыши, например, операции включения и выключения источника света. Для этого откроем скрипт theMain.gd и допишем сверху, где-нибудь после строки onready var hero_visual = $Hero/Visual строчку onready var light = $Hero/OmniLight - чтобы завести ссылку на источник света.
![](https://habrastorage.org/getpro/habr/upload_files/73b/39c/353/73b39c353953a368b014c41421df04fa.jpg)
Теперь сместимся по скрипту ниже и выше function _process() добавим следующие строки:
func _physics_process(delta):
if Input.is_action_pressed("mouse_L"):
light.show()
if Input.is_action_pressed("mouse_R"):
light.hide()
![](https://habrastorage.org/getpro/habr/upload_files/bba/d55/046/bbad55046c38bf4150cdefd08df5f26c.jpg)
Как видно, в коде сейчас идут друг за другом несколько функций: _unhandled_input, _physics_process и _process. Для простоты можно считать, что они расположены в порядке от самой медленно срабатывающей функции, до самой быстрой. На самом деле всё немного сложнее (первая происходит лишь в моменты ввода, вторая крутится в цикле с фиксированной частотой, третья крутится в цикле с максимальной частотой), но что касается проверок ввода вроде if Input.is_action_pressed - если расположить их в _unhandled_input (хотя она предназначена скорее для обработки events), то скорость срабатывания может быть недостаточной (эффект происходит когда кнопка чётко нажата, с фиксацией на ней). В то время как внутри _physics_process скорость срабатывания скорее всего уже оптимальная, не говоря уже о _process. Проверки разового нажатия (if Input.is_action_just_pressed вместо длительного action_pressed) так и вовсе не сработают внутри _unhandled_input.
Когда нужно сделать, чтобы что-то самостоятельно (без пользовательского ввода) происходило в цикле с меньшей частотой, чем у функции _physics_process (не делая слишком много вычислений в каждом такте), то можно в ней завести таймер, который будет запускать нужные операции через определённые отрезки времени, снижая общую вычислительную нагрузку.
Можно запустить игру и убедиться, что при нажатии на кнопки мыши источник света включается и отключается. Также можно ради интереса временно перетащить проверки ввода из _physics_process в _unhandled_input, для того чтобы выяснить насколько медленнее будут срабатывать кнопки мыши. Кстати, стоит помнить, что отжатие (как и нажатие) какой-то кнопки тоже вызывает срабатывание функции _unhandled_input.
Теперь можно упростить код ранее созданного движения/вращения персонажа, сократив количество проверок и перебросив его в _physics_process. Для этого выполним сначала предварительные действия - выделим всё, что было внутри функции _prоcess и нажмём Ctrl + K, чтобы строки закомментировались и были исключены из выполнения.
![](https://habrastorage.org/getpro/habr/upload_files/af6/7d0/ef4/af67d0ef40f37e8c96f17dc6475de0d5.jpg)
Вверху скрипта удалим строчки var horizontal_joy = 0 и var vertikal_joy = 0.
![](https://habrastorage.org/getpro/habr/upload_files/630/084/c0c/630084c0cf520536135b104dc73e51e4.jpg)
Затем добавляем сюда строчку var move_direction = Vector3.ZERO
![](https://habrastorage.org/getpro/habr/upload_files/f85/42e/987/f8542e9875dbb3c9c67409418c39999e.jpg)
Спустимся к функции _unhanlled_input и заменим её код на такой:
func _unhandled_input(event):
if game_mode == 1:
G.player_place = main_hero.global_translation
move_direction.x = Input.get_axis("ui_left","ui_right")
move_direction.z = Input.get_axis("ui_up","ui_down")
print(move_direction)
if move_direction != Vector3.ZERO:
hero_visual.look_at(hero_visual.global_transform.origin + move_direction, G._Y)
![](https://habrastorage.org/getpro/habr/upload_files/60d/8a5/a37/60d8a5a371dd66dfb8d95e646ea376b8.jpg)
Далее переписываем функцию _physics_process таким образом:
func _physics_process(delta):
if game_mode == 1:
if Input.is_action_pressed("mouse_L"):
light.show()
if Input.is_action_pressed("mouse_R"):
light.hide()
if rcast.is_colliding() == false:
if move_direction != Vector3.ZERO:
main_hero.translation += move_direction * hero_speed * delta
else:
HealthDamage(1)
if health < 0:
health = 0
game_mode = 0
G.player_place = Vector3.ZERO
move_direction = Vector3.ZERO
ui_startscreen.show()
![](https://habrastorage.org/getpro/habr/upload_files/7c6/0a4/45d/7c60a445d7c29f4a298833a2b481c024.jpg)
Запускаем игру.
![](https://habrastorage.org/getpro/habr/upload_files/17d/76d/bbf/17d76dbbfdfb2cde3161807d83e72194.jpg)
В логе теперь выводится общий вектор move_direction, получаемый при вводе от пользователя в _unhandled_input. Если вектор ненулевой (то есть нужные ключи направления были нажаты), то персонаж поворачивается. Во время _physics_process персонаж, пока вектор направления остаётся куда-то повёрнут (то есть пока кнопки движения не отжаты) - движется. Если отжать кнопки движения, то в момент отжатия _unhandled_input вычислит, что move_direction снова стал нулевым вектором и, соответственно, _physics_process перестанет двигать персонажа. Правда следует учитывать, что событие отжатия может остаться необработаным функцией _unhandled_input, если пользователь в процессе куда-то переключился из окна игры - тогда персонаж залипнет в движении, до тех пор пока в контексте не окажется игровое окно и снова не поступит ввод от пользователя.
Теперь закомментированную функцию _process можно удалить, так как пока она не понадобится.
Выстрелы и прочее
Сделаем теперь простенький выстрел. Переключимся на 3D в сцену MainScene. Выделим узел Main и добавим новый узел Area.
![](https://habrastorage.org/getpro/habr/upload_files/8e6/2b5/ee6/8e62b5ee68bf2255097ea6c25cd2fb1d.jpg)
Добавим к Area узел CollisionShape, выберем ему форму сферы (New SphereShape) в поле Shape в инспекторе.
![](https://habrastorage.org/getpro/habr/upload_files/f5f/cbd/2d1/f5fcbd2d117b5f658a41f7e0d93b3ebb.jpg)
Откроем более подробные настройки SphereShape (щёлкнув по cfvjq надписи SphereShape) и поставим радиус 0.5.
![](https://habrastorage.org/getpro/habr/upload_files/95f/7de/4c5/95f7de4c5132b93408ac60caa65d7393.jpg)
Переименуем узел Area в MagicStrike. Сохраним как префаб (правая кнопка, Save Brunch as Scene).
![](https://habrastorage.org/getpro/habr/upload_files/5b2/142/a72/5b2142a72aad23942dd45cc4f84b3cd8.jpg)
Щёлкнем по папке Prefabs. Создадим внутри неё папку Spawned, куда и сохраним наш префаб MagicStrike.
![](https://habrastorage.org/getpro/habr/upload_files/98e/8b5/9e5/98e8b59e5c6f0df711447b453b784365.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/ea6/ecf/d80/ea6ecfd803686a4f279ef7bfe2e4713d.jpg)
Заходим внутрь префаба.
![](https://habrastorage.org/getpro/habr/upload_files/d51/eeb/cb9/d51eebcb9d2dedf9829f0927c32de721.jpg)
Выделим корневой узел и добавим к нему (Ctrl + A) новый узел MeshInstance, чтобы сделать выстрелу какую-то визуальную форму. Заходим в новом узле в его поле Mesh в инспекторе и выбираем New PrismMesh.
![](https://habrastorage.org/getpro/habr/upload_files/96a/990/e12/96a990e12b97d49cb67bcac1c01977f8.jpg)
В поле Scale поставим размеры 0.3 по всем осям.
![](https://habrastorage.org/getpro/habr/upload_files/5d6/a56/87d/5d6a5687d6f4db7b7c039c809af2e4bc.jpg)
Выделим корневой узел (Magic Strike) и нажмём на свиток с зелёным плюсом, чтобы добавить скрипт.
![](https://habrastorage.org/getpro/habr/upload_files/ed7/637/fdc/ed7637fdce61209e7406987860986f38.jpg)
Нажимаем на иконку папки. Поднимаемся пару раз вверх.
![](https://habrastorage.org/getpro/habr/upload_files/237/ebe/6e4/237ebe6e4e8e6416fd90b84fadd9ad1f.jpg)
![](https://habrastorage.org/getpro/habr/upload_files/86b/b53/1ff/86bb531ff2eafada58b0b1e276fcc788.jpg)
Щелкаем два раза на папке Scripts.
![](https://habrastorage.org/getpro/habr/upload_files/8a4/c51/951/8a4c519517c3f9dc7040180a56dbc496.jpg)
Меняем название на theMagicStrike и жмём Open.
![](https://habrastorage.org/getpro/habr/upload_files/5dd/370/8f3/5dd3708f3aa81f524782b3b63ae47367.jpg)
Нажимаем Create.
![](https://habrastorage.org/getpro/habr/upload_files/501/b16/1c4/501b161c4d35e75696b6005acb0d8e84.jpg)
Открылся скрипт выстрела. Меняем его код на следующий:
extends Area
var timer = 0.0
var lifetime = 2.0
func _physics_process(delta):
if timer > -1.0:
timer += delta
if timer > lifetime:
timer = -1.0
queue_free()
![](https://habrastorage.org/getpro/habr/upload_files/7db/232/d73/7db232d73254fba7d376019569429cb3.jpg)
Сохраним скрипт. Запустим игру (сохранив тем самым открытые сцены). На точке старта должен появляться белый треугольник, и через пару мгновений пропадать.
Переключимся на 3D, закроем сцену MagicStrike, оказавшись в MainScene.
Удалим теперь сам узел MagicStrike с главной сцены.
![](https://habrastorage.org/getpro/habr/upload_files/7c4/6b3/9ca/7c46b39ca8ef16c6ca7b656ec56a5359.jpg)
Делаем мы это потому, что иметь выстрел изначально в сцене нам не нужно, мы будем создавать его префаб во время игры, из кода.
Заходим в скрипты (Scripts), открываем скрипт theMain. Пишем ниже первой строки новую: var magic_strike = preload("res://Prefabs/Spawned/MagicStrike.tscn")
![](https://habrastorage.org/getpro/habr/upload_files/c11/ae5/d6e/c11ae5d6e0d29527e6b4806c727b45c8.jpg)
Таким образом мы предзагрузим префаб-выстрел, чтобы создавать его в процессе игры.
Теперь спускаемся ниже, в функцию physics_process и пописываем в условие if Input.is_action_pressed("mouse_L"): , ниже light.show() новые строчки:
var cloned_pref = magic_strike.instance()
cloned_pref.transform = main_hero.transform
cloned_pref.translation.y = 0.5
self.add_child(cloned_pref)
![](https://habrastorage.org/getpro/habr/upload_files/b59/a32/de4/b59a32de4bd3b8faf570d77aadedd5c6.jpg)
Запустим игру, видим, что если нажимать правую кнопку мыши, то за персонажем образуется целая "змейка" из треугольных выстрелов. Это происходит потому, что мы поместили создание выстрела в условие if Input.is_action_pressed("mouse_L"), где is_action_pressed означает, что событие происходит всё время, когда нажата кнопка.
![](https://habrastorage.org/getpro/habr/upload_files/897/16f/cbd/89716fcbd549e0ea03d932cc558ef933.jpg)
Поправим это, дописав выше только что написанного куска кода отдельное условие, которое будет происходить разово, только в момент нажатия кнопки. Вот, как нужно переписать эту часть:
if Input.is_action_just_pressed("mouse_L"):
var cloned_pref = magic_strike.instance()
cloned_pref.transform = main_hero.transform
cloned_pref.translation.y = 0.5
self.add_child(cloned_pref)
![](https://habrastorage.org/getpro/habr/upload_files/b02/98f/6c5/b0298f6c5399d42e212c3fafb85f6f53.jpg)
Теперь на каждое нажатие левой кнопки мыши создаётся один треугольный выстрел.
![](https://habrastorage.org/getpro/habr/upload_files/8b7/091/c55/8b7091c550900a841328b4ae6829f749.jpg)
Переключимся теперь в 3D в основную сцену. Зайдём в узел уровня (Level_A1).
![](https://habrastorage.org/getpro/habr/upload_files/b6d/54e/76b/b6d54e76b05da43f2993169f86437c03.jpg)
Выделим узел врага (Enemy_red) и сделаем ещё 4 копии (Ctrl + D).
![](https://habrastorage.org/getpro/habr/upload_files/485/bcb/355/485bcb35528396b0ac594dba7f9f3d1b.jpg)
Теперь растащим их примерно по углам уровня. И одного в центре.
![](https://habrastorage.org/getpro/habr/upload_files/7fb/9ed/9a5/7fb9ed9a55f013730accd3bed43bd67c.jpg)
Запустим игру. Теперь врагов стало много.
![](https://habrastorage.org/getpro/habr/upload_files/4eb/005/f33/4eb005f335012a40a6bb87d4f5dc721f.jpg)
Разве что они все сжимаются в одного, когда проходит некоторое время и во время перезапуска. Немного подправим этот момент. Открываем скрипты, заходим в theAI_red.gd и напишем временное частичное решение, переписав код таким образом:
extends Spatial
var speed = 1.0
var my_point = Vector3.ZERO
func _ready():
my_point = self.translation
func _physics_process(delta):
self.look_at(Vector3(G.player_place.x,self.translation.y,G.player_place.z), G._Y)
self.translate(-G._Z * speed * delta)
if G.player_place == Vector3.ZERO:
self.translation = my_point
![](https://habrastorage.org/getpro/habr/upload_files/0de/f60/ddc/0def60ddca1f14992b36ad040e7d892b.jpg)
Теперь каждый враг запоминает свою начальную позицию, а также смотрит - не оказался ли внезапно игрок в нулевых координатах (что случается при рестарте уровня). Таким образом слепившихся вместе врагов можно сбросить на их начальные позиции, потеряв всё здоровье от соприкосновения с колоннами.
На этом пока и остановимся.
Код получившихся в итоге скриптов
G.gd
extends Node
const _X = Vector3.RIGHT
const _Y = Vector3.UP
const _Z = Vector3.BACK
var player_place = Vector3.ZERO
func _ready():
pass # Replace with function body.
theMain.gd
extends Spatial
var magic_strike = preload("res://Prefabs/Spawned/MagicStrike.tscn")
onready var ui_startscreen = $StartScreen
onready var healthbar = $Interface/HealthGauge/Bar
onready var main_hero = $Hero
onready var hero_visual = $Hero/Visual
onready var light = $Hero/OmniLight
onready var rcast = $Hero/Visual/RayCast
var move_direction = Vector3.ZERO
var hero_speed = 2.2
var game_mode = 0
var health = 200
func _ready():
main_hero.translation.x += 10.0
func _unhandled_input(event):
if game_mode == 1:
G.player_place = main_hero.global_translation
move_direction.x = Input.get_axis("ui_left","ui_right")
move_direction.z = Input.get_axis("ui_up","ui_down")
print(move_direction)
if move_direction != Vector3.ZERO:
hero_visual.look_at(hero_visual.global_transform.origin + move_direction, G._Y)
func _physics_process(delta):
if game_mode == 1:
if Input.is_action_pressed("mouse_L"):
light.show()
if Input.is_action_just_pressed("mouse_L"):
var cloned_pref = magic_strike.instance()
cloned_pref.transform = main_hero.transform
cloned_pref.translation.y = 0.5
self.add_child(cloned_pref)
if Input.is_action_pressed("mouse_R"):
light.hide()
if rcast.is_colliding() == false:
if move_direction != Vector3.ZERO:
main_hero.translation += move_direction * hero_speed * delta
else:
HealthDamage(1)
if health < 0:
health = 0
game_mode = 0
G.player_place = Vector3.ZERO
move_direction = Vector3.ZERO
ui_startscreen.show()
func HealthDamage(amount):
health -= amount
if health > 0:
healthbar.rect_size.x = health
func _on_StartButton_pressed():
ui_startscreen.hide()
game_mode = 1
main_hero.translation = Vector3(0.0,0.0,0.0)
health = 200
HealthDamage(0)
theAI_red.gd
extends Spatial
var speed = 1.0
var my_point = Vector3.ZERO
func _ready():
my_point = self.translation
func _physics_process(delta):
self.look_at(Vector3(G.player_place.x,self.translation.y,G.player_place.z), G._Y)
self.translate(-G._Z * speed * delta)
if G.player_place == Vector3.ZERO:
self.translation = my_point
theMagicStrike.gd
extends Area
var timer = 0.0
var lifetime = 2.0
func _physics_process(delta):
if timer > -1.0:
timer += delta
if timer > lifetime:
timer = -1.0
queue_free()
![](https://habrastorage.org/getpro/habr/upload_files/bda/b0a/135/bdab0a135c856f898ff4b50a95374454.jpg)
Предыдущая статья:
glebasterBajo
Игровой персонаж при виде врагов "кирпичи откладывает", я правильно понял? )