FellowPablo

FellowPablo

На Пикабу
поставил 1 плюс и 2 минуса
372 рейтинг 16 подписчиков 5 подписок 32 поста 0 в горячем

YouTube Shorts из терминала. Как автоматизировать создание видео с помощью FFMPEG и Bash

YouTube Shorts из терминала. Как автоматизировать создание видео с помощью FFMPEG и Bash Программирование, Разработка, Gamedev, Инди игра, Инди, YouTube, Youtube Shorts, Видео, Длиннопост

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

Для продвижения игры я начал публиковать Shorts на YouTube, но это отнимало много времени и ресурсов. Будучи инженером, я стараюсь автоматизировать рутинные задачи, поэтому сделал решение, которое самостоятельно нарезает видео на 60-секундные фрагменты.


Проблема инди-игр

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

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

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

  • Частота публикации важнее уникальности. Конечно, адаптировать контент под каждую соцсеть — круто, но это отнимает много времени и ресурсов. Эффективнее публиковать несколько простых материалов, чем один уникальный.

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

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

Небольшой лайфхак для увеличения охвата на YouTube. Если при постинге видео убрать галочку Publish to subscriptions feed and notify subscribers, то знакомые с игрой люди не будут видеть его в своей ленте.

YouTube Shorts из терминала. Как автоматизировать создание видео с помощью FFMPEG и Bash Программирование, Разработка, Gamedev, Инди игра, Инди, YouTube, Youtube Shorts, Видео, Длиннопост

Окно с параметрами видео перед публикацией


Что было раньше

Сейчас у меня есть два основных YouTube-канала — игровой и личный. На первом публикую горизонтальные и вертикальные видео для продвижения своей игры. Часть из них уходят в GameJolt, Reddit и другие площадки. На втором — только горизонтальные, но некоторые из них посвящаю разработке игр.

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

YouTube Shorts из терминала. Как автоматизировать создание видео с помощью FFMPEG и Bash Программирование, Разработка, Gamedev, Инди игра, Инди, YouTube, Youtube Shorts, Видео, Длиннопост

Ограничение по тексту


Возможности для автоматизации

Решение автоматизировать процесс долго вынашивать не пришлось, но какие здесь есть возможности для этого? Рассмотрю несколько примеров.

  1. Нарезать горизонтальные видео, чтобы получать больше охватов.

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

YouTube Shorts из терминала. Как автоматизировать создание видео с помощью FFMPEG и Bash Программирование, Разработка, Gamedev, Инди игра, Инди, YouTube, Youtube Shorts, Видео, Длиннопост

Маскот игры

3. Автоматизировать производство горизонтальных видео за счет подбора визуала. Для этого буду использовать контент из пула видео и хэш-таблицы тегов на основе семантического анализа текста.

В статье начну с малого и реализую только первый пункт. Остальные решения оставлю для следующих материалов.

Итак, мне нужен «черный ящик» для горизонтальных видео, который будет:

  • менять формат с 16:9 на 9:16,

  • заполнять лишнее пространство размытым фоном,

  • нарезать видео в соответствии с таймкодом, но не более 60 секунд,

  • добавлять текст с нумерацией выпуска и призывом посмотреть полное видео.


Выбор инструментария и философия

Не вижу смысла заморачиваться с UI, поэтому буду использовать bash-скрипты. Для обработки видео выбираю библиотеку FFMPEG — поверхностное изучение показало, что ее будет достаточно. Устанавливаю на MacOS с помощью Homebrew:

brew install ffmpeg

Пакеты для других платформ можно найти здесь.

Чтобы продолжить движение этого проекта в рамках философии UNIX, я нашел инструмент для загрузки YouTube-видео из терминала — youtube-dl. С его помощью я удалил свое первое отрендеренное видео.

Утилита проста в использовании. Сперва запрашиваю доступные форматы для скачивания видео:

youtube-dl -F "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

После — получаю список:

[info] Available formats for dQw4w9WgXcQ:

format code  extension  resolution note

249  webm  audio only tiny  46k , webm_dash container, opus @ 46k (48000Hz), 1.18MiB

250  webm  audio only tiny  61k , webm_dash container, opus @ 61k (48000Hz), 1.55MiB

140  m4a  audio only tiny  129k , m4a_dash container, mp4a.40.2@129k (44100Hz), 3.27MiB

251  webm  audio only tiny  129k , webm_dash container, opus @129k (48000Hz), 3.28MiB

...

398  mp4  1280x720  720p  657k , mp4_dash container, av01.0.05M.08@ 657k, 25fps, video only, 16.62MiB

