Added Steam support for Windows

This commit is contained in:
Vladislav Khorev 2026-07-14 19:41:36 +03:00
parent 9da0ae1016
commit 76287dda46
2 changed files with 412 additions and 306 deletions

View File

@ -1,16 +1,19 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
project(witcher001 LANGUAGES C CXX) project(ShadowOverBishkekDemo LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Опция для интеграции Steamworks SDK (по умолчанию выключена)
option(STEAMSDK "Enable Steamworks SDK integration" OFF)
include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/ThirdParty.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/ThirdParty.cmake)
# =========================================== # ===========================================
# Основной проект witcher001 # Основной проект ShadowOverBishkekDemo
# =========================================== # ===========================================
add_executable(witcher001 add_executable(ShadowOverBishkekDemo
../src/main.cpp ../src/main.cpp
../src/Game.cpp ../src/Game.cpp
../src/Game.h ../src/Game.h
@ -104,21 +107,21 @@ add_executable(witcher001
) )
# Установка проекта по умолчанию для Visual Studio # Установка проекта по умолчанию для Visual Studio
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT witcher001) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ShadowOverBishkekDemo)
# include-пути проекта # include-пути проекта
target_include_directories(witcher001 PRIVATE target_include_directories(ShadowOverBishkekDemo PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/../src" "${CMAKE_CURRENT_SOURCE_DIR}/../src"
) )
set_target_properties(witcher001 PROPERTIES set_target_properties(ShadowOverBishkekDemo PROPERTIES
OUTPUT_NAME "witcher001" OUTPUT_NAME "ShadowOverBishkekDemo"
) )
# Определения препроцессора: # Определения препроцессора:
# PNG_ENABLED включает код PNG в TextureManager # PNG_ENABLED включает код PNG в TextureManager
# SDL_MAIN_HANDLED отключает переопределение main -> SDL_main # SDL_MAIN_HANDLED отключает переопределение main -> SDL_main
target_compile_definitions(witcher001 PRIVATE target_compile_definitions(ShadowOverBishkekDemo PRIVATE
WIN32_LEAN_AND_MEAN WIN32_LEAN_AND_MEAN
PNG_ENABLED PNG_ENABLED
SDL_MAIN_HANDLED SDL_MAIN_HANDLED
@ -127,10 +130,10 @@ target_compile_definitions(witcher001 PRIVATE
) )
# Линкуем с SDL2main, если он вообще установлен # Линкуем с SDL2main, если он вообще установлен
target_link_libraries(witcher001 PRIVATE SDL2main_external_lib) target_link_libraries(ShadowOverBishkekDemo PRIVATE SDL2main_external_lib)
# Линкуем сторонние библиотеки # Линкуем сторонние библиотеки
target_link_libraries(witcher001 PRIVATE target_link_libraries(ShadowOverBishkekDemo PRIVATE
SDL2_external_lib SDL2_external_lib
libpng_external_lib libpng_external_lib
zlib_external_lib zlib_external_lib
@ -145,7 +148,55 @@ target_link_libraries(witcher001 PRIVATE
# Линкуем OpenGL (Windows) # Линкуем OpenGL (Windows)
if(WIN32) if(WIN32)
target_link_libraries(witcher001 PRIVATE opengl32) target_link_libraries(ShadowOverBishkekDemo PRIVATE opengl32)
endif()
# ===========================================
# Интеграция Steamworks SDK (если STEAMSDK=ON)
# ===========================================
if(STEAMSDK)
message(STATUS "Steamworks SDK integration is ENABLED.")
# Макрос STEAMSDK для условной компиляции в C++ коде (#ifdef STEAMSDK)
target_compile_definitions(ShadowOverBishkekDemo PRIVATE STEAMSDK)
set(STEAM_SDK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/steamworks_sdk_164/sdk")
# Подключение заголовочных файлов Steamworks
target_include_directories(ShadowOverBishkekDemo PRIVATE
"${STEAM_SDK_DIR}/public"
"${STEAM_SDK_DIR}/public/steam"
)
# Определение разрядности сборки (x86 vs x64) для выбора версий lib и dll
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(STEAM_LIB_DIR "${STEAM_SDK_DIR}/redistributable_bin/win64")
set(STEAM_LIB_NAME "steam_api64")
set(STEAM_DLL_NAME "steam_api64.dll")
else()
set(STEAM_LIB_DIR "${STEAM_SDK_DIR}/redistributable_bin")
set(STEAM_LIB_NAME "steam_api")
set(STEAM_DLL_NAME "steam_api.dll")
endif()
# Поиск библиотеки линковки (.lib)
find_library(STEAM_LIBRARY
NAMES ${STEAM_LIB_NAME}
PATHS ${STEAM_LIB_DIR}
NO_DEFAULT_PATH
)
if(STEAM_LIBRARY)
target_link_libraries(ShadowOverBishkekDemo PRIVATE ${STEAM_LIBRARY})
message(STATUS "Found Steam API library: ${STEAM_LIBRARY}")
else()
message(FATAL_ERROR "Steam API library (${STEAM_LIB_NAME}) NOT found in ${STEAM_LIB_DIR}!")
endif()
# Генерация временного файла steam_appid.txt с ID демо-версии (4945840)
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/steam_appid.txt" "4945840")
else()
message(STATUS "Steamworks SDK integration is DISABLED.")
endif() endif()
# =========================================== # ===========================================
@ -155,19 +206,19 @@ if (WIN32)
# SDL2: в Debug - SDL2d.dll, в Release - SDL2.dll # SDL2: в Debug - SDL2d.dll, в Release - SDL2.dll
set(SDL2_DLL_SRC "$<IF:$<CONFIG:Debug>,${SDL2_INSTALL_DIR}/bin/SDL2d.dll,${SDL2_INSTALL_DIR}/bin/SDL2.dll>") set(SDL2_DLL_SRC "$<IF:$<CONFIG:Debug>,${SDL2_INSTALL_DIR}/bin/SDL2d.dll,${SDL2_INSTALL_DIR}/bin/SDL2.dll>")
set(SDL2_DLL_DST "$<IF:$<CONFIG:Debug>,$<TARGET_FILE_DIR:witcher001>/SDL2d.dll,$<TARGET_FILE_DIR:witcher001>/SDL2.dll>") set(SDL2_DLL_DST "$<IF:$<CONFIG:Debug>,$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/SDL2d.dll,$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/SDL2.dll>")
set(LIBZIP_DLL_SRC "$<IF:$<CONFIG:Debug>,${LIBZIP_BASE_DIR}-Debug/bin/zip.dll,${LIBZIP_BASE_DIR}-Release/bin/zip.dll>") set(LIBZIP_DLL_SRC "$<IF:$<CONFIG:Debug>,${LIBZIP_BASE_DIR}-Debug/bin/zip.dll,${LIBZIP_BASE_DIR}-Release/bin/zip.dll>")
set(ZLIB_DLL_SRC "$<IF:$<CONFIG:Debug>,${ZLIB_INSTALL_DIR}/bin/zd.dll,${ZLIB_INSTALL_DIR}/bin/z.dll>") set(ZLIB_DLL_SRC "$<IF:$<CONFIG:Debug>,${ZLIB_INSTALL_DIR}/bin/zd.dll,${ZLIB_INSTALL_DIR}/bin/z.dll>")
set(ZLIB_DLL_DST "$<IF:$<CONFIG:Debug>,$<TARGET_FILE_DIR:witcher001>/zd.dll,$<TARGET_FILE_DIR:witcher001>/z.dll>") set(ZLIB_DLL_DST "$<IF:$<CONFIG:Debug>,$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/zd.dll,$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/z.dll>")
set(SDL2TTF_DLL_SRC "$<IF:$<CONFIG:Debug>,${SDL2TTF_BASE_DIR}-Debug/bin/SDL2_ttfd.dll,${SDL2TTF_BASE_DIR}-Release/bin/SDL2_ttf.dll>") set(SDL2TTF_DLL_SRC "$<IF:$<CONFIG:Debug>,${SDL2TTF_BASE_DIR}-Debug/bin/SDL2_ttfd.dll,${SDL2TTF_BASE_DIR}-Release/bin/SDL2_ttf.dll>")
set(SDL2MIXER_DLL_SRC "$<IF:$<CONFIG:Debug>,${SDL2MIXER_BASE_DIR}-Debug/bin/SDL2_mixerd.dll,${SDL2MIXER_BASE_DIR}-Release/bin/SDL2_mixer.dll>") set(SDL2MIXER_DLL_SRC "$<IF:$<CONFIG:Debug>,${SDL2MIXER_BASE_DIR}-Debug/bin/SDL2_mixerd.dll,${SDL2MIXER_BASE_DIR}-Release/bin/SDL2_mixer.dll>")
add_custom_command(TARGET witcher001 POST_BUILD add_custom_command(TARGET ShadowOverBishkekDemo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Copying DLLs to output folder..." COMMAND ${CMAKE_COMMAND} -E echo "Copying DLLs to output folder..."
# Копируем SDL2 (целевое имя всегда SDL2.dll) # Копируем SDL2 (целевое имя всегда SDL2.dll)
@ -178,7 +229,7 @@ add_custom_command(TARGET witcher001 POST_BUILD
# Копируем LIBZIP (целевое имя всегда zip.dll) # Копируем LIBZIP (целевое имя всегда zip.dll)
COMMAND ${CMAKE_COMMAND} -E copy_if_different COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${LIBZIP_DLL_SRC}" "${LIBZIP_DLL_SRC}"
"$<TARGET_FILE_DIR:witcher001>/zip.dll" "$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/zip.dll"
COMMAND ${CMAKE_COMMAND} -E copy_if_different COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${ZLIB_DLL_SRC}" "${ZLIB_DLL_SRC}"
@ -186,15 +237,31 @@ add_custom_command(TARGET witcher001 POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${SDL2TTF_DLL_SRC}" "${SDL2TTF_DLL_SRC}"
"$<TARGET_FILE_DIR:witcher001>" "$<TARGET_FILE_DIR:ShadowOverBishkekDemo>"
COMMAND ${CMAKE_COMMAND} -E copy_if_different COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/SDL_mixer-release-2.8.0/install-$<CONFIG>/bin/SDL2_mixer$<$<CONFIG:Debug>:d>.dll" "${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/SDL_mixer-release-2.8.0/install-$<CONFIG>/bin/SDL2_mixer$<$<CONFIG:Debug>:d>.dll"
"$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/SDL2_mixer$<$<CONFIG:Debug>:d>.dll"
# This is only for profiling:
#"${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/SDL_mixer-release-2.8.0/install-Release/bin/SDL2_mixer$<$<CONFIG:Debug>:d>.dll"
"$<TARGET_FILE_DIR:witcher001>/SDL2_mixer$<$<CONFIG:Debug>:d>.dll"
) )
# Если включен Steam, добавляем шаги копирования DLL и appid в этот же POST_BUILD
if(STEAMSDK)
add_custom_command(TARGET ShadowOverBishkekDemo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Copying Steam SDK components..."
# Копирование dll к исполняемому файлу
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${STEAM_LIB_DIR}/${STEAM_DLL_NAME}"
"$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/${STEAM_DLL_NAME}"
# Копирование steam_appid.txt к исполняемому файлу
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_CURRENT_BINARY_DIR}/steam_appid.txt"
"$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/steam_appid.txt"
# Дублируем steam_appid.txt в корень сборки (полезно при отладке прямо из MSVC)
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CMAKE_CURRENT_BINARY_DIR}/steam_appid.txt"
"${CMAKE_BINARY_DIR}/steam_appid.txt"
)
endif()
endif() endif()
# =========================================== # ===========================================
@ -209,12 +276,12 @@ set(RUNTIME_RESOURCE_DIRS
# Копируем ресурсы и шейдеры в папку exe и в корень build/ # Копируем ресурсы и шейдеры в папку exe и в корень build/
foreach(resdir IN LISTS RUNTIME_RESOURCE_DIRS) foreach(resdir IN LISTS RUNTIME_RESOURCE_DIRS)
add_custom_command(TARGET witcher001 POST_BUILD add_custom_command(TARGET ShadowOverBishkekDemo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Copying ${resdir} to runtime folders..." COMMAND ${CMAKE_COMMAND} -E echo "Copying ${resdir} to runtime folders..."
# 1) туда, где лежит exe (build/Debug, build/Release и т.п.) # 1) туда, где лежит exe (build/Debug, build/Release и т.п.)
COMMAND ${CMAKE_COMMAND} -E copy_directory COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_SOURCE_DIR}/../${resdir}" "${CMAKE_SOURCE_DIR}/../${resdir}"
"$<TARGET_FILE_DIR:witcher001>/${resdir}" "$<TARGET_FILE_DIR:ShadowOverBishkekDemo>/${resdir}"
# 2) в корень build, если захочешь запускать из этой папки # 2) в корень build, если захочешь запускать из этой папки
COMMAND ${CMAKE_COMMAND} -E copy_directory COMMAND ${CMAKE_COMMAND} -E copy_directory
"${CMAKE_SOURCE_DIR}/../${resdir}" "${CMAKE_SOURCE_DIR}/../${resdir}"

View File

@ -11,29 +11,23 @@
#include <emscripten/html5.h> #include <emscripten/html5.h>
#endif #endif
#ifdef STEAMSDK
// For Emscripten the game is heap-allocated so it can be destroyed and #include "steam_api.h"
// re-created when the WebGL context is lost and restored (e.g. fullscreen).
// For Android and Desktop a plain global value is used (no context loss).
#ifdef EMSCRIPTEN
ZL::Game* g_game = nullptr;
//static SDL_Window* win_x = nullptr;
static SDL_GLContext glContext_x; // Не указатель, а сам объект (который суть void*)
#else
ZL::Game game;
#endif #endif
#ifdef EMSCRIPTEN #if defined(EMSCRIPTEN)
static SDL_GLContext glContext_x;
#endif
// Единый указатель на объект игры для всех платформ
ZL::Game* g_game = nullptr;
void MainLoop() { void MainLoop() {
g_game->update(); g_game->update();
} }
#else
void MainLoop() {
game.update();
}
#endif
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
@ -212,7 +206,8 @@ extern "C" int SDL_main(int argc, char* argv[]) {
// Настройка игры // Настройка игры
try { try {
game.setup(); game.setup();
} catch (const std::exception &e) { }
catch (const std::exception& e) {
__android_log_print(ANDROID_LOG_ERROR, "Game", "Game setup failed: %s", e.what()); __android_log_print(ANDROID_LOG_ERROR, "Game", "Game setup failed: %s", e.what());
return 1; return 1;
} }
@ -231,12 +226,24 @@ extern "C" int SDL_main(int argc, char* argv[]) {
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__linux__) #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__linux__)
int main(int argc, char *argv[]) { int main(int argc, char* argv[]) {
SDL_GLContext ctx = nullptr;
try try
{ {
#ifdef STEAMSDK
// Передаем явный App ID демо-версии
if (SteamAPI_RestartAppIfNecessary(4945840)) {
return 0;
}
if (!SteamAPI_Init()) {
std::cerr << "SteamAPI_Init() failed! Steam client might not be running.\n";
return 1;
}
#endif
SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitorv2"); SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitorv2");
//SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "1");
constexpr int CONST_WIDTH = ZL::Environment::CONST_DEFAULT_WIDTH; constexpr int CONST_WIDTH = ZL::Environment::CONST_DEFAULT_WIDTH;
constexpr int CONST_HEIGHT = ZL::Environment::CONST_DEFAULT_HEIGHT; constexpr int CONST_HEIGHT = ZL::Environment::CONST_DEFAULT_HEIGHT;
@ -256,6 +263,7 @@ int main(int argc, char *argv[]) {
// window, so it opens directly in the right mode instead of flashing // window, so it opens directly in the right mode instead of flashing
// windowed -> fullscreen once settings are loaded later during setupPart2(). // windowed -> fullscreen once settings are loaded later during setupPart2().
{ {
const std::string settingsContent = ZL::readSavedTextFile("settings.json"); const std::string settingsContent = ZL::readSavedTextFile("settings.json");
if (!settingsContent.empty()) { if (!settingsContent.empty()) {
try { try {
@ -263,22 +271,24 @@ int main(int argc, char *argv[]) {
if (settingsRoot.contains("fullscreen")) { if (settingsRoot.contains("fullscreen")) {
ZL::Environment::isFullscreen = settingsRoot["fullscreen"].get<bool>(); ZL::Environment::isFullscreen = settingsRoot["fullscreen"].get<bool>();
} }
if (settingsRoot.contains("isHighDPIEnabled")) { if (settingsRoot.contains("isHighDPIEnabled")) {
ZL::Environment::isHighDPIEnabled = settingsRoot["isHighDPIEnabled"].get<bool>(); ZL::Environment::isHighDPIEnabled = settingsRoot["isHighDPIEnabled"].get<bool>();
} }
} catch (const std::exception& e) {
}
catch (const std::exception& e) {
std::cerr << "[settings] Failed to parse settings.json: " << e.what() << std::endl; std::cerr << "[settings] Failed to parse settings.json: " << e.what() << std::endl;
} }
} }
} }
Uint32 windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE; Uint32 windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE;
if (ZL::Environment::isFullscreen) { if (ZL::Environment::isFullscreen) {
windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
} }
//ZL::Environment::isFullscreen = false;
SDL_DisplayMode dm; SDL_DisplayMode dm;
if (SDL_GetCurrentDisplayMode(0, &dm) == 0) { if (SDL_GetCurrentDisplayMode(0, &dm) == 0) {
std::cout << "Desktop resolution: " << dm.w << "x" << dm.h << " @ " << dm.refresh_rate << "Hz\n"; std::cout << "Desktop resolution: " << dm.w << "x" << dm.h << " @ " << dm.refresh_rate << "Hz\n";
@ -290,35 +300,29 @@ int main(int argc, char *argv[]) {
int windowWidth = ZL::Environment::width; int windowWidth = ZL::Environment::width;
int windowHeight = ZL::Environment::height; int windowHeight = ZL::Environment::height;
//In case if the game is launched on a small screen
if (dm.w <= 1280 && dm.h <= 720) { if (dm.w <= 1280 && dm.h <= 720) {
windowWidth = round(dm.w / 1.5); windowWidth = round(dm.w / 1.5);
windowHeight = round(dm.h / 1.5); windowHeight = round(dm.h / 1.5);
} }
ZL::Environment::window = SDL_CreateWindow( ZL::Environment::window = SDL_CreateWindow(
"Shadow Over Bishkek", "Shadow Over Bishkek Demo",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
windowWidth, windowHeight, windowWidth, windowHeight,
windowFlags windowFlags
); );
SDL_GLContext ctx = SDL_GL_CreateContext(ZL::Environment::window); ctx = SDL_GL_CreateContext(ZL::Environment::window);
SDL_GL_MakeCurrent(ZL::Environment::window, ctx); SDL_GL_MakeCurrent(ZL::Environment::window, ctx);
// On HiDPI displays the drawable (physical) size is larger than the if (ZL::Environment::isHighDPIEnabled) {
// logical window size. Use the drawable size so the GL framebuffer
// covers every physical pixel and the image stays sharp.
if (ZL::Environment::isHighDPIEnabled)
{
int drawW, drawH; int drawW, drawH;
SDL_GL_GetDrawableSize(ZL::Environment::window, &drawW, &drawH); SDL_GL_GetDrawableSize(ZL::Environment::window, &drawW, &drawH);
ZL::Environment::width = drawW; ZL::Environment::width = drawW;
ZL::Environment::height = drawH; ZL::Environment::height = drawH;
std::cout << "HiDPI enabled, drawable size: " << drawW << "x" << drawH << std::endl; std::cout << "HiDPI enabled, drawable size: " << drawW << "x" << drawH << std::endl;
} }
else else {
{
int winW, winH; int winW, winH;
SDL_GetWindowSize(ZL::Environment::window, &winW, &winH); SDL_GetWindowSize(ZL::Environment::window, &winW, &winH);
ZL::Environment::width = winW; ZL::Environment::width = winW;
@ -326,18 +330,53 @@ int main(int argc, char *argv[]) {
std::cout << "HiDPI disabled, window size: " << winW << "x" << winH << std::endl; std::cout << "HiDPI disabled, window size: " << winW << "x" << winH << std::endl;
} }
game.setup(); // Динамическое создание объекта игры
g_game = new ZL::Game();
g_game->setup();
while (!game.shouldExit()) { while (!g_game->shouldExit()) {
game.update(); g_game->update();
SDL_Delay(2); SDL_Delay(2);
#ifdef STEAMSDK
SteamAPI_RunCallbacks();
#endif
} }
// Явное уничтожение объекта игры ДО завершения работы Steamworks
delete g_game;
g_game = nullptr;
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {
std::cout << e.what() << std::endl; std::cerr << "Exception caught: " << e.what() << std::endl;
if (g_game) {
delete g_game;
g_game = nullptr;
}
}
catch (...)
{
std::cerr << "Unknown exception happened: " << std::endl;
if (g_game) {
delete g_game;
g_game = nullptr;
}
} }
#ifdef STEAMSDK
// Деинициализация Steamworks происходит только тогда, когда все объекты игры гарантированно уничтожены
SteamAPI_Shutdown();
#endif
// Очистка ресурсов SDL
if (ctx) {
SDL_GL_DeleteContext(ctx);
}
if (ZL::Environment::window) {
SDL_DestroyWindow(ZL::Environment::window);
}
SDL_Quit();
return 0; return 0;
} }
#endif #endif