《世嘉新人培训教材—游戏开发》作为经典的游戏开发教程,提供了相关样例代码供我们进行开发使用。但是该样例是基于VS进行编写构建的,而本人日常喜欢CLion进行C/C++开发,于是准备使用cmake重新组织该书籍的样例项目:2DGraphics1中的NimotsuKunBox和drawPixels。当然,这个过程不仅是移植,也是对cmake组织项目一个深入的实践。

对现有样例项目的认识与构建

样例代码结构

在进行cmake迁移前,有必要对现有的VS体系的代码结构进行了解。

GameLib(样例根目录)

该目录下主要存放了:

  1. 各个样例会使用的工具静态库/头文件

  2. src:样例源码;

  3. tools:工具二进制程序。

GameLib/src目录

该目录下主要存放:

  1. 各种数字+下划线开头的文件夹:书中使用到的各种样例工程;

  2. GameLibs文件夹:生成GameLib根目录中的静态库/头文件的源码。

GameLib/src/GameLibs目录

该目录主要存放:

  1. GameLib根目录下各个被样例项目使用的静态库/头文件的源码
  2. Modules:其他静态库项目的依赖静态库。

使用VS构建样例项目静态库

在GameLib下,本书的译者已经帮我们编写了一个基本的指南:

编译顺序

在系统环境变量中添加 GAME_LIB_DIR 值为源码工程的根目录

注意要重启visual studio

①先编译类库的Modules

src\GameLibs\Modules\Modules.sln

②再编译各个小功能的类库

比如 src\GameLibs\2DActionGame\GameLib.sln

③最后编译游戏本身

比如 src\01_FirstGame\FirstGame.sln

为什么要按照这样的顺序呢?请看下面这个例子

譬如对src\02_2DGraphics1\2DGraphics1.sln 来说,

首先用vs打开它,右键点击 drawPixels查看属性

在链接器 的附加库目录一栏可以看到 $(GAME_LIB_DIR)\2DGraphics1\lib;%(AdditionalLibraryDirectories)

这意味着它需要在2DGraphics1\lib中查找某些类库,

具体要用什么类库呢?可以点击 链接器 -> 输入 ,看到附加依赖项中有 GameLib_d.lib;%(AdditionalDependencies)

如何才能生成这个 GameLib_d.lib呢?

打开 src\GameLibs\2DGraphics1\GameLib.sln 编译即可

但是,通过右键Framework属性, 查看库管理器 的附加依赖项可以看到 Modules_d.lib

这就要求必须先编译好 Modules工程

于是打开 src\GameLibs\Modules\Modules.sln 编译即可。

这里有两个关键点需要牢记:

  1. 需要配置环境变量GAME_LIB_DIR,原因在于后续即将编译的各个样例,都会使用$(GAME_LIB_DIR)然后找到对应的类库;
  2. 编译有一个顺序:先核心静态库:Modules;然后各个样例需要的GameLib;最后是样例。

编译核心的Modules

加载$(GAME_LIB_DIR)\src\GameLibs\Modules\Modules.sln进行构建

使用vs编译后会生成$(GAME_LIB_DIR)\src\GameLibs\Modules\lib\Modules_d.lib_d代表Debug的静态库)

编译各独立样例需要的GameLib

在本文中,我们的目标是构建2DGrphics1-NimotsuKunBox项目,所以我们加载2DGrphics1的GameLib:$(GAME_LIB_DIR)\src\GameLibs\2DGraphics1\GameLib.sln进行构建,通过配置也能看到的确需要Modules_d.lib

该项目构建完成后,会生成:$(GAME_LIB_DIR)\2DGraphics1\lib\GameLib_d.lib,并且会将GameLib_d.lib静态库以及相关头文件都复制到$(GAME_LIB_DIR)\2DGraphics1\中,:

$(GAME_LIB_DIR)\2DGraphics1\
├─include
│ └─GameLib
│ │ Framework.h
│ │
│ └─Base
│ DebugStream.h