399  mp4  1920x1080  1080p 1180k , mp4_dash container, av01.0.08M.08@1180k, 25fps, video only, 29.83MiB

248  webm  1920x1080  1080p 1556k , webm_dash container, vp9@1556k, 25fps, video only, 39.34MiB

137  mp4  1920x1080  1080p 3024k , mp4_dash container, avc1.640028@3024k, 25fps, video only, 76.45MiB

18  mp4  640x360  360p  343k , avc1.42001E, 25fps, mp4a.40.2 (44100Hz) (best)

Пометка best  — это не лучший формат, а формат с заранее склеенным видео и аудио.

Выбираю нужный формат для видео — в моем случае webm (251) и mp4 (137). Чтобы не скачивать их по-отдельности, использую опцию сложения:

youtube-dl -f 137+251 "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

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


Написание кода

  1. Смена формата видео и размытый фон

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

ffmpeg -i video.mp4 -filter_complex \

"[0:v]scale=-1:1920,crop=1080:1920,gblur=sigma=20[bg]; \

[0:v]scale=1080:-1[ov]; \

[0:a]volume=1.0[audio];

[bg][ov]overlay=(W-w)/2:(H-h)/2[mix]" \

-map "[mix]" -map "[audio]" -r 60 result.mp4 -y

Команда запускает библиотеку FFMPEG, чтобы передать на вход файл video.mp4. Применяю к нашему потоку filter_complex. Ниже расскажу, какие фильтры я использовал.

Вторая строка скрипта пропорционально меняет ширину нулевого видеопотока 0:v на его длину scale=-1:1920. Обрезает видео до вертикального формата crop=1080:1920 и размывает фон gblur-sigma=20. После — возвращает преобразованное видео в новый поток bg.

Третья строка снова берет 0:v, пропорционально меняет его длину на ширину scale=1080:-1 и возвращает результат в новый поток ov.

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

В четвертой строке смешиваю потоки bg и ov и располагаю видео в центре экрана overlay=(W-w)/2:(H-h)/2. Результат перемещаю в mix. После — отправляю потоки в финальный файл при помощи -map"[mix]" -map "[audio]".
Выставляю 60 FPS и сохраняю результат в result.mp4. Флаг -y перезаписывает файл, если он уже существует.

YouTube Shorts из терминала. Как автоматизировать создание видео с помощью FFMPEG и Bash Программирование, Разработка, Gamedev, Инди игра, Инди, YouTube, Youtube Shorts, Видео, Длиннопост

Результат

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

2. Нарезка видео

Если длительность видео превышает 60 секунд, то YouTube не пропустить его в раздел Shorts. Для этого нужно модифицировать предыдущий скрипт.

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

Для таймкодов на YouTube использую следующий формат:

00:00 - Text
01:40 - Text2

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

time_codes=()

while read -r line; do

minutes=${line:0:2}

seconds=${line:3:2}

minutes="$(printf "%.0f" "$minutes")"

seconds="$(printf "%.0f" "$seconds")"

time_codes+=("$(($minutes*60+$seconds))")

done < $2

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

Далее смотрю на длительность видео с помощью команды ffprobe:

lengths=()

prev=

for key in "${!time_codes[@]}"; do

if [[ $key == 0 ]]; then

prev=${time_codes[$key]}

else

current=${time_codes[$key]}

length=$((current-prev))

lengths+=("$length")

prev=$current

fi

done


vid_len=$(ffprobe -v error -select_streams v:0 -show_entries stream=duration -of default=noprint_wrappers=1:nokey=1 $1)

vid_len=${vid_len%.*}

length=$((vid_len-prev))

lengths+=("$length")


Теперь к рендерингу самих видео! Для этого модифицирую команду filter_complex:

for i in "${!lengths[@]}"; do

target="${1}_$(($i+1)).mp4"

ffmpeg -ss ${time_codes[$i]} -i $1 -filter_complex \

"[0:v]scale=-1:1920,crop=1080:1920,gblur=sigma=20[bg]; \

[0:v]scale=1080:-1[ov]; \

[0:a]volume=1.0[audio];\

[bg][ov]overlay=(W-w)/2:(H-h)/2[mix];" \

-map [mix] -map [audio]  -t ${lengths[$i]} -r 60 $target -y

done

Здесь я добавил флаг -ss ${time_codes[$i]} для рендеринга с заданного момента в секундах, а также -t ${lengths[$i]}, который задает соответствующую длительность видео.

Отлично! Теперь видео подходят для раздела Shorts.

3. Добавление текста

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

Далее модифицирую код, который считывает таймкоды:

name1=""

name2=""

time_codes=()

while read -r line; do

if [[ $name1 == "" ]]; then

name1=$line

elif [[ $name2 == "" ]]; then

name2=$line

else

minutes=${line:0:2}

seconds=${line:3:2}

minutes="$(printf "%.0f" "$minutes")"

seconds="$(printf "%.0f" "$seconds")"

time_codes+=("$(($minutes*60+$seconds))")

fi

done < $2

Создаю переменные для параметров текста:

text_size=80

margin_top=160

margin_bottom=320

line_spacing=100

font="$HOME/tools/Ubuntu/Ubuntu-Bold.ttf"

text_border=5


Теперь модифицируем команду для генерации клипов:

ffmpeg -ss ${time_codes[$i]} -i $1 -filter_complex \

"[0:v]scale=-1:1920,crop=1080:1920,gblur=sigma=20[bg]; \

[0:v]scale=1080:-1[ov]; \

[0:a]volume=1.0[audio];\

[bg][ov]overlay=(W-w)/2:(H-h)/2,\

drawtext=text='$name1':fontfile=$font:fontcolor=white:fontsize=$text_size:x=w/2-text_w/2:y=$margin_top\

:bordercolor=black:borderw=$text_border,\

drawtext=text='$name2':fontfile=$font:fontcolor=white:fontsize=$text_size:x=w/2-text_w/2:y=$margin_top+$line_spacing\

:bordercolor=black:borderw=$text_border,\

drawtext=text='Часть $(($i+1))':fontfile=$font:fontcolor=white:fontsize=$text_size:x=w/2-text_w/2:y=$margin_top+$line_spacing*2+20\

:bordercolor=black:borderw=$text_border,\

drawtext=text='Полное видео':fontfile=$font:fontcolor=white:fontsize=$text_size:x=w/2-text_w/2:y=h-$margin_bottom-$line_spacing\

:bordercolor=black:borderw=$text_border,\

drawtext=text='на канале':fontfile=$font:fontcolor=white:fontsize=$text_size:x=w/2-text_w/2:y=h-$margin_bottom\

:bordercolor=black:borderw=$text_border[mix];" \

-map [mix] -map [audio]  -t ${lengths[$i]} -r 60 $target -y

Несмотря на большое количество кода, все довольно просто. В блоке с размытием фона появилось пять команд drawtext. У каждой есть текст, шрифт, цвет, размер, позиция по x, позиция по y, цвет и толщина обводки.

Результат.


Заключение

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

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

Показать полностью 4 1

Once you go Rust, you never go back. Создание игры для программистов на Bevy

Ранее я уже описывал, как я дошёл до того, чтобы заняться геймдевом, как я готовился к этому и как выбирал инструменты. Здесь же я хочу сосредоточиться именно на самой игре, а также Bevy и Rust, как основных инструментах разработки.

Once you go Rust, you never go back. Создание игры для программистов на Bevy Разработка, Gamedev, Инди, Инди игра, Программирование, Гифка, Длиннопост

Логотипы выбранных инструментов

Итак, игра с незамысловатым названием HackeRPG (Hacker + RPG), экшн, где управление основано на кодинге. Проект, в силу своей специфики, рассчитан на узкую аудиторию или, другими словами, «на своих». Из этого следует, что было бы хорошим тоном добавить пасхалки и привычные разработчикам фичи.

Дабы упростить дальнейшее понимание решений, которые я реализовывал, будет правильно хотя бы немного описать движок Bevy. Главная его особенность - ECS (Entity component system), где

  • Component - любая сущность игре(будь то спрайт, свет или текст);

  • Entity - грубо говоря, контейнер, куда мы складываем все компоненты;

  • System - действия с компонентами, которые выполняются в бесконечном цикле.

Once you go Rust, you never go back. Создание игры для программистов на Bevy Разработка, Gamedev, Инди, Инди игра, Программирование, Гифка, Длиннопост

Визуализация ECS

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

За конкретными гайдам по самому движку - на официальный сайт или искать мануалы на YouTube.

UI

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

Once you go Rust, you never go back. Создание игры для программистов на Bevy Разработка, Gamedev, Инди, Инди игра, Программирование, Гифка, Длиннопост

И не говорите мне, что ваш рабочий день выглядит не так

Задача достаточно тривиальная, но с учётом ECS - не самая удобная. Однако после некоторых оптимизации и разбиения функционала и модулей можно добиться вполне сносной архитектуры.

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

fn setup_menu_system(

mut commands: Commands,

game_assets: Res<GameAssets>

) {

commands.spawn(

NodeBundle {

style: Style {

width: Val::Percent(100.),

height: Val::Percent(100.),

flex_direction: FlexDirection::Column,

justify_content: JustifyContent::Center,

align_items: AlignItems::Center,

..default()

},

..default()

}

).with_children(|parent| {

spawn_console(

&game_assets,

parent,

);

});

}

Где структура commands добавляет на экран элемент NodeBundle с дочерними элементами, которые создаёт моя собственная функция spawn_console.

Хотелось бы отметить удобство работы с HUD слоем: в Bevy она реализована на основе концепции FlexBox, которая будет привычна любому, кто более или менее знаком с веб фронтенд разработкой.

Важный нюанс используемой мной концепции UI - input и output зачастую отображаются одним и тем же компонентом. Т.е. при наличии такого меню:

Say hello

>hello█

Где первая строка - output, а вторая (кроме первого символа и символа каретки) - input.

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

fn menu_output_system(

mut console_query: Query<(&mut Text, &TextInput)>

) {

if let Ok((mut output, input)) = console_query.get_single_mut() {

let output_text = get_menu_output();

output.sections[0].value = output_text + text_input.text.as_str();

}

}

Где Query используется для того, чтобы получить сущности, у которых присутствуют указанные компоненты, TextInput - мой кастомный компонент, который хранит данные о вводе и автодополнении, а функция get_menu_output - генерит строку для вывода. Довольно минималистично и аккуратно.

Осталось добавить обработку ввода и MVP терминала готово! Для этого я решил использовать две вот такие системы:

fn menu_char_input_system(

mut ev_char: EventReader<ReceivedChar>,

mut console_query: Query<(&mut TextInput, &mut TextCaret)>

) {

for ev in ev_char.read() {

if ev.char.is_control() {

return;

}

if let Ok((mut input, mut caret)) = console_query.get_single_mut() {

insert_char_at_caret_position(

&mut input,

ev.char.to_string(),

&caret

);

update_caret_on_input(&mut caret);

}

}

}

fn menu_control_input_system(

keyboard: Res<Input<KeyCode>>,

mut console_query: Query<(&mut TextInput, &mut TextCaret)>

mut ev_exit: EventWriter<AppExit>

) {

if let Ok((mut input, mut caret)) = console_query.get_single_mut() {

if keyboard.just_pressed(KeyCode::Return) {

match input.text.as_str() {

"0" => ev_exit.send(AppExit),

_ => {}

}

input.text = "".to_string();

reset_caret(&mut caret);

}

}

}

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

Once you go Rust, you never go back. Создание игры для программистов на Bevy Разработка, Gamedev, Инди, Инди игра, Программирование, Гифка, Длиннопост

Результат проделанной работы

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

В остальном UI довольно тривиален: отображение текста, анимация спрайтов, движение - типичный контент самых простых туториалов. Поэтому продолжу описывать именно «прогерские» фичи.

Gameloop

Следующий шаг: реализация главной фичи игры - управления персонажем с помощью команд. Для ввода текста можно без проблем использовать ранее описанную menu_char_input_system, но теперь её можно сделать несколько более глобальной. Уберём из названия слово menu и заставим её работать не только на экране меню, но и в самой игре. Удалось переиспользовать ранее написанный код, значит что-то правильное есть в архитектуре, а это хорошо! А вот control_input_system потребуется реализовать другую. Опустим boilerplate code и сразу к сути:

...

if let Some(command) = command_from_string(input.text.to_string()) {

player_commands.queue.push(command);

}

...

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

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

После этого в ход вступает система, которая по одной исполняет команды из очереди, если нет текущей активной команды:

fn handle_command_queue_system(

mut commands_query: Query<&mut PlayerCommands>

) {

for (mut commands) in commands_query.iter_mut() {

if check_in_progress(&commands) {

continue;

}

set_current_command(&mut commands_query);

}

}

Можно заметить, что здесь используется for и iter_mut() вместо if let Ok и get_single_mut(). Таким образом мы обрабатываем компоненты, которых в игре может быть несколько. Этот сниппет - программное олицетворение моего оптимизма, который внушает мне, что проект найдёт интерес и признание и запрос на мультиплеер появится. В остальном - происходит проверка есть ли текущая активная команда с помощью check_in_progress, если её нет, то команда переносится из вектора query в переменную current.

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

fn handle_current_move_command_system(

mut commands_query: Query<(&mut PlayerCommands, &mut Movable)>

) {

for (mut commands, mut movable) in command_query.iter_mut() {

match &commands.current {

Move(x,y) => handle_move_command(x,y,&mut movable),

_ => {}

}

}

}

В вышеописанной функции мы проверяем, является ли текущая команда командой move. Если да, то обрабатываем её. Такое разбиение создаёт некоторый boilerplate, однако убирает ад с огромным количеством Query, практика показала, что это решение достаточно удобное.

Once you go Rust, you never go back. Создание игры для программистов на Bevy Разработка, Gamedev, Инди, Инди игра, Программирование, Гифка, Длиннопост

Результат

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

IDE

А теперь настало время прыгнуть в кроличью нору: время добавить внутриигровую IDE. Описание основных фичей связанных с реализацией терминала уже было ранее. Разве что перемещение каретки не было описано, но это я оставлю, как домашнее задание. Далее же задача весьма тривиальна - рекурсивный парсинг. Но вот проблема: Rust не дружит с использованием рекурсии в регулярных выражениях, а значит решение одно:

Once you go Rust, you never go back. Создание игры для программистов на Bevy Разработка, Gamedev, Инди, Инди игра, Программирование, Гифка, Длиннопост

Костыли

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

#[derive(PartialEq, Debug, Clone)]

pub struct CodeBlock {

pub name: String,

pub block_type: CodeBlockType,

pub content: Vec<CodeBlockContent>,

}

#[derive(PartialEq, Debug, Clone)]

pub enum CodeBlockType {

Function(Vec<String>),

Daemon,

Virus,

}

#[derive(PartialEq, Debug, Clone)]

pub enum CodeBlockContent {

Block(InnerCodeBlock),

Lines(Vec<String>),

}

#[derive(PartialEq, Debug, Clone)]

pub struct InnerCodeBlock {

pub block_type: InnerCodeBlockType,

pub content: Vec<CodeBlockContent>,

}

#[derive(PartialEq, Debug, Clone)]

pub enum InnerCodeBlockType {

If(String),

For(ForLoopInnerCodeBlock),

While(String),

}

#[derive(PartialEq, Debug, Clone)]

pub struct ForLoopInnerCodeBlock {

pub variable_name: String,

pub from: PlayerCommandInput,

pub to: PlayerCommandInput,

}

Каждый блок кода имеет тип (Function, Daemon или Virus) с нужными для его работы параметрами (только у функций есть вектор имён аргументов), а также хранит содержимое его тело в векторе типов CodeBlockContent. Внутренний контент, в свою очередь, может быть либо строками, которые будут парсится ранее упомянутой command_from_string, либо структурами InnerCodeBlock, которые аналогичны CodeBlock, но могут иметь типы If, For или While.

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

Once you go Rust, you never go back. Создание игры для программистов на Bevy Разработка, Gamedev, Инди, Инди игра, Программирование, Гифка, Длиннопост

Так выглядит IDE в игре

Итоги

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

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

  • удобство разработки;

  • качество итогового продукта;

  • маленький размер сборки;

Всё это однозначно того стоит.

Показать полностью 7

Работаю над маскотом для игры

Про рождение идеи игры, текущее положение дел и планы на ближайшее будущее

Демо версия HackeRPG уже доступна в Steam

https://store.steampowered.com/app/2671770/HackeRPG/

Демо версия HackeRPG уже доступна в Steam Gamedev, Инди, Инди игра, Разработка, Программирование, Steam, Steam халява, Демо

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

А от тех, кому это всё же удастся, жду фидбек!

Показать полностью

HackeRPG - Трейлер для Steam

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

Больше 5 месяцев разработки, скоро наконец доберусь до Steam

Добавил поддержку циклов в игру

Теперь во внутриигровой IDE можно использовать стандартные виды циклов:

While

Добавил поддержку циклов в игру Разработка, Программирование, Инди игра, Инди, Gamedev, Pixel Art, Код, Хакеры, Разработчики игр, Гифка, Длиннопост

Пример использования

Цикл while будет выполнять код в его теле до тех пор пока выполняется условие, переданное в качестве аргумента (или же вызвавший его поток будет прерван).

For

Добавил поддержку циклов в игру Разработка, Программирование, Инди игра, Инди, Gamedev, Pixel Art, Код, Хакеры, Разработчики игр, Гифка, Длиннопост

Пример использования

Цикл for имеет более явное ограничение времени работы. При его запуске инициализируется переменная, которая будет изменятся в заданном диапазоне, эту переменную так же можно использовать в командах (как показано в примере).

Какие идеи есть по креативному использованию циклов в игре? Они могут полезными, когда я возьмусь за туториал в игре.

Показать полностью 1
Отличная работа, все прочитано!