Движок с нуля
Часть 1 · Старт · глава 1.5

Проект в Visual Studio

Откроешь проект в Visual Studio и соберёшь его там.

Окно Visual Studio

Отдельные файлы ты в Visual Studio уже писал. Теперь открой в Visual Studio весь проект.

Прежде чем писать код, выключи в Visual Studio серые подсказки. Пока ты печатаешь, справа от курсора может появляться бледный серый текст: это Copilot предлагает дописать строку или целую функцию, а клавиша Tab вставляет серый текст в файл. Код движка в этом курсе ты пишешь сам: так ты встретишь те же ошибки, что разобраны в главах, и будешь знать, зачем нужна каждая строка. Подсказка, принятая случайным нажатием Tab, подложит в файл код, которого ты не писал и не читал.

Большую часть курса ты будешь проводить в этом окне. Окно знакомое, но проект, открытый как папка с CMakeLists.txt, устроен иначе, чем проект из мастера Visual Studio, а в знакомых панелях есть полезное, мимо чего обычно проходят. Пройди по частям окна.

Из чего состоит окно Visual Studio

Так выглядит Visual Studio, когда в ней открыта твоя папка MyEngine. У окна пять частей: сверху главное меню, под ним верхняя полоса с кнопкой запуска, слева обозреватель решений, в центре редактор, снизу нижняя панель. Панели можно переставить, поэтому у тебя обозреватель решений может стоять справа: названия панелей от этого не меняются.
Solution ExplorerОбозреватель решений показывает дерево твоей папки таким, какое оно на диске, а двойной щелчок по файлу открывает его в редакторе. Папки build и out создала настройка CMake: build — команда из консоли, out — Visual Studio, когда открыла папку. Поэтому файлы в них руками не правят. Файл в дереве ещё не значит файл в программе: компилятор получит только те файлы, что перечислены в CMakeLists.txt, в команде add_executable. Поэтому новый файл .cpp, например Window.cpp, мало создать в папке: его имя нужно дописать в скобки команды add_executable после main.cpp через пробел, получится add_executable(app main.cpp Window.cpp), и сохранить CMakeLists.txt. При сохранении Visual Studio сама запустит настройку CMake заново, и новый файл попадёт в программу.

В обозревателе решений над деревом файлов стоит строка кнопок, и в ней есть кнопка с листом и фиолетовым значком, Switch Views. Эта кнопка переключает представление — то, в каком виде обозреватель показывает проект. Какое представление выбрано, написано в заголовке обозревателя после названия панели: сейчас это Folder View, дерево папки на диске.

Щелчок по кнопке Switch Views заменяет дерево списком представлений, а в заголовке обозревателя вместо имени представления появляется слово Views. В списке четыре строки:

  • Open a solution, folder, or other item — заголовок списка, сама эта строка ничего не делает;
  • Folder View выделено жирным, потому что это представление выбрано сейчас;
  • CMake Targets View показывает проект так, как его видит CMake;
  • Engine.slnx — файл решения, который CMake записал в папку build, когда ты настраивал проект из консоли. Visual Studio нашла этот файл в папке и предлагает открыть проект через него. В этом курсе проект открыт как папка, поэтому строка Engine.slnx не нужна.

Двойной щелчок по строке CMake Targets View в списке представлений покажет вместо папки проект CMake. В этом дереве:

  • Engine Project — сам проект, имя взято из команды project в CMakeLists.txt;
  • app (executable) — цель app из команды add_executable, а слово executable значит, что из этой цели получится программа .exe. Внутри узла цели лежат файлы, которые CMake отдаёт цели app. Так видно, попадёт ли файл в программу: файла, который ты создал, но не дописал в add_executable, в узле app (executable) не будет;
  • CMakeLists.txt лежит в узле проекта, а не в узле цели: этот файл описывает весь проект;
  • Pinned Targets — закреплённые цели. Когда целей в движке станет много, нужные можно закрепить здесь, чтобы не искать нужную цель в дереве.