└─lib
Framework_d.idb
Framework_d.pdb
GameLib_d.lib(lib库)
GameLib_d.pdb
Modules_d.pdb

目前为止,我们生成了如下的两静态库以及头文件:

  1. $(GAME_LIB_DIR)\src\GameLibs\Modules\lib\Modules_d.lib
  2. $(GAME_LIB_DIR)\2DGraphics1\lib\GameLib_d.lib
  3. $(GAME_LIB_DIR)\2DGraphics1\include(头文件)

当然,因为我们的GameLib_d.lib是使用Modules_d.lib进行构建的,已经将Modules_d.lib链接到了GameLib_d.lib内部了,所以接下来我们的cmake项目不再需要Modules_d.lib了。

演示2DGraphics1-NimotsuKunBox和drawPixels项目

使用VS打开$(GAME_LIB_DIR)\src\02_2DGraphics1\2DGraphics1.sln"解决方案,该解决方案中有如下的5个样例:

NimotsuKunBox
NimotsuKunBoxWithTermination
NimotsuKunTextOnly
NimotuKunDot
drawPixels

将NimotsuKunBox项目作为启动项目,然后运行可以看到如下的界面:

将drawPixels作为启动项,运行可看到如下效果:

接下来,我们将使用cmake来移植这两个项目。

使用cmake搭建2DGraphics1项目

在经过前戏后,我们终于编译出了2DGraphics1所需要的GameLib_d.lib静态库以及相关的头文件,并且,我们还构建了2DGraphics1样例解决方案中的NimotsuKunBox和drawPixels项目。接下来我们将创建一个cmake项目,移植该样例中的两个项目。

搭建初始项目

首先,我们建立一个文件夹2DGraphics1_cmake,在该文件夹中,我们再创建两个文件夹:NimotsuKunBox和:drawPixels,并且这两个文件夹中分别各自创建一个main.cpp文件和CMakeLists.txt,内容如下:

2DGraphics1_cmake/NimotsuKunBox:

// 2DGraphics1_cmake/NimotsuKunBox/main.cpp
#include <iostream>
int main() {
std::cout << "hello, NimotsuKunBox" << std::endl;
}
CMAKE_MINIMUM_REQUIRED(VERSION 3.22)
PROJECT(NimotsuKunBox) SET(CMAKE_CXX_STANDARD 11) ADD_EXECUTABLE(NimotsuKunBox main.cpp)

2DGraphics1_cmake/drawPixels:

// 2DGraphics1_cmake/drawPixels/main.cpp
#include <iostream>
int main() {
std::cout << "hello, drawPixels" << std::endl;
}
CMAKE_MINIMUM_REQUIRED(VERSION 3.22)
PROJECT(drawPixels) SET(CMAKE_CXX_STANDARD 11) ADD_EXECUTABLE(drawPixels main.cpp)

接着,我们在2DGraphics1_cmake中创建一个CMakeLists.txt来统一管理NimotsuKunBox和drawPixels这两个cmake项目,内容如下:

CMAKE_MINIMUM_REQUIRED(VERSION 3.22)
PROJECT(2DGraphics1_cmake) SET(CMAKE_CXX_STANDARD 11) # cmake子项目,子项目的名称就是子目录下的CMakeLists.txt中的PROJECT一致的名称
ADD_SUBDIRECTORY(NimotsuKunBox)
ADD_SUBDIRECTORY(drawPixels)

于是,当前整体目录结构如下:

2DGraphics1_cmake
├─ CMakeLists.txt
├─ NimotsuKunBox
│ ├─ CMakeLists.txt
│ └─ main.cpp
└─ drawPixels
├─ CMakeLists.txt
└─ main.cpp

cmake实践点:子项目管理

父级CMakeLists.txt可以通过ADD_SUBDIRECTORY来添加子CMake项目。这里有一篇特别详细的博文CMake基础 第13节 构建子项目 - 橘崽崽啊 - 博客园 (cnblogs.com)

头文件与静态库添加

