Русский
Русский
English
Статистика
Реклама

Gdscript

Как собрать паука в Godot, Unigine или PlayCanvas

04.01.2021 22:21:38 | Автор: admin
С наступившим 21-м годом 21-го века.
В данной статье пробегусь по особенностям работы в трёх игровых движках, на примере написания кода для паукообразного средства передвижения.




Общая структура паукомобиля подразумевалась следующая есть 6 лап, на каждой копия управляющего скрипта, который отслеживает перемещение основной конструкции и переставляет лапу в новую позицию, когда она отдаляется на определённое расстояние.
Для более точной посадки лап планировалось добавить и рейкасты, но, допустим, в том же Godot я хотел сделать паука для игры с видом сверху, при котором подобная точность не особо нужна.
Таким образом задача состояла в формировании нужной структуры внутри движка и написании кода отдельной лапки. Вот что получилось в разных средах разработки:



Godot


Здесь у меня уже был готов небольшой проект с машинками и паука я решил добавить в одну из сцен (префабов), содержащую в себе подкласс машинок, которые не имеют колёс.
Сама сцена specific_base устроена таким образом, что в основе узел-пустышка, который просто висит где-то в мире, без движения, а по миру перемещается kinematic body внутри него. Камера находится внутри сцены, но вне body, просто следуя за ним.



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



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


Код внутри редактора Godot

Пишем код. Я использую GDScript, потому как особого смысла писать именно на C# в Годо не вижу (не настолько фанат фигурных скобочек):

extends Spatialexport var distance = 2.5#максимальная дистанция, после которой случится перерасчётexport var step = 1#переменная для дополнительного смещения лап (вещь необязательная)#ссылки на центр паука и одну из позиций лап, а также элементы для их храненияexport (NodePath) var spidercenter = nullvar trg_centerexport (NodePath) var spiderleg = nullvar trg_leg#переменные для расстояний по осям x и zvar x_dis = 0.0var z_dis = 0.0#переменная-таймер, а также флагvar time_lag = -1.0# инициализацияfunc _ready():self.hide()#скрыть лапуtrg_center = get_node(spidercenter)#запомнить объектыtrg_leg = get_node(spiderleg)LegPlace()#один раз вызвать установку лапки на позицию# основной циклfunc _process(delta):        #развернуть лапу в направлении центра паука. можно ввести таймер, чтобы делать это через малые интервалыself.look_at(trg_center.global_transform.origin, Vector3(0,1,0))        #включить видимость, если лапа была невидимой. это делалось для того, чтобы показывать её снова, после того как она скрывается в момент перестановки (чтобы перестановка выглядела как появление лапы в новой позиции уже развёрнутой в нужную сторону, а не перенесением с последующим разворачиванием). на самом деле можно было вынести внеочередной разворот и последующий показ лапы в LegPlaceif self.visible == false: self.show()if time_lag>=0:#если флаг-таймер запущен, то наращивать его значениеtime_lag +=1*delta if time_lag>0.06:#при истечении задержки сбросить флаг и вызвать перерисовкуtime_lag = -1.0LegPlace()else:#пока флаг неактивен считать дистанции от лапы до позиции лапы по двум осямx_dis = abs(trg_leg.global_transform.origin.x - self.global_transform.origin.x)z_dis = abs(trg_leg.global_transform.origin.z - self.global_transform.origin.z)if (x_dis + z_dis) > distance:#если дистанция больше лимита, запустить флагtime_lag = 0.0passfunc LegPlace():#собственно, сама функция перестановки лапыself.hide()step = step*(-1)self.global_transform.origin = trg_leg.global_transform.origin+Vector3(0,0,0.5*step)



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



Unigine


После того, как была готова реализация для Godot, я решил перенести это решение и в Unigine engine. Там у меня тоже имелся проект с машинками, правда чтобы не перегружать его я сделал отдельный паучий форк, чтобы впоследствии, наверное, вовсе убрать из него колёса и развивать как-то отдельно.



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



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


Unigine для редактирования кода запускает внешнюю среду

Код:

using System;//стандартная "шапка"using System.Collections;using System.Collections.Generic;using Unigine;//уникальный идентификатор компонента, генерируемый при создании скрипта[Component(PropertyGuid = "5a8dd6f85781adf7567432eae578c5414581ddac")]public class theLegBehavior : Component{[ShowInEditor][Parameter(Tooltip = "CenterSpider")]//указатель на центр паукаprivate Node spiderCenter = null;[ShowInEditor][Parameter(Tooltip = "Target Leg Point")]//указатель на точку крепленияprivate Node legPoint = null;//переменные для вычислений дистанций по осямprivate float x_dis= 0.0f;private float z_dis= 0.0f;private float ifps;//переменная для дельтатаймprivate float time_lag = -1.0f;//таймер-флагprivate void Init()//инициализация{node.Enabled = false;//скрыть лапуLegPlace();//вызвать перестановку лапы}private void Update()//основной цикл{ifps = Game.IFps;//сохранить дельтатаймif (time_lag>=0.0f){//далее уже знакомая конструкцияtime_lag += 1.0f*ifps;if (time_lag>=0.6f) {time_lag = -1.0f;LegPlace();}}else{x_dis = MathLib.Abs(legPoint.WorldPosition.x - node.WorldPosition.x);z_dis = MathLib.Abs(legPoint.WorldPosition.z - node.WorldPosition.z);            if (x_dis + z_dis > 0.8f){time_lag = 0.0f;}}}        //функция перерасчёта положения лапы. здесь уже финальный показ лапы встроен внутрь функции. также тут происходит единичный разворот в сторону центра паука. а постоянный разворот считается вне этого скрипта, а в отдельном, наброшенном на лапу скрипте, хотя я по сути уже это вынес оттуда и можно включить в Update этого скрипта.private void LegPlace(){node.Enabled = false;vec3 targetDirection = vec3.ZERO;targetDirection = (legPoint.WorldPosition - node.WorldPosition);quat targetRot = new quat(MathLib.LookAt(vec3.ZERO, targetDirection, vec3.UP, MathLib.AXIS.Y));quat delta = MathLib.Inverse(targetRot);delta.z = 0;delta.Normalize();node.WorldPosition = legPoint.WorldPosition;        targetDirection = (spiderCenter.WorldPosition - node.WorldPosition);node.SetWorldDirection(targetDirection, vec3.UP, MathLib.AXIS.Y);node.Enabled = true;}}



Видеонарезка паучьего теста в Unigine



PlayCanvas


PlayCanvas игровой движок под webGL, использующий javascript. Недавно начал в нём разбираться. Напоминает нечто среднее между Unity и Godot, но с разработкой онлайн редактор открывается в браузере.

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

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



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



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


В playcanvas редактор кода запускается вновой вкладке браузера

Код:
var TheLegBehavior = pc.createScript('theLegBehavior');//ссылка на центр паукаTheLegBehavior.attributes.add('N_spiderCenter', { type: 'entity' });//ссылка на точку постановки этой лапкиTheLegBehavior.attributes.add('N_legPoint', { type: 'entity' });this.x_dis = 0.0;this.z_dis = 0.0;this.time_lag = -1.0;// initialize code called once per entityTheLegBehavior.prototype.initialize = function() {        };// update code called every frameTheLegBehavior.prototype.update = function(dt) {    if (this.N_spiderCenter) {        this.entity.lookAt(this.N_spiderCenter.getPosition());    }};// постапдейтTheLegBehavior.prototype.postUpdate = function(dt) {    //    if (this.N_spiderCenter) {//        this.entity.lookAt(this.N_spiderCenter.getPosition());//    }    if (time_lag>=0.0){        time_lag+=1.0*dt;        if (time_lag>=0.06){            time_lag=-1.0;            this.LegUpdate();        }            } else {                x_dis = Math.abs(this.entity.getPosition().x-this.N_legPoint.getPosition().x);        z_dis = Math.abs(this.entity.getPosition().z-this.N_legPoint.getPosition().z);                if ((x_dis+z_dis)>3.0){         time_lag=0.0;        }                    }};TheLegBehavior.prototype.LegUpdate = function() {        if (this.N_legPoint) {        this.entity.setPosition(this.N_legPoint.getPosition());    }    //    if (this.N_spiderCenter.enabled === false) {//        this.entity.enabled = false;//    }//    if (this.N_spiderCenter.enabled === true) {//        this.entity.enabled = true;//    }    };


В целом, пока получилась заготовка паучка с четырьмя лапками и не совсем оптимальными расчётами.

Потестить получившегося на текущий момент кадавра можно здесь:
https://playcanv.as/p/rOebDLem/

Я лично пробовал запускать на не слишком мощном смартфоне через Chrome и Dolphin, графика становится похожей на PsOne и вычисления для некоторых лап не работают, пока на экране виден уровень, у края карты лапы проявляются. На слабом ноутбуке наблюдались очень сильные тормоза в Chrome, но в Firefox всё катается нормально. На ноутбуке с дискретной видеокартой и на стационарнике летает в обоих этих браузерах.

На ПК, в отличие от смартфонов в этой демке работает прыжок (по кнопке пробел), заготовка стрейфа (Q и E) и перезагрузка уровня (на R).

Итог



Как видите, языки и движки разные, но тем не менее многие вещи в семействе скриптовых языков делаются довольно похожим образом. А как часто вы осваивали новые для себя игровые движки и фреймворки, с какими основными трудностями столкнулись?
Подробнее..

Механики ловушек и механизмов в Godot Engine

