Вітання, шановні інді та не тільки!
Якщо ви коли-небудь думали про те, щоб займатись програмуванням та робити ігри, то моя стаття для вас. Одразу попереджу, що я не рекомендуватиму пігулку для миттєвого ставання крутезним розробником як Джон Кармак. Також не поспішатиму рекомендувати обирати такий от очевидний спосіб на кшталт Unity. Натомість покажу інструмент яким сам вирішив користутватись й розглянути його як альтернативний до відомих нам рушіїв. Що ми зараз і зробимо.

Перш за все, я говоритиму про використання бібліотеки RayLib та її переваги.

Отож, які важливі момменти з RayLib я для себе виділив, щоб порекомендувати дану бібліотеку?

Принцип роботи з бібліотекою

Якщо ви колись працювали з SFML чи SDL, то розумієте суть програмування з бібліотеками. RayLib не є винятком. Як оголошено на сайті, вам потрібно буде програмувати у класичний («спартанський») спосіб: код і нічого більше.Тобто ваша IDE і помічні інструменти. Або ж текстовий редактор. Але без UI.

Цей спосіб може відлякати тих, хто ніколи не займався програмуванням. Але оскільки я уточнив, що пишу це для програмістів, то чому б фокус на коді має відштовхувати? Розраховую на те, що у вас є ціль або шлях як у самурая.

Я додав цей пункт як перший і як основу саме тому, що практика написання коду є одним з найважливіших методів вдосконалення навички, запорука шляху сильного спеціаліста. Нехай мене поб'ють кабачками, але я ризикну порівняти кодинг зі спортом, де розв'язання задач це заняття на тренажері, а мозок це «розумовий м'яз» в певному сенсі. Складнощі та помилки ваші найкращі друзі, які вас навчать більше та ефективніше, ніж відео "Як стати програмістом за тиждень".

І хоч я не заперечую, що в рушіях теж є можливість писати код, але ваш фокус розгалужується між різними інструментами в редакторі й ви не завжди пишете код. Дуже часто треба саме клацати UI частину програми в пересуванні акторів сцени. В противагу цьому навіть той самий Level Design є також у коді.

Варіяція мов та платформ для розробки

Мене здивував великий вибір мов, де можна використовувати RayLib. Але й не дивно, оскільки бібліотека написана на C (С99), тому одразу ж доступна і для С++. Але й, окрім того, можна використовувати з 50+ мовами, що вражає. Тобто навіть коли ви починали знайомитись з програмуванням з того ж Python'a і ніколи не пробували С, то цілком можете почати розробку зі знайомою вам мовою. Чи навіть %вставте сюди будь-яку мову, я певен, що вона буде підтримуватись, справді%. SFML та SDL також мають вибір, але не настільки великий.

Платформи, куди можна випускати ваші роботи, також похваляться варіятивністю. Ось вам стандартна ігрова платформа — Windows. А ось ще Linux та macOS. Наче все? Але ні, тут ще й Raspberry Pi є. Ой, ну і як без HTML чи Android? Crossplatform це круто!

Не базою єдиною

Часом, функціоналу мало, через що доводиться вигадувати щось на ходу. Але raysan5 попіклувався і про додаткові бібліотеки.

  • rlgl — для роботи з шаром абстракції на OpenGL;

  • raymath — для роботи з математичними функціями;

  • raudio — для роботи зі звуками;

  • raygui — для легкого створення інтерфейсу;

  • rpng — для роботи з png форматом файлів;

  • rres — для запаковування ресурсів для гри;

Всі вони, як і основна бібліотека, легко встановлюються і не мають залежностей (не треба нічого додаткового встановлювати). Що важливо, окремі бібліотеки не є обов'язковими, тобто основний header файл не перевантажений функціоналом, який ви не будете навіть використовувати. Це економить вам місце, а також зберігає порядок. Все на своїх місцях, при потребі довантажуєте.

А також є інструменти, які є самостійними програмами розробленими самою RayLib і можна встановити на комп'ютер. Там є редактори звукових ефектів, пакувальники ресурсів і тому подібне. Не зупинятимусь на переліку, зацікавлених направляю сюди!

Простота

Простота це друге ім'я RayLib. Простота у коді, простота у документації та простота у візуалі. Зазначу, що простоту я називаю у візуальному сприйнятті. Наприклад, нумо розяглянемо важливе на сайті. Приємний візуал та лише потрібне. Ось вам вкладки.

