Godot 4. Первое знакомство.

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

В этом уроке мы познакомимся с Godot, изучим его основы и создадим платформер. Без разницы, умеете ли вы программировать или нет, я объясняю все с нуля. Если готовы, начинаем.

Что такое Godot

Godot — это полностью бесплатный игровой движок, с открытым исходным кодом.

Он позволяет делать игры и программы под:

  • Web
  • Windows
  • Linux (в том числе Mac)
  • Android
  • IOS
  • PS4 и Nintendo Switch — только через посредника, подробнее в документации

И неофициально под пиратские прошивки:

Также, сам движок Godot, доступен на:

  • Linux
  • Windows
  • Web (т.е. в браузере)
  • Android

Да, именно так, вы можете создавать свои игры с телефона (в теории), что не советую, т.к. у телефона маленький экран и это неудобно. Используйте лучше планшет с клавиатурой и мышкой, или ноутбук приобретите.

Godot, в отличии от других движков (Unity, Unreal Engine), не требователен к железу, и работает быстро даже на самом устарелом железе.

Немного теории

Кратко о том как все устроенно в Godot:

  • В Godot все состоит из сцен.
  • Внутри сцен находятся узлы(Nodes) или другие сцены. Другими словами, в сцену можно вложить другую сцену или узел.
  • Узлы — это кубики из которых строятся сцены
  • Узлы могут передавать сообщения другим узлам с помощью сигналов, но об этом позже.

Начинаем работу.

Скачайте Godot с официального сайта. Далее, создайте проект newTest.

У вас откроется проект, разберем за что отвечает каждая часть интерфейса.

Разбираемся с интерфейсом.

Разберем каждую часть:

  • Nodes — здесь располагается дерево узлов и сцен
  • Resources — файловый менеджер, отсюда будем забирать ресурсы.
  • Main workspace — главное окошко, в нем будем расставлять врагов на уровне, рисовать карту и т.д.
  • Current Node Settings — настройки выбранного узла из дерева узлов Nodes

Сверху расположены режимы:

  • 2D — для 2D сцен
  • 3D — для 3D сцен
  • Script — для скриптом хранящих логику сцены
  • AssetLib — магазин с бесплатными ресурсами для Godot

Выберите 2D режим.

Теперь разберем узлы (nodes):

  • Синий— отвечает за узлы для работы с 2D графикой.
  • Красный— отвечает за 3D графику
  • Зеленый— за пользовательский интерфейс (за GUI)

В этом уроке, мы не будем создавать интерфейс и работать с 3D графикой, т.е. будем использовать только синие узлы.

Ок, теперь, создайте вот такое дерево узлов:

Дерево узлов

Для этого нажимайте + (вверху), или используйте комбинацию Ctrl+A. Имя узла то же, что и на скриншоте сверху.

Вложенные узлы являются частью узла родителя, т.е. перемещаются вместе с ним. В нашем примере: Camera2D вложена в CharacterBody2D, т.е. Camera2D — ребенок, CharactedBody2D — родитель.

Разберем кратко добавленные узлы:

  • Node2D — контейнер, куда мы можем вложить другие объекты.
  • CharacterBody2D — объект, который взаимодействует с физикой Godot, т.е. может взаимодействовать с уровнем (останавливаться при столкновении со стеной), и другими объектами на уровне. Его будем использовать для игрока.
  • Camera2D — камера, через которую игрок видит игру.
  • Sprite2D — спрайт, т.е. обычная картинка
  • CollisionShape2D — форма коллизии

Теперь, выберите Sprite2D, и установите ему спрайт перетаскиванием картинки в поле Texture.

Задали спрайт игрока, отлично. Теперь, задайте CollisionShape (Форма коллизии), в качестве типа, выберем прямоугольник (RectangleShape2D)

Подгоните форму коллизии под размер спрайта

Давайте сгруппируем CharacterBody2D, чтобы случайно не передвинуть CollisionShape2D. Сгруппированные узлы, всегда, перемещаются вместе.

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

Переименование узлы

И скомпилируем.

Получили следующее.

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

Создаем уровень

Скачайте тайлы с OpenGameArt (файл sheet.png) и переместите его в ваш Godot проект.

Далее, в TileMap, создайте новый Tileset ()

Переместите туда тайлы (sheet.png)

Соглашаемся

