Здравствуйте снова. В этом выпуске я расскажу о том, как исправил механику карабканья, показанную во втором выпуске, покажу механику взаимодействия, для создания интерактива. Это по-прежнему будет доработка персонажа, так что окружающий мир будет подвергнут минимальным изменениям, но главный герой будет очень сильно улучшен. Правда до дерева навыков ещё далеко, поэтому оставайтесь на связи и я покажу как можно реализовать всё, что придёт нам в голову.
Добавьте в сцену игрока узел RayCast2D
Вот так выглядит мой персонаж. RayCast добавьте в начало стрелки.
В общем покажу весь код своего персонажа и постараюсь прокомментировать его наиболее понятно.
На данный момент это самый актуальный код персонажа, который я только создал. Из-за того, что код полностью готов, мне нечего добавить к нему отдельно. А так как я вынес уроки по ловушкам в отдельный цикл, то мне также нечего сказать про примеры использования системы взаимодействий. Также мне стало интересно, что вы предпочли бы узнать в следующей части и представил ниже опрос составленный из пришедших мне в голову идей для механик. Спасибо за прочтение и до следующих публикаций.
Предыдущие статьи:
Улучшение системы карабканья из второго выпуска и другое
Добавьте в сцену игрока узел RayCast2D
Вот так выглядит мой персонаж. RayCast добавьте в начало стрелки.
В общем покажу весь код своего персонажа и постараюсь прокомментировать его наиболее понятно.
# В этот раз будет очень много кода, потому что я не представляю себе все эти системы по отдельности.
extends KinematicBody2D
signal timer_ended # Сигнал о отключении таймера в _process
const 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 = 0
var 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_cast
ray_cast.add_exception($WallRight)
ray_cast.add_exception(self)
func _process(_delta: float) -> void: # метод _process
if timer > 0 or timer_enabled:
timer -= _delta # Уменьшаем таймер на _delta
if timer <= 0 and timer_enabled:
timer_enabled = false
timer = 0 # Сбрасываем значение и выключаем таймер
emit_signal("timer_ended") # Испускаем сигнал таймера.
if self.direction != 0:
self.ray_cast.cast_to *= -1
self.prev_direction = self.direction # обновляем предыдущее направление если текущее не равно 0
func _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 += GRAVITY
else: # Иначе падаем в 4 раза медленнее
self.velocity.y += float(GRAVITY) / 4
self.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_FLOOR
is_double_jump = true
elif self.is_on_wall():
self.current_state = States.ON_WALL
is_double_jump = true
elif self.is_on_floor() and self.is_on_wall():
self.current_state = States.ON_WALL
func fall() -> void:
self.velocity.y += GRAVITY
func 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 = false
func 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_pressed
if (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 = 0
elif self.walls[2]: # Почти то же самое что с движением по левой стене, только направления местами поменял
if direction < 0:
climb_up()
elif direction > 0:
climb_down()
else:
self.velocity.y = 0
# else: # Я думал что это будет нужно, но видимо это осталось лишним
# self.velocity.y = 0
else: # Иначе если не карабкаемся по стене и от неё не прыгаем просто передвигаемся
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")
return
func jump() -> void: # Совершенно никаких изменений со второго выпуска в прыжке
if Input.is_action_just_pressed("ui_accept"):
if is_on_floor():
self.velocity.y = -JUMP_POWER
if !is_on_floor() and is_double_jump:
is_double_jump = false
self.velocity.y = -JUMP_POWER
func wall_jump() -> void:
if Input.is_action_just_pressed("ui_accept") and Input.is_action_pressed("ui_climb"):
self.is_wall_jump = true
self.velocity.y = -JUMP_POWER
if walls[0]:
self.timer = 0.3
self.timer_enabled = true
self.right_pressed = 1 # Это приравнивание как я понял вынужденная мера из-за слишком простого механизма перемещения
yield(self, "timer_ended") # Подождать сигнал таймера
self.right_pressed = Input.get_action_strength("ui_right")
# Сбросить перемещение влево
elif walls[2]:
self.timer = 0.3
self.timer_enabled = true
self.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: # И если это не null
if 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 += 1
func _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()
Заключение
На данный момент это самый актуальный код персонажа, который я только создал. Из-за того, что код полностью готов, мне нечего добавить к нему отдельно. А так как я вынес уроки по ловушкам в отдельный цикл, то мне также нечего сказать про примеры использования системы взаимодействий. Также мне стало интересно, что вы предпочли бы узнать в следующей части и представил ниже опрос составленный из пришедших мне в голову идей для механик. Спасибо за прочтение и до следующих публикаций.
Daar
Большое спасибо, тоже несколько раз начинал изучение данного движка, но вечно сил не хватало.
stalker320 Автор
Я уже давно в этом движке. Поэтому решил переодически публиковать статьи по механикам. Лично у меня месяца 2-3 ушло, чтобы полностью написать и отработать данный скрипт, но я его всё-таки довел до этого уровня. Удачных изучения движка и разработок.
bushuy
Не подскажете какая это версия движка? Просто код какой-то немного странный.
stalker320 Автор
Это 3.2.3. Я использую статическую типизацию и match кейсы. Ничего сверхъестественного я не внедрил. Просто я люблю использовать непопулярные фишки этого языка программирования.
bushuy
Спасибо, статьи интересные.
Daar
Вспомнил от чего бросил! Хреновая работа с сетью, точнее сервер писать надо только на godot. А нужно было сервер в другой среде делать. Там сейчас есть websocket, но он очень облегчен, в неё даже SSL нельзя прикрутить и трафик открыто идет. Пока жду 4 версию.
stalker320 Автор
Понятно. Я пока не планировал делать онлайн игры в ближайшее время, поэтому ещё не изучал данную проблему.