在前面我们已经编译出了GameLib_d.lib,并且把头文件已经复制到了指定目录。现在,我们在项目根目录中创建一个@lib文件夹,专门放置静态库和头文件,目录状态如下:

2DGraphics1_cmake
├─ CMakeLists.txt
├─ NimotsuKunBox
│ └─ // ... ...
├─ drawPixels
│ └─ // ... ...
└─ @lib // => 存放要使用的静态库和头文件
├─ include
│ └─ GameLib
│ ├─ Base
│ │ └─ DebugStream.h
│ └─ Framework.h
└─ lib
├─ Framework_d.idb
├─ Framework_d.pdb
├─ GameLib_d.lib
├─ GameLib_d.pdb
└─ Modules_d.pdb

配置NimotsuKunBox项目

为了让NimotsuKunBox项目中的能够使用到根目录下的静态库和头文件,我们需要配置NimotsuKunBox/CMakeLists.txt,添加头文件和静态库:

  SET(CMAKE_CXX_STANDARD 11)

+ MESSAGE("Current CMakeLists.txt dir:\n${CMAKE_CURRENT_SOURCE_DIR}")
+
+ # 配置头文件查找路径
+ INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../@lib/include)
+ # 配置链接库文件查找路径
+ LINK_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../@lib/lib)
+
ADD_EXECUTABLE(NimotsuKunBox main.cpp)
+
+ # 实际链接
+ TARGET_LINK_LIBRARIES(NimotsuKunBox GameLib_d.lib)

之后,我们将在VS中能够运行的NimotsuKunBox项目代码拷贝到当前的main.cpp中,由于篇幅的关系,就不贴出代码本身了,给一个整体的修改:

编译问题

当我们尝试运行该项目的时候,发现至少有以下几个问题:

问题1:在CLion+msvc编译器下,编码字符报错:warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失。

该问题原因在于CLion中的文件是默认使用的UTF-8编码,而msvc在不指定的情况默认以当前代码页(936)编码方式读取文件(代码页936(Codepage 936)是Microsoft的简体中文字符集标准,是东亚语文的四种双字节字符集(DBCS)之一。其最初版本和GB 2312一模一样,但在推出Windows 95时扩展成GBK)。

在CMake中想要给msvc指定文件编码方式,需要在CMakeLists.txt配置如下内容:

