Основы организации: файловая структура, CMake и разделение на модули
Основы организации: файловая структура, CMake и разделение на модули
Добро пожаловать в курс «Архитектура C++ проектов: Создание редактора уровней на Raylib». Вы здесь, потому что хотите перестать писать «спагетти-код» и научиться создавать инструменты, которые не стыдно показать коллегам и удобно поддерживать самому.
Многие начинающие разработчики сталкиваются с одной и той же проблемой: проект начинается с одного файла main.cpp. Затем появляется player.cpp, потом enemy.cpp, и внезапно папка проекта превращается в свалку из десятков файлов, в которых невозможно ориентироваться. Когда вы пытаетесь написать редактор уровней — инструмент сложный и требующий строгой логики — такой хаос становится фатальным.
В этой статье мы заложим фундамент вашего будущего редактора. Мы не напишем ни строчки игровой логики сегодня, но мы сделаем кое-что более важное: создадим профессиональный каркас проекта.
Почему структура важна?
Представьте, что вы строите дом. Если вы свалите кирпичи, доски и цемент в одну кучу, дом построить не получится. В программировании то же самое. Хорошая структура проекта обеспечивает:
- Масштабируемость: Добавление новых фич не ломает старые.
- Читаемость: Вы (и другие) знаете, где искать конкретный код.
- Переносимость: Проект легко собрать на Windows, Linux или macOS.
Стандартная файловая структура C++ проекта
В индустрии сложился определенный стандарт организации папок. Мы будем придерживаться современной структуры, разделяющей исходный код, заголовки и сторонние библиотеки.
Разберем назначение каждой директории:
src/(Source): Здесь лежат файлы реализации с расширением.cpp. Это «мясо» вашего проекта.include/(Include): Здесь хранятся заголовочные файлы.hили.hpp. Это публичный интерфейс ваших модулей.external/илиlibs/: Место для сторонних библиотек (в нашем случае здесь будет жить Raylib).assets/: Ресурсы игры — текстуры, звуки, шрифты и файлы конфигурации.build/: Папка для артефактов сборки. Мы никогда не коммитим её в git, так как она содержит временные файлы компилятора.
Хорошая архитектура — это не когда нечего добавить, а когда нечего убрать, и при этом всё лежит на своих местах.
Система сборки: Почему CMake?
Вы упомянули, что хотите «нормальную структуру». В мире C++ это практически синоним использования CMake. Это мета-система сборки, которая генерирует инструкции для нативной системы сборки вашей ОС (Makefile для Linux, Solution для Visual Studio, Project для Xcode).
Базовый CMakeLists.txt
В корне вашего проекта всегда должен лежать файл CMakeLists.txt. Вот минимальный шаблон для нашего редактора:
cmake_minimum_required(VERSION 3.14)
# Название проекта и язык
project(RaylibLevelEditor VERSION 1.0 LANGUAGES CXX)
# Указываем стандарт C++ (C++17 — золотая середина для геймдева сейчас)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Добавляем папку с исходниками
add_subdirectory(src)
# Создаем исполняемый файл
add_executable(RaylibLevelEditor src/main.cpp)
Однако, просто создать файл недостаточно. Нам нужно подключить библиотеку Raylib.
Интеграция Raylib через CMake
Есть много способов подключить библиотеку: скачать бинарники, использовать git submodules или пакетные менеджеры (Conan, Vcpkg). Для учебного проекта самым надежным и простым способом является FetchContent. Это модуль CMake, который сам скачает исходники Raylib и подключит их к проекту при первой сборке.
Обновим наш CMakeLists.txt:
cmake_minimum_required(VERSION 3.14)
project(RaylibLevelEditor VERSION 1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# --- Подключение Raylib ---
include(FetchContent)
FetchContent_Declare(
raylib
GIT_REPOSITORY https://github.com/raysan5/raylib.git
GIT_TAG master
)
FetchContent_MakeAvailable(raylib)
# --------------------------
# Настройка путей к заголовкам
include_directories(include)
# Собираем все .cpp файлы из папки src
file(GLOB_RECURSE SOURCES "src/*.cpp")
add_executable(RaylibLevelEditor ${SOURCES})
# Линкуем (связываем) нашу программу с raylib
target_link_libraries(RaylibLevelEditor PRIVATE raylib)
Что здесь происходит?
FetchContent_Declare: Мы говорим CMake, откуда скачать Raylib.FetchContent_MakeAvailable: CMake скачивает, настраивает и готовит библиотеку к использованию.target_link_libraries: Мы говорим компилятору: «Когда будешь собирать наш редактор, используй код из Raylib».
Разделение на модули: Отказ от монолита
Главная ошибка при создании редакторов — писать код игры и код редактора вперемешку. Редактор уровней — это программа, которая использует движок вашей игры, но также имеет свой собственный интерфейс (UI).
Мы разделим проект на три логических слоя:
- Core (Ядро): Обертки над Raylib, управление окном, входными данными (Input), ресурсами. Этот код ничего не знает об игре.
- Game (Игра): Логика сущностей, физика, рендеринг игрового мира. Этот код использует Core.
- Editor (Редактор): UI инструменты, панели выбора тайлов, сетка, сохранение/загрузка. Этот код управляет Game и использует Core.
Пример организации кода
Вместо того чтобы писать всё в main.cpp, мы создадим класс приложения.
Файл include/app.h:
#pragma once
class App {
public:
void Run();
private:
void Update();
void Draw();
void Load();
};
Файл src/app.cpp:
#include "app.h"
#include "raylib.h"
void App::Run() {
InitWindow(800, 600, "Level Editor");
SetTargetFPS(60);
Load();
while (!WindowShouldClose()) {
Update();
Draw();
}
CloseWindow();
}
void App::Load() {
// Загрузка ресурсов
}
void App::Update() {
// Логика обновления кадров
}
void App::Draw() {
BeginDrawing();
ClearBackground(RAYWHITE);
DrawText("Editor Initialized!", 10, 10, 20, DARKGRAY);
EndDrawing();
}
И, наконец, наш src/main.cpp становится предельно чистым:
#include "app.h"
int main() {
App app;
app.Run();
return 0;
}
Зачем нужен #pragma once?
В примере выше вы видели директиву #pragma once. Это страж включения (header guard). Если вы случайно подключите один и тот же .h файл в нескольких местах (что неизбежно в больших проектах), компилятор без этой директивы попытается объявить одни и те же классы дважды и выдаст ошибку. #pragma once гарантирует, что файл будет обработан компилятором только один раз.
Практическое задание: Сборка проекта
Теперь ваша задача — воспроизвести эту структуру на своем компьютере.
- Создайте папку
RaylibEditor. - Внутри создайте подпапки
src,include,assets. - Создайте файл
CMakeLists.txtс кодом, приведенным выше (версия с FetchContent). - Создайте
src/main.cpp,src/app.cppиinclude/app.h. - Запустите конфигурацию CMake:
- Создайте папку
build. - Откройте терминал в папке
build. - Выполните
cmake ... - После завершения выполните
cmake --build .(или откройте сгенерированный проект в IDE).
- Создайте папку
Если вы увидели окно с надписью "Editor Initialized!" — поздравляю! Вы создали профессиональную заготовку для вашего будущего редактора уровней.
В следующей статье мы займемся наполнением слоя Core и создадим надежную обертку для работы с окном и входными данными, чтобы наш редактор мог корректно обрабатывать события мыши и клавиатуры.