C++ 目前还没有一个标准的 C++ 依赖包管理器,传统上都是手动去下载源码编译(经典的例如 make
),或者直接下载预编译好的库文件(例如没有开源的)和头文件。之后在项目里配置对应的头文件路径和库路径。这个过程非常繁琐,而且枯燥。对于 Windows 上的用户,其实可以考虑使用 NuGet 安装所需要的依赖。NuGet 会自动下载依赖并配置好,省去很多繁琐的过程。
简单来讲,它是一个包管理的平台。类似,Python 的 Pip、JavaScript 的 npm。
在 【工具】 中可以打开 NuGet。
搜索 SDL 并添加到项目中。
现在 SDL 库就引入到项目中了,就是这么简单。可以直接引用头文件使用 SDL 库。接下来,尝试使用一下 SDL。
关于这部分内容,推荐参考:Xcode与C++之游戏开发:创建环境
实现一个基础的游戏框架:
// Game.h
#pragma once
#include
// Game class
class Game
{
public:
Game();
// 初始化游戏
bool Initialize();
// 运行游戏循环直到游戏结束
void RunLoop();
// 关闭游戏
void Shutdown();
private:
// 处理进程输入
void ProcessInput();
// 更新游戏
void UpdateGame();
// 生成输出
void GenerateOutput();
// 通过 SDL 创建窗体
SDL_Window* mWindow;
// 继续运行
bool mIsRunning;
};
对应的具体实现:
// Game.cpp
#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;
// 有 event 在队列就一直循环
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
mIsRunning = false;
break;
default:
break;
}
}
// 获取键盘的状态
const Uint8* state = SDL_GetKeyboardState(nullptr);
// 如果按了 Esc,结束循环
if (state[SDL_SCANCODE_ESCAPE])
{
mIsRunning = false;
}
}
void Game::UpdateGame()
{
}
void Game::GenerateOutput()
{
}
创建一个 Main.cpp
调用 Game
:
#include
#include "Game.h"
int main(int argc, char* argv[])
{
Game game;
if (game.Initialize())
{
game.RunLoop();
}
game.Shutdown();
return 0;
}
可以直接编译运行。
上面的代码中,SDL_WINDOW_OPENGL
属性指定了使用 OpenGL,下一步就是要创建一个 OpenGL 的上下文(OpenGL 被设计成一个状态机)。上下文可以看成是 OpenGL 的 World(世界),包含了色彩缓冲区、加载的图像或者模型等等。
// Game.h
SDL_GLContext mContext;
Game::Initialize()
设置 OpenGL 参数,并初始化上下文:
// Game.cpp
/// 设置 OpenGL 参数
// core OpenGL
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
// 指定版本 4.2
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
// RGBA
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 的头文件:
// Game.h
#include
要初始化 GLEW,要在创建上下文之后,添加下面的代码:
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 还需要使用到系统自带的 OpenGL32.lib,否则下面代码会出现链接错误。
void Game::GenerateOutput()
{
// 灰色
glClearColor(0.86f, 0.86f, 0.86f, 1.0f);
// 清除颜色缓冲区
glClear(GL_COLOR_BUFFER_BIT);
// TODO: 绘制场景
// 交换缓冲区
SDL_GL_SwapWindow(mWindow);
}
由于是系统自带,只需要在项目设置中的附加依赖项直接加入 OpenGL32.lib
:
这就意味着 OpenGL 环境搭建好并正常工作了。
推荐参考这篇继续开发 OpenGL:Xcode与C++之游戏开发:OpenGL
NuGet 的确省去了很多繁琐的过程,但很明显,很多包的更新赶不上 Visual Studio 的工具集,而 NuGet 大概是为了可重入才绑定了对应的工具集,导致有时候无法找到对应版本的库。NuGet 显然并不完美,无法做到全自动,但相对于传统的方式,还是大大简化了。不过,这种方式对于源码版本的控制显然会更难,推崇源码构建的开发者自然不会选择用这种方式。自主可控和方便快捷之间永远是需要权衡的。对于初学者,显然使用 NuGet 搭建环境还是比较方便的。