Вот и все, теперь вы можете нарисовать уровень с помощью тайлов. Выбираете тайл и рисуете им.

Однако, у этих тайлов нет коллизии, что значит что игрок будет проваливаться сквозь них. Что-бы это исправить, создадим Физический слой (Physics Layer)

Теперь, мы можем настроить коллизию для каждого тайла (по аналогии с игроком), для этого:

  • Зайдите в Tileset (внутри узла TileMap)
  • Выберите режим Paint
  • Выберите Physics Layer 0

Далее, выбирайте тайл — справа, и устанавливайте ему форму коллизии — слева.

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

Спасибо Godot 4 (в ранних версиях такого не было), код игрока создался автоматически.

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

Управление следующее:

  • Движение игрока задается стрелками
  • Прыжок на пробел

Но давайте напишем свою логику игрока. Таким образом, у вас будет понимание как писать игры на этом движке.

Основы GDScript.

Если вы знакомы с Python, или любым другим языком программирования, то можете пропустить данный блок и прокрутить статью до блока Настраиваем управление. Т.к. здесь, я буду объяснять самые основы.

Разберем основные понятия:

  • Переменная — это именованные данные. Например, speed = 123
  • Константа — неизменяемая переменная
  • Функция — блок кода, который выполняется при вызове функции
  • Ветвление — это блок кода, который выполняется только если условие — истина.
  • Комментарии — удобные подсказки для себя любимого, не влияют на код, но очень сильно помогают разобраться в нем и не забыть что за что отвечает.

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

Начнем с самого простого, с комментариев. Пишутся они так:

#ваш_комментарий_пишите_здесь_что_хотите

Т.е. начинаются с # (решетки). Комментарии не влияют на код, они нужны для удобства программиста.

Переменные

Нужны они для хранения данных, например, координат игрока.

Переменные создаются по такому шаблону:

var имя_переменной

Переменная создана, ей можно задать значение

имя_переменной = 1337

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

имя_переменной_другая = имя_переменной

И пример использования переменных:

var posX
var posY = 10
posX = posY

Математические операции с переменными

Также, можно производить математические операции, примерно так:

var x1 = 5
var x2 = 10
var sum = x1+x2 #15
var div = 5/x1 #1
var mul = 5*x2 #50
var reminder = 10%7 #3

где

  • + сложение
  • — вычитание
  • * умножение
  • / деление
  • % остаток от деления

Также, есть упрощенная форма записи.

x1 += 5

Что, то же самое что и:

x1 = x1 + 5

Подобным образом можно упростить и другие операции:

x1 += 5
x1 -= 5
x1 /= 5
x1 *= 5

Массив значений

Коллекция значений одного типа, создается массив так:

var test_arr = [1,2,3]
  • test_arr — названия переменной массива
  • [1,2,3] — значения массива

Вы можете создать пустой массив, опустив данные в скобках

var test_arr = []

чтобы получить значение, пишите индекс массива в квадратных скобках.

test_arr[1] #2
test_arr[0] #1
test_arr[2] #3
test_arr[4] #error

Если выйти за границу массива [4], получим ошибку.

Используйте append, для добавления элементов в конец массива

test_arr.append(1337)

append — значит добавить в переводе с английского

Ладно, с основами массивов разобрались, двигаемся дальше.

Константы

Константа — это неизменяемые именованные данные.

Пишутся так:

const имя_константы = 300.0

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

const SPEED = 300.0
var x_pos += SPEED

Функции.

Функция позволяет вызвать блок кода (несколько строк кода подряд). Границы блока определяются форматированием, таким образом.

func test123():
  #Start block
  var i=0
  var b = 1
  b += i
  #Close block

Т.е. прям как в дереве узлов в Godot.

Начало блока определяется : (двоеточием). Если несколько строк кода имеют одинаковое количество пробелов, значит они в 1-ом блоке.

блок_1:
  var b = 1
  var a = 121
  блок_2:
    b = 5
    a = 11
    var d = 333
  var c = 1

Смысл, думаю, понятен, если нет, спрашивайте в комментариях к статье.

Еще, переменные изолированны границами блока. Я имею ввиду, что блок_2 в примере выше, имеет доступ к переменным a и b, в нем же создается переменная d

блок_2:
  b = 5
  a = 11
  var d = 333