18.10.2020 20:21:07 | Автор: admin
Здравствуйте. Эта статья ответвление от цикла статей по механикам для реализации платформеров, так как здесь я буду рассказывать о создании ловушек и механизмов, которые могут быть использованы не только в платформерах.

Но сразу скажу о некоторых моментах, которые должны быть реализованы в персонаже:
  1. Перемещение. Без него подойти к механизму или ловушке банально не выйдет
  2. Взаимодействие. Для активации объекта нужно вызвать определенный метод в объекте, если он существует. В данном цикле будет предположено, что это interact()
  3. Умение умирать. Персонаж должен умирать, чтобы в ловушках был смысл. Если персонаж не умеет умирать ловушки его не убьют, как бы глупо это не звучало

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

Шипы


Итак, начнём с шипов. Для того, чтобы их создать вам нужно реализовать следующую структуру файлов:

Некоторые пояснения: Spikes2D StaticBody2D, Timer нужен для периодического переключения состояния шипов, его сигнал timeout присоедините к скрипту. Сигнал body_entered от Area2D присоединить к скрипту в корне сцены.
А теперь приступим к написанию скрипта для этой сцены.
tool # чтобы сделать изменение состояния динамическимextends StaticBody2D# Основные 2 параметра.export (bool) var spikes_showed: bool = falseexport (bool) var timer_enabled: bool = false # Дублируем первые две переменные для того, чтобы динамически изменять.var spikes_showed_previous: bool = falsevar timer_enabled_previous: bool = falsefunc _process(_delta: float) -> void:if self.timer_enabled and $Timer.is_stopped():$Timer.start() # Если self.timer_enabled == true и $Timer остановлен - запустить таймерif self.spikes_showed != self.spikes_showed_previous:if self.spikes_showed:$AnimatedSprite.play("show") # Нужны 2 анимации.else:$AnimatedSprite.play("hide")self.spikes_showed_previous = self.spikes_showedfunc _on_Area2D_body_entered(body: Node) -> void:if body.name.ends_with("Actor") and self.spikes_showed:body.dead()func _on_Timer_timeout() -> void:if self.timer_enabled:self.spikes_showed = !self.spikes_showed$Timer.start()

Этого хватит чтобы убить игрока по соприкосновению с выдвигающимися шипами.

Телепорт


Сегодня(18 октября 2020 года) я потрудился придумать телепорт во второй раз. Но в отличии от первой попытки я не создавал отдельного Position2D в сцене телепорта, а просто добавил NodePath как экспортируемую переменную, чтобы указать объект к позиции которого нужно телепортироваться. Для того чтобы всё работало как надо достаточно создать следующую структуру:

Мне было в лом обрезать фото, да так думаю будет более наглядно показано, что как выглядит
И вот скрипт отдельно, чтобы не создавать трудности при перепечатывании:
extends Area2D # В этот раз мне было лень писать данный скрипт как toolexport (bool) var portal_opened: bool = false # Указывает - открыт ли портал, что можно понять из названияexport (NodePath) var destination_node: NodePath # Узел к которому этот объект будет телепортировать. Должен иметь параметр position чтобы игра не сломалась.func interact(portal_user: Node):if portal_user.name.ends_with("Actor"):if (destination_node != null): # Если выбрали нужный узел в редактореportal_user.global_position = get_node(destination_node).global_position # переместить игрока в глобальное положение того узла, на который указывает NodePathfunc _on_AnimatedSprite_animation_finished():if $AnimatedSprite.animation == "portal_open":$AnimatedSprite.play("default")  # Если анимация открытия портала закончилась - включить анимацию default. Но можно без этой функции просто нарисовать спрайт и заставить его быть включенным всегда в цикле.

Главной особенностью портала является также то, что если два портала направить друг на друга они будут телепортировать игрока друг к другу, что не создаст цикл благодаря тому, что персонажу для использования портала нужно с ним взаимодействовать.

Заключение


Для начала этого под цикла статей думаю данной выпуска хватит. В 4 выпуске основной серии статей я расскажу о том как научить персонажа взаимодействовать с порталами, ключами и дверьми. И подкиньте в комментариях идеи механик, если вам интересно узнать их реализацию. Спасибо за прочтение и до следующих выпусков.
Подробнее..

Механики для реализации платформера на Godot engine. 4 часть

20.10.2020 18:21:10 | Автор: admin
Здравствуйте снова. В этом выпуске я расскажу о том, как исправил механику карабканья, показанную во втором выпуске, покажу механику взаимодействия, для создания интерактива. Это по-прежнему будет доработка персонажа, так что окружающий мир будет подвергнут минимальным изменениям, но главный герой будет очень сильно улучшен. Правда до дерева навыков ещё далеко, поэтому оставайтесь на связи и я покажу как можно реализовать всё, что придёт нам в голову.

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

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

В общем покажу весь код своего персонажа и постараюсь прокомментировать его наиболее понятно.
# В этот раз будет очень много кода, потому что я не представляю себе все эти системы по отдельности.extends KinematicBody2Dsignal timer_ended # Сигнал о отключении таймера в _processconst UP_VECTOR: Vector2 = Vector2(0, -1) # Направление вверхconst GRAVITY: int = 40# Скорость паденияconst MOVE_SPEED: int = 100# Скорость перемещенияconst JUMP_POWER: int = 480# Сила прыжкаconst CLIMB_SPEED: int = 40# Скорость карабканьяconst WALL_JUMP_SPEED: int = 80# Скорость прыжка от стеныenum States {ON_FLOOR, ON_WALL} # Как я выяснил, этому скрипту нужно только 2 состоянияonready var ray_cast: Object = $RayCast2D # Для реализации взаимодействия с другими объектами. Будет пояснён позжеvar velocity: Vector2 = Vector2.ZERO # Ускорение.var walls: Array = [false, false, false] # Для определения стен. Стена слева, стена сверху, стена справа.var timer_enabled: bool = false# Отвечает за включение таймераvar climbing: bool = false# Поднимаемся мы по стене, или просто падаем вдоль неёvar is_wall_jump: bool = false# Прыгаем ли мы от стены, или нетvar is_double_jump: bool = true # Двойной ли прыжокvar right_pressed: float = 0# Трансляция силы нажатия на стрелки влево и вправо, что позволяет подменить значенияvar left_pressed: float = 0var timer: float = 0# Таймерvar prev_direction: float = 0# Предыдущее направление. Нужно для того чтобы анимация бездействия воспроизводилась в обоих направленияхvar direction: float = 0# Текущее направление движения.var keys: int = 0 # Количество ключей. Нужно для открытия дверей, соответственноvar current_state: int = States.ON_FLOOR# Текущее состояние персонажаfunc _ready():ray_cast.add_exception($WallLeft) # говорит что не нужно обрабатывать лучу ray_castray_cast.add_exception($WallRight)ray_cast.add_exception(self)func _process(_delta: float) -> void: # метод _processif timer > 0 or timer_enabled:timer -= _delta# Уменьшаем таймер на _deltaif timer <= 0 and timer_enabled:timer_enabled = falsetimer = 0# Сбрасываем значение и выключаем таймерemit_signal("timer_ended") # Испускаем сигнал таймера.if self.direction != 0:self.ray_cast.cast_to *= -1self.prev_direction = self.direction# обновляем предыдущее направление если текущее не равно 0func _physics_process(_delta: float) -> void:self.control_character()self.pause_opened()# Вызываем для проверки - открыта ли паузаif (!self.climbing):# Если не карабкаемся, то проверяемif (!self.is_wall_jump): # Если прыжок от стены то увеличиваем self.velocity.y на гравитациюself.velocity.y += GRAVITYelse:# Иначе падаем в 4 раза медленнееself.velocity.y += float(GRAVITY) / 4self.velocity = self.move_and_slide(self.velocity, UP_VECTOR) # Обновить self.velocity из текущего состоянияfunc check_states() -> void:if self.is_on_floor():self.current_state = States.ON_FLOORis_double_jump = trueelif self.is_on_wall():self.current_state = States.ON_WALLis_double_jump = trueelif self.is_on_floor() and self.is_on_wall():self.current_state = States.ON_WALLfunc fall() -> void:self.velocity.y += GRAVITYfunc update_controls(): # Обновляем информации о нажатиях на кнопки "влево" и "вправо" if !is_wall_jump: # Если не прыгаем от стены сейчас - обновляемself.left_pressed = Input.get_action_strength("ui_left")self.right_pressed = Input.get_action_strength("ui_right")func control_character() -> void:# Об этом я уже рассказывалcheck_states()# Проверить состоянияupdate_controls()# Обновить данные о нажатии на стрелки влево и вправоself.interact_with()# Взаимодействие с другими объектами+match current_state:States.ON_WALL:self.climb()self.move()if !climbing:self.jump()self.fall()self.wall_jump()States.IN_AIR:self.jump()self.move()self.fall()States.ON_FLOOR:self.jump()self.move()func climb():if (walls[0] or walls[2]):# Если стена слева или справа - self.climbing = нажато ли событие "ui_climb". Тут вам самим нужно создать событие self.climbing = Input.is_action_pressed("ui_climb")else:# Иначе просто не карабкаемсяself.climbing = falsefunc climb_up() -> void:# Ползем вверх по стенеself.velocity.y = (CLIMB_SPEED)func climb_down() -> void:# ползем вниз по стенеself.velocity.y = (-CLIMB_SPEED)func move() -> void: # Перемещение. Я его доделал, чтобы по левой стенеself.direction = self.right_pressed - self.left_pressedif (self.climbing and !self.is_wall_jump):if self.walls[0]:# Если левая стенаif direction > 0:# Если движемся вправо - карабкаемся вверхclimb_up()elif direction < 0:# Иначе если движемся влево - спускаемся внизclimb_down()else:# Иначе никак не двигаемся по вертикалиself.velocity.y = 0elif self.walls[2]:# Почти то же самое что с движением по левой стене, только направления местами поменялif direction < 0:climb_up()elif direction > 0:climb_down()else:self.velocity.y = 0#else:# Я думал что это будет нужно, но видимо это осталось лишним#self.velocity.y = 0else: # Иначе если не карабкаемся по стене и от неё не прыгаем просто передвигаемсяself.velocity.x = self.direction * float(MOVE_SPEED) * (1 + (float(self.is_wall_jump) / 2))if !(climbing): # Анимацииif direction == 0:$AnimatedSprite.flip_h = (-self.prev_direction >= 0)$AnimatedSprite.play("idle")else:$AnimatedSprite.flip_h = direction < 0$AnimatedSprite.play("run")returnfunc jump() -> void: # Совершенно никаких изменений со второго выпуска в прыжкеif Input.is_action_just_pressed("ui_accept"):if is_on_floor():self.velocity.y = -JUMP_POWERif !is_on_floor() and is_double_jump:is_double_jump = falseself.velocity.y = -JUMP_POWERfunc wall_jump() -> void:if Input.is_action_just_pressed("ui_accept") and Input.is_action_pressed("ui_climb"):self.is_wall_jump = trueself.velocity.y = -JUMP_POWERif walls[0]:self.timer = 0.3self.timer_enabled = trueself.right_pressed = 1# Это приравнивание как я понял вынужденная мера из-за слишком простого механизма перемещенияyield(self, "timer_ended")# Подождать сигнал таймераself.right_pressed = Input.get_action_strength("ui_right") # Сбросить перемещение влево elif walls[2]:self.timer = 0.3self.timer_enabled = trueself.left_pressed = 1# Это приравнивание как я понял вынужденная мера из-за слишком простого механизма перемещенияyield(self, "timer_ended")self.left_pressed = Input.get_action_strength("ui_left")# Сбросить перемещение вправо self.is_wall_jump = false # Перестаём прыгать от стеныfunc interact_with() -> void: # Метод взаимодействияif Input.is_action_pressed("ui_use"):# Если нужная кнопка нажатаvar coll: Object = self.ray_cast.get_collider()# Определяем что столкнулосьif coll:# И если это не nullif coll.has_method("open"):# Проверяем, дверь это или объект взаимодействияuse_key(coll)elif coll.has_method("interact"):use_object(coll)func use_object(collider: Object) -> void:# Используй объектcollider.interact(self)# В дополнительном уроке так активировались порталыfunc use_key(collider: Object) -> void:# Метод открывает все двери.if self.keys > 0:# Если ключи естьcollider.open()# Открой объектself.keys -= 1# И убери ключ из инвентаря за ненадобностьюfunc key_picked_up():self.keys += 1func _on_WallRight_body_entered(_body):# Я уже рассказывал об этих определителях стен.if (_body.name != self.name):# Если с ними что-то столкнулось - они изменят соответствующуюself.walls[2] = true# переменную в массиве walls на true или false.func _on_WallRight_body_exited(_body):# self.walls[2] = false#func _on_WallLeft_body_entered(_body):#if (_body.name != self.name):#self.walls[0] = true#func _on_WallLeft_body_exited(_body):#self.walls[0] = false#func dead():# $Particles2D.emitting = true # Если вы добавили частицы крови - можете убрать комментарийLevelMgr.goto_scene("res://scenes/dead_screen/dead_screen.tscn") # Переход на экран смерти. Сделать чтобы отлет частиц был виден пока не придумал какfunc pause_opened(): # Открывает окно паузыif Input.is_action_just_pressed("ui_cancel"): # Если соответствующая кнопка нажата$PositionResetter/WindowDialog.popup_centered()