Вкладка Cheatsheet (Шпаргалка) містить усі виписані функції та короткий опис. Ось вам приклад малої частини:

Тут продемонстровані базові функції, які допожуть у створенні першого вікна та маніпуляціями з ним
Cheatsheet

Вкладка Examples містить приклади різних реалізацій як от банальний рух м'ячика на стрілочках, вивід зображення чи рух в 3D в просторі.

Якщо є якесь питання «А як реалізувати *щось*?», то радше за все відповідь знайдеться тут
Examples

А вкладка Games вже покаже готові роботи, які ви можете там же і випробувати.

Ігри створені спільнотою
Games


Для тих, кого цікавить приклад коду.
Ось вам реалізація на мові С «туману війни» та квадрату, яка його розсіює від raysan5. Переклав коментарі самої програми (окрім шапки з копірайтом) для кращого розуміння, але можете відкрити англійську версію, якщо вам так зручніше.

/*******************************************************************************************
*
*   raylib [textures] example - Fog of war
*
*   Example originally created with raylib 4.2, last time updated with raylib 4.2
*
*   Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
*   BSD-like license that allows static linking with closed source software
*
*   Copyright (c) 2018-2023 Ramon Santamaria (@raysan5)
*
********************************************************************************************/

#include "raylib.h"

#include <stdlib.h>                 // Необхідне для функцій: calloc(), free()

#define MAP_TILE_SIZE    32         // Розмір тайлів 32х32 пікселів
#define PLAYER_SIZE      16         // Розмір гравця
#define PLAYER_TILE_VISIBILITY  2   // Гравець може бачити 2 тайли навколо себе

// Map data type
typedef struct Map {
    unsigned int tilesX;            // кількість тайлів у площині Х
    unsigned int tilesY;            // кількість тайлів у площині Y 
    unsigned char *tileIds;         // ID тайлів (tilesX*tilesY), визначає які тайли малювати
    unsigned char *tileFog;         // Стан тайлів війни (tilesX*tilesY), визначає який тип туману: повний чи частковий
} Map;