Так вот, блок_1 к переменной d доступа не имеет, и следовательно, подобный псевдо-код на выдуманном языке вызовет выдуманную ошибку

блок_1:
  var b = 1
  var a = 121
  блок_2:
    b = 5
    a = 11
    var d = 333
  var c = 1
  d = 123

Ок. Вернемся к реальному коду. Чтобы вызвать функцию test123 используется эта комманда:

test123()

Т.е. шаблон такой:

имя_функции(аргументы)

В нашем случае, аргументов — нет, поэтому оставляет скобки пустыми.

В функцию можно передать аргументы (данные извне). Вот так:

func test123(randName1, randName2, test1):
  return randName1 + randName2 + test1

test123(2,2,50)

Т.е. аргументы это randName1, randName2, test1, значения этих аргументов задается при вызове функции, т.е. в нашем случае:

  • randName1 = 2
  • randName2 = 2
  • test1 = 50

В функции появилась новая комманда return. Она указывает что возвращает функция. Т.е. функцию проще представить как черный ящик, который что-то там делает, и на выходе выдает то что нам нужно. Так вот, выдает он с помощью return. В нашем случае, функция возвращает сумму аргументов (2,2,50).

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

func test1():
  var i=0
  var b=1
  return
  var c = i+b

Либо же, преждевременно вернуть значение. Т.е. в данном случае этот код:

var c = i+b

Не выполнится, т.к. мы уже вышли из функции с помощью return

Это полезно при ветвлении(if,else,elif,match) или в циклах (for, while), который я пока не буду описывать. Я уже жалею что не вывел блок основ программирования в отдельную статью.

Ветвления

Ветвления неразрывно связаны с булевой логикой и условиями.

Переменные могут иметь булево значение:

var testBool = true

Булево значение, может быть правдой или ложью (true и false)

Условия, по итогу, возвращают булево значение.

var testBool
testBool = 1 > 2 #false
testBool = 1 < 2 #true
testBool = 1 >= 2 #false
testBool = 1 <= 2 #true
testBool = 1 == 2 #false
testBool = 1 != 2 #true
testBool = not true #false
testBool = true and true #true
testBool = true and false #false
testBool = true or false #true
testBool = false or false #false
testBool = not(false and true) #not(false) = true

И подробное объяснение:

  • 1 > 2 — 1 больше 2
  • 1 < 2 — 1 меньше 2
  • 1 >= 2 — 1 больше или равно 2-м
  • 1 <= 2 — 1 меньше или равно 2-м
  • 1 == 2 — 1 равно 2-м
  • 1 != 2 — 1 неравно 2-м
  • not true — инвертировать булево значение (true превращается в false и наоборот)
  • true and true — возвращает true если оба значения true, в ином случае, вернет false
  • true or false — возвращает true если хотя-бы 1-о значение равно true, в ином случае, вернет false

Эти условия можно использовать в ветвлении, примерно так:

if(условие) #Если условие истина, выполняем этот блок
  какой_то_код
else: #Если ложь, выполняем этот
  другой_какой_то_код

Операторы ветвления (if,else), позволяют выполнять блок кода в зависимости от условия.

И в качестве примера:

var deserveFiveMark = false
var yourMark
if(deserveFiveMark):
  yourMark = 5
else:
  yourMark = 2

Блок else, можно не писать если нет необходимости. Также, можно делать зависимые условия, т.е. перебирать варианты, если первый не подошел проверяем второй и так далее, если какой-то подошел, то другие варианты не используем. Делается это так:

var yourMark = 3
if(yourMark == 5):
  print("Five")
elif(yourMark == 4):
  print("Four")
elif(yourMark == 3):
  print("Three")
elif(yourMark == 2):
  print("Two")
elif(yourMark == 1):
  print("One")

Блок elif это сокращенние от else if, т.е. elif это удобная запись этого:

if(yourMark == 5):
  print("Five")
else:
  if(yourMark == 4):
    print("Four")
  else:
    if(yourMark == 4):
      print("Three") 
    ...

Ладно, этого вам хватит на первое время, двигаемся дальше.

Настраиваем управление.

Сперва, настроим управление, для этого зайдем в Проект->Настройки проекта->Список действий.

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

Создайте действия:

  • move_right
  • move_left
  • jump

Введя названия в поле, и нажав ENTER

