Godot | Open Dungeon | Часть beta

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Больше функционала для минималистичного прототипа игры: объекты уровня, враги, апгрейд управления, глобальный скрипт и статичные выстрелы.

что получится в итоге
что получится в итоге

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

В этой статье мы пошагово обновим прототип alfa до состояния beta:

Запускаем Godot. Открываем проект Open Dungeon, который мы создали ранее в части alpha . Открывается сцена MainScene.

Уровень

Добавим к Main (корневой узел сцены) новый узел Spatial. Перетащим в него все копии Pol и Pillar, затем переименуем этот узел в Level_A1.

далее выбрать выбрать в списке узел Spatial
далее выбрать выбрать в списке узел Spatial
Перетащить выделенные объекты в Spatial
Перетащить выделенные объекты в Spatial
Переименовать Spatial в Level_A1
Переименовать Spatial в Level_A1

Щёлкаем по узлу правой кнопкой, выбирая Save Branch as Scene. Делаем двойной щелчок на папке Prefabs (входя в неё) затем нажимаем сверху справа Create Folder. Пишем имя папки - Levels. Ок. Сохраняем префаб уровня.

Теперь на основной сцене остался только узел level_A1, все вложенные элементы сжались внутрь него. В Godot можно включить возможность редактирования содержания сцены-префаба, не открывая его полностью (в меня по правой кнопке нужно отметить пункт Editable Children), но мы не будем этого делать и для редактирования содержимого добавленных сцен-префабов будем открывать их в отдельном окне.

Откроем Level_A1 отдельной сценой, щёлкнув по значку рядом с глазом.

Выделим узлы Pol_A и Pol_A2, скопируем (Ctrl + D). Справа на панели инспектора в разделе Transform в поле Translation допишем к 0 в ячейке Z ещё минус 10 и нажмём ввод (Enter). Каждый скопированный кусочек сдвинется на минус 10 пунктов, и мы получим уже 4 примыкающих друг к другу куска пола.

Когда нужно изменить числовые параметры в инспекторе (прибавить отнять определённое число), то вместо рассчёта и написания нового результата можно дописывать в поле к текущему параметру + число или - число, чтобы Godot вычислил новый результат автоматически. Если выделены несколько объектов, то изменение параметров полей в инспекторе будет касаться сразу их всех (если, конечно, объекты однотипные, или когда конкретный редактируемый параметр есть у всех выделенных объектов).

Также скопируем три узла Pillar и отодвинем их дальше за синюю стрелку по оси Z.

Сундуки

Выделим отдельно последний узел Pillar_A6 и разберём его обратно, на составляющее, достав из связанного состояния. Для этого нажмём на узел правой кнопкой и выберем пункт Make Loсal.

Растребушив префаб удаляем нижний приплюснутый кубик (MeshInstance3), колонну уменьшаем до куба и сдвигаем вниз, верxний сплющенный куб опускаем сверху, сохраняя на небольшом расстоянии.

Зайдём в поле Mesh в каждом из этих двух MeshInstance, выбрав там опцию Make Unique. Визуально ничего не изменится, однако таким образом мы переназначили сеткам форму и она теперь не привязана к форме, оставшейся у кубиков внутри префабов с колоннами.

Также нужно нажать на узел CollisionShape и тоже выбрать опцию Make Unique в выпадающем списке для его Shape. Если этого не сделать, то вы сами можете заметить, что коллизия связанна с коллизиями колонн - потянув за точку куба и увидев, что полупрозрачные кубики колонн тоже тянутся. После применения Make Unique эта коллизия станет уникальной, не связанной с прочими.

Вариант, когда Make Unique не был выполнен - коллайдеры в колоннах тянутся вслед за редактируемым коллайдером объекта.
Вариант, когда Make Unique не был выполнен - коллайдеры в колоннах тянутся вслед за редактируемым коллайдером объекта.

Переименовываем узел Pillar_A6 в Chest_A. В поле Translation в инспекторе нажимаем значок сброса кооординат, чтобы Chest_A оказался в начале координат (0 по всем трём осям).

Находим его. И превратим в префаб (правая кнопка, Save Branch as Scene).

Сейчас у нас открыта папка Levels внутри Prefabs, поднимемся выше и сохраняем Chest_A сюда. Также сохраним текущую сцену Level_A1 (Верхняя панель редактора - Scene - Save Scene).

Зайдём теперь внутрь Chest_A, щелкнув по соответствующему значку.

Выделим узел Area, и в инспекторе в ячейках Layer у Collision оставим включенной только ячейку 3, таким образом коллизия сундуков будет отличаться от коллизии колонн, располагаясь в другом слое.

Выделим корневой узел Chest_A и добавим новый узел Spatial. Переместим его немного вверх и вправо по оси Z, чтобы он оказался на боковой стороне нашего квадратного сундука, в щели между основанием и "крышкой".