Заключение

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

Механики для реализации платформера на Godot engine. 5 часть

15.11.2020 16:06:22 | Автор: admin
Здравствуйте. В предыдущем опросе читатели выбрали следующие пункты на момент создания данной статьи: система характеристик оружия, система здоровья персонажа. А вот дерево навыков я так и не сообразил как правильно реализовать Точнее как создать интерфейс, чтобы отображать и управлять деревом.Предыдущие части:

Система здоровья

Ну начнём пожалуй с системы здоровья персонажа. Я решил создать отдельный файл для всего необходимого. Он назван health_system.gd и лежит в папке скриптов. Важно: это не синглтон, а просто скрипт. Дальше вы поймёте почему.
# Теперь я буду использовать синтаксис javascript. В нём хоть var подсвечивается.extends Object # Расширяем базовый объектclass_name Health # Подписываем класс как Health и после он доступен из любого места игры как тот-же Node, или Node2D.signal death # Добавляем сигнал "death", чтобы испускать его, когда персонаж умирает. Этим персонажем может быть не обязательно даже игрок, или вообще что-то живое.var health: int = 100 setget set_health, get_health # Здоровье и setget для его измененияvar max_health: int = 100# верхний иvar min_health: int = 0# нижний предел здоровьяfunc set_health(new_health: int) -> void: # Функция для того, чтобы ставить new_health здоровья игроку при вызове, что будет не больше или меньше чем можно# warning-ignore:narrowing_conversion # О том что clamp возвращает целое значение, отрезая дробную часть.health = clamp(new_health, min_health, max_health) # Ограничиваем здоровьеif health == min_health: # А если здоровья - заданный минимум - мы умираем, меньше не будет.emit_signal("death") # Испускаем сигнал о том что нечто с этим здоровьем умерлоreturnfunc get_health() -> int:return health # получаем текущее здоровье. Если возвращать константу - игрок будет бессмертным, но константа должна быть больше максимального получаемого урона в игре, иначе мы всё равно умрём.func set_min_max_health(new_max: int = 100, new_min: int = 0) -> void: # Задача верхнего и нижнего предела здоровья. Тут поддерживается отрицательный минимум, что позволит создать эффект грани жизни и смерти, что поднимет адреналин игрока, как было в первой части того же Assasin's creed, что улучшало ощущение хорошей игры.self.max_health = new_maxself.min_health = new_minreturnfunc damaged(dmg: int = 0) -> void: # Получение урона.self.health -= dmg # После некоторых разбирательств я выяснил следующее: set_health() вызывается вместо стандартного "=" и подобных, что позволяет не писать логику смерти в получении урона.return
Чтобы его добавить к персонажу нужно всего-лишь добавить к переменным
# ...var health: Health = Health.new()# ...# И подключаем сигнал смерти в _readyfunc _ready():health.connect("death", self, "dead") #сигнал "смерть" к self.dead(). Смерть я писал в предыдущих выпусках, а если точнее в 3-ей части.
И теперь игрок имеет здоровье. Правда нас всё по-прежнему убивает с 1 удара, потому что я не рассказал об оружии, что будет наносить N урона, а не просто убивать.

Система оружия

А если точнее базовое оружие, которое можно прикреплять к разным объектам и брать данные от него.Ближе к делу, поэтому начну сразу с кода.
extends Nodeclass_name Weapon # Подписываем как Weapon, чтобы можно было прикреплятьenum WeaponTypes {MILLITARY, RANGED, BLOCKER} # Ближний бой, дальний и блокировщикexport (NodePath) var weapon_owner_node_path: NodePath # Путь к владельцуexport (int) var damage: int = 10 # уронexport (int) var critical_damage: int = 20 # критический уронexport (float) var critical_damage_chance = 0.2 # шанс критического урона. Бросаем кубик, если число меньше этого - атакуем критическим урономexport (WeaponTypes) var weapon_type: int = 0 # Тип оружия из перечисленияexport (int) var max_damage_distance: int = 0x20 # Дальность атаки чтобы нанести максимальный урон, если больше - урон будет меньше. В разработке. По умолчанию 32 пикселяvar weapon_owner: Node # Заготовка weapon_owner, чтобы в _ready задать из NodePath, что позволит немного оптимизировать множественные вызовыvar use: FuncRef func _ready() -> void:weapon_owner = WeaponController.get_node_(weapon_owner_node_path)match weapon_type: # ставим в use нужный FuncRef, что вызывается с помощью use.call_func() аргументы в скобки.WeaponTypes.MILLITARY:use = funcref(self, "attack") # Обычная атакаWeaponTypes.RANGED:use = funcref(self, "ranged_attack") # Дальняя атакаWeaponTypes.BLOCKER:use = funcref(self, "block") # Блок атаки врагаfunc attack(enemy: Entity) -> bool:print("attack")enemy.health.damaged(self.damage)return true # Удачность нанесения уронаfunc ranged_attack(enemy: Entity) -> bool:var dmg: float = damageif weapon_owner.global_position.distance_to(enemy.global_position) > max_damage_distance:dmg -= damage / 4enemy.health.damaged(round(dmg))return truefunc block(enemy: Entity) -> bool:print(randi() > critical_damage_chance) # В разработке. Нужно чтобы гасить урон от атак врагов, но я до конца не придумал как реализовать его.return true
Добавляем этот узел к объекту, к примеру к пуле:
extends Entity # Тут я не буду рассказывать обо всём, потому что этот урок о том как прикреплять скрипты здоровья и сцены оружия, а не о создании пушек и других ловушек. О пушке расскажу в следующем уроке о механизмах и ловушках.var direction = Vector2(-1, 0) setget set_directionconst SPEED: int = 40onready var weapon: Weapon = $Weapon # Задаём оружиеfunc _ready():health.connect("death", self, "dead") # health есть уже в Entity, но это единственное что есть в нём.func _physics_process(delta):var motion: Vector2 = direction * SPEED * deltavar collision = move_and_collide(motion, false)if collision != null and collision.collider != null:var coll = collision.colliderif coll.name == "TileMap":self.dead()else:weapon.use.call_func(coll) # Атакуем объект со здоровьемself.dead()print(coll.health.get_health())func set_direction(vec: Vector2) -> void:direction = vecfunc dead():self.queue_free() # Метод смерти просто освобождает этот объект через queue_free()
Вот как выглядит сцена пули:Надеюсь этой картинки хватит чтобы понять как работает сцена Weapon.