Теперь, к данным действиям привяжем кнопки. Для этого нажмите на +

И нажмите на кнопку (для move_left это A), далее ОК

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

Возвращаемся к коду.

Давайте заменим код игрока на следующий:

extends CharacterBody2D #Наследуем CharacterBody2D

#Прописываем константы
const SPEED = 300.0
const JUMP_VELOCITY = -400.0
const GRAVITY = 500.0

# Переопределяем _physics_process
func _physics_process(delta):
  #Если игрок не на полу, т.е. в воздухе, то применяем гравитацию
  if not is_on_floor():
    velocity.y += GRAVITY*delta
  #Если игрок на полу, и только что нажата кнопка jump, то даем вверх скорость игроку
  if Input.is_action_just_pressed("jump") and is_on_floor():
    velocity.y = JUMP_VELOCITY
	
  var direction = 0
  if Input.is_action_pressed("move_left"):
    direction += 1
  if Input.is_action_pressed("move_right"):
    direction -= 1
  #Даем горизонтальную скорость игроку
  velocity.x = direction*SPEED
  #move_and_slide применяет скорость к игроку, взаимодействует с физикой движка.
  move_and_slide()

Рассмотрим его:

extends CharacterBody2D #Наследуем CharacterBody2D

Если кратко, когда я говорю что что-то наследуется от CharacterBody2D, я имею ввиду что CharacterBody2D берется как шаблон, т.е. в нашем случае, наш объект (узел) является свежей копией CharacterBody2D, это значит что данный скрипт имеет доступ ко всем переменным и функциям CharacterBody2D.

#Прописываем константы
const SPEED = 300.0
const JUMP_VELOCITY = -400.0
const GRAVITY = 500.0

Обычные константы, мы их уже проходили

# Переопределяем _physics_process
func _physics_process(delta):

Данная функция вызывается самим движком 60 раз в секунду (еще _ready, _input, _process вызываются но об этом позже). Поэтому я и написал переопределяем в комментарии.

#Если игрок не на полу, т.е. в воздухе, то применяем гравитацию
  if not is_on_floor():
    velocity.y += GRAVITY*delta

Функция in_on_floor() возвращает true если игрок на полу, иначе false. Т.е. в данном коде, мы применяем гравитацию к игроку (увеличивает скорость по оси y) на GRAVITY и умножаем на delta.

delta — это время между кадрами, нужна она для получения стабильной скорости при разных FPS. Т.е. если мало FPS то delta большая, если много FPS, то delta — маленькая. Умножив скорость на delta, можно получить стабильную скорость независимо от FPS.

Еще, заметьте, мы нигде не определяли переменную velocity. А получили её от CharacterBody2D, после наследования.

 #Если игрок на полу, и только что нажата кнопка jump, то даем вверх скорость игроку
  if Input.is_action_just_pressed("jump") and is_on_floor():
    velocity.y = JUMP_VELOCITY

Рассмотрим новую комманду Input.is_action_just_pressed.

Input — это синглтон (singleton), т.е. переменная доступная везде. У неё есть функции:

  • is_action_jump_pressed — действие только что нажато. Срабатывает 1 раз в момент нажатия действия.
  • is_action_pressed — дествие нажато. Срабатывает каждый кадр при нажатии действия.
  • is_action_just_released — дествие только что отжато. Срабатывает 1 раз в момент отжатия действия.

Есть и другие функции, но это самые часто используемые. В качестве аргумента принимают названние действия.

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

var direction = 0
if Input.is_action_pressed("move_left"):
  direction += 1
if Input.is_action_pressed("move_right"):
  direction -= 1
#Даем горизонтальную скорость игроку
velocity.x = direction*SPEED

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

И самое последнее:

move_and_slide()

Данная функция просчитывает всю физику за вас. Т.е:

  • Не дает пройти сквозь CollisionShape2D
  • Прибавляет скорость к позиции игрока
  • Обновляет значение функции is_on_floor()
  • Влияет на скорость игрока (velocity)
  • и т.д.

Полезная функция, скажу я вам.

Ок, теперь все разобрали. Если остались вопросы, оставляйте их в комментариях.

Заключение.

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

Итоговый результат.

Пожалуйста отключи блокировщик рекламы, или внеси сайт в белый список!

Please disable your adblocker or whitelist this site!