Теперь перетащим "крышку" (MeshInstance4) внутрь Spatial.

Выделим узел Spatial и повернём его по оси X потянув за красный круг (как бы приоткрывая "крышку"). Или же можно вбить угол поворота в редакторе (набираем в инспекторе для Rotation Degrees -25 в ячейке X).

Сохраним текущую сцену. Закроем её вкладку, нажав на крестик рядом с названием.

Оказываемся снова в сцене уровня. Сделаем пару копий Chest_A (Сtrl + D) сдвигая каждую немного по Z. Затем выделим все и переместим правее по оси X, в сторону от колонн.

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

Заготовка для врага

Сделаем копию последнего сундука Chest_A3. Выделим получившийся Chest_A4 и сбросим его Translation в инспекторе.

Щелкаем по Chest_A4 правой кнопкой и разбираем его на составляющие (Make Local).

Переименуем узел в Enemy_red.

У обоих кубиков (MeshInstance2 и MeshInstance4) в поле Shape инспектора применим Make Unique.

А вот в узле CollisionShape выберем другую форму коллизии вместо текущей кубической - SphereShape. Радиус сферы установим в 1.5.

Теперь нажимаем правой кнопкой на узле Enemy_red и сжимаем его в сцену-префаб (правая кнопка, Save Branch as Scene). Сохраняем в ту же текущую папку (Prerfabs) под предлагаемым именем.

Сохраняем текущую сцену уровня. Заходим внутрь сцены Enemy_red. Выделим первый кубик (MeshInstance2), откроем вкладку Material в инспекторе. Щелкнув на пустом поле создадим новый материал, выбрав New SpatialMaterial.

Щёлкаем по белой сфере и в открывшихся подробных настройках нового материала открываем вкладку Albelo. Устанавливаем красный цвет в поле Color.

Сохраняем материал, нажав на иконку дискетки и выбрав Save As...

Щёлкаем два раза по папке Materials, меняем имя на emeny_red_mat и жмём сохранить.

Выделим ещё раз узел MeshInstance2, чтобы появились его настройки в инспекторе. Кликаем по выпадающему списку на красной сфере в его поле material и выбираем опцию Copy.

Теперь выбираем следующий кубик (MeshInstance4), открываем его вкладку Material и в выпадающем списке у поля empty (либо по правой кнопке) выбираем самый нижний вариант - Paste. Красный материал копируется, и теперь оба кубика красные. Сохраним пока сцену.

Настало время настроить слои коллизий, чтобы они стали более информативными. Для этого идём в Project - Project Settings...

Откроется окошко с кучей настроек.

Проматываем левую колонку вниз и в Layer Names кликаем на строку 3D Physics. В полях справа дописываем свои названия для некоторых слоёв. Layer 2 - solid object, Layer 3 - chest, Layer 4 - enemy, Layer 5 - player. Закрываем настройки, нажав внизу кнопку close.

Выделим теперь узел Area, откроем в инспекторе вкладку Collision, наведём на отмеченную ячейку - там горит подсказка, что слой 3 это chest (сундук). Но в данном случае мы делаем префаб врага, поэтому выключим слой 3 и включим 4 - enemy.

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

Жмём на иконку папки, поднимаемся из папки Prefabs на уровень выше и заходим в папку scripts, где уже лежит скрипт основной сцены - Main.gd.

Правим название на theAI_red и нажимаем Open. Теперь можно нажать Create.

Попадаем в новый созданный скрипт. Внизу можно нажать на стрелочку, чтобы показать/скрыть панельку с перечнем текущих используемых скриптов.

Видим там, что у нас сейчас выбран скрипт theAI_red.gd. Удаляем всё, что ниже первой строчки и пишем строки:

func _physics_process(delta):
	self.translation.x += delta

Сохраняем текущий выбранный скрипт, щёлкнув по File - Save. Запускаем игру (Кнопка Play, горячая клавиша F5).

Видим, что враг появляется в той же точке, что и игрок, и всё время убегает вправо.

Глобальный скрипт и враг-преследователь

Теперь заведём глобальный одиночный скрипт, который будет загружаться вне сцен и хранить какие-то данные, которые можно будет увидеть из прочих скриптов, где бы они не находились. Для этого щёлкнем на строке res:// в небольшом левом окошке с иерархией ресурсов. Нажимаем кнопку New Script.

Далее щёлкаем на значок папки. Меняем предложенное название скрипта на букву G (G.gd) и нажимаем Open. Создаём скрипт, нажав на Create.

Теперь откроем его, сделав двойной клик по G.gd в списке ресурсов.

