Данная статья будет на тему использования игровой библиотеки Raylib, написанной на совершенно неведомому автору статьи языку Ruby, но адаптированному под "базированный" С++. Для начала автор предлагает погрузиться в небольшую предысторию, чтобы читателям стало понятно, какие исходные данные имелись у будущего пользователя Raylib и чтобы возникли предположения, что же из этого могло выйти.

Итак, на момент знакомства с библиотекой стаж программирования был около 4 месяцев. 4 месяца, конечно, 4 месяцем рознь, и так как способность постигать новое проявляется у людей с разной силой, стоит сказать, что автор ей наделён относительно слабовато, и за вышеописанные 4 месяца едва ли различал функцию от структуры, а об объектно‑ориентированности, присущей «плюсам» даже не слышал. Что же было в арсенале у автора? Функции, конечно же, без параметров, причём, умение прописать условие, понимание циклов и, просто обязательно, замечательнейшая привычка описывать всё в «мейне». Итак, с таким вооружением подуставший от консоли и мечтающий о графике автор и отправился покорять зарекомендованную знакомым (хорошим программистом) библиотеку.

Стоит ещё заметить, возвращаясь к предыстории, что был у автора и опыт работы с классическим для начинающих Renpy, что, конечно же, помимо лежащих на поверхности плюсов, повлекло за собой и привычку к удобству и желание прийти «на готовенькое».

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

К счастью для автора и его будущих отношений с Raylib, ему вовремя объяснили, что же ни в коем случае нельзя убирать из кода, что же это за такие константы, объяснили, даже, почему их нельзя убирать, но пылкий лишь к «экшену» автор этого, конечно же, не запомнил. Впрочем, посмотрев на «константы» сейчас, уже профессионально повзрослевший автор осознает, что понимать там и нечего, что та же SetTargetFPS — это своего рода встроенный таймер, труднодоступный для работы пользователю, но состоящий в бесконфликтной дружбе со спрятанными под графикой шестерёнками библиотеки, что BeginDrawing и парная ему EndDrawing — своего рода разметки, как в изученном позднее XAML (просто сравните самое базовое <Button {желаемые настройки} ></Button> с BeginDrawing(); {Ваш код} EndDrawing(); — есть же некое сходство?) и так далее. Однако же, на первых своих шагах знакомства с Raylib, понимание может изрядно загрузить ещё совсем не готового к такой информации пользователя, внутренний ребёнок (не с психологической точки зрения, а как будущий специалист, которому требуется время, чтобы вырасти), обязательно возмутится и, попросив его не грузить, попросту перестанет всё это слушать. Или же автор - законченный лентяй, оттого и так вышло, кто знает? Суть в том, что константы придётся запомнить и совсем необязательно в их работу вникать, есть - и ладно.

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

Шаг нулевой

посмотреть примеры, коих вполне себе достаточно для начинающего, выбрать тот, что придётся по душе (тут стоит не переборщить, ведь важно не только восхититься кодом, но и понять его), скопировать код себе в страницу редактора кода (об этом будет сказано чуть ниже, ведь никак нельзя упускать тот факт, что сидеть у нас будет пользователь не за метафорическим рулём привычной "студии", а управлять удивляющей своей конструкцией спайкой компилятора gcc и Notepad++), закомментировать его полностью и потихоньку открывать его части, чтобы в них разбираться. Предположим, слишком оптимистично предположим, что описанному в статье новичку понравится второй пример, ссылка на который будет указана ниже, но картинка представлена прямо сейчас:

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

Что мы тут имеем? Да самый простой, выражаясь языком геймдева, character controller. По‑русски — стрелка «вверх», и шарик стремится к верхнему углу, «вниз» — опускается, «влево» и «вправо» — осуществляется движение в соответствующую сторону. Смотрим код, открывающийся при нажатии на пример:

int main(void)
{
    // Initialization
    //--------------------------------------------------------------------------------------
    const int screenWidth = 800;
    const int screenHeight = 450;

    InitWindow(screenWidth, screenHeight, "raylib [core] example - keyboard input");

    Vector2 ballPosition = { (float)screenWidth/2, (float)screenHeight/2 };

    SetTargetFPS(60);               // Set our game to run at 60 frames-per-second
    //--------------------------------------------------------------------------------------

    // Main game loop
    while (!WindowShouldClose())    // Detect window close button or ESC key
    {
        // Update
        //----------------------------------------------------------------------------------
        if (IsKeyDown(KEY_RIGHT)) ballPosition.x += 2.0f;
        if (IsKeyDown(KEY_LEFT)) ballPosition.x -= 2.0f;
        if (IsKeyDown(KEY_UP)) ballPosition.y -= 2.0f;
        if (IsKeyDown(KEY_DOWN)) ballPosition.y += 2.0f;
        //----------------------------------------------------------------------------------

        // Draw
        //----------------------------------------------------------------------------------
        BeginDrawing();

            ClearBackground(RAYWHITE);

            DrawText("move the ball with arrow keys", 10, 10, 20, DARKGRAY);

            DrawCircleV(ballPosition, 50, MAROON);

        EndDrawing();
        //----------------------------------------------------------------------------------
    }

    // De-Initialization
    //--------------------------------------------------------------------------------------
    CloseWindow();        // Close window and OpenGL context
    //--------------------------------------------------------------------------------------

    return 0;
}

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

Шаг первый

Откроем 5, 6, 8 и 42 строчку. Почему именно так? А потому что логика нам подсказывает, что если окно открыто, то в конце кода его стоит закрыть. Итак, важная пара - InitWindow и CloseWindow. Не в лифте же родились, как говорится. Так что закрывать не забываем, мы же скобки закрываем, когда в них функцию помещаем, так и тут. Смотрим на параметры нашей функции. Для удобства можно воспользоваться шпаргалкой и посмотреть на строение нашей "константной" функции (она находится в cheatsheet, вкладка с которым была открыта у автора во время буквально каждого использования приложения). Что мы видим? В параметрах указана длина, ширина и название окна. Разобрались? Идём дальше.

Ширина, высота и текстовое значение для названия - вроде нет затруднений, верно?
Ширина, высота и текстовое значение для названия - вроде нет затруднений, верно?

Шаг второй

Открываем SetTargetFPS (12 строчка). Знакомое слово, верно? FPS, автор имел в виду. Из игр оно пришло, Frames Per Second оно значит, а по-русски - кадры в секунду. Стоит задуматься. Получается, у нас будут кадры и будут секунды? Опять же, как говорилось выше, вникать в это новичку будет слишком сложно, но автор статьи надеется, что последующее суждение хоть немного всё разъяснит.

Шаг третий

Строчка 16. Что же мы видим дальше? Цикл while? А он тут зачем? Ещё и условие у while не самое понятное (автор привык к простым «вайлам», к while (x!= 5) и не более того, а тут какая‑то функция, да ещё и неизвестная). Переведём название нашей «тёмной лошадки». Окну следует закрыться. Следует закрыться. Опять же, пользуясь логикой, можно догадаться, что именно «следует» ему закрываться тогда, когда пользователь нажимает на крестик. Стоит обратить внимание на восклицательный знак, который, насколько даже профессионально юный автор знает, является отрицанием или же «не». То есть окну не следует закрываться. Значит, приложение работает, раз не следует. Выходит, цикл будет крутиться, пока приложение работает? Но автору, ещё в профессиональном плане совсем ребёнку, доводилось баловаться с циклами (а Вы этим грешили? Автор уверен, что все заставляли несчастную консоль до посинения выводить тот или иной символ либо же набор). Так вот, а в чём же связь этого дурачества и темы данного пункта, в именно — цикла while с уже более‑менее понятным условием? А в том, что тут и закладывается описанный выше механизм. С какой скоростью консоль выводит номерки, если запустить бесконечный while (true)? Да с максимальной! А Raylib? Со скоростью 60 раз в секунду! Почему так? А потому что именно автор это и прописал, чётко сказав: «SetTargetFPS (60). 60 раз в секунду. 60 обновлений картинки и 60 итераций. Что‑то прояснилось, верно?

Шаг четвертый

Разобравшись с циклом, раскомментируем всё до BeginDrawing и не забудем EndDrawing. Почему не забудем? Вспоминаем аналогию со скобками и окошком. Параметров у данной функции нет, туда даже ничего прописывать не надо, она - самая настоящая константа: просто есть, её обязательно используем и не мудрим.

Шаг пятый

ClearBackground(RAYWHITE). Очистить фон и цвет в параметрах. Приставки "RAY" пугаться не стоит - если посмотреть в тот же cheatsheet, можно увидеть, что это - всего лишь слегка отличный от стандартного белый, просто цвет, что-то по типу консольных цветов, даже так же положенный в enum. Получается, мы прописали заливку фона белым цветом. Видите, это не просто разовая акция, это всё в цикле и происходить такое событие будет ровно 60 раз в секунду, повинуясь условию выше. Следует раскрыть ещё одну строчку, чтобы убедиться окончательно, как же это работает. Шестой шаг: прописываем отрисовку круга при помощи DrawEllipseV. В параметрах у нас неизвестный тип Vector2 и уже разобранный ранее Color. Vector2 мы уже видели наверху, но в силу желания объяснить сначала механику, автор оставил этот загадочный "вектор" на потом, и вот, "потом" пришло. Что же мы имеем? Стоит заметить, обязательно стоит, что ничего общего с динамическим контейнером "вектор" данный Vector2 не имеет - размер у "двойного вектора" строго ограничен X и У, когда вектор, который а-ля массив может заполняться, пока память не кончится и вообще представляет собой настолько разнящуюся с Vector2 вещь, что обсуждать его будет попросту неуместно в данной статье.

