Всем привет!

Давно хотели создать свой движок но не знаете как?

Я вам расскажу!

Итак, нам надо установить Visual studio 2022 с Windows SDK.

компоненты
компоненты

Вот следущее нам надо установить и дополнительно языковой пакет 'Английский'

Все установили? Теперь погнали к коду!

Создаем на С++ пустой проект

И называем Beta-Engine

Сначала надо зайти в настройки и делаем так:

И создаем файл Main.cpp

Для тех кто не знает как создать

Нажимаем на папку пкм и 'Create new item'

И приступаем к коду!

Я буду обозначать концы функций как

//winapi functon

И Т.Д.

Нам надо сначала включить в проект файлы WinApi

#include <Windows.h

Отлично написали.

теперь нам надо Создать точку входа и сразу делаем дескриптор окна

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
  //win api start
  //дескриптор окна
  HWND hWnd;
  
  //этот калсс содержит информацию о нашем окне
  WNDCLASSEX wc;
    
  //win api end
}

Теперь нам надо же заполнить информацию об окне?

Ну так поехали:

//очищаем память для окна
ZeroMemory(&wc, sizeof(WNDCLASSEX));

//заполняем информацию
wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW; //стиль окна
    wc.lpfnWndProc = WindowProc; //функция обработки сообщений
    wc.hInstance = hInstance; // у меня нет коментариев
    wc.hCursor = LoadCursor(NULL, IDC_ARROW); //стандартный курсор
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW; //задний фон
    wc.lpszClassName = L"WindowClass1"; //название класса

//win api end

Пока вы пишете это я расскажу прикол:

Я сижу на ПК пишу этот пост и на телефоне вылезает реклама, я мышкой попытался 'закрыть рекламу на телефоне'

Пишем дальше:

Чтобы создать окно надо же его зарегистрировать?Конечно!

//регистрируем окно
RegisterClassEx(&wc);

И создаем окно наконец-то

//создаем окно
hWnd = CreateWindowEx(NULL,
                          L"WindowClass1",    //имя класса
                          L"Our First Windowed Program",   // название окна
                          WS_OVERLAPPEDWINDOW,    //стиль окна
                          300,    // x-позиция
                          300,    // y-позиция
                          500,    // ширина окна
                          400,    // высота окна
                          NULL,    // у нас нет родительских окон,значит Null
                          NULL,    // мы не используем меню, NULL
                          hInstance,    // Дескриптор приложения
                          NULL);    // используем только когда мульти-оконость, NULL


//win api end

Теперь показываем окно(кстати во всем коде много раз используется наш hwnd и wc):

ShowWindow(hWnd, nCmdShow);

Создаем обработку сообщений

 MSG msg;


while(GetMessage(&msg, NULL, 0, 0))
{
  //переводим сообщение в правильный формат
  TranslateMessage(&msg);

  //отправляем сообщения в windproc
  DispatchMessage(&msg);
}
 return msg.wParam; 
//win api закончился
СПОЙЛЕР: весь код для тех кто запутался
#include <Windows.h>

int WINAPI WinMain(HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow)
{
    //win api start
    //дескриптор окна
    HWND hWnd;

    //этот калсс содержит информацию о нашем окне
    WNDCLASSEX wc;

    
    //очищаем память для окна
    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    //заполняем информацию
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW; //стиль окна
    wc.lpfnWndProc = WindowProc; //функция обработки сообщений
    wc.hInstance = hInstance; // у меня нет коментариев
    wc.hCursor = LoadCursor(NULL, IDC_ARROW); //стандартный курсор
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW; //задний фон
    wc.lpszClassName = L"WindowClass1"; //название класса
    
    
    //регистрируем окно
    RegisterClassEx(&wc);

    //создаем окно
    hWnd = CreateWindowEx(NULL,
        L"WindowClass1",    //имя класса
        L"Our First Windowed Program",   // название окна
        WS_OVERLAPPEDWINDOW,    //стиль окна
        300,    // x-позиция
        300,    // y-позиция
        500,    // ширина окна
        400,    // высота окна
        NULL,    // у нас нет родительских окон,значит Null
        NULL,    // мы не используем меню, NULL
        hInstance,    // Дескриптор приложения
        NULL);    // используем только когда мульти-оконость, NULL

    ShowWindow(hWnd, nCmdShow);
    MSG msg;


    while (GetMessage(&msg, NULL, 0, 0))
    {
        //переводим сообщение в правильный формат
        TranslateMessage(&msg);

        //отправляем сообщения в windproc
        DispatchMessage(&msg);
    }
    return msg.wParam;
    //win api закончился
}

Чтобы закончить обработку сообщений нам надо написать windowproc

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    // сортируем сообщения
    switch(message)
    {
        // сообщения когда закрывают окно
        case WM_DESTROY:
            {
                // закрываем окно
                PostQuitMessage(0);
                return 0;
            } break;
    }

    // Обработка всех сообщений, не полученных инструкцией switch
    return DefWindowProc (hWnd, message, wParam, lParam);
}

Теперь в начало кода надо сделать прототип windowproc

LRESULT CALLBACK WindowProc(HWND hWnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam);

Вот куда ставить:

вот весь код:

Весь код
#include <Windows.h>

LRESULT CALLBACK WindowProc(HWND hWnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow)
{
    //win api start
    //дескриптор окна
    HWND hWnd;

    //этот калсс содержит информацию о нашем окне
    WNDCLASSEX wc;


    //очищаем память для окна
    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    //заполняем информацию
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW; //стиль окна
    wc.lpfnWndProc = WindowProc; //функция обработки сообщений
    wc.hInstance = hInstance; // у меня нет коментариев
    wc.hCursor = LoadCursor(NULL, IDC_ARROW); //стандартный курсор
    wc.hbrBackground = (HBRUSH)COLOR_WINDOW; //задний фон
    wc.lpszClassName = L"WindowClass1"; //название класса


    //регистрируем окно
    RegisterClassEx(&wc);

    //создаем окно
    hWnd = CreateWindowEx(NULL,
        L"WindowClass1",    //имя класса
        L"Our First Windowed Program",   // название окна
        WS_OVERLAPPEDWINDOW,    //стиль окна
        300,    // x-позиция
        300,    // y-позиция
        500,    // ширина окна
        400,    // высота окна
        NULL,    // у нас нет родительских окон,значит Null
        NULL,    // мы не используем меню, NULL
        hInstance,    // Дескриптор приложения
        NULL);    // используем только когда мульти-оконость, NULL

    ShowWindow(hWnd, nCmdShow);
    MSG msg;


    while (GetMessage(&msg, NULL, 0, 0))
    {
        //переводим сообщение в правильный формат
        TranslateMessage(&msg);

        //отправляем сообщения в windproc
        DispatchMessage(&msg);
    }
    return msg.wParam;
    //win api закончился
}
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    // сортируем сообщения
    switch (message)
    {
        // сообщения когда закрывают окно
    case WM_DESTROY:
    {
        // закрываем окно
        PostQuitMessage(0);
        return 0;
    } break;
    }

    // Обработка всех сообщений, не полученных инструкцией switch
    return DefWindowProc(hWnd, message, wParam, lParam);
}

Запускаем и видим:

Пока что нам надо добавить сам d3d

#include <d3d11.h> //добавляем в начало


#pragma comment (lib, "d3d11.lib")

теперь объявляем:

IDXGISwapChain *swapchain;             // Указатель на интерфейс цепочки буферов
ID3D11Device *dev;                     // указатель на интерфейс устройства Direct3D
ID3D11DeviceContext *devcon;           // указатель на контекст устройства Direct3D

объявляем функции(позже мы их напишем)

void d3dinit(HWND hWnd);
void d3dcl(void);

после show window пишем:



d3dinit(hWnd)

вот так примерно:

и также перед 'return msg.wparam'

уже создаем функцию d3dinit в самом конце

void d3dinit(HWND hWnd)
{
  //создаем структуру которая держит информацию о  буффере
  DXGI_SWAP_CHAIN_DESC scd;
  //очищаем память для использования
  ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));

  scd.BufferCount = 1;  //Один задний буфер
  scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // используем 32 битный цвет
  scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; //Как следует использовать цепочку буферов
  scd.OutputWindow = hWnd;                                // Окно, которое будет использоваться
  scd.SampleDesc.Count = 4;                               // кол-во уровней сглаживания
  scd.Windowed = TRUE;                                    // оконный/полноэкранный режим


  //создаем девайс d3d
    D3D11CreateDeviceAndSwapChain(NULL,
                                  D3D_DRIVER_TYPE_HARDWARE,
                                  NULL,
                                  NULL,
                                  NULL,
                                  NULL,
                                  D3D11_SDK_VERSION,
                                  &scd,
                                  &swapchain,
                                  &dev,
                                  NULL,
                                  &devcon);
}

Написали? Молодцы

Теперь следущую функцию d3dcl