... ...
SET(CMAKE_CXX_STANDARD 11) # 配置编译器以指定编码读取代码源文件
ADD_COMPILE_OPTIONS("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
ADD_COMPILE_OPTIONS("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>") MESSAGE("Current CMakeLists.txt dir:\n${CMAKE_CURRENT_SOURCE_DIR}")
... ...

问题2:GameLib_d.lib(MemoryManager.obj) : error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MTd_StaticDebug”不匹配值“MDd_DynamicDebug”(main.cpp.obj 中)

这一类报错通常比较普遍,简单来讲就是:GameLib_d.lib这个库是一个静态库带Debug(MTd_StaticDebug),但是我们的项目链接步骤是以动态库的方式链接这些库文件。对于这个问题,有两种方式来解决,一种就是重新编译GameLib为一个dll(动态链接库);另一种则是修改当前项目的链接方式为静态库链接。当然,简便起见,我们修改项目的链接形式为静态库链接形式:

 ... ...
SET(CMAKE_CXX_STANDARD 11)
ADD_COMPILE_OPTIONS("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
ADD_COMPILE_OPTIONS("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>") +# 设置策略CMP0091为NEW,新策略
+if (POLICY CMP0091)
+ CMAKE_POLICY(SET CMP0091 NEW)
+endif (POLICY CMP0091)
+
MESSAGE("Current CMakeLists.txt dir:\n${CMAKE_CURRENT_SOURCE_DIR}") LINK_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../@lib/lib) ADD_EXECUTABLE(NimotsuKunBox main.cpp) +# 设置MT/MTd
+SET_PROPERTY(
+ TARGET NimotsuKunBox
+ PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
+
# 实际链接
TARGET_LINK_LIBRARIES(NimotsuKunBox GameLib_d.lib)

关于这块配置的细节,可以参考这篇文章:CMake设置MSVC工程MT/MTd/MD/MDd_Copperxcx的博客-CSDN博客_cmake mt

问题3:error LNK2019: 无法解析的外部符号 _main,函数 "int __cdecl invoke_main(void)" (?invoke_main@@YAHXZ) 中引用了该符号

稍有C/C++开发经验的开发者看到这个报错其实心里还是有底的,应该是没有提供main函数作为函数的入口。但是对于我们的项目,细心的读者发现似乎样例代码中确实是没有提供main入口函数的。那么,为什么vs项目能够正确运行起来呢?观察vs中的项目属性—连接器—系统,会发现子系统(SubSystem)的值是:/SUBSYSTEM:WINDOWS

在cmake项目中,我们可以按照如下的方式进行配置:

# 设置MT/MTd
SET_PROPERTY(
TARGET NimotsuKunBox
PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") + # 配置 "/SUBSYSTEM:WINDOWS"
+ TARGET_LINK_OPTIONS(NimotsuKunBox PRIVATE "/SUBSYSTEM:WINDOWS") # 实际链接
TARGET_LINK_LIBRARIES(NimotsuKunBox GameLib_d.lib)

实际上,配置成了/SUBSYSTEM:WINDOWS之后也是需要有一个入口函数的,这个入口函数其实是在Modules那个项目里面定义好了的,具体可以搜索Modules项目中的int APIENTRY _tWinMain函数实现:

int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)

上述问题处理完成后,我们通过项目编译

配置drawPixels项目

实际上,配置drawPixels项目的CMakeLists.txt和NimotsuKunBox的CMake配置结构上没有区别,只是需要把相关的项目名字等换位drawPixels即可。最终运行的效果和之前的vs下是一致的~

附录:项目地址

本cmake移植的项目地址在:w4ngzhen/2DGraphics1_cmake (github.com)

《世嘉新人培训教材—游戏开发》2DGraphics1项目cmake构建的更多相关文章

  1. Cocos2d-x手机游戏开发与项目实践具体解释_随书代码

    Cocos2d-x手机游戏开发与项目实战具体解释_随书代码 作者:沈大海  因为原作者共享的资源为UTF-8字符编码.下载后解压在win下显示乱码或还出现文件不全问题,现完整整理,解决全部乱码问题,供 ...

  2. 学习手机游戏开发的两个方向 Cocos2d-x 和 Unity 3D/2D,哪个前景更好?

    如题! 首先说一说学习手机游戏(移动游戏)这件事. 眼下移动互联网行业的在以井喷状态发展.全球几十亿人都持有智能终端设备(ios android),造就了非常多移动互联网创业机会: 一.移动社交 微信 ...

  3. 内置3D对象-Unity3D游戏开发培训

    内置3D对象-Unity3D游戏开发培训 作者:Jesai 2018-02-12 19:21:58 五大面板: -Hierachy:当前场景中的物体 图 1-1 -Project:项目中的所有资源 图 ...

  4. Unity3D游戏开发培训

    Unity3D游戏开发培训 作者:Jesai 时间:2017-01-08 修改:2017-01-09 12:36:15 1         项目的构成 图1-1 如图1-1所示,Unity3D的项目构 ...

  5. 项目游戏开发日记 No.0x000005

    14软二杨近星(2014551622) 还有一周就要交项目了, 看着周围的人也都忙碌了起来, 看着大部分人的项目都已经初具容貌, 我们团队里面也搞得人心惶惶, 一来是, 时间不多了, 还有很多事情要做 ...

  6. 项目游戏开发日记 No.0x000002

    14软二杨近星(2014551622) 项目开发的开始, 到现在已经很久了, 软件工程的课也上了很久了, 不过, 我们的游戏现在依然还没有影子, 只能说...还是啥也不会... 从一开始, 兴致勃勃地 ...

  7. [Unity3D]Unity3D游戏开发之飞机大战项目解说

    大家好,我是秦元培,欢迎大家继续关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei. 首先感谢大家对我博客的关注,今天我想和大家分享的是一个飞机大战的项目.这是一个比較综合的 ...

  8. .Net Core ORM选择之路,哪个才适合你 通用查询类封装之Mongodb篇 Snowflake(雪花算法)的JavaScript实现 【开发记录】如何在B/S项目中使用中国天气的实时天气功能 【开发记录】微信小游戏开发入门——俄罗斯方块

    .Net Core ORM选择之路,哪个才适合你   因为老板的一句话公司项目需要迁移到.Net Core ,但是以前同事用的ORM不支持.Net Core 开发过程也遇到了各种坑,插入条数多了也特别 ...

  9. 用户输入- Unity3D游戏开发培训

    用户输入- Unity3D游戏开发培训   作者:Jesai 时间:2018-02-12 14:28:45 用户输入Input 鼠标按键: -方法:GetMouseButton(); -方法:GetM ...

  10. 投影方式- Unity3D游戏开发培训

    投影方式- Unity3D游戏开发培训   作者:Jesai 2018-02-12 20:33:13 摘  要 透视投影是3D渲染的基本概念,也是3D程序设计的基础.掌握透视投影的原理对于深入理解其他 ...

随机推荐

  1. 设计模式 - 创建型模式 - 单例模式(C++)

    1.前言 单例模式属于创建型模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点. 单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方 ...

  2. Delphi dbgrideh颜色设置

    DBGridEh中分行分列.单元格的颜色设置(1)分行不同颜色设置:在DBGridEh1DrawColumnCell中写: if ADOQuery1.RecNo mod 2=0 then begin ...

  3. PostgreSQL-可以通过localhost连接,无法通过IP地址连接。

    (1)如果PostgreSQL配置文件中没有允许访问该服务器的IP地址,则需要先添加允许访问的IP地址,并在防火墙中开放相应的端口.(2)在PostgreSQL配置文件postgresql.conf中 ...

  4. CF1902

    A 只要不是全 \(1\) 即可. B 二分完成天数. C \(x\) 取差的 \(gcd\),\(a_{n+1}\) 见缝插针. D 用一个 map 记录按原始操作序列,要走到 \((x,y)\) ...

  5. NC223888 红色和紫色.md

    题目链接 题目 题目描述 漫长的生命总是无聊的.这天,小红和紫准备玩一个染色游戏. 她们拿出了一个有 \(n*m\) 个格子的网格,每个格子只能被染成红色或紫色.每个人可以任意选择一个格子染成红色和紫 ...

  6. NC26253 小石的妹子

    题目链接 题目 题目描述 小石有 n 个妹子,每个妹子都有一个细心程度 \(a_i\)和一个热心程度 \(b_i\) , 小石想给她们一个重要程度 \(t_i\)​(重要程度为 1 表示最重要,重要程 ...

  7. Java设计模式-装饰者模式Decorator

    介绍 装饰者模式的核心思想是通过创建一个装饰对象(即装饰者),动态扩展目标对象的功能,并且不会改变目标对象的结构,提供了一种比继承更灵活的替代方案.需要注意的是,装饰对象要与目标对象实现相同的接口,或 ...

  8. vue+antv g6+element-ui完整流程图

    最近一直在研究流程图相关的技术,一次在逛GitHub时发现了一个技术栈为vue+g6+element-ui的项目,基础功能完好,如node与edge的托拉拽,主界面如下:

  9. 矩池云快速安装torch-sparse、torch-geometric等包

    租用机器,按自己需要的环境选择一个环境,我这里选择的是Pytorch 1.10. 租用成功后点击租用页面的 Jupyterlab 链接. Jupyterlab 里新建一个Terminal 用来安装环境 ...

  10. C++ auto与循环

    C++ auto与循环 C++ auto 的介绍 typeid(p).name();可以输出auto的类型 auto 是 C++11 引入的一个关键字,用于自动类型推导.编译器会根据初始化表达式的类型 ...