Имеющий теперь уже небольшой опыт работы с Unity, автор подтверждает многоразовое и многофункциональное использование данного "вектора" в игровых движках и, ознакомившись с некоторым количеством образовательного контента, приходит к выводу, что ничего сложного в этом Vector2 нет. Автор предполагает, что возможный читатель его статьи ещё не знает, что же такое структура и тем более класс, о чём на четвёртом месяце программирования знать очень даже похвально, отчего и попытается объяснить попроще, что же за зверь этот Vector2 и его ещё не упомянутый, но не менее значимый собрат Vector3. А Vector2 — это всего лишь пара из двух значений типа float, Х и У да ещё и Z, если мы имеем дело с Vector3. Объявить данный тип можно одним махом, через фигурные скобки (пример Vector2 position = {0.5f, 1.5f}, где 0.5 будет «иксом», а 1.5 — «игреком», а можно более детально (пример — Vector2 place; place.X = 4; place.Y = 6.7f;) и оба случая при отсутствии ошибок скомпилируются. По сути, его инициализация мало чем отличается от той, что обычно проделывают с переменной, только тут переменных две (или три в случае Vector3) и ни об одной не стоит забывать. Итак, с Vector2 всё понятно, с отрисовкой, автор надеется, тоже. Вначале — что рисуем (берём полное название из cheatsheet), затем позиция (если взглянуть на тот же cheatsheet, можно увидеть, что она бывает либо «векторной», либо из независимых друг от друга двух переменных — разницы существенной нет, дело чистого удобства) и, наконец, цвет.

Имеющий теперь уже небольшой опыт работы с Unity, автор подтверждает многоразовое и многофункциональное использование данного "вектора" в игровых движках и, ознакомившись с некоторым количеством образовательного контента, приходит к выводу, что ничего сложного в этом Vector2 нет. Автор предполагает, что возможный читатель его статьи ещё не знает, что же такое структура и тем более класс, о чём на четвёртом месяце программирования знать очень даже похвально, отчего и попытается объяснить попроще, что же за зверь этот Vector2 и его ещё не упомянутый, но не менее значимый собрат Vector3. А Vector2 — это всего лишь пара из двух значений типа float, Х и У да ещё и Z, если мы имеем дело с Vector3. Объявить данный тип можно одним махом, через фигурные скобки (пример Vector2 position = {0.5f, 1.5f}, где 0.5 будет «иксом», а 1.5 — «игреком», а можно более детально (пример — Vector2 place; place.X = 4; place.Y = 6.7f;) и оба случая при отсутствии ошибок скомпилируются. По сути, его инициализация мало чем отличается от той, что обычно проделывают с переменной, только тут переменных две (или три в случае Vector3) и ни об одной не стоит забывать. Итак, с Vector2 всё понятно, с отрисовкой, автор надеется, тоже. Вначале — что рисуем (берём полное название из cheatsheet), затем позиция (если взглянуть на тот же cheatsheet, можно увидеть, что она бывает либо «векторной», либо из независимых жруг от друга двух переменных — разницы существенной нет, дело чистого удобства) и, наконец, цвет.

Последний (шестой) шаг

Раскомментируем return 0 (прекращение работы функции, подтверждающее, что та подошла к концу без ошибок и исключений) и запускаем. Что мы видим? Кружок на экране. Ничего он не делает, просто висит в пространстве и не более того. Для начала самое то. Добавим же второй кружок. Нужно ли прописывать заново очистку фона? Определённо нет! Почему же? Наверное, описанное ниже для большинства покажется очевидным, но автор лишь на практическом примере убедился, что раз код идет сверху вниз, нижнее "перекрытие" попросту заслонит собой верхний красный шарик. Раскомментировав логику управления кругом (и тут новые "константные" или же вшитые функции - (IsKeyDown(KEY_RIGHT) и так далее, бояться их не стоит, а использовать - вполне себе да), автору приходит на ум идея, что можно добавить подобное движение и с пробелом - тут возникают колебания между сменой цвета на момент нажатия клавиши и резкого подлёта вверх, - но автор останавливается ни на чём и бережёт силы для дальнейших приключений. Нет, можносразу перескочить на уровень посложнее - КНОПКА. Правда, об этом автор расскажет уже в следующей статье и пока что оставляет данный текст на анализ со стороны Вас, читателей.

Обещанная ссылка на примеры и на саму библиотеку

Комментарии (1)


  1. allpandasarecute
    28.06.2023 22:38

    Только Raylib на C написан, а не на Ruby