//------------------------------------------------------------------------------------
// Тут починається головна програма
//------------------------------------------------------------------------------------
int main(void)
{
    // Ініціалізація
    //--------------------------------------------------------------------------------------
    int screenWidth = 800;
    int screenHeight = 450;

    InitWindow(screenWidth, screenHeight, "raylib [textures] example - fog of war");

    Map map = { 0 };
    map.tilesX = 25;
    map.tilesY = 15;

    // Примітка: Ми можемо використовувати 256 значень для id тайлів та їх станів,
    // мабуть, нам не знадобиться стільки станів, тому ми можемо оптимізувати,
    // використавши 2 біти на тайл (зменшивши розмір на 4) але логіка буде складнішою
    map.tileIds = (unsigned char *)calloc(map.tilesX*map.tilesY, sizeof(unsigned char));
    map.tileFog = (unsigned char *)calloc(map.tilesX*map.tilesY, sizeof(unsigned char));

    // Завантаження мапи тайлів (генеруємо 2 тайл id для тесутвання)
    // Примітка: id мапи тайлів варто таки підтягувати з окремого файлу
    for (unsigned int i = 0; i < map.tilesY*map.tilesX; i++) map.tileIds[i] = GetRandomValue(0, 1);

    // Позиція гравця на екрані (координати пікселів, а не тайлів)
    Vector2 playerPosition = { 180, 130 };
    int playerTileX = 0;
    int playerTileY = 0;

    // Рендер текстур для рендеру туману війни
    // Примітка: Для отримання ефекту гладенького розвіювання туману, ми використаємо рендер текстури задля 
    // рендеру туману маленького розміру (один піксель на тайл) і змасштабуємо на малюванні з білінійним
    // фільтруванням
    RenderTexture2D fogOfWar = LoadRenderTexture(map.tilesX, map.tilesY);
    SetTextureFilter(fogOfWar.texture, TEXTURE_FILTER_BILINEAR);

    SetTargetFPS(60);               // Вказуємо виконання гри при 60 кадрах на секунду
    //--------------------------------------------------------------------------------------

    // Головний ігровий цикл
    while (!WindowShouldClose())    // Встановлюємо реагування на закриття вікна чи натискання Esc
    {
        // Оновлення значень
        //----------------------------------------------------------------------------------
        // Рух гравця 
        if (IsKeyDown(KEY_RIGHT)) playerPosition.x += 5;
        if (IsKeyDown(KEY_LEFT)) playerPosition.x -= 5;
        if (IsKeyDown(KEY_DOWN)) playerPosition.y += 5;
        if (IsKeyDown(KEY_UP)) playerPosition.y -= 5;

        // Перевірка місцезнаходження на виявлення виходу за межі тайлів мапи
        if (playerPosition.x < 0) playerPosition.x = 0;
        else if ((playerPosition.x + PLAYER_SIZE) > (map.tilesX*MAP_TILE_SIZE)) playerPosition.x = (float)map.tilesX*MAP_TILE_SIZE - PLAYER_SIZE;
        if (playerPosition.y < 0) playerPosition.y = 0;
        else if ((playerPosition.y + PLAYER_SIZE) > (map.tilesY*MAP_TILE_SIZE)) playerPosition.y = (float)map.tilesY*MAP_TILE_SIZE - PLAYER_SIZE;

        // Відвіданий туман робимо частковим
        for (unsigned int i = 0; i < map.tilesX*map.tilesY; i++) if (map.tileFog[i] == 1) map.tileFog[i] = 2;

        // Отримуємо чинну позицію тайлів з позиції гравця на пікселів
        playerTileX = (int)((playerPosition.x + MAP_TILE_SIZE/2)/MAP_TILE_SIZE);
        playerTileY = (int)((playerPosition.y + MAP_TILE_SIZE/2)/MAP_TILE_SIZE);

        // Перевірка видимості і оновлення туману
        // Примітка: Перевіряємо межі тайлмапів на виявлення обробки тайлів за межами масиву (it could crash program)
        for (int y = (playerTileY - PLAYER_TILE_VISIBILITY); y < (playerTileY + PLAYER_TILE_VISIBILITY); y++)
            for (int x = (playerTileX - PLAYER_TILE_VISIBILITY); x < (playerTileX + PLAYER_TILE_VISIBILITY); x++)
                if ((x >= 0) && (x < (int)map.tilesX) && (y >= 0) && (y < (int)map.tilesY)) map.tileFog[y*map.tilesX + x] = 1;
        //----------------------------------------------------------------------------------

        // Малювання
        //----------------------------------------------------------------------------------
        // Малювання туману війни для малого рендеру текстур для автоматичного згладження при масштабуванні
        BeginTextureMode(fogOfWar);
            ClearBackground(BLANK);
            for (unsigned int y = 0; y < map.tilesY; y++)
                for (unsigned int x = 0; x < map.tilesX; x++)
                    if (map.tileFog[y*map.tilesX + x] == 0) DrawRectangle(x, y, 1, 1, BLACK);
                    else if (map.tileFog[y*map.tilesX + x] == 2) DrawRectangle(x, y, 1, 1, Fade(BLACK, 0.8f));
        EndTextureMode();

        BeginDrawing();

            ClearBackground(RAYWHITE);

            for (unsigned int y = 0; y < map.tilesY; y++)
            {
                for (unsigned int x = 0; x < map.tilesX; x++)
                {
                    // Малювання тайлів з id
                    DrawRectangle(x*MAP_TILE_SIZE, y*MAP_TILE_SIZE, MAP_TILE_SIZE, MAP_TILE_SIZE,
                                  (map.tileIds[y*map.tilesX + x] == 0)? BLUE : Fade(BLUE, 0.9f));
                    DrawRectangleLines(x*MAP_TILE_SIZE, y*MAP_TILE_SIZE, MAP_TILE_SIZE, MAP_TILE_SIZE, Fade(DARKBLUE, 0.5f));
                }
            }

            // Малювання гравця
            DrawRectangleV(playerPosition, (Vector2){ PLAYER_SIZE, PLAYER_SIZE }, RED);


            // Малювання туману війни (scaled to full map, bilinear filtering)
            DrawTexturePro(fogOfWar.texture, (Rectangle){ 0, 0, (float)fogOfWar.texture.width, (float)-fogOfWar.texture.height },
                           (Rectangle){ 0, 0, (float)map.tilesX*MAP_TILE_SIZE, (float)map.tilesY*MAP_TILE_SIZE },
                           (Vector2){ 0, 0 }, 0.0f, WHITE);

            // Малювання поточного тайлу гравця
            DrawText(TextFormat("Current tile: [%i,%i]", playerTileX, playerTileY), 10, 10, 20, LIME);

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

    // Деініціалізація
    //--------------------------------------------------------------------------------------
    free(map.tileIds);      // Звільнення пам'яті виділеної для id тайлів
    free(map.tileFog);      // Звільнення пам'яті виділеної для типів туманів

    UnloadRenderTexture(fogOfWar);  // Відвантажити рендер текстур

    CloseWindow();          // Закриття вікна та контексту OpenGL
    //--------------------------------------------------------------------------------------

    return 0;
}

Що дасть нам ось такий результат:

Опис картинки
Квадрат це наш керований об'єкт, який рухається на площині та розсіює туман війни. Проте ми все ще не бачимо всієї розвіданої території, бо зір нашого персонажа обмежений.

Як на мене, приклади охайно оформлені та допоможуть розібратись поступово, що та як працює. Завітайте на сайт і подивіться самі.

Вільний код

Код RayLib доступний на загал, ви можете його змінювати під свої потреби, хоч і не маєте права, як трактує ліцензія zlib, стверджувати, що вам належить ця бібліотека чи те, що ваша версія (якщо ви міняли код бібліотеки) є оригіналом. У вас не візьмуть жодної гривні за використання. Це чудова можливість для новачків спробувати цю потужну бібліотеку задарма.

Я наголошую на FOSS не як фанатик, а саме для того, що не потрібно одразу бігти і купувати програми або піратити (фу!), а подивитись на пропозиції вільних та безкоштовних програмних забезпечень. Якщо вам цього мало і бачите потребу в платному програмному забезпеченні, тоді так, чому б ні.


Наразі, це все. Цим текстом жодним чином не хотів би вказати на єдиний правильний варіянт розробки. Адже для кожної роботи та кожного майстра є свій інструмент, який він використовує.

Поділіться якими інструментами (бібліотеками/рушіями) ви користуєтесь. Якщо вам сподобалась стаття, тисніть «Оплески» чи дайте знати свою думку в коментарях. Буду вдячний і за поширення!

Поділись своїми ідеями в новій публікації.
Ми чекаємо саме на твій довгочит!
Лавашик про ґеймдев
Лавашик про ґеймдев@lavashyk

Веду блоґ про ґеймдев

205Прочитань
1Автори
13Читачі
Підтримати
На Друкарні з 15 квітня

Більше від автора

  • Змісти свій мовний фокус, або чому ти можеш споживати англійською

    Цей текст буде рефлексією на проблему, яку я помітив впродовж років перебування на просторах української частини Інтернету. Всі роки ми намагаємось споживати максимально українською та відмовитись від російського. Це правильна позиція, я повністю підтримую. Але...

    Теми цього довгочиту:

    Ігри
  • Впустіть Linux у своє життя

    Це стаття-роздуми, яка буде корисна користувачам Windows, яким тісно і не подобається операційна система, якою зараз користуються. Для розширення свого світогляду, надзвичайно корисним є пробувати нове. Так шукаються найкращі рішення. Спробуйте Linux, раптом це те, що шукаєте?

    Теми цього довгочиту:

    Комп’ютер

Вам також сподобається

  • XCOM 2: War of the Chosen

    Перша гра серії Х-СОМ була випущена далекого 1994 року. Гравець керує організацією, що бореться із вторгненням інопланетян.

    Теми цього довгочиту:

    Ігри
  • Вони знають. Динаміка в групах.

    У тому, що окремий учасник групи (сім'ї, компанії, колективу) розповідає про себе, є речі, які однаково добре знають і він сам, і інші учасники групи - Я відомий. Є те, що він знає про себе, а інші ні - Я прихований. Є речі, які інші знають про нього, а він сам ні - білі плями.

    Теми цього довгочиту:

    Психологія

Коментарі (7)

Бляха, давним давно хотів її почіпати але ніяк руки не доходили

Вам також сподобається

  • XCOM 2: War of the Chosen

    Перша гра серії Х-СОМ була випущена далекого 1994 року. Гравець керує організацією, що бореться із вторгненням інопланетян.

    Теми цього довгочиту:

    Ігри
  • Вони знають. Динаміка в групах.

    У тому, що окремий учасник групи (сім'ї, компанії, колективу) розповідає про себе, є речі, які однаково добре знають і він сам, і інші учасники групи - Я відомий. Є те, що він знає про себе, а інші ні - Я прихований. Є речі, які інші знають про нього, а він сам ні - білі плями.

    Теми цього довгочиту:

    Психологія