Чтобы вернуться к папке на диске, снова нажми Switch Views и дважды щёлкни по строке Folder View.

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

  • кнопка со стрелками в обе стороны, Sync with Active DocumentСинхронизировать с активным документом, Ctrl+[, затем S, раскрывает дерево до файла, открытого в редакторе, и выделяет этот файл. Когда в движке появятся десятки файлов в разных папках, так проще всего узнать, где лежит файл, который ты сейчас правишь;
  • кнопка с двумя уголками вверх, Collapse AllСвернуть все, сворачивает все раскрытые папки дерева одним щелчком;
  • поле поиска под кнопками, Ctrl+;, оставляет в дереве только файлы, в имени которых есть набранные буквы: набери main, и в дереве останется main.cpp.
Редактор проверяет код прямо во время набора, ещё до сборки. Этим занимается IntelliSense. На картинке в строке 5 опечатка: вместо std::cout написано std::cot. Ошибку редактор отмечает красной волнистой линией, а предупреждение — зелёной. Сделай такую же опечатку у себя и наведи указатель мыши на линию: всплывёт текст ошибки. Полоска внизу редактора показывает, сколько таких мест в открытом файле, и строку, на которой стоит курсор. Потом верни std::cout.

Пока ты печатаешь имя, список подсказок предлагает варианты, которые есть в подключённых файлах. Здесь набрано std::co, и список показал имена из std, которые начинаются с этих букв. Поэтому если нужного имени в списке нет, чаще всего не хватает строки #include с нужным файлом. Если список закрылся, Ctrl+Space открывает его снова.

Подсказки работают и после того, как имя набрано. Наведи указатель мыши на cout, и всплывёт его описание. Когда откроешь скобку после имени функции, появится подсказка с её параметрами. Клавиша F12 на слове откроет файл, где это имя объявлено: так видно, откуда взялось чужое имя. А Alt+F12 покажет то же в маленьком окне над кодом, не уводя тебя из твоего файла.

Сборкой управляют четыре элемента верхней полосы:

  • список x64-Debug выбирает конфигурацию, и у каждой конфигурации своя папка сборки;
  • список app.exe выбирает, что запускать. Visual Studio заполняет его целями из CMakeLists.txt, поэтому надпись Select Startup ItemВыбрать элемент запуска вместо имени значит, что настройка CMake ещё идёт или закончилась ошибкой;
  • закрашенный треугольник, он же F5, собирает программу и запускает её под отладчиком;
  • контурный треугольник, он же Ctrl+F5, собирает и запускает без отладчика.
Вот что даёт запуск через F5. Щёлкни по серой полосе слева от номеров строк напротив строки 5: появится красная точка, это точка останова. Нажми F5: программа остановится перед этой строкой, а жёлтая стрелка покажет строку, которая выполнится следующей. Эта строка ещё не выполнена: в консоли пока ничего нет. В этом и состоит отладка: программа не бежит до конца, а стоит на твоей строке, и ты разглядываешь программу, пока она не может измениться. Дальше программа пойдёт только по твоей команде: F5 пускает её до следующей точки останова или до конца. При запуске через Ctrl+F5 точки останова не срабатывают. Отлаживать удобно только в варианте Debug: в другом варианте, Release, компилятор переставляет и выбрасывает строки ради скорости, и остановиться ровно на твоей строке уже нельзя.

Пока программа стоит, верхняя полоса меняется: вместо кнопок запуска там появляется панель отладки. Слева направо на ней такие кнопки:

  • две полоски, Break AllПрервать все, останавливают работающую программу на той строке, где она сейчас; пока программа уже стоит, как сейчас, кнопка серая;
  • красный квадрат, Stop DebuggingОстановить отладку, Shift+F5, завершает программу совсем;
  • круговая стрелка, RestartПерезапустить, Ctrl+Shift+F5, останавливает программу и запускает заново;
  • стрелка вправо, Show Next StatementПоказать следующий оператор, возвращает редактор к жёлтой стрелке, если ты ушёл смотреть другой файл;
  • синяя стрелка дугой над точкой, Step OverШаг с обходом, F10, выполняет строку, на которой стоит жёлтая стрелка, и переходит к следующей. Строка 5 печатает приветствие, поэтому после этого шага в консоли появится Hello, Engine!.
Пока программа стоит на точке останова, вкладки нижней панели другие: Visual Studio показывает то, что нужно при отладке. На вкладке Call StackСтек вызовов видна цепочка вызовов: верхняя строка — функция, в которой программа стоит сейчас, а ниже те, что её вызвали. На вкладке LocalsЛокальные перечислены переменные этой функции с текущими значениями; в твоей программе сейчас нет переменных, поэтому вкладка пуста. Значение любой переменной можно увидеть и в самом коде: наведи на её имя указатель мыши, и рядом всплывёт текущее значение. После остановки программы прежние вкладки вернутся сами, поэтому пугаться новой раскладки не нужно.
Допустим, опечатка std::cot осталась и ты запустил сборку. Причины сбоя собраны на вкладке Error ListСписок ошибок: код ошибки, описание, файл и номер строки. Читай сверху и исправляй первую ошибку от компилятора: следующие часто оказываются её следствием. Здесь C2039 сообщает, что в std нет имени cot, а C2065 говорит о том же слове ещё раз, поэтому исправление опечатки уберёт обе ошибки. Двойной щелчок по строке таблицы откроет файл на нужной строке, а щелчок по коду ошибки откроет её описание на сайте Microsoft.
Одна опечатка попала в таблицу несколько раз, потому что ошибки приходят из двух источников. Код на букву E написала IntelliSense, сборка для этого не нужна. Коды на букву C написал компилятор во время сборки, и только его ошибки действительно мешают собрать программу: изредка IntelliSense подчёркивает код, который компилируется без ошибок. Если сомневаешься, переключи правый список над таблицей с Build + IntelliSenseСборка + IntelliSense на Build OnlyТолько сборка: останутся только ошибки сборки. Левый список, Entire SolutionВсё решение, выбирает, откуда брать ошибки: из всего проекта или только из открытых файлов. Кнопки со счётчиками скрывают и показывают ошибки, предупреждения и сообщения, поэтому пустая таблица при упавшей сборке обычно значит, что кнопка ошибок отжата.

На вкладке OutputВывод одно окно хранит несколько журналов, а показывает один: его выбирает список Show output fromПоказать выходные данные из. Для нашей работы нужны два журнала:

  • журнал CMake хранит вывод настройки;
  • журнал Build хранит вывод сборки.

Если в окне OutputВывод нет нужных строк, сначала проверь список Show output fromПоказать выходные данные из: возможно, открыт не тот журнал. Сюда стоит заглянуть за тем, чего нет в таблице ошибок: строки с note уточняют ошибку, например показывают, где объявлено std. Двойной щелчок по строке с error здесь тоже открывает файл на нужной строке.

Закрытая панель никуда не пропадает: меню ViewВид откроет её снова. Запомни три сочетания: Ctrl+Alt+L для обозревателя решений, Ctrl+\, затем E для списка ошибок и Ctrl+Alt+O для вывода. Если не помнишь, где лежит пункт меню, кнопка SearchПоиск (Ctrl+Q) найдёт его по названию.

Меню Build и Project нужны, когда сборка ведёт себя странно. Меню BuildСборка отвечает за сборку:

  • Build AllСобрать все, Ctrl+Shift+B, собирает весь проект, но компилирует только изменившиеся файлы;
  • Rebuild AllПерестроить все компилирует всё заново, а Clean AllОчистить все только удаляет результаты прошлой сборки, ничего не собирая;
  • Build app.exeСобрать app.exe, Ctrl+B, собирает одну цель app, а CompileКомпилировать компилирует только открытый файл и линкер не запускает;

Меню ProjectПроект отвечает за настройку CMake:

  • Configure CacheНастроить кэш запускает настройку заново;
  • Delete Cache and ReconfigureУдалить кэш и выполнить перенастройку удаляет кэш CMake и настраивает с нуля.

Панели Visual Studio можно расставить по-своему: перетащить мышью к другому краю окна, сложить вкладками в одну панель или вынести в отдельное окно, например на второй монитор. Так можно дать списку ошибок и выводу больше места или держать обозреватель решений справа, ближе к редактору. Сцена ниже показывает все три способа на примере обозревателя решений.

Как перетащить и закрепить панель в Visual Studio

Наведи указатель мыши на заголовок панели, полосу с надписью Solution ExplorerОбозреватель решений над деревом файлов, нажми левую кнопку мыши и, не отпуская кнопку, потяни. Панель уйдёт со своего места, и за указателем поедет полупрозрачная копия панели, а освободившееся место займёт редактор кода. Пока кнопка зажата, Visual Studio показывает значки-указатели: стрелки у краёв окна и крест из пяти квадратов над той областью окна, где сейчас указатель. Панель встанет туда, где ты отпустишь кнопку мыши над одним из значков. Нижние панели так же тащат за вкладку, например за вкладку OutputВывод.
Чтобы поставить панель к краю окна, веди указатель на стрелку у правого края. Когда указатель над стрелкой, стрелка становится синей, а синяя заливка показывает, какое место займёт панель. Отпусти кнопку мыши, и обозреватель решений встанет справа во всю высоту окна. Боковые квадраты креста работают так же, но ставят панель к краю не всего окна, а той области, над которой стоит крест.
Чтобы сложить две панели в одну, веди указатель на нижнюю панель с вкладками OutputВывод и Error ListСписок ошибок: крест переедет на нижнюю панель. Наведи указатель на квадрат в центре креста: синяя заливка закрасит всю нижнюю панель, то есть обозреватель решений встанет не рядом с нижней панелью, а внутрь. Отпусти кнопку мыши, и обозреватель решений станет третьей вкладкой рядом с Output и Error List. Между вкладками переключаются щелчком по вкладке.
Если отпустить кнопку мыши не над значком, панель останется отдельным окном поверх Visual Studio. Отдельное окно можно перетащить на второй монитор. Чтобы вернуть окно на место, где панель стояла до перетаскивания, зажми Ctrl и дважды щёлкни по заголовку окна. Если панели перепутались совсем, меню WindowОкно → Reset Window LayoutСбросить макет окна вернёт исходную раскладку.

Полезные сочетания клавиш

Эти клавиши работают в Visual Studio сразу после установки.

Редактирование кода
Клавиши Что делают
Ctrl+Z отменить последнее изменение
Ctrl+Shift+Z вернуть отменённое
Ctrl+D продублировать текущую строку
Ctrl+F / Ctrl+H найти текст в файле / найти и заменить
Alt+↑ / Alt+↓ переместить строку вверх или вниз
Ctrl+K, затем Ctrl+C закомментировать выделенные строки
Ctrl+K, затем Ctrl+U раскомментировать их
Ctrl+K, затем Ctrl+D отформатировать весь файл: выровнять отступы и пробелы
Управление Visual Studio
Клавиши Что делают
Ctrl+Shift+B собрать проект
F5 собрать и запустить с отладкой
Ctrl+F5 запустить без отладки: окно с результатом не закроется сразу
Shift+F5 остановить запущенную программу
Ctrl+Shift+F5 остановить программу и запустить заново
F9 поставить или убрать точку останова на строке с курсором
F10 выполнить строку и перейти к следующей
F12 или Ctrl + щелчок мышью перейти к определению: открыть то место, где объявлено слово под курсором
Ctrl+Shift+F искать текст во всех файлах проекта
Ctrl+Tab переключиться между открытыми файлами

Настройка в Visual Studio

В консоли ты собрал проект двумя командами. Visual Studio умеет то же самое, и настройку она запускает сама: когда ты открыл папку, ты не ввёл ни одной команды, а CMake уже отработал. Вывод CMake остался в окне OutputВывод.

Что делает Visual Studio, когда открывает CMake-проект

Output
1> CMake generation started for default configuration: 'x64-Debug'.1> Command line: "C:\WINDOWS\system32\cmd.exe" /c ""C:\Program Files\Microsoft Visual Studio\18\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe"  -G "Ninja"  -DCMAKE_BUILD_TYPE:STRING="Debug" -DCMAKE_INSTALL_PREFIX:PATH="D:\dev\MyEngine\out\install\x64-Debug" -DCMAKE_C_COMPILER:FILEPATH="C:/Program Files/Microsoft Visual Studio/18/Community/VC/Tools/MSVC/14.51.36231/bin/Hostx64/x64/cl.exe" -DCMAKE_CXX_COMPILER:FILEPATH="C:/Program Files/Microsoft Visual Studio/18/Community/VC/Tools/MSVC/14.51.36231/bin/Hostx64/x64/cl.exe"   -DCMAKE_MAKE_PROGRAM="C:\Program Files\Microsoft Visual Studio\18\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\Ninja\ninja.exe" "D:\dev\MyEngine" 2>&1"1> Working directory: D:\dev\MyEngine\out\build\x64-Debug1> [CMake] -- The CXX compiler identification is MSVC 19.51.36260.01> [CMake] -- Detecting CXX compiler ABI info1> [CMake] -- Detecting CXX compiler ABI info - done1> [CMake] -- Check for working CXX compiler: C:/Program Files/Microsoft Visual Studio/18/Community/VC/Tools/MSVC/14.51.36231/bin/Hostx64/x64/cl.exe - skipped1> [CMake] -- Detecting CXX compile features1> [CMake] -- Detecting CXX compile features - done1> [CMake] -- Configuring done (2.6s)1> [CMake] -- Generating done (0.0s)1> [CMake] CMake Warning:1> [CMake]   Manually-specified variables were not used by the project:1> [CMake]1> [CMake]     CMAKE_C_COMPILER1> [CMake]1> [CMake]1> [CMake] -- Build files have been written to: D:/dev/MyEngine/out/build/x64-Debug1> Extracted CMake variables.1> Extracted source files and headers.1> Extracted code model.1> Extracted toolchain configurations.1> Extracted includes paths.1> CMake generation finished.
Слева папка MyEngine, справа Visual Studio. Открыв папку, Visual Studio нашла CMakeLists.txt и сама запустила настройку, ту же, что мы делали командой cmake -S . -B build. Строки с [CMake] пишет CMake, остальные — Visual Studio.
Первая строка сообщает, для чего Visual Studio запускает настройку: configuration: ‘x64-Debug’. Одну и ту же программу можно собрать по-разному, например для отладки или для выпуска пользователям. Каждый такой способ сборки Visual Studio называет конфигурацией и даёт ей имя. Имя x64-Debug состоит из двух частей: x64 означает программу для 64-битной Windows, Debug — вариант с проверками и отладочными сведениями, удобный для поиска ошибок. Так как других конфигураций у тебя нет, настройка выполняется только для этой. Это же имя стоит в верхней полосе окна, рядом с кнопкой запуска .
Command line — команда, которую Visual Studio выполнила за тебя. Запускает Visual Studio свой cmake.exe, а не тот, что ты ставил отдельно: он приходит вместе с нагрузкой C++.

-G “Ninja” — другой генератор. Схема та же, что в консоли: CMake с помощью генератора пишет список дел, а сборщик его выполняет. Но генератор здесь другой, поэтому и список записан в другом формате: не app.vcxproj, а build.ninja. Выполняет файл build.ninja не MSBuild, а сборщик Ninja. Ninja тоже входит в Visual Studio, ставить его не нужно.

Почему же Visual Studio берёт Ninja, а не MSBuild, которым ты собирал проект в консоли? Сам по себе MSBuild для Visual Studio родной: когда в ней создают обычный проект C++ без CMake, она записывает файлы .slnx и .vcxproj и собирает проект именно MSBuild. Но здесь открыта папка с CMakeLists.txt, и Ninja подходит ей лучше по двум причинам:

  • Ninja проще. MSBuild работает с файлами в формате Visual Studio: описанием всего проекта в файле решения .slnx и описанием каждой цели в файле app.vcxproj. Когда Visual Studio открывает просто папку, этих файлов в ней нет, и пришлось бы создавать их. Ninja обходится одним небольшим файлом build.ninja, в котором только список команд. Ninja сразу запускает эти команды, поэтому после правки одного файла проект пересобирается быстрее.
  • Ninja есть на Linux и macOS. Один и тот же проект собирается там точно так же, как в Windows, поэтому для папки с CMakeLists.txt Visual Studio выбирает Ninja по умолчанию.
Working directory — своя папка сборки, out\build\x64-Debug. Твоя build из консоли не тронута: у проекта теперь две независимые сборки.
Дальше знакомое: CMake нашёл компилятор и проверил его пробными программами — так же, как в консоли. Строки про Windows SDK нет: с генератором Ninja его выбирает сама Visual Studio.
CMake Warning — не ошибка. Visual Studio передала компилятор C, а в CMakeLists.txt написано LANGUAGES CXX. Компилятор C не понадобился, и CMake сообщает об этом. Исправлять ничего не нужно.

Extracted… — после настройки Visual Studio спрашивает у CMake, что он узнал о проекте, и запоминает ответы. Вот что Visual Studio спросила у CMake:

  • CMake variables — настройки CMake, например выбранный компилятор;
  • source files and headers — файлы проекта: у нас main.cpp;
  • code model — модель кода. У нас одна цель, app, поэтому в списке запуска появился app.exe;
  • toolchain configurations — компилятор и его настройки;
  • includes paths — папки с файлами для #include: по ним Visual Studio автодополняет код.
CMake generation finished — настройка закончена, список дел build.ninja записан, но программы ещё нет. Сборка начнётся по кнопке в верхней полосе окна или по клавишам Ctrl+F5: Visual Studio запустит Ninja, а он выполнит список: вызовет компилятор для main.cpp, потом линкер. В папке out\build\x64-Debug появится app.exe. Цепочка та же, что в консоли, только вместо MSBuild работает Ninja.

Сборка и запуск

Output
>------ Build started: Project: CMakeLists, Configuration: Debug ------[1/2] Building CXX object CMakeFiles\app.dir\main.cpp.obj[2/2] Linking CXX executable app.exe

Это вывод Ninja. Вывод Ninja короче, чем вывод MSBuild: один шаг сборки — одна строка. В начале строки в квадратных скобках два числа: [1/2] значит «шаг 1 из 2», [2/2] — «шаг 2 из 2». Здесь шагов два: сначала компиляция, потом линковка.

  • [1/2] Building CXX object — первый шаг: Ninja запустил cl.exe, и из main.cpp получился объектный файл main.cpp.obj. Это тот же main.obj, что при сборке в консоли, только Ninja оставляет в имени .cpp.
  • [2/2] Linking CXX executable — второй шаг: link.exe соединил main.cpp.obj со стандартной библиотекой и записал готовую программу в файл app.exe.
Консоль отладки
Hello, Engine! D:\dev\MyEngine\out\build\x64-Debug\app.exe (process 27816) exited with code 0 (0x0).Press any key to close this window . . .

Программа напечатала свою строку и завершилась. Остальное дописала Visual Studio:

  • путь D:\dev\MyEngine\out\build\x64-Debug\app.exe — это другой app.exe: он из папки out, которую создала Visual Studio, а не из build\Debug, которую создала консоль;
  • exited with code 0 — тот же код возврата, что ты видел через $LASTEXITCODE;
  • окно ждёт нажатия клавиши, чтобы ты успел прочитать вывод.