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

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

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



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


Добавьте в сцену игрока узел 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()

Заключение


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

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

Какие механики показать в следующем выпуске? Будут показаны только 2-3 механики из всех выбранных в опросе. Остальные по количеству голосов будут опубликованы в последующих выпусках

  • 45,4%Атаки ближнего и дальнего боя5
  • 45,4%ИИ для противников/союзников5
  • 45,4%Система карманов для хранения предметов5
  • 63,6%Система характеристик оружия7
  • 54,6%Система здоровья персонажа6
  • 36,4%Система эффектов, которая поможет изменить свойства персонажа4
  • 63,6%Дерево навыков7
Реклама
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее

Комментарии 8

    +1
    Большое спасибо, тоже несколько раз начинал изучение данного движка, но вечно сил не хватало.
      0
      Я уже давно в этом движке. Поэтому решил переодически публиковать статьи по механикам. Лично у меня месяца 2-3 ушло, чтобы полностью написать и отработать данный скрипт, но я его всё-таки довел до этого уровня. Удачных изучения движка и разработок.
        +1

        Не подскажете какая это версия движка? Просто код какой-то немного странный.

          0
          Это 3.2.3. Я использую статическую типизацию и match кейсы. Ничего сверхъестественного я не внедрил. Просто я люблю использовать непопулярные фишки этого языка программирования.
            +1

            Спасибо, статьи интересные.

          –1
          Вспомнил от чего бросил! Хреновая работа с сетью, точнее сервер писать надо только на godot. А нужно было сервер в другой среде делать. Там сейчас есть websocket, но он очень облегчен, в неё даже SSL нельзя прикрутить и трафик открыто идет. Пока жду 4 версию.
            0
            Понятно. Я пока не планировал делать онлайн игры в ближайшее время, поэтому ещё не изучал данную проблему.
              0

              Вы вводите людей в заблуждение, тщательно не изучив данный вопрос. SSL очень даже прикручивается — https://docs.godotengine.org/en/stable/tutorials/networking/index.html
              Есть готовые написаные сервера, например Nakama, но кроме всего прочего благодаря GDNative сервер пишется на чем угодно.

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

        Самое читаемое