Всем привет!
Давно хотели создать свой движок но не знаете как?
Я вам расскажу!
Итак, нам надо установить 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)
codecity
08.06.2023 17:17+1Очень понравился стиль статьи - похоже на излюбленные всеми firststeps.ru, metanit.com ravesli.com (а так же w3schools.com). Бывает полезно чтобы быстро въехать в тему.
forthuse
08.06.2023 17:17+2Есть подозрение, что подобный код представленный в статье по предлагаемым примерам уже без особого напряга выдаст AI и он будет рабочий.
Поправьте, если не прав.kAIST
08.06.2023 17:17+2Выдаст конечно, потому что подобный код тысячи раз публиковали в различных туториалах.
Но ценность подобных статей (не этой правда), не в коде, а в знаниях.
fedorro
08.06.2023 17:17+1И что в нем хорошего? Делай раз, делай два, некогда думать, делай три, просто потому что так написано.
Вот тут тоже самое но расписан каждый параметр, каждой функции, с картинками что - куда и почему:
http://www.directxtutorial.com/Lesson.aspx?lessonid=11-1-3
А такие статьи только Хабр засоряют - опытные люди и без картинок знают как файл в проект добавить и всё вот это, а новички мало что ценного извлекут, кмк.
kovserg
08.06.2023 17:17+1А какой смысл писать игровой движок на WinApi и Directx? (Кроме любви к искусству)
Malizia
08.06.2023 17:17Полагаю что WinAPI как оконный менеджер (а так же работа с файлами, сетью и пр), а DirectX как рендер. WinAPI хорошо документирован, DX, возможно, тоже. Для начала неплохо - не нужно будет отвлекаться на детали реализации того или иного оконного менеджера и рендера, не тащить boost или что-то подобное, а сосредоточиться на функционале движка. Тот же UE использует WinAPI и DX в конкретной реализации платформонезависимого интерфейса.
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 и есть их посмотреть то становиться как-то тоскливо от того что предлагается.
Malizia
08.06.2023 17:17+1Кроссплатформенность не всем нужна, а если имеешь опыт работы с WinAPI и COM, то и бороться не с чем. В общем, каждый выбирает себе стек для работы исходя из личного опыта. Лично я отказался от идеи писать свой движок и взял UE как инструмент с возможностью доработки необходимого функционала. Неплохая статья на тему написания своего игрового движка: How to make your own game engine (and why)
Sixshaman
08.06.2023 17:17+1Но ведь это всё неинтересно!
Именно все эти COM, низкоуровневый менеджмент ресурсов, системные вызовы — именно это самое интересное в подобных проектах. Когда у тебя уже есть готовый инструментарий, какой смысл чем-либо заниматься?
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, но касательно проектирования игровых движков (не помню, как называется, но есть на русском, в том числе и в виде статей на хабре вроде)
Einherjar
08.06.2023 17:17+3Целевая аудитория не совсем понятна. Тем кому требуется туториал как создавать пустой проект очень рано лезть в такую низкоуровневую штуку, как directx, если начинающему хочется потыкать 3д, то лучше начинать с каких то готовых движков, на чистом dx ловить особо нечего - если делать что то более продвинутое чем вращающиеся чайники, то гарантировано утопание в количестве велосипедов которые придется изобрести. А тем кто уже готов погружаться в системные апи 3д графики уже точно незачем туториалы по созданию окон на винапи.
net_racoon
08.06.2023 17:1715 лет назад писал свой движок, это было интересно. Сейчас есть всякие Unity и UE, писать свой движок смысла ноль. Лучше разобраться как технологии работают ИМХО.
simenoff
08.06.2023 17:17+2Кто-то же должен писать игровые движки
net_racoon
08.06.2023 17:17Должен, но если это не большая компания, а один человек, который только учится окна создавать через WinAPI, то ничего из этого не выйдет.
SIISII
08.06.2023 17:17Ну, для того, чтобы быть пригодным к работе в коллективе, создающем движки (в том же Эпике, например), надо самому уметь программировать на уровне API системы, а не используя готовые библиотеки и движки. Так что изучать там сначала придётся примерно то, что в данной статье рассматривается, и смысл в этом самый что ни на есть прямой. Другое дело, что заниматься этим надо не в качестве второго проекта сразу после ХеллоВорлда, а соответственно, все рассказы про то, как установить Студию и создать в ней проект, в статье "создаём движок с нуля" даром не нужны. Вот описать логику работы с сообщениями Винды следовало б -- но здесь этого как раз и нету, есть только код, "магически" их обрабатывающий.
SIISII
08.06.2023 17:17+1Игровой движок, и даже графический движок (который -- лишь часть игрового) -- это, мягко говоря, несколько более сложная программа, чем оконный цикл, в котором дёргается ДиректХ для рендеринга картинки, представленной в виде готовых структур данных. Что же касается подачи материала, то для тех, кто уже способен разобраться с устройством графдвижка, он неудобен: им не требуется разжёвывать, как ставить Студию, как что писать и т.д. -- им не пошаговые инструкции "для чайников" нужны, а описание принципов; оформить это в виде кода они и сами смогут. Ну а означенные "чайники" могут лишь тупо повторить сделанное, но так и не поймут, почему так, зачем так и что оно вообще делает. В общем, ни то ни сё.
mklochkov
30 лет назад под винду писали так же, не изменилось ничего, вот совсем ничего. Всё в точности как в этой книге написано: https://bookmix.ru/book.phtml?id=3752854 (книга, в свою очередь, была написана на основе второго издания книги Charles Petzold "Programming Windows")
Концепция эмуляции объектно-ориентированного подхода на голом Си в исполнении Microsoft - оказалась исключительно живуча, несмотря на своё изначальное уродство.