void d3dcl(void)
{
    // закройте и освободите все существующие COM-объекты
    swapchain->Release();
    dev->Release();
    devcon->Release();
}

теперь запускаем:

Как и в прошлый раз

Всем пока! В следущей части сделаем отрисовку кадров.

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


  1. mklochkov
    08.06.2023 17:17
    +4

    30 лет назад под винду писали так же, не изменилось ничего, вот совсем ничего. Всё в точности как в этой книге написано: https://bookmix.ru/book.phtml?id=3752854 (книга, в свою очередь, была написана на основе второго издания книги Charles Petzold "Programming Windows")

    Концепция эмуляции объектно-ориентированного подхода на голом Си в исполнении Microsoft - оказалась исключительно живуча, несмотря на своё изначальное уродство.


  1. codecity
    08.06.2023 17:17
    +1

    Очень понравился стиль статьи - похоже на излюбленные всеми firststeps.ru, metanit.com ravesli.com (а так же w3schools.com). Бывает полезно чтобы быстро въехать в тему.


    1. forthuse
      08.06.2023 17:17
      +2

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


      1. kAIST
        08.06.2023 17:17
        +2

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

        Но ценность подобных статей (не этой правда), не в коде, а в знаниях.


    1. fedorro
      08.06.2023 17:17
      +1

      И что в нем хорошего? Делай раз, делай два, некогда думать, делай три, просто потому что так написано.

      Вот тут тоже самое но расписан каждый параметр, каждой функции, с картинками что - куда и почему:

      http://www.directxtutorial.com/Lesson.aspx?lessonid=11-1-3

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


  1. kovserg
    08.06.2023 17:17
    +1

    А какой смысл писать игровой движок на WinApi и Directx? (Кроме любви к искусству)


    1. Malizia
      08.06.2023 17:17

      Полагаю что WinAPI как оконный менеджер (а так же работа с файлами, сетью и пр), а DirectX как рендер. WinAPI хорошо документирован, DX, возможно, тоже. Для начала неплохо - не нужно будет отвлекаться на детали реализации того или иного оконного менеджера и рендера, не тащить boost или что-то подобное, а сосредоточиться на функционале движка. Тот же UE использует WinAPI и DX в конкретной реализации платформонезависимого интерфейса.


      1. kovserg
        08.06.2023 17:17
        +3

        Движки бывают разные есть 2D есть 3D, а колупаться с winapi и directx это сразу убъёт энтузиазм. Для 2D лучше готовые кросплатформенные библиотеки libsdl /c/, love2d /lua/, libgdx /java/ и другие для интерактивных историй renpy /python/ (+ инструменты типа spine или dragonbones ). В них всё общение с джойтиками, тач скином, шрифтами, форматами фалов, музыкой, звуками и отрисовкой уже обернули в удобное апи. И заниматься уже самим движком игры, а не бороться с устаревшей OC и COM на C++ в 100500 раз.
        А если двигаться в сторону 3D или AR то путь еще длиннее и тернистее и теории без которой далеко не уехать надо значительно больше.


        ps: Для directx есть штатные примеры от ms и есть их посмотреть то становиться как-то тоскливо от того что предлагается.


        1. Malizia
          08.06.2023 17:17
          +1

          Кроссплатформенность не всем нужна, а если имеешь опыт работы с WinAPI и COM, то и бороться не с чем. В общем, каждый выбирает себе стек для работы исходя из личного опыта. Лично я отказался от идеи писать свой движок и взял UE как инструмент с возможностью доработки необходимого функционала. Неплохая статья на тему написания своего игрового движка: How to make your own game engine (and why)


        1. Sixshaman
          08.06.2023 17:17
          +1

          Но ведь это всё неинтересно!

          Именно все эти COM, низкоуровневый менеджмент ресурсов, системные вызовы — именно это самое интересное в подобных проектах. Когда у тебя уже есть готовый инструментарий, какой смысл чем-либо заниматься?


          1. kovserg
            08.06.2023 17:17

            Ваша цель сделать игровой движок или сношения с чужим апи?


  1. Le0Wolf
    08.06.2023 17:17
    +10

    Зачем на хабре копипаста стартового проекта оконного приложения и примера кода из репозиьория майкрософт? Зачем описание того, как запускать Visual Studio? Вы собираетесь учить писать игровой движок тех, кто даже программу на компьютере запустить не в силах?

    Создание окна и создание класса окна можно разделить по методам (так даже стартовый шаблон делает), работу с окном завернуть в класс, либо хотя бы отделить от этого всего работу с DirectX. Глобальный указатель на класс окна нафиг не нужен ибо он используется только для его регистрации. ZeroMemory использовался в гайдах 90-х, сейчас у языка есть куда лучший способ сделать тоже самое.

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

    Опять же, почему при создании окна такие параметры? Чем, к примеру, не устроил размер и позиция по умолчанию или почему эти значения не берутся, к примеру, из конфига или хотя бы глобальных констант? Почему стиль WS_OVERLAPPEDWINDOW? Почему первый параметр CreateWindowEx равен NULL и что он вообще значит? Почему в этом случае не использовался просто CreateWindow, учитывая, что между ними разница как раз именно в этом параметре? Почему вообще NULL, а не nullptr (или как там его правильно?)?

    Почему нет вызова UpdateWindow?

    Почему в цикле сообщений используется GetMessage, который ставит цикл на паузу пока нет сообщний, вместо другого, который этого не делает? Зачем там TranslateMessage? И да, он, если что, не "переводит сообщение в правильный формат", а делает нечто иное, о чем можно почитать на MSDN или хотя-бы на стековерфлоу

    PostQuitMessage не закрывает окно, а делает ровно то, что следует из его названия: отсылает сообщение завершения работы приложения с кодом возврата (0- нет ошибок). Если окон будет несколько, то не окна закроется, а приложение, а если точнее, что завершится цикл обработки сообщений (тот самый, с GetMessage)

    Нет описания, зачем определение функции WndProc в начале нужно

    По инициализации DirectX вообще ноль объяснений

    За ручные вызовы Release() вообще стоило бы руки отрывать. Для кого умные указатели придумали?

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

    И ещё совет: если только начинаешь изучать программирование, то даже не стоит пытаться написать свой игровой движок ибо это куда сложнее, чем создать копипастой окно и создать парочку объектов DirectX.

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


  1. Einherjar
    08.06.2023 17:17
    +3

    Целевая аудитория не совсем понятна. Тем кому требуется туториал как создавать пустой проект очень рано лезть в такую низкоуровневую штуку, как directx, если начинающему хочется потыкать 3д, то лучше начинать с каких то готовых движков, на чистом dx ловить особо нечего - если делать что то более продвинутое чем вращающиеся чайники, то гарантировано утопание в количестве велосипедов которые придется изобрести. А тем кто уже готов погружаться в системные апи 3д графики уже точно незачем туториалы по созданию окон на винапи.


  1. Travisw
    08.06.2023 17:17

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


    1. Malizia
      08.06.2023 17:17

      Game Engine Architecture by Jason Gregory


  1. net_racoon
    08.06.2023 17:17

    15 лет назад писал свой движок, это было интересно. Сейчас есть всякие Unity и UE, писать свой движок смысла ноль. Лучше разобраться как технологии работают ИМХО.


    1. simenoff
      08.06.2023 17:17
      +2

      Кто-то же должен писать игровые движки


      1. net_racoon
        08.06.2023 17:17

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


        1. SIISII
          08.06.2023 17:17

          Ну, для того, чтобы быть пригодным к работе в коллективе, создающем движки (в том же Эпике, например), надо самому уметь программировать на уровне API системы, а не используя готовые библиотеки и движки. Так что изучать там сначала придётся примерно то, что в данной статье рассматривается, и смысл в этом самый что ни на есть прямой. Другое дело, что заниматься этим надо не в качестве второго проекта сразу после ХеллоВорлда, а соответственно, все рассказы про то, как установить Студию и создать в ней проект, в статье "создаём движок с нуля" даром не нужны. Вот описать логику работы с сообщениями Винды следовало б -- но здесь этого как раз и нету, есть только код, "магически" их обрабатывающий.


  1. SIISII
    08.06.2023 17:17
    +1

    Игровой движок, и даже графический движок (который -- лишь часть игрового) -- это, мягко говоря, несколько более сложная программа, чем оконный цикл, в котором дёргается ДиректХ для рендеринга картинки, представленной в виде готовых структур данных. Что же касается подачи материала, то для тех, кто уже способен разобраться с устройством графдвижка, он неудобен: им не требуется разжёвывать, как ставить Студию, как что писать и т.д. -- им не пошаговые инструкции "для чайников" нужны, а описание принципов; оформить это в виде кода они и сами смогут. Ну а означенные "чайники" могут лишь тупо повторить сделанное, но так и не поймут, почему так, зачем так и что оно вообще делает. В общем, ни то ни сё.