Заключение

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

Принцип работы EditorScript

21.06.2021 00:15:47 | Автор: admin

Всем привет. С вами я, stalker320, и сегодня я хотел бы рассказать про написание служебных скриптов, запускающихся в редакторе. Всем желающим прочесть - добро пожаловать под кат


EditorScript - это такой скрипт, который можно запустить по нажатию комбинации Ctrl+Shift+X прямо из движка. Он может исполнять различные служебные функции. К примеру создать необходимую структуру каталогов в проекте.

toolextends EditorScriptvar folders: PoolStringArray = [# Наши папки к созданию"res://assets/textures/",# Их определяет разработчик"res://assets/fonts/",# Просто напишите сюда все конечные пути, что хотите создать"res://resources/","res://addons/","res://scenes/","res://scripts/singletons/","res://scripts/resources/","res://scripts/editor_scripts/"]var placeholder: Resource = load("res://placeholder.tres")# Ресурс-пустышка для сохранения структуры файлов. По желаниюfunc _run() -> void: # Входная функция этого скриптаvar dir = Directory.new()for folder in folders: # проход по списку папокif !dir.dir_exists(folder):var err = OKerr = dir.make_dir_recursive(folder)if err != OK:prints("Error", err)returnelse:if !dir.file_exists(folder.plus_file("placeholder.tres")):# Создаём файл плейсхолдера для гита если не существуетerr = ResourceSaver.save(folder.plus_file("placeholder.tres"), placeholder)if err != OK:prints("Error", err) returnelse:# Говорим что создали (не обязательно)prints("Making", folder)prints("Making", folder.plus_file("placeholder.tres"))print("Successful. Structure created or already exists.")return

К сожалению я освоил их не очень хорошо, поэтому создать генератор какого-нибудь API для дальнейшего моддинга игры фанатами пока не вышло. (Хотя создать необходимую структуру файлов вышло, а это значит, что кто-либо другой может расширить функционал через словарь и создавать необходимые скрипты). Кроме того, чтобы создавать нужную структуру файлов прямо в редакторе, через метод get_scene() можно взаимодействовать и с самой сценой (Достойные примеры скриптов и сцен, к сожалению, отсутствуют).

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

Спасибо за внимание. Не думаю, что буду писать статьи в дальнейшем очень часто, но у меня есть идеи, о чём можно написать.

Подробнее..

Godot, 1000 мелочей

01.07.2020 20:13:32 | Автор: admin
Недавно открыл для себя Godot engine, опенсурсный игровой движок. Делюсь некоторыми приёмами и заметками, в основном из области 3д, кода или общих моментов.


У Godot в целом репутация скорее 2д движка, которое проработано довольно хорошо, но и не так давно появившиеся 3д возможности позволяют делать трёхмерные игры. Особенно если вы в состоянии оптимизировать какие-то вещи самостоятельно, не делаете слишком уж тяжёлую и комплексную игру, а также вас устраивают текущие варианты рендера. Ну или можете ждать будущих оптимизаций и появления vulkan.

Из коробки в движке есть некоторая физика, в том числе джоинты и колёсный транспорт. Нет встроенного редактора terrain, но можно воспользоваться плагином, импортировать из специализированных программ или просто как меш из 3д пакета. В последнем случае ради производительности придётся самостоятельно резать ландшафт на фрагменты-чанки, и скорее всего делать им отдельные меши под коллизии. Что касается мешей под форму коллизии ландшафта чтобы не было визуальных нестыковок, нужно триангулировать модель во время экспорта из 3д пакета. Например, один из простейших способов это сделать в Blender экспортировать меш в формате collada (.dae), там по дефолту стоит галочка triangulate.

Godot практикует подход всё есть сцена и заточен под древовидную структуру элементов. На практике это означает, что новые объекты на уровень добавляются как ветки к уже существующим на нём узлам, их можно сворачивать в префабы и открывать отдельно, как будто это их отдельный маленький локальный мирок. Таким образом очень удобно редактировать всевозможные сохранённые составные объекты, единственный момент если на вашем уровне был выставлен свет, то, допустим, зайдя внутрь персонажа, в его локальную сцену, вы этого освещения не увидите и настраивая его материалы не будете понимать как они смотрятся на свету. Эту проблему можно решать по разному, например, переключаясь в окно уровня и оценивая происходящие изменения с персонажем, после сохранения его сцены, там. Или просто временно бросить источник света внутрь префаба с персонажем.


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

Что касается языков, то если не рассматривать низкоуровневый способ, то наиболее ходовые варианты это скриптовые языки GDScript и C#, а также визуальный скриптинг для чего-то простого. GDScript лучше интегрирован в движок, имеет больше примеров, не требует запуска внешней среды, а в плане общей структуры тут будет происходить всё то же, что в варианте C# объявление переменных, вызов функции инициализации, цикл процессов, цикл физических процессов и так далее. Так что выбор GDScript в качестве основного скриптового языка разработки имеет смысл. Пересаживаясь на C# получим разве что большую аккуратность и подробность записи, теряя в лаконичности, но усиливая разборчивость и контроль над ситуацией фигурные скобочки вместо использования табуляции, отметки конца строки, более формальную типизацию.
Что касается глобальных переменных, то для их использования что в GDScript, что в C# потребуется добавить в параметрах проекта глобальный скрипт/скрипты в автозагрузку, к переменным которого можно будет обращаться глобально.
Также в Godot действует ограничение -на каждом объекте не может быть больше одного скрипта. Но этот момент можно обойти, например, повесив второй скрипт на дочерний объект.

Сигналы


Для обмена данными между узлами в Godot можно также воспользоваться иерарией, ведь объекты крепятся друг к другу, как ветки к дереву. Правда у этого подхода свои подводные камни, ведь в нашей игре иерархия может быть динамически изменяемой. К тому же обращение к иерархии в пределах одной сцены это одно, а вот когда у тебя сцена внутри сцены внутри сцены, то с этим возникают некоторые сложности, хотя бы даже чисто в понимании происходящего.
Один из способов со всем этим управляться и не слишком привязываться именно к конкретной текущей иерархии это сигналы. Какое-то количество общих сигналов уже предустановлено можно заглянуть в панель сигналов объекта, чтобы прикрепить строку с обработкой получения одного из них в скрипт того же объекта или другого объекта со скриптом, в пределах сцены. Если же нужно сделать свой собственный сигнал, то делается это так:


Заводим сигнал

Излучаем его в том же скрипте при нажатии кнопки или других условиях

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


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

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


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

Ещё одна полезная вещь, сокращающая количество ненужных сигналов вместо отправки личного сигнала, один объект может постучаться в другой, чтобы тот сам отправил уже заведённый в нём сигнал. Например, взрыв получает сигнал о том, что коснулся игрока, и в обработчике этого сигнала выясняет, есть ли у игрока метод самоуничтожения, запуская его в том случае, если обнаружил. Игрок, в этом своём вызванном методе отправляет сигнал в корневую сцену о том, что умер. В скрипте корневой сцены обработчик сигнала смерти игрока стирает мир и собирает игровое меню.



CGS-объекты



Один из полезных 3д инструментов в Godot примитивы constructive solid geometry. Проще говоря это объекты, поддерживающие булевы операции пересечение, исключение, объединение. Кроме набора примитивов есть универсальный CGS Mesh, в качестве формы для которого можно установить уже произвольный меш.



Пустышка CGS Combiner потребуется для использования в иерархии, чтобы управлять приоритетом применения операций, если в этом есть необходимость при сборе какой-то сложной конструкции.

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

Следующее применение статических CGS имитация разрушаемости и повреждений. для этого требуется расставить CGS примитивы в режиме исключения, как дыры, огрызки и вмятины, а затем временно скрыть, включая видимость в нужный момент. Ограничение тут в том, что повреждать мы можем только поверхность CGS объекта, к тому же повреждение должно быть изначально прикреплено к нему как дочернее либо крепиться через код, как потомок. Зато такой вариант по гибкости настройки уже значительно выигрывает по сравнению с заготовленным в 3д-пакете разрушаемым объектом.




Далее у нас идут CGS движущиеся. Через код, либо записанную анимацию. В целом через этот способ можно реализовывать какие-то эффекты, но очень желательно чтобы подобные анимации не крутились на сцене в цикле. Несколько анимированных CGS могут заметно посадить производительность, к тому же в Godot оптимизирует не все вещи, и если вы не вырубите анимированные CGS самостоятельно, то они будут продолжать расходовать производительность вне видимости камеры.
Тем не менее эффекты анимированных CGS уже сложно заменить решениями 3д пакета, поэтому вы можете быть заинтересованы именно в их использовании (по крайней мере, если не собираетесь рисовать продвинутое 3д через код). Главное найти им правильное применение, лучше всего их использовать точечно, в определённых местах, включая анимацию по триггеру. Как эффект открытия прохода или прочий разовый спецэффект. Естественно, чем проще форма CGS объектов тем лучше. А основную вычислительную нагрузку они оказывают именно в процессе соприкосновения в движении, притом нет особой разницы, какой именно объект двигать относительно другого.


На видео есть момент где машинка падает сквозь дыру в мосту, когда анимированная CGS-капсула проходит через CGS Mesh с моделькой моста.
Подробнее..

Твоя первая игра на Godot Engine

02.12.2020 18:09:40 | Автор: admin

1. Предисловие

Здравствуй, в данной статье я хочу в максимально сжатой форме познакомить тебя с основами создания простых 2d платформеров на движке Godot. Иногда мы будем останавливаться на некоторых важных моментах, а иногда пропускать ненужную тебе на начальном уровне информацию.

2.Стартуем!

Думаю установить сам движок не составит труда. После установки открываем его и нажимаем на кнопку новый проект.

Создание проекта.Создание проекта.

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

3.Знакомство с интерфейсом

2D сцена в Godot Engine.2D сцена в Godot Engine.

Итак, мы создали твой первый проект! Отличное начало, на сегодня хватит. Ладно, а если серьезно, то изучать интерфейс программы, особенно на первых парах, очень важно. Перед тобой открылась интересная картина с пустой 3d сценой, но она нам сегодня не понадобится, поэтому переходим в вкладку 2d. Кнопка находится сверху посередине. Стало немного проще, не правда ли? Ну, а теперь перейдем к самому интерфейсу программы (его кстати можно настроить под себя, перетащив какие-то элементы левой кнопкой мыши, но пока лучше оставит все как есть).

4.Работа с файлами через Godot

Не очень удобно каждый раз лазить в проводник, чтобы достать какие-то файлы, согласен? Для этого в Godot существует отдельное окошко, оно находится в левом нижнем углу редактора.

Проводник в Godot Engine.Проводник в Godot Engine.

Пока что там пусто, но это потому, что мы еще ничего не добавили. Так давай сделаем это! Ссылку на архив я прикрепил в конце поста, скачивай его и наслаждайся халявными спрайтами для игры (сразу говорю, они не мои). Если конечно у тебя заготовлена своя графика, то милости прошу.Итак, выделяем все нужные нам файлы и переносим их в окошечко. Вжух и они скопировались в движок! Но на этом магия не заканчивается! Здесь можно делать все то, что ты делаешь в обычном проводнике! Одним словом, полная свобода действий.

Но давай пока оставим эту часть экрана в покое и перейдем к более серьезным вещам.

5.Работа со сценами

Создание новых сцен.Создание новых сцен.

Посмотри в верхний левый угол редактора. Здесь есть вкладка, которая называется Сцена. Давай добавим твою первую сцену! Делается это просто, тебе нужно либо нажать на плюсик, либо ввести сочетание клавиш ctrl + A. Перед тобой выплыло меню, в поиске которого нужно ввести заветное слово Node. Мы давай это будет наша основная сцена, назовем ее World, но название в принципе неважно. Чтобы переименовать сцену нужно лишь дважды щелкнуть на нее левой кнопкой мыши. Теперь давай добавим на сцену игрока!

Многие просто добавляют объект Sprite, но это большая ошибка! Так делать нельзя! Запомни это раз и навсегда! Мы с тобой, как продвинутые пользователи добавим не Sprite, а KinematicBody2D.

Теперь древо твоего проекта выглядит так:

Добавляем игрока.Добавляем игрока.

Как ты наверное успел заметить, напротив нашего KinematicBode2D висит какой-то желты значок. Что он тут забыл? Дело в том, что наш объект пока что не имеет форму, вот Godot и ругается. Но прежде чем добавить форму нашему игроку, давай добавим его спрайт( И не забудь заменить название KinrmaticBode2D на Player ). Для этого нажми один раз правой кнопкой мыши на нашего Player и сочетанием клавиш ctrl + A добавь объект Sprite. Потом опять нажми на Игрока и добавь объект CollisionShape2D. У тебя должна быть примерно такая картина:

Добавляем в спрайт и границы игрока.Добавляем в спрайт и границы игрока.

Если все так, едем дальше. Теперь зададим картинку спрайта нашего персонажа. Выбираем объект Sprite, а потом перетаскиваем из моего архива картинку Player.png( или твою картинку) в раздел Texture. Если картинка импортировалась с сжатым качеством, просто нажми на нее, и в Godot в верхнем левом углу перейди в вкладку Импорт, там в разделе Flags убери галочку с пункта Filter и нажми Переимпортировать. Если не помогло, то просто перезапусти Godot.

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

Добавляем текстуру спрайта игрока.Добавляем текстуру спрайта игрока.

Что же делать? Без паники, все поправимо в пару кликов. В левой части панели Инспектор выбираем параметр Hframes, и подгоняем его по размерам ( у меня это 25). Ну что, поменялась картинка?

Устанавливаем границы спрайта.Устанавливаем границы спрайта.

Супер, едем дальше! Ты еще не забыл про CollisionShape2D? Выделяй его и в пункте Shape выбирай Новый RectangleShape2D. Теперь изменяй его под размер персонажа. У меня получилось так:

CollisionShape2d.CollisionShape2d.

6.Отдельные сцены в Godot

Это все конечно классно,но хорошим тоном в Godot является создание отдельных сцен для объектов. Поэтому нам нужно сделать так, чтобы объект Player был отдельной сценой. Но не создавать же нам все заново? Нет, для этого в движке предусмотрена отдельная функция.Нажмите на Player правой кнопкой мыши и выберете Сохранить ветку,как ветку.

Создание сцены из ветки.Создание сцены из ветки.

Теперь Player это отдельная сцена, отлично!Чтобы перейти на сцену игрока достаточно нажать на иконку:

Перейдем на сцену игрока и приступим к очень интересному занятию программированию.

7. Скрипт игрока, GDscript

Для того чтобы добавить скрипт какому-либо объекту нежно просто выбрать этот объект и нажать на иконку свитка:

Создать скрипт.Создать скрипт.

После этого выплывет такая табличка:

Скрипт для игрока.Скрипт для игрока.

Нажимаем Создать и у нас открывается встроенный редактор кода в Godot.Теперь начинается более сложная часть туториала, поэтому слушай внимательнее.Пока что наш персонаж просто стоит на сцене и ничего не делает, это слишком скучно. Ну так давай сделаем управление персонажем!Что нам для этого понадобится? Нам нужен скрипт, который будет обрабатывать нажатия клавиш с клавиатуры, двигать персонажа, проигрывать анимацию. Но давай пойдем по порядку и начнем с самого простого управления.

Простое управление.Простое управление.

Пишем вот такой код, не волнуйся сейчас все объясню. Первая строчка объявляет Godot, что мы используем объект KinematicBody2D. Ее создал сам движок. На 3 и 4 строчке мы задаем две константы, отвечающие за ускорение и максимальную скорость. Они нужны для плавного перемещения персонажа по сцене. На 6 строчке объявляем переменную для вектора перемещения. После этого на 8 строчке создаем функцию physicsprocess, это системная функция движка. Она нужна, чтобы привязать к персонажу физику. В нашем случае - это физика перемещения и сила гравитации. 9 строчка отвечает за управление по оси X. Метод Input помогает нам считывать те самые кнопки для управления (стрелка влево и стрелка вправо). После на 11 строчке мы проверяем была ли нажата какая-то кнопка. Потом мы перемещаемся влево или вправо.

Как ты заметил, мы прибавляем к координате игрока произведение направления по координате на ускорение и на какую-то delta. Вопрос, что такое delta? Delta показывает сколько времени (в секундах, тип float) прошло с момента отрисовки прошлого кадра.Зачем это сделано? Если мы не будем привязывать передвижение игрока ко времени, то оно автоматически привязывается к частоте процессора. На крутых компьютерах или телефонах разница незаметна, но запустив приложение на старом пк или телефоне, ты все поймешь. Поэтому всегда привязывай передвижение к delta!

Потом в строке 13 мы используем какой-то clamp. Опять неразбериха! Все просто, clamp, как можно догадаться из названия, сжимает значение переменной. Сделано это для оптимизации и плавности движения.Ну и в последней строке мы просто запускаем передвижение нашего игрока. Не так уж все и сложно!

8. Первый запуск.

Вот сделали мы все это с тобой, а где результат? Ну так давай поскорее запустим с тобой первую демку! Все очень просто, нажми клавишу F5, после этого выплывет окно, которое скажет тебе, что основания сцена не выбрана. В нашем случае основная сцена World.tscn. Выбираем ее и снова жмем F5. Должно появиться что-то такое:

Окно демки.Окно демки.

В верхнем левом углу можно заметить маленькую часть нашего персонажа. Давай приведем все в порядок. Для этого сначала закрое окошко демки и перейдем в настройки проекта. Чтобы это сделать, в левой верхней части нажми на Проект, а в выплывшем окне нажми Настройки проекта. Здесь переходим в вкладку Window и ставим разрешение на 320x180. Почему такое маленькое? Все просто, мы с тобой задали разрешение экрана в самой сцене, для платформера такие размеры идеальны. А для экрана самой демки нужно задать нормальное разрешение. Это можно сделать в пунктах Test Width и Test Height. Я задам его в формате 1280x720. Спустимся пониже и в пункте Mode ставим 2d, а в Aspect ставим keep. Для красоты предлагаю обратно перейти на сцену и передвинуть персонажа в середину экрана. Делается это легко, просто зажми персонажа левой кнопкой мыши и начни перетаскивать. Теперь все приготовления закончены,можно запускать демку.

Красота, правда? Попробуй передвигать персонажа стрелками на клавиатур. Получилось? Да, но персонаж будто скользит по экрану и не может остановиться. Не волнуйся, скоро мы это исправим. Однако сейчас наш персонаж как бы висит в воздухе, это ней есть хорошо. Давай сделаем землю для нашего игрока!

9.Tilemaps

Что такое Tilemap? Тайлы это плитки, вместе образующие сетку тайлов. Чаще всего они принимают форму квадратов. Как же их добавить в наш платформер? Очень просто, для начала выбери объект World(нашу основную сцену), нажми клавиши ctrl + A и выбери TileMap.

Теперь в этом окошке выбири Tile Set и нажми Новый TileSet.Снова нажми на TileSet, должно получиться ка-то так:

Добавляем анимацию.Добавляем анимацию.

Давай добавим спрайт для нашего tilemap, для этого нажми на плюс снизу и выбери tile.png.

Следующий шаг будет довольно сложным, поэтому слушай внимательно.Итак, в вкладке Регион полостью выделяем нашу картинку, в вкалдке snap options ставим step по x и y на 16. Такие же действия повторяем в вкладках столкновение, перекрытие, навигация, битовая маска. А последней мы остановимся поподробней.

В ней мы нажимаем на квадратик и выделяем весть тайл. Тоже самое проделываем и в других вкладках. Вот как в итоге должно получиться.

Задаем границы тайла.Задаем границы тайла.

Отлично, сохраняем все и переходим обратно на сцену. Еще рах кликаем на Tilemap и в раздеел Cell меняем size на 16x16.Теперь можно делать уровень!

Создаем простой уровень.Создаем простой уровень.

Вот как у меня получилось. Супер, но на нашего игрока до сих пор не действует гравитация, давай это исправим. Для этого перейдем в скрипт player и введем там такой код.

Константы для прыжка и гравитации.Константы для прыжка и гравитации.

Здесь к существующим переменным мы добавляем friction, gravity, jumpforce, airresistance. Названия говорят сами за себя, поэтому объяснять за что они отвечают я не буду.

Реализация прыжка и гравитации.Реализация прыжка и гравитации.

Следом идет сама сила гравитации. Мы прибавляем к motion.y силу тяжести, умноженную на delta. Это действие заставляет нашего игрока падать вниз, если под ним ничего нет. После этого скрипт обрабатывает нажатия на кнопки, характерные для прыжка (стрелочка вверх). И заставляет игрока падать вниз, когда он уже прыгнул.

Вот собственно и все, давай запустим демку!Все работает. Но нет никаких анимаций,и передвижение из-за этого выглядит очень топорно.

Как выглядит при запуске.Как выглядит при запуске.

10.Анимации

Простые анимации в Godot сделать очень легко. Для этого перейдем на сцену player и добавим туда AnimationPlayer. Жмем на кнопку анимация, далее жмем новый и вводим название анимации. Сделаю анимацию для бега и назову ее Run.Чтобы добавить новый кадр для анимации нужно перейти в sprite.

Добавляем кадры в анимацию.Добавляем кадры в анимацию.

Напротив пункта frame есть клчик,если нажать на него, то кадр из спрайта добавиться в анимацию. Постепенно увеличиваем frame от 0 до 8 и ключиком добавляем кадр в анимацию. Вот как в итоге это должно выглядеть:

Создаем анимацию из кадров.Создаем анимацию из кадров.

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

Добавляем переменные для анимации.Добавляем переменные для анимации.

Добавляем две переменные sprite и player. Но ты заметил, они какие-то странные. Почему в начале стоит слово onready, что за странное значение этой переменной? Сейчас все объясню. Переменные типа onready нужны для взаимодействий с другими объектами на сцене. В данном случае мы подключаем их для воспроизведения анимации и получения спрайта игрока.

Анимация при ходьбе.Анимация при ходьбе.

На 16-ой строчке мы проигрываем анимацию ходьбы. Однако здесь еще появилась какая-то странная 22 строчка, что она делает? Она зеркально отражает анимацию игрока в зависимости от того, куда он идет. А на 24 строке мы говорим, что если игрок стоит, то проигрывать нужно анимацию idle.

Анимация прыжка.Анимация прыжка.

На 22 строчке мы проигрываем анимацию прыжка, если игрок не на земле. Вот собственно и все изменения в коде.

Заключение

Если ты все правильно делал, то у тебя должен получиться простой платформер. Что делать дальше? Да все что угодно! Улучшай свои навыки в использовании движка, создавай свои собственные игры изучай новые фишки. А этом я вынужден с тобой проститься, надеюсь ты хорошо провел время и научился чему-то новому.

Вот все материалы для этого туториала:

Подробнее..

Из песочницы Механики для реализации платформера на игровом движке GodotEngine

03.10.2020 14:23:19 | Автор: admin
Здравствуйте, я разрабатываю на Godot Engine игры примерно 2 года, поэтому решил создать сборник программного кода для реализации базовых механик платформера. Данный сборник рассчитан на новых, в использовании Godot engine, пользователей.

Базовый код


Для того чтобы персонаж подчинялся физике нужно вызвать move_and_slide() в методе _process() или _physics_process().

Во всём последующем коде я буду использовать статическую типизацию.

extends KinematicBody2D# Пока не ввели поддержку gdscript во вставках кода, придётся обходиться подсветкой pythonvar velocity: Vector2 = Vector2.ZERO # Объявляем переменную ускорения.# Нужна чтобы указывать смещение персонажа# относительно текущей позицииfunc _physics_process(_delta: float) -> void:# Ниже будет размещён программный кодself.velocity = self.move_and_slide(self.velocity, Vector2(0, -1)) # Перемещает персонажа в направлении указанном в первом аргументе.# Второй аргумент - нормаль пола, или же направление вверх.# Возвращает новое ускорение с учётом столкновений.

Данного кода будет достаточно чтобы заставить любой объект KinematicBody2D двигаться в зависимости от столкновений c другими объектами, но управлять этим объектом невозможно, тем более в платформерах этот объект по-прежнему не может падать вниз. Для того чтобы объект начал падать, нужно увеличивать значение self.velocity.y на положительное значение. Дело в том, что Godot Engine считает обе координаты из левого верхнего угла в правый нижний в режиме 2D. Для того чтобы объект падал нужно к ускорению прибавлять что-то. Обычно я использую константу GRAVITY, которая задаётся в начале программы. Далее будет представлен код с изменениями для того, чтобы объект падал.

extends KinematicBody2Dconst GRAVITY: int = 40# скорость падения в 40 пискелей будет достаточной, чтобы всё работало как надоvar velocity: Vector2 = Vector2.ZERO # Переменная ускоренияfunc _physics_process(_delta: float) -> void:# Ниже вставлять вызовы функций перемещения# Ниже можно ничего не трогатьself.velocity.y += GRAVITY# Умножать на _delta не нужно, мы же не используем self.move_and collideself.velocity = self.move_and_slide(self.velocity, Vector2(0, -1))# И да, объект сам перестанет падать на земле. Здесь не нужно усложнять.

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

Перемещение


Недавно я почерпнул один интересный способ управления персонажем, который стал альтернативой первому освоенному мной способу. Далее я покажу оба способа:

  • Первый, новый для меня способ
  • func move_character() -> void:var direction: float = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left") # Для определения направления нужно определить разницу между силой нажатия# перемещения вправо(+ по x, от 0 до 1) и влево(- по x, от 0 до 1).# Если вправо = 0, то скорость < 0 и наоборот, а если зажаты влево и# вправо, то направление равно нулю.self.velocity.x = direction * MOVE_SPEED # Умножаем на скорость.# вставить const MOVE_SPEED = # Скорость персонажа в пикселях под const GRAVITY
    
  • Второй способ
  • func move_character() -> void: # Гораздо медленнееvar direction: float = 0if Input.is_action_pressed("ui_left"):direction = -1elif Input.is_action_pressed("ui_right"):direction = 1else:direction = 0self.velocity.x = direction * MOVE_SPEED# вставьте const MOVE_SPEED = #скорость персонажа в пикселях# под const GRAVITY
    

Прыжок


Прыжок реализовывается почти также легко и однозначно, как первый способ перемещения. Для этого нужно создать константу силы прыжка, как скорость перемещения в начале и поставить значение большее чем значение гравитации. Сразу перейдём к программному коду:

func jump() -> void:if self.is_on_floor(): # Проверяем, на полу ли игрок.# Можно усложнить чтобы ввести двойной, но этот выпуск рассчитан на начинающихif Input.is_action_pressed("ui_jump"): # Назначаем в настройках событие# нажатия ui_jump и назначаем в него кнопку прыжка.# или используем "ui_up"self.velocity.y -= JUMP_POWER# const JUMP_POWER... создаём как и говорил ранее константу силы прыжка около остальных

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

Механики для реализации платформера на Godot engine. 2 часть

03.10.2020 20:19:18 | Автор: admin
Здравствуйте, это продолжение предыдущей статьи о создании игрового персонажа в GodotEngine. Я наконец понял, как реализовать некоторые механики, такие как второй прыжок в воздухе, карабканье по, и прыжок от стены. Первая часть была более простой по насыщенности, так как с чего-то же нужно было начинать, чтобы потом доработать или переделать.

Для начала я решил собрать весь предыдущий код, чтобы те, кто использовали информацию из предыдущей статьи поняли, как я представлял себе программу полностью
extends KinematicBody2D# Константыconst GRAVITY: int = 40const MOVE_SPEED: int = 120 # Скорость перемещения персонажа в пикселяхconst JUMP_POWER: int = 80 # Скорость прыжка# Переменныеvar velocity: Vector2 = Vector2.ZEROfunc _physics_process(_delta: float) -> void:# Ниже вставлять вызовы функций перемещенияmove_character() # Перемещение персонажаjump()# Ниже можно ничего не трогатьself.velocity.y += GRAVITYself.velocity = self.move_and_slide(self.velocity, Vector2(0, -1))func move_character() -> void:var direction: float = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left") self.velocity.x = direction * MOVE_SPEEDfunc jump() -> void:if self.is_on_floor():if Input.is_action_pressed("ui_accept"): # Я вспомнил про событие ui_accept# Оно вмещает в себя нажатие прыжкаself.velocity.y -= JUMP_POWER

Надеюсь тем, кто читал предыдущую статью стало примерно понятно, как всё работает. Теперь вернёмся к разработке.

Машина состояний


Машина состояний(в моём понимании) часть программы, что определяет состояние чего либо: в воздухе, на полу, на потолке, или на стене, а также определяет что должно происходить с персонажем в том или ином месте. В GodotEngine есть такая вещь как enum, что создаёт перечисление, где каждый элемент является, заданной в коде, константой. Думаю лучше покажу это на примере:
enum States { # Создаётся перечисление States, к константам которого можно обращаться через States.IN_AIR, States.ON_FLOOR...IN_AIR, # В воздухеON_FLOOR, # На полу ON_WALL # На стене}

Данный код можно смело положить в самое начало скрипта игрового персонажа и держать в голове, что он существует. Следом инициализируем переменную в нужном месте var current_state: int = States.IN_AIR, которая равна нулю, если использовать print. Далее нужно как-то определять что игрок в текущем состоянии будет делать. Думаю многим опытным разработчикам пришедшим из C++ знакома конструкция switch () {case:}. В GDScript есть похожая адаптированная конструкция, хотя и switch также есть в планах у разработчиков. Конструкция называется match. Думаю будет правильнее показать данную конструкцию в деле, так как рассказывать будет сложнее, чем показывать:
func _physics_process(_delta: float) -> void:# Ниже функции перемещенияmatch (self.current_state):States.IN_AIR:# Вызов методов что доступны в воздухе.self.move_character()States.ON_FLOOR:# Вызов методов, что доступны на земле.self.move_character()self.jump()States.ON_WALL:# вызов методов, что доступны, если мы упремся лицом в стену. Пока кроме перемещения ничего нет.self.move_character()# Ниже будет остальной код

Но мы до сих пор не меняем состояния. Нужно создать отдельную функцию, которую будем вызывать перед match-ем, чтобы изменять переменную current_state которую стоит добавить в код к остальным переменным. А функцию назовём update_state().
func update_state() -> void:# Тут всё зависит от запланированных разработчиком возможностей персонажа.if self.is_on_floor():self.current_state = self.States.ON_FLOORelif self.is_on_wall() and !self.is_on_floor():# Когда персонаж только на стене.self.current_state = self.States.ON_WALLelif self.is_on_wall() and self.is_on_floor():# Ситуация угла. Будем в данном случае на стене.self.current_state = self.States.ON_WALLelse: # Во всех других случаях будем в воздухеself.current_state = self.states.IN_AIR

Теперь, когда машина состояний готова, мы можем добавлять уйму функций. В том числе и добавить анимации к персонажу Даже не так Мы можем добавить тонну анимаций персонажу. Система стала модульной. Но на этом мы не закончили с кодом. Я сказал в начале, что покажу, как делать дополнительный прыжок в воздухе, карабканье по, и прыжок от стены. Начнём по порядку.

Дополнительный прыжок в воздухе


Во-первых, добавьте вызов прыжка в состоянии States.IN_AIR в наш match, который мы чуток доработаем.
Вот код нашего прыжка, который я исправил:
func jump() -> void:# Старую проверку в мусор. Мы сделаем её позже.if Input.is_action_pressed("ui_accept"): # Назначаем в настройках событиеif self.current_state == self.States.ON_FLOOR:# Как раньше, но добавляем проверку через текущее_состояниеself.velocity.y -= JUMP_POWERelif (self.current_state == self.States.IN_AIR or self.current_state == self.States.ON_WALL)and self.second_jump == true:# Тут проверяем на другие состояния и можем ли мы вообще прыгнуть второй разself.velocity.y = -JUMP_POWER# Сбрасываем накопленное ускорение падения и совершаем прыжокself.second_jump = false# Не забудьте добавить var second_jump: bool = true в самый верх. и в update_state()# Добавьте после if self.is_on_floor(): self.second_jump = true # чтобы сбрасывать состояние прыжка после приземления на пол.

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

Карабканье по стенам


К нашему сожалению, Нормаль стены GodotEngine не позволяет узнать, из чего следует, что нам придётся создать небольшой костыль. Для начала я сделаю сноску имеющихся на данный момент переменных, чтобы можно было проще сказать что изменилось.
extends KinematicBody2D# Сигналыsignal timer_ended # нужно чтобы заставить работать yield в wall_jump, что основан на обмане управления.# Константыconst GRAVITY: int = 40const MOVE_SPEED: int = 120 # Скорость перемещения персонажа в пикселяхconst JUMP_POWER: int = 80 # Скорость прыжкаconst WALL_JUMP_POWER: int = 60 # Сила прыжка от стены. Нужно для соответственной функцииconst CLIMB_SPEED: int = 30 # Скорость вскарабкивания# Переменныеvar velocity: Vector2 = Vector2.ZEROvar second_jump: bool = truevar climbing: bool = false # Нужно чтобы определять, карабкается ли игрок по стене, или нет.var timer_working: bool = falsevar is_wall_jump: bool = false # Нужно, чтобы определить, а от стены ли мы прыгаемvar left_pressed: bool = false # Для искусственного зажатия кнопки влевоvar right_pressed: bool = false # Для искусственного зажатия кнопки вправоvar current_state: int = States.IN_AIRvar timer: float = 0 # счётчик таймера, что будет встроен в _process(delta: float)var walls = [false, false, false] # определения стен и потолка. Нулевой и второй - стены. Первый - потолок.# Пока нужны только нулевой и второй# Перечисленияenum States {IN_AIR, # В воздухеON_FLOOR, # На полу ON_WALL # На стене}# И я сделаю чуть больше чем сказал, добавив метод _process() с самодельным таймеромfunc _process(delta: float):if timer_working:timer -= deltaif timer <= 0:emit_signal("timer_ended")timer = 0

Теперь нужно определять по какой стене игрок карабкается.
Вот дерево сцены, что вам стоит подготовить для реализации определителя стороны стены
image
Разместите 2 Area2D по бокам персонажа и CollisionShape2D обоих не должны пересекаться с персонажем. Подпишите соответственно объекты WallLeft/WallRight и присоедините сигналы _on_body_endered и _on_body_exited к единственному скрипту персонажа. Вот код который нужен чтобы определять стены(Добавить в самый конец скрипта):
# Надеюсь тут всё интуитивно понятно# Если нет, то комментарии вам в помощьfunc _on_WallRight_body_entered(_body):if (_body.name != self.name):self.walls[0] = true # Если засечённый объект не мы, объект слева - стенаfunc _on_WallRight_body_exited(_body):self.walls[0] = false # Когда тело вышло из коллизии другого объекта - стены слева нетfunc _on_WallLeft_body_entered(_body):if (_body.name != self.name):self.walls[2] = true # Если засечённый объект не мы, объект справа - стенаfunc _on_WallLeft_body_exited(_body):self.walls[2] = false # Когда тело вышло из коллизии другого объекта - стены справа нет

Приступим к методу карабканья. В коде всё будет сказано за меня
func climbing() -> void:if (self.walls[0] or self.walls[2]): # Если стена слева или стена справа есть# Создайте новый action в настройках и назовите ui_climb. Об этом я уже говорил в первой части.self.climbing = Input.is_action_pressed("ui_climb")else:self.climbing = false

И нужно переписать управление move_character() для того, чтобы можно было не просто держаться, а карабкаться вверх вниз, благо у нас есть direction
func move_character() -> void:var direction: float = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left") if !self.climbing:self.velocity.x = direction * MOVE_SPEEDelse:self.velocity.y = direction * CLIMB_SPEED

И исправляем наш _physics_process()
func _physics_process(_delta: float) -> void:# Ниже функции перемещенияmatch (self.current_state):States.IN_AIR:self.move_character()States.ON_FLOOR:self.move_character()self.jump()States.ON_WALL:self.move_character()# Ниже можно ничего не трогатьif !self.climbing:self.velocity.y += GRAVITYself.velocity = self.move_and_slide(self.velocity, Vector2(0, -1))

Теперь персонаж должен уметь карабкаться по стенам.

Прыжок от стены


Теперь реализуем прыжок от стены.
func wall_jump() -> void:if Input.is_action_just_pressed("ui_accept") and Input.is_action_pressed("ui_climb"): # Если нажата 1 раз кнопка прыжка и зажата кнопка карабканьяself.is_wall_jump = true # Мы прыгаем от стены = даself.velocity.y = -JUMP_POWER # Изменяем ускорение до -JUMP_POWERif walls[0]: # Если стена слеваself.timer = 0.5 # Установить self.timer на 0.5 секундыself.timer_enabled = true # Включаем таймерself.left_pressed = true # ставим переменную left_pressed на даyield(self, "timer_ended") # Дожидаемся срабатывания сигнала timer_endedself.left_pressed = false # отпускаем left_pressedif walls[2]: # Если стена справаself.timer = 0.5 # Установить self.timer на 0.5 секундыself.timer_enabled = true # Включаем таймерself.right_pressed = true # ставим переменную right_pressed на даyield(self, "timer_ended") # Дожидаемся срабатывания сигнала timer_endedself.right_pressed = false # отпускаем right_pressedself.is_wall_jump = false # Прыгнули. Больше не на стене

Добавляем вызов этого метода в наш match -> States.ON_WALL и мы присоединили наш метод к остальной части _physics_process().

Заключение


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

Механики для реализации платформера на Godot engine. 3 часть

15.10.2020 22:20:39 | Автор: admin
Здравствуйте, это уже 3-я часть сборника механик для реализации платформера. На этот раз мы поговорим о жизни, смерти и сохранении с последующей загрузкой. Это будет не совсем урок по реализации платформера, но без данной части программы мы особо и не сможем нормально играть. Только бесконечные уровни а-ля игровые автоматы начала девяностых.

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



Пожалуй начнём в обратном порядке: сначала система сохранения и загрузки, потом о смерти и жизни.

Система сохранения и загрузки


Вообще по умолчанию Godot Engine создаёт папку для данных пользователя где-то в AppData/godot/app_userdata/[название проекта], если не включить галочку в use custom user dir.



Вот как это выглядит в проекте.

После этих действий, проект будет создавать папку в appdata/[название проекта]. Также user:// будет указывать туда. Теперь мы точно знаем куда будут отправляться любые файлы проекта. Теперь стоит создать в проекте папку scripts куда мы будем ложить все скрипты для синглтонов. О создании синглтонов рассказано в документации, так что рассказывать ещё и это будет лишним.
Ладно. Приступим к коду.

# Код снова будет настроен на python, так как он больше всего похож на gdscript, а последний пока не ввели.# scripts/lvl_mgr.gd допустим в этом файле будет храниться информация для переходов между уровнямиextends Node;var current_level: int = 0 # Для выбора уровня если у вас полностью линейный платформер с нумерацией уровней.var current_scene: Node = null # Фактически - указатель на текущую сцену. Для редактора - переменная равная какому-то узлуfunc load_level(lvl_id: int = 0) -> void:var lvl_name = "res://levels/level%s/level%s.tscn" % [lvl_id, lvl_id]# Создаём переменную чтобы хранить путь к уровнюself.current_level = lvl_id # запоминаем текущий уровень для того, чтобы перезагружаться отсюдаgoto_scene(lvl_name) # Вызываем метод перехода к сценеfunc goto_scene(path: String) -> void:call_deferred("_deferred_goto_scene", path)func _deferred_goto_scene(path: String) -> void:# Здесь всё и будет происходить.current_scene.free() # Очищаем переменнуюvar scene = ResourceLoader.load(path) # Загружаем нужную сценуcurrent_scene = scene.instance() # Создаём из упакованной сцены узелget_tree().get_root().add_child(current_scene) # Добавляем к корню дерева сцен наше дерево из сценыget_tree().set_current_scene(current_scene) # Опционально. Делает совместимым с SceneTree.change_scene() API. Бла-бла-бла. Если коротко, то если это добавить мы сможем держать N деревьев из сцен и переключаться между ними.

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

Итак, приготовления более-менее закончены, а теперь приступим к самой системе сохранений-загрузок.

# scripts/save_mgr.gd Подключить как синглтон SaveManagerextends Nodevar file: File = File.new() # Создаём обработчик файловvar dir: Directory = Directory.new()var save_data = { # Данные для сохранения. Чтобы не формировать их каждый раз"current_level": LevelManager.current_level}func _ready():if !dir.dir_exists("user://saves"):dir.make_dir("user://saves")func update_save_data() -> void: # Для обновления данныхself.save_data["current_level"] = LevelManager.current_levelfunc upload_save_data() -> void: # Для загрузки данных во все фрагменты программы.LevelManager.current_level = self.save_data["current_level"]func save_progess(file_name: String) -> void:file.open("user://" + file_name, File.WRITE)# Теперь мы как-то должны упаковать данные что сохраняем.# Пусть это будет словарь.self.update_save_data() # Собирает по игре данные в save_data.file.store_string(str(self.save_data)) # Сохраняет данные из save_data в форме строкиfile.close()returnfunc load_progress(file_name: String) -> void: # Нужно тоже получить имя файла сохранения, если их в игре > 1file.open("user://" + file_name, File.READ)# Также открыли файл.self.save_data = JSON.parse(file.get_as_text()).result # Вытаскивает в self.save_data результат парсинга содержимого файла.self.upload_save_data() # Вызов этого метода вытаскивает из save_data все данные по правильным местамfile.close()return

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

Смерть


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

res://+-----+- scenes|+-----+ dead_screen||+---- dead_screen.tscn||+---- dead_screen.gd|+ ...+ ...

Теперь откройте нашего персонажа и создайте новый метод dead().

# . . .func dead() -> void:LevelManager.goto_scene("res://scenes/dead_screen/dead_screen.tscn")return# . . .

В сцене dead_screen.tscn примерно следующее создайте:


И вот скрипт:

extends Controlfunc _on_exit_pressed(): # Выход в главное менюLevelManager.goto_scene("res://scenes/main_menu/main_menu.tscn") # Такая сцена должна существоватьfunc _on_restart_pressed():LevelManager.load_level(LevelManager.current_level) # Если нажать restart_level - загрузится текущий уровень

Ну а живем мы пока не мертвы

Заключение


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

P. s. И да, отпишитесь в комментариях о том, о чём мне стоит рассказать в следующий раз. Постараюсь подготовить материал достаточно качественно.
Подробнее..

Механики ловушек и интерактивных объектов в Godot Engine. Часть 2

27.10.2020 16:17:12 | Автор: admin
Приветствую вас во второй части механик ловушек и интерактивных объектов в Godot Engine. Я решил пока-что выпустить эту часть, чтобы выпустить эту часть, чтобы показать механики, которые отвечают за направление уровня в играх. Я имею в виду двери, ключи и движущиеся платформы. В данном случае движущиеся по желанию игрока. Такие платформы можно будет ронять, использовать как мост и другое.Для того чтобы всё работало мы будем использовать NodePath. Только в этом случае не для перемещения игрока, а для перемещения объектов относительно текущего положения.Предыдущие статьи:

Переключатель

Для начала создадим переключатель. Это будет Area2D со спрайтом и коллизией. Он будет иметь метод interact(_interactor: Node), для того чтобы взаимодействовать с другими объектами, на которые будет указывать NodePath. Вот скрипт этого переключателя:
toolextends Area2Dexport (NodePath) var dependent_node: NodePathexport (bool) var toggle: bool = falsevar prev_toggle: bool = falsefunc _process(_delta: float) -> void:if prev_toggle != toggle: # Отвечает за воспроизведение нужной анимации. Нуждается в доработкеif toggle == true:$AnimatedSprite.play("toggle_on")else:$AnimatedSprite.play("toggle_off")prev_toggle = togglefunc interact(_interactor: Node) -> void:if !$AnimatedSprite.is_playing(): # Если Анимация воспроизводится и есть метод "toggle" у dependent_bode if get_node(dependent_node).has_method("toggle"):self.toggle = !self.toggle # поменять значение toggleget_node(dependent_node).toggle(toggle) # Вызвать метод toggle у dependent_node и передать toggle
А так выглядит сцена переключателя

Улучшение шипов

Теперь вспомните те шипы. Теперь мы улучшим их, чтобы добавить интерактива в игру. Пусть они будут переключаться также рычагом.
# Добавьте в конец того скрипта шиповfunc toggle(toggled: bool) -> void:self.showed = toggled
Теперь скрипт будет выглядеть следующим образом:
toolextends StaticBody2Dexport var showed = falseexport (bool) var timer = falsevar prev_showed = showedfunc _physics_process(_delta):if timer and $Timer.is_stopped():$Timer.start()if showed != prev_showed:if showed:$AnimatedSprite.play("show")else:$AnimatedSprite.play("hide")prev_showed = showedfunc _on_Area2D_body_entered(body):if body.name.ends_with("Actor") and showed:body.dead()func _on_Timer_timeout():if timer:showed = !showed$Timer.start()func toggle(toggled: bool) -> void:self.showed = toggled
Как-то так. Теперь игрок может менять состояние шипов.

Ключи и двери

Ключи

В скрипте игрока было число ключей и правила их добавления. Теперь нужно показать как они выглядят и дать их рецепт. Ключи будут подходить ко всем дверям, но будут ломаться после открытия.
extends Area2D # Как всегда спрайт и коллизия среди детей корневого узла сценыfunc _on_Key_body_entered(body):if body.name == "Actor": # Если подобравший ключ - Actorbody.key_picked_up() # Вызови метод подбора ключаself.queue_free() # Удалить с поля ключ
Теперь если войти в ключ он автоматически подберется. И держите скриншот того, как выглядит мой ключ.

Двери

По-началу двери могли только открываться, но это было в начале. Теперь я придумал как позволить им закрываться.
extends StaticBody2Dexport (bool) var key_can_used: bool = truefunc open():if !key_can_used: returnself.visible = falsefunc close():if !key_can_used: returnself.visible = truefunc toggle(vis: bool):self.visible = vis
Я добавил возможность переключить состояние двери через рычаг. Вот как выглядит сцена в редакторе.

Заключение

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

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru