В предыдущей статье мы рассмотрели азы создания нового проекта в Godot. И с этими поверхностными знаниями можно разве что поглядеть demo-версии игр.
Во второй части на повестке дня у нас:
1) Экспорт готового проекта в бинарные файлы для выбранной архитектуры.
2) Новые анимации. Параметры персонажа.
3) Управление.
3) GDScript. Добро пожаловать в настоящий кодинг!
4) Импорт простейших Tilesets.
5) Бонус: разбор устройства простейших задников.
Ну и как обычно, много картинок!
В прошлой статье я упоминал что авторы не предусматривали бинарники для linux x86. На время написания статьи бинарные файлы всё ещё были не готовы. Но экспортировать готовые проекты, demo или просто тесты уже хотелось. Но что делать, если нет Шаблонов экспорта? Правильно! Скомпилировать их самому!
Переходим в директорию с исходными файлами и компилируем
Средства отладки:
После успешной компиляции переименовываем свежеполученный файл в linux_x11_32_debug
Сам шаблон экспорта:
Переименовываем в linux_x11_32_release
Запаковываем в zip-архив.
Скармливаем архив Godot. Вуаля, теперь можно экспортировать проект для ОС GNU/Linux любой разрядности.
Да даже под ОС и разрядности, отличные от Вашей. Бинарные файлы для Windows без проблем смог завести в wine, главное не забыть снять или поставить галочку 64bits, и выключить debugging, если не нужен.
Подробнее про настройки экспорта можно почитать на сайте проекта на английском языке. Если будет актуально и востребовано, можно будет подумать о русской локализации статей и создания русскоязычной wiki по Godot.
В прошлом уроке я заранее подготовил текстурку со спрайтами бега и прыжка нашего Капитана. Поэтому я думаю создать две анимации jumping и run не составит труда. Я думаю это не трудно будет сделать. Скажу лишь параметры:
Да, прыжок — это только прыжок. Значит нужна ещё анимация падения.
Три пункта:
1) Напоминаю. Проверьте, стоит ли галочка на параметре Camera2D/Current, отвечающая за привязку объекта «камера» к объекту player.
2) Добавим Node CollisionShape2D. Эта «Нода» отвечает за положение нашего Капитана в 2D-пространстве.
В Inspector выбираем CollisionShape2D -> Shape -> New RayShape2D. В 2-х миллиметрах правее появится небольшая стрелочка — нажимаем на неё — откроются параметры RayShape2D.
Параметр RayShape2D/Lenght оставим 20, а вот Custom Solver Bias выставим 0.5 (этим мы разрешим небольшое смещение)
Теперь в самой игре в будущем, если персонаж будет «не попадать ногами по полу», его положение можно будет отрегулировать этой стрелочкой. Именно она проверяет «касается ли спрайт пола».
3) С геометрией веселее. Для начала надо добавить Node CollisionPolygon2D. Теперь нам надо нарисовать этот самый полигон, дающий персонажу «массу».То-есть надо нарисовать то, чем герой будет «биться об стенки» при столкновении с ними. Иначе он будет просто проходить «сквозь стены».
Выбираем карандаш. Теперь левой клавишей мыши «ставим» 3 вершины треугольника. Два на каждом из плечей, и один в районе живота. И правой клавишей (просто щёлкнуть на рабочей области) рисуем треугольник. Желательно чтобы он в итоге получился равнобедренный или равносторонний. С нижней вершиной на оси Y. Теперь увеличим его. Немного. Иначе персонаж будет проходить сквозь потолок например.
Готово, наш персонаж — болванчик готов. Теперь самое лёгкое — управление.
Тут я немного в ступоре. Дело в том что из меня программист честно сказать не очень. Я сам многое не понимаю. И читаю книжки по Python чтобы лучше разбираться в вопросе. Потому что GDScript очень похож на него.
Небольшая просьба — если знаете как оформить лучше — напишите пожалуйста. Буду рад учесть все замечания.
Напомню предыдущий код отображения простейшей анимации:
Добавляем и изменяем код:
Начинаем функцию integrate_forces. Вот что пронеё сказано в Help:
Измените функцию, чтобы использовать свою физику взаимодействий. Ну хорошо, этим и займёмся.
PS не знаю как комментировать код дальше. Поэтому просто оставлю его так:
Блин, это был ужас.
PS чтобы заработала гравитация, надо поставить галочку в Scene -> Project Settings -> Physics2D -> Default Gravity
Рассказать подробно про tileset я в этот раз не успеваю. Могу лишь предложить свою готовую сцену (из моего проекта) или Вам создать свою из TileSet. Скачать сет можно по ссылке ниже, после чего добавить в проект Node TileMap. В настройках к ней надо добавить готовый TileSet ( TileSet -> Load -> tileset.xml ). Простейший пол/землю нарисовать сможете. Как рисовать уровни, TileSets я подробно расскажу в следующем уроке.
Создаём новую сцену. Добавляем в неё «Node» ParallaxBackground. Называем её parallax_bg и сохраняем как parallax_bg.xml.
К ней создаём ParallaxLayer 4 штуки. Называем их sky, clouds, mount_1, mount_2. Соответственно небо, облака, и горы.
Разрешение нашей игры 800x600 (Посмотрите в настройках Scene -> Project Settings -> Display). Поэтому ставим каждой «Ноде» параметр Mirroring 800,0 — зеркалирование по оси Y. Поменяйте параметры Scale у «Нод» clouds, mount_1, mount_2 на 0.1,1; 0.2,1 и 0.4,1 соответственно.
К Node sky добавляем спрайт самого неба. Свой я уже нарисовал заранее. А вы можете скачать всё по ссылкам ниже.
На рабочей области появилось сразу 2 полоски. Переносим на рабочую область изображения, масштабируем. Видим что изменяя главный спрайт меняется и его зеркальное отражение.
К Node clouds добавляем облачка. Думаю можно будет продублировать каждый из 3-х чтобы в итоге было 6, mirror дублирует ещё раз, итого у нас будет аж 12 облаков!
Теперь к Node mount_1 и mount_2 добавим горы. Но слой mount_2 поднимем выше mount_1.
Сохраняем сцену. Открываем stage.xml. И к нашему Stage добавляем через «плюсик» сцену с задниками.
И как обычно небольшое видео геймплея:
Небольшой FAQ (Будет пополняться):
Скачать:
Спасибо, если осилили и дочитали до конца. В случае недочётов пишите в личку. Спасибо, увидимся!
Во второй части на повестке дня у нас:
1) Экспорт готового проекта в бинарные файлы для выбранной архитектуры.
2) Новые анимации. Параметры персонажа.
3) Управление.
3) GDScript. Добро пожаловать в настоящий кодинг!
4) Импорт простейших Tilesets.
5) Бонус: разбор устройства простейших задников.
Ну и как обычно, много картинок!
Компиляция....?
В прошлой статье я упоминал что авторы не предусматривали бинарники для linux x86. На время написания статьи бинарные файлы всё ещё были не готовы. Но экспортировать готовые проекты, demo или просто тесты уже хотелось. Но что делать, если нет Шаблонов экспорта? Правильно! Скомпилировать их самому!
Переходим в директорию с исходными файлами и компилируем
cd ./godot
Средства отладки:
scons bin/godot target=release_debug tools=no
После успешной компиляции переименовываем свежеполученный файл в linux_x11_32_debug
Сам шаблон экспорта:
scons bin/godot_rel target=release tools=no
Переименовываем в linux_x11_32_release
Запаковываем в zip-архив.
find ./* -name "linux_x11_32_*" -exec zip ./linux_x11_32_templates.zip "{}" +
Скармливаем архив Godot. Вуаля, теперь можно экспортировать проект для ОС GNU/Linux любой разрядности.
Да даже под ОС и разрядности, отличные от Вашей. Бинарные файлы для Windows без проблем смог завести в wine, главное не забыть снять или поставить галочку 64bits, и выключить debugging, если не нужен.
Подробнее про настройки экспорта можно почитать на сайте проекта на английском языке. Если будет актуально и востребовано, можно будет подумать о русской локализации статей и создания русскоязычной wiki по Godot.
Анимация бега и прыжков
В прошлом уроке я заранее подготовил текстурку со спрайтами бега и прыжка нашего Капитана. Поэтому я думаю создать две анимации jumping и run не составит труда. Я думаю это не трудно будет сделать. Скажу лишь параметры:
run:
Len(s): 0.9
Step(s): 0.1
Looping: yes
jumping:
Len(s): 1
Step(s): 0.1
Animation: ○х○○○○х○х○х , где кружки - последовательность кадров от 24 до 30, а крестики - их отсутствие.
Looping: no
Да, прыжок — это только прыжок. Значит нужна ещё анимация падения.
falling:
Len(s): 0.1
Step(s): 0.1
Animation: кадр №30
Looping: yes
Параметры персонажа
Три пункта:
- Камера
- Касание с землей
- Геометрия персонажа
1) Напоминаю. Проверьте, стоит ли галочка на параметре Camera2D/Current, отвечающая за привязку объекта «камера» к объекту player.
2) Добавим Node CollisionShape2D. Эта «Нода» отвечает за положение нашего Капитана в 2D-пространстве.
В Inspector выбираем CollisionShape2D -> Shape -> New RayShape2D. В 2-х миллиметрах правее появится небольшая стрелочка — нажимаем на неё — откроются параметры RayShape2D.
Параметр RayShape2D/Lenght оставим 20, а вот Custom Solver Bias выставим 0.5 (этим мы разрешим небольшое смещение)
Теперь в самой игре в будущем, если персонаж будет «не попадать ногами по полу», его положение можно будет отрегулировать этой стрелочкой. Именно она проверяет «касается ли спрайт пола».
3) С геометрией веселее. Для начала надо добавить Node CollisionPolygon2D. Теперь нам надо нарисовать этот самый полигон, дающий персонажу «массу».То-есть надо нарисовать то, чем герой будет «биться об стенки» при столкновении с ними. Иначе он будет просто проходить «сквозь стены».
Выбираем карандаш. Теперь левой клавишей мыши «ставим» 3 вершины треугольника. Два на каждом из плечей, и один в районе живота. И правой клавишей (просто щёлкнуть на рабочей области) рисуем треугольник. Желательно чтобы он в итоге получился равнобедренный или равносторонний. С нижней вершиной на оси Y. Теперь увеличим его. Немного. Иначе персонаж будет проходить сквозь потолок например.
Готово, наш персонаж — болванчик готов. Теперь самое лёгкое — управление.
Создание клавиш управления
- Стандартное управление.
Создать такое довольно легко. Идём в Scene -> Project Settings -> Input Map
Создаём 3 клавиши управления — move_left, move_right и jump. Назначаем им клавиши. Готово!
Как видно, Godot умеет работать и с геймпадами, правда для каждого надо будет настраивать своё управление. Но об этом как-нибудь в другой раз.
- Управление с тачскрина.
Тут тоже особо ничего сложного нет. Привязываем к нашему герою Node CanvasLayer с названием «ui». А к ней в свою очередь — 3 копии TouchScreenButton. Называем их left, right и jump.
К каждой привязываем изображение клавиши. Они сразу появятся в рабочей области программы. Расставляем так, как нам кажется будет удобнее. Не забываем что синее окошко — это «область показа камеры».
В разделы Action вписываем параметры move_left, move_right и jump соответственно. Ну и выставляем параметр Visibility Mode в TouchScreen Only. Готово!
GDScript
Тут я немного в ступоре. Дело в том что из меня программист честно сказать не очень. Я сам многое не понимаю. И читаю книжки по Python чтобы лучше разбираться в вопросе. Потому что GDScript очень похож на него.
Внимание!
- Движок не понимает кириллицу. Но если случайно на ней что-то написать, он её просто не отображает. А просто помечает всю строчку с ней, как одну большую ошибку.
- Отступы очень важны. Не там отступ — и герой может провалиться в пол, могут выполняться не те функции.
- Ещё раз повторяю, в программировании я нуб, но постараюсь объяснить всё как можно проще.
Небольшая просьба — если знаете как оформить лучше — напишите пожалуйста. Буду рад учесть все замечания.
Напомню предыдущий код отображения простейшей анимации:
Что было
extends RigidBody2D
var anim=""
func _integrate_forces(s):
var new_anim=anim
new_anim=«idle»
if (new_anim!=anim):
anim=new_anim
get_node(«anim»).play(anim)
Добавляем и изменяем код:
Объявляем переменные
extends RigidBody2D
var anim="" #переменная анимации
var siding_left=false #переменная перемещения влево
var jumping=false #переменная прыжка
var stopping_jump=false #переменная завершения прыжка
var WALK_ACCEL = 800.0 #скорость перемещения по горизонтали
var WALK_DEACCEL= 800.0 #скорость торможения
var WALK_MAX_VELOCITY= 800.0 #максимальная скорость
var GRAVITY = 900.0 #гравитация
var AIR_ACCEL = 200.0 #скорость перемещения по горизонтали в прыжке
var AIR_DEACCEL= 200.0 #торможение прыжка (перемещения в обратном направлении) в прыжке. Значение, близкое к нулю будет делать игру похожую на Mario Bros'
var JUMP_VELOCITY=460 #скорость прыжка
var STOP_JUMP_FORCE=900.0 #сила торможения прыжка
var MAX_FLOOR_AIRBORNE_TIME = 0.15 #Время касания, после которого уже прыгнуть нельзя. То-есть время, в течении которого можно сделать второй прыжок. Не меняйте, если не хотите "двойных" или бесконечных прыжков.
var airborne_time=1e20 #время в воздухе
var floor_h_velocity=0.0
Начинаем функцию integrate_forces. Вот что пронеё сказано в Help:
Измените функцию, чтобы использовать свою физику взаимодействий. Ну хорошо, этим и займёмся.
PS не знаю как комментировать код дальше. Поэтому просто оставлю его так:
Пишем функцию взаимодействия
func _integrate_forces(s):
var lv = s.get_linear_velocity()
var step = s.get_step()
var new_anim=anim
var new_siding_left=siding_left
Получаем управление
var move_left = Input.is_action_pressed("move_left")
var move_right = Input.is_action_pressed("move_right")
var jump = Input.is_action_pressed("jump")
#Замедление по оси x (торможение)
lv.x-=floor_h_velocity
floor_h_velocity=0.0
Поиск земли (проверяем контакт текстуры с полом)
var found_floor=false
var floor_index=-1
for x in range(s.get_contact_count()):
var ci = s.get_contact_local_normal(x)
if (ci.dot(Vector2(0,-1))>0.6):
found_floor=true
floor_index=x
if (found_floor):
airborne_time=0.0
else:
airborne_time+=step #время, проведенное в воздухе
var on_floor = airborne_time < MAX_FLOOR_AIRBORNE_TIME
Процесс прыжка
if (jumping):
if (lv.y>0):
#Завершаем прыжок если он закончен (достигли наивысшей точки прыжка)
jumping=false
elif (not jump):
stopping_jump=true
if (stopping_jump):
lv.y+=STOP_JUMP_FORCE*step
Движение персонажа на земле
if (on_floor):
if (move_left and not move_right):
if (lv.x > -WALK_MAX_VELOCITY):
lv.x-=WALK_ACCEL*step
elif (move_right and not move_left):
if (lv.x < WALK_MAX_VELOCITY):
lv.x+=WALK_ACCEL*step
else:
var xv = abs(lv.x)
xv-=WALK_DEACCEL*step
if (xv<0):
xv=0
lv.x=sign(lv.x)*xv
#Проверка прыжка
if (not jumping and jump):
lv.y=-JUMP_VELOCITY
jumping=true
stopping_jump=false
#Проверка перемещения и смена анимации
if (lv.x < 0 and move_left):
new_siding_left=true
elif (lv.x > 0 and move_right):
new_siding_left=false
if (jumping):
new_anim="jumping"
elif (abs(lv.x)<0.1):
new_anim="idle"
else:
new_anim="run"
Движение персонажа в воздухе
else:
if (move_left and not move_right):
if (lv.x > -WALK_MAX_VELOCITY):
lv.x-=AIR_ACCEL*step
elif (move_right and not move_left):
if (lv.x < WALK_MAX_VELOCITY):
lv.x+=AIR_ACCEL*step
else:
var xv = abs(lv.x)
xv-=AIR_DEACCEL*step
if (xv<0):
xv=0
lv.x=sign(lv.x)*xv
if (lv.y<0):
new_anim="jumping"
else:
new_anim="falling"
Перемещение персонажа
if (new_siding_left!=siding_left):
if (new_siding_left):
get_node("sprite").set_scale( Vector2(-1,1) )
else:
get_node("sprite").set_scale( Vector2(1,1) )
siding_left=new_siding_left
Смена анимации
if (new_anim!=anim):
anim=new_anim
get_node("anim").play(anim)
Применение скорости перемещения на земле
if (found_floor):
floor_h_velocity=s.get_contact_collider_velocity_at_pos(floor_index).x
lv.x+=floor_h_velocity
Применение гравитации на это всё безобразие
lv+=s.get_total_gravity()*step
s.set_linear_velocity(lv)
Блин, это был ужас.
Теперь всё вместе без комментариев:
extends RigidBody2D
var anim=""
var siding_left=false
var jumping=false
var stopping_jump=false
var WALK_ACCEL = 300.0
var WALK_DEACCEL= 300.0
var WALK_MAX_VELOCITY= 400.0
var GRAVITY = 900.0
var AIR_ACCEL = 300.0
var AIR_DEACCEL= 300.0
var JUMP_VELOCITY=460
var STOP_JUMP_FORCE=200.0
var MAX_FLOOR_AIRBORNE_TIME = 0.15
var airborne_time=1e20
var floor_h_velocity=0.0
func _integrate_forces(s):
var lv = s.get_linear_velocity()
var step = s.get_step()
var new_anim=anim
var new_siding_left=siding_left
var move_left = Input.is_action_pressed("move_left")
var move_right = Input.is_action_pressed("move_right")
var jump = Input.is_action_pressed("jump")
lv.x-=floor_h_velocity
floor_h_velocity=0.0
var found_floor=false
var floor_index=-1
for x in range(s.get_contact_count()):
var ci = s.get_contact_local_normal(x)
if (ci.dot(Vector2(0,-1))>0.6):
found_floor=true
floor_index=x
if (found_floor):
airborne_time=0.0
else:
airborne_time+=step
var on_floor = airborne_time < MAX_FLOOR_AIRBORNE_TIME
if (jumping):
if (lv.y>0):
jumping=false
elif (not jump):
stopping_jump=true
if (stopping_jump):
lv.y+=STOP_JUMP_FORCE*step
if (on_floor):
if (move_left and not move_right):
if (lv.x > -WALK_MAX_VELOCITY):
lv.x-=WALK_ACCEL*step
elif (move_right and not move_left):
if (lv.x < WALK_MAX_VELOCITY):
lv.x+=WALK_ACCEL*step
else:
var xv = abs(lv.x)
xv-=WALK_DEACCEL*step
if (xv<0):
xv=0
lv.x=sign(lv.x)*xv
if (not jumping and jump):
lv.y=-JUMP_VELOCITY
jumping=true
stopping_jump=false
if (lv.x < 0 and move_left):
new_siding_left=true
elif (lv.x > 0 and move_right):
new_siding_left=false
if (jumping):
new_anim="jumping"
elif (abs(lv.x)<0.1):
new_anim="idle"
else:
new_anim="run"
else:
if (move_left and not move_right):
if (lv.x > -WALK_MAX_VELOCITY):
lv.x-=AIR_ACCEL*step
elif (move_right and not move_left):
if (lv.x < WALK_MAX_VELOCITY):
lv.x+=AIR_ACCEL*step
else:
var xv = abs(lv.x)
xv-=AIR_DEACCEL*step
if (xv<0):
xv=0
lv.x=sign(lv.x)*xv
if (lv.y<0):
new_anim="jumping"
else:
new_anim="falling"
if (new_siding_left!=siding_left):
if (new_siding_left):
get_node("sprite").set_scale( Vector2(-1,1) )
else:
get_node("sprite").set_scale( Vector2(1,1) )
siding_left=new_siding_left
if (new_anim!=anim):
anim=new_anim
get_node("anim").play(anim)
if (found_floor):
floor_h_velocity=s.get_contact_collider_velocity_at_pos(floor_index).x
lv.x+=floor_h_velocity
lv+=s.get_total_gravity()*step
s.set_linear_velocity(lv)
PS чтобы заработала гравитация, надо поставить галочку в Scene -> Project Settings -> Physics2D -> Default Gravity
Импорт TileSets
Рассказать подробно про tileset я в этот раз не успеваю. Могу лишь предложить свою готовую сцену (из моего проекта) или Вам создать свою из TileSet. Скачать сет можно по ссылке ниже, после чего добавить в проект Node TileMap. В настройках к ней надо добавить готовый TileSet ( TileSet -> Load -> tileset.xml ). Простейший пол/землю нарисовать сможете. Как рисовать уровни, TileSets я подробно расскажу в следующем уроке.
Бонус! Добавление задников!
Создаём новую сцену. Добавляем в неё «Node» ParallaxBackground. Называем её parallax_bg и сохраняем как parallax_bg.xml.
К ней создаём ParallaxLayer 4 штуки. Называем их sky, clouds, mount_1, mount_2. Соответственно небо, облака, и горы.
Разрешение нашей игры 800x600 (Посмотрите в настройках Scene -> Project Settings -> Display). Поэтому ставим каждой «Ноде» параметр Mirroring 800,0 — зеркалирование по оси Y. Поменяйте параметры Scale у «Нод» clouds, mount_1, mount_2 на 0.1,1; 0.2,1 и 0.4,1 соответственно.
К Node sky добавляем спрайт самого неба. Свой я уже нарисовал заранее. А вы можете скачать всё по ссылкам ниже.
На рабочей области появилось сразу 2 полоски. Переносим на рабочую область изображения, масштабируем. Видим что изменяя главный спрайт меняется и его зеркальное отражение.
К Node clouds добавляем облачка. Думаю можно будет продублировать каждый из 3-х чтобы в итоге было 6, mirror дублирует ещё раз, итого у нас будет аж 12 облаков!
Теперь к Node mount_1 и mount_2 добавим горы. Но слой mount_2 поднимем выше mount_1.
Сохраняем сцену. Открываем stage.xml. И к нашему Stage добавляем через «плюсик» сцену с задниками.
И как обычно небольшое видео геймплея:
Небольшой FAQ (Будет пополняться):
- Если персонаж не двигается — проверьте добавили и правильно ли добавили клавиши управления
- Проблемы с анимацией — правильно ли добавили анимацию. Проверьте looping анимаций idle, run и falling.
- Персонаж проваливается в пол/проходит сквозь стены, потолки — увеличьте треугольник CollisionPolygon2D.
- Персонаж частично в текстурах/выше них — поработайте со стрелкой CollisionShape2D
Скачать:
- Исходники проекта
- Задники (zip)
- Скомпилированные шаблоны экспорта для Linux x86 (zip)
- Бинарники Demo урока Linux x86
- Бинарники Demo урока Linux amd64
- Бинарники Demo урока Windows x86
- Бинарники Demo урока Windows x86_64
- Бинарники Demo урока MacOS x86
- Бинарники Demo урока MacOS amd64
Спасибо, если осилили и дочитали до конца. В случае недочётов пишите в личку. Спасибо, увидимся!