Уберём все закомментированные строки и выше _ready() напишем строку var player_place = Vector3.ZERO . Сохраним скрипт. Таким образом сейчас у нас есть переменная player_place для хранения координат, доступная в остальных скриптах по имени G.player_place.

Заходим в Project - Project Settings...

Переключаемся там на вкладку AutoLoad. Нажимаем на значок папки. Выбираем G.gd. Open.

Далее нажимаем самую правую кнопку Add. Появляется строчка с параметрами. Закрываем окно, нажав внизу на Close. Теперь скрипт G стал доступен из других скриптов по своему имени.

Переключимся в скрипт основной сцены theMain.gd.

Допишем ниже строки func _process() пару строк с новой функцией, реагирующей на события ввода:

func _unhandled_input(event):
	G.player_place = main_hero.global_translation

Если пользователь что-то нажимает, то мы будем отправлять координаты героя (main_hero.global_translation) в переменную глобального скрипта (player_place).

Сохраняем скрипт. Переключаемся в 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), Vector3.UP)

	self.translate(-Vector3.BACK * speed * delta)

Запускаем игру. Сейчас враг стал преследовать игрока по его координатам, вращаясь вокруг своей оси, если подошёл совсем близко.

Движется враг "спиной", поэтому немного подправим его сцену. Переключаемся в режим 3D, оказавшись на сцене Enemy_red. Выделим корневой узел (Enemy_red) и создадим новый Spatial (автоматически переименуется в Spatial2, так как выше есть узел с таким именем)

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

Теперь выделим узел Visual и во вкладке Rotation Degrees инспектора поставим поворот на 180 по Y.

Запускаем игру, враг теперь поворачивается правильной стороной.

Немного упростим запись предустановленных векторов. Переключимся на скрипты и допишем в G.gd после первой строки упрощённые записи единичных векторов по основным осям:

const _X = Vector3.RIGHT
const _Y = Vector3.UP
const _Z = Vector3.BACK

Сохраним скрипт и вернёмся в 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)

То есть, например, вместо записи Vector3.RIGHT мы теперь используем G._Y

Сохраняем скрипт. Закрываем сцены Enemy_red и Level_A1 (щелкая по крестикам). Оказываемся в главной сцене, переключаемся на 3D.

Добавим источник света, чтобы картина стала повеселее. Щёлкаем на узел Hero, добавляем ему новый узел OmniLight.

Поднимем источник света выше (примерно на 3 по y). В настройках источника света в инспекторе увеличим Range до 7 и включим галочку Enabled во вкладке Shadows (чтобы источник света отбрасывал тени, а не только добавлял освещённости).

Запускаем игру. Теперь всё смотрится бодрее.

Улучшаем управление

Донастроим управление. Перемещение стрелками хотелось бы дублировать кнопками WASD. Откроем Project - Project Settings... Переключимся на вкладку Input Map.

Находим здесь строчку ui_left. Этот ключ уже используется в нашем управлении персонажем, теперь осталось добавить в него дополнительную кнопку. Нажимаем на плюсик сбоку, выбирая Key.

Появится окошко с предложением нажать кнопку. Нажимаем A и жмём ок.

Внутри группы записей под ui_left появилась новая строчка.

Аналогичным образом добавляем D в ui_right, W в ui_up и S в ui_down.

Добавим также пару своих новых ключей, связанных с кнопками мыши. Для этого в верхней строчке Action пишем имя нового ключа mouse_L и нажимаем справа кнопку Add.

Находим строчку mouse_L, нажимаем на плюс справа и на этот раз выбираем пункт Mouse Button.

Здесь сразу выставлена левая кнопка, поэтому сразу подтверждаем нажав на Add.

Аналогичным образом заведём ключ для правой кнопки мыши - mouse_R.

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

Повесим теперь на кнопки мыши, например, операции включения и выключения источника света. Для этого откроем скрипт theMain.gd и допишем сверху, где-нибудь после строки onready var hero_visual = $Hero/Visual строчку onready var light = $Hero/OmniLight - чтобы завести ссылку на источник света.

Теперь сместимся по скрипту ниже и выше function _process() добавим следующие строки:

func _physics_process(delta):
	if Input.is_action_pressed("mouse_L"):
		light.show()
	
	if Input.is_action_pressed("mouse_R"):
		light.hide()

Как видно, в коде сейчас идут друг за другом несколько функций: _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, чтобы строки закомментировались и были исключены из выполнения.

Вверху скрипта удалим строчки var horizontal_joy = 0 и var vertikal_joy = 0.

Затем добавляем сюда строчку var move_direction = Vector3.ZERO

Спустимся к функции _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)

Далее переписываем функцию _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()

Запускаем игру.

