C++ 目前还没有一个标准的 C++ 依赖包管理器,传统上都是手动去下载源码编译(经典的例如 make
),或者直接下载预编译好的库文件(例如没有开源的)和头文件。之后在项目里配置对应的头文件路径和库路径。这个过程非常繁琐,而且枯燥。对于 Windows 上的用户,其实可以考虑使用 NuGet 安装所需要的依赖。NuGet 会自动下载依赖并配置好,省去很多繁琐的过程。
什么是 NuGet
简单来讲,它是一个包管理的平台。类似,Python 的 Pip、JavaScript 的 npm。
创建一个项目
Visual Studio 2022
平台工具集:v143
NuGet
在 【工具】 中可以打开 NuGet。
引入 SDL2 库
搜索 SDL 并添加到项目中。
现在 SDL 库就引入到项目中了,就是这么简单。可以直接引用头文件使用 SDL 库。接下来,尝试使用一下 SDL。
Game 基础框架
实现一个基础的游戏框架:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #pragma once #include <SDL.h> class Game { public : Game (); bool Initialize () ; void RunLoop () ; void Shutdown () ; private : void ProcessInput () ; void UpdateGame () ; void GenerateOutput () ; SDL_Window* mWindow; bool mIsRunning; };
对应的具体实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 #include "Game.h" Game::Game () :mWindow (nullptr ) , mIsRunning (true ) { } bool Game::Initialize () { int sdlResult = SDL_Init (SDL_INIT_VIDEO); if (sdlResult != 0 ) { SDL_Log ("Unable to initialize SDL: %s" , SDL_GetError ()); return false ; } mWindow = SDL_CreateWindow ( "OpenGLNu" , 100 , 100 , 1024 , 768 , SDL_WINDOW_OPENGL ); if (!mWindow) { SDL_Log ("Failed to create window: %s" , SDL_GetError ()); return false ; } return true ; } void Game::RunLoop () { while (mIsRunning) { ProcessInput (); UpdateGame (); GenerateOutput (); } } void Game::Shutdown () { SDL_DestroyWindow (mWindow); SDL_Quit (); } void Game::ProcessInput () { SDL_Event event; while (SDL_PollEvent (&event)) { switch (event.type) { case SDL_QUIT: mIsRunning = false ; break ; default : break ; } } const Uint8* state = SDL_GetKeyboardState (nullptr ); if (state[SDL_SCANCODE_ESCAPE]) { mIsRunning = false ; } } void Game::UpdateGame () {} void Game::GenerateOutput () {}
创建一个 Main.cpp
调用 Game
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> #include "Game.h" int main (int argc, char * argv[]) { Game game; if (game.Initialize ()) { game.RunLoop (); } game.Shutdown (); return 0 ; }
可以直接编译运行。
OpenGL
上面的代码中,SDL_WINDOW_OPENGL
属性指定了使用 OpenGL,下一步就是要创建一个 OpenGL 的上下文(OpenGL 被设计成一个状态机)。上下文可以看成是 OpenGL 的 World(世界),包含了色彩缓冲区、加载的图像或者模型等等。
1 2 SDL_GLContext mContext;
Game::Initialize()
设置 OpenGL 参数,并初始化上下文:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 SDL_GL_SetAttribute (SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute (SDL_GL_CONTEXT_MAJOR_VERSION, 4 ); SDL_GL_SetAttribute (SDL_GL_CONTEXT_MINOR_VERSION, 2 ); SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 8 ); SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 8 ); SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 8 ); SDL_GL_SetAttribute (SDL_GL_ALPHA_SIZE, 8 ); SDL_GL_SetAttribute (SDL_GL_DOUBLEBUFFER, 1 ); SDL_GL_SetAttribute (SDL_GL_ACCELERATED_VISUAL, 1 ); mContext = SDL_GL_CreateContext (mWindow);
OpenGL 支持向后兼容,需要手动查询所需要的扩展,这个过程也很枯燥。为了简化这个过程,通常会使用 OpenGL Extension Wrangler Library(GLEW)。GLEW 可以自动初始化当前 OpenGL 上下文所支持的所有扩展函数。
在 NuGet 中搜索 GLEW
并安装到项目:
可以看到有两个下载比较多的 GLEW,从发布日期上看,glew.v140 会比较新,但名称绑定了 v140。这里还是选择了 GLEW
安装完成之后,引入 GLEW 的头文件:
要初始化 GLEW,要在创建上下文之后,添加下面的代码:
1 2 3 4 5 6 glewExperimental = GL_TRUE; if (glewInit () != GLEW_OK){ SDL_Log ("Failed to initialize GLEW." ); return false ; }
如果在 v143 下尝试编译,会发现链接失败了,因为依赖包指定了版本
其实 C++ API 还是相对比较稳定的,我觉得绑定平台反而看起来不是一个很好设计。
直接复制修改
复制了 v110
,修改文件夹名为 v143
。修改 glew.targets
文件,将 v110
替换为 v143
。当然,这种方法比较简单粗暴,不是特别提倡。
NuGet 中,动态运行库 .dll
是单独列出来的,同样拷贝 v110
文件夹作为 v143
,修改 glew.redist.targets
,将 v110
替换成为 v143
。
这样就可以运行了。
然而,对于 Debug 版本,由于目前我用的是 V143
,并没有 v110
的工具集,会发现运行的时候少了一个 msvcr110d.dll
。这个问题比较好解决,直接去 下载 一个对应的 dll
,拷贝 Debug 程序的目录下就可以了(不推荐拷到 System 下面)。
这样,无论是 Debug 版本还是 Release 都可以正常运行了。
OpenGL 的依赖
OpenGL 还需要使用到系统自带的 OpenGL32.lib,否则下面代码会出现链接错误。
1 2 3 4 5 6 7 8 9 10 11 12 void Game::GenerateOutput () { glClearColor (0.86f , 0.86f , 0.86f , 1.0f ); glClear (GL_COLOR_BUFFER_BIT); SDL_GL_SwapWindow (mWindow); }
由于是系统自带,只需要在项目设置中的附加依赖项直接加入 OpenGL32.lib
:
最终
这就意味着 OpenGL 环境搭建好并正常工作了。
小结
NuGet 的确省去了很多繁琐的过程,但很明显,很多包的更新赶不上 Visual Studio 的工具集,而 NuGet 大概是为了可重入才绑定了对应的工具集,导致有时候无法找到对应版本的库。NuGet 显然并不完美,无法做到全自动,但相对于传统的方式,还是大大简化了。不过,这种方式对于源码版本的控制显然会更难,推崇源码构建的开发者自然不会选择用这种方式。自主可控和方便快捷之间永远是需要权衡的。对于初学者,显然使用 NuGet 搭建环境还是比较方便的。