В логе теперь выводится общий вектор move_direction, получаемый при вводе от пользователя в _unhandled_input. Если вектор ненулевой (то есть нужные ключи направления были нажаты), то персонаж поворачивается. Во время _physics_process персонаж, пока вектор направления остаётся куда-то повёрнут (то есть пока кнопки движения не отжаты) - движется. Если отжать кнопки движения, то в момент отжатия _unhandled_input вычислит, что move_direction снова стал нулевым вектором и, соответственно, _physics_process перестанет двигать персонажа. Правда следует учитывать, что событие отжатия может остаться необработаным функцией _unhandled_input, если пользователь в процессе куда-то переключился из окна игры - тогда персонаж залипнет в движении, до тех пор пока в контексте не окажется игровое окно и снова не поступит ввод от пользователя.

Теперь закомментированную функцию _process можно удалить, так как пока она не понадобится.

Выстрелы и прочее

Сделаем теперь простенький выстрел. Переключимся на 3D в сцену MainScene. Выделим узел Main и добавим новый узел Area.

Добавим к Area узел CollisionShape, выберем ему форму сферы (New SphereShape) в поле Shape в инспекторе.

Откроем более подробные настройки SphereShape (щёлкнув по cfvjq надписи SphereShape) и поставим радиус 0.5.

Переименуем узел Area в MagicStrike. Сохраним как префаб (правая кнопка, Save Brunch as Scene).

Щёлкнем по папке Prefabs. Создадим внутри неё папку Spawned, куда и сохраним наш префаб MagicStrike.

Заходим внутрь префаба.

Выделим корневой узел и добавим к нему (Ctrl + A) новый узел MeshInstance, чтобы сделать выстрелу какую-то визуальную форму. Заходим в новом узле в его поле Mesh в инспекторе и выбираем New PrismMesh.

В поле Scale поставим размеры 0.3 по всем осям.

Выделим корневой узел (Magic Strike) и нажмём на свиток с зелёным плюсом, чтобы добавить скрипт.

Нажимаем на иконку папки. Поднимаемся пару раз вверх.

Щелкаем два раза на папке Scripts.

Меняем название на theMagicStrike и жмём Open.

Нажимаем Create.

Открылся скрипт выстрела. Меняем его код на следующий:

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()

Сохраним скрипт. Запустим игру (сохранив тем самым открытые сцены). На точке старта должен появляться белый треугольник, и через пару мгновений пропадать.

Переключимся на 3D, закроем сцену MagicStrike, оказавшись в MainScene.

Удалим теперь сам узел MagicStrike с главной сцены.

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

Заходим в скрипты (Scripts), открываем скрипт theMain. Пишем ниже первой строки новую: var magic_strike = preload("res://Prefabs/Spawned/MagicStrike.tscn")

Таким образом мы предзагрузим префаб-выстрел, чтобы создавать его в процессе игры.

Теперь спускаемся ниже, в функцию 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)

Запустим игру, видим, что если нажимать правую кнопку мыши, то за персонажем образуется целая "змейка" из треугольных выстрелов. Это происходит потому, что мы поместили создание выстрела в условие if Input.is_action_pressed("mouse_L"), где is_action_pressed означает, что событие происходит всё время, когда нажата кнопка.

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

		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)

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

Переключимся теперь в 3D в основную сцену. Зайдём в узел уровня (Level_A1).

Выделим узел врага (Enemy_red) и сделаем ещё 4 копии (Ctrl + D).

Теперь растащим их примерно по углам уровня. И одного в центре.

Запустим игру. Теперь врагов стало много.

Разве что они все сжимаются в одного, когда проходит некоторое время и во время перезапуска. Немного подправим этот момент. Открываем скрипты, заходим в 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

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

На этом пока и остановимся.

Код получившихся в итоге скриптов

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()

Предыдущая статья:

Часть alpha

Источник: https://habr.com/ru/post/684502/


Интересные статьи

Интересные статьи

Введение В предыдущей статье "Сам себе PKI: Теория на примере Let’s Encrypt" мы рассмотрели теорию и разобрали пример, как происходит установка HTTPS соединения между браузером и веб-сайтом ...
Прошло полгода с выпуска пробной статьи про создание MMORPG в телеграме. Изменился мир, изменились и планы по игре.Почему больше не в TelegramПосле выпуска первой статьи,...
Недавно мы на Хабр Карьере устроили конкурс ко дню эйчара и попросили эйчаров и ИТ-рекрутеров рассказать нам о самых смешных собеседованиях, которые стали легендами в их компаниях. И...
Это заключительная часть расследования о Scala-движении в России. В первой части я узнал от Романа Гребенникова о воронежском бомонде, C++ и Erlang, а от Романа Тимушева о первой Akka и рождении ...
Начав изучать ROS (Robotic operation system), сначала поражаешься, как тут «все сложно», от количества информации про топики, ноды,actions голова идет кругом. И, первое желание — вернуться в упра...