Giter VIP home page Giter VIP logo

simplecloneofminecraft-dx11-cpp's Introduction

基于DirectX11的Minecraft模拟

一、项目介绍

1.1 项目需求

简单描述:

利用DirectX 11和c++语言实现对游戏《Minecraft》(以下简称MC)的基本游戏内容的模拟

设计目标:

  1. 模拟MC中以立方体为元素的世界及地形,搭建一个简单场景。
  2. 玩家可以通过鼠标左右键放置和摧毁方块。方块被摧毁后会在地面出现并在人物接近的时候会自动拾取。
  3. 利用合适的几何体搭建角色的头部、手部、身体、双脚。
  4. 实现键盘控制人物的基本移动功能,并且在移动和摧毁方块时,人物模型的各身体部分要有合适的摆动。
  5. 实现第一人称与第三人称视角控制
  6. 用算法在随机位置渲染多个目标(目标用简单的几何体代替)
  7. 实现目标血条显示。血条显示在目标位置上方,并且会随着玩家与血条的方位进行旋转,使得血条正面始终朝向目标。
  8. 目标可以被攻击,血量减少为0时销毁目标。
  9. 实现天空盒。
  10. 实现合理光源布置。

1.2 需求分析

1.2.1搭建场景

分析:首先,作为一款开放自由的沙盒游戏,MC中的绝大部分的元素都由一个个方块(正方体)组成。所以游戏的场景自然是通过一块块不同种类的方块堆积排列而成的。抽离出场景中的其中一块方块,针对方块这一游戏物体元素,我们需要考虑它的以下几个方面:1.网格模型 2 表面材质纹理 3. 相关交互。来完成不同方块的创建(具体实现在请阅读后面内容)。

随后就是利用创建好的方块在游戏三维空间中按一定顺序,规律排列这些不同的方块,使其最终呈现为一个合理完整的游戏场景。

1.2.2 鼠标左右键进行方块的放置和摧毁,方块被摧毁后生成掉落物并在人物靠近时自动拾取

分析:在MC中最主要的游戏操作无非就是通过破坏方块来获取改变游戏场景、获取不同的游戏方块物品,以及通过放置不同的方块在游戏中建造各种建筑。而想要通过鼠标这一外接硬件设备在游戏中实现实时的交互,就要实现对鼠标按键的检测。

当程序检测到鼠标的按键后,若要实现方块的放置。最主要的就是如何定位方块生成的位置。这里我的核心思路是利用一条从屏幕中心发出的射线与原先场景进行碰撞检测,来定位方块的位置。

若要实现方块的销毁。最主要的就是如何判定玩家所选中的位置是否真的有方块存在。这里我的思路是,同样利用射线确定方块的位置,然后利用方块位置作为检索信息搜索所有场景中方块的实例数据,来判定是否有方块存在。

最后是方块掉落物的生成和自动拾取。当一个方块实例被摧毁后,根据其方块类型生成相应的掉落方块实例,并实现浮空检测,旋转的模拟。最后当玩家与掉落物距离小于一定范围时,删除该掉落物实例,即被玩家拾取。

1.2.3 构建人物模型

分析:在MC中玩家是通过控制一个人物游玩,那必然就需要一个代表玩家的人物模型。人物模型主要由身体,手和腿组成。这里可直接利用不同形状大小的立方体拼接成一个简易的人物模型,用于后序人物模型动画的展示。

1.2.4 实现人物移动的控制,以及人物相关行为时对应的人物模型动画

分析:人物移动的控制通过键盘来实现。类似与鼠标的操作,需要实现程序对键盘不同按键的检测。当检测到相关按键被按下时,通过改变人物的位置的来实现人物的移动和跳跃等行为。此处的难点在于检测玩家的周围是否有方块阻挡人物移动。

人物在移动的同时有,身体部位的动画可以让人物动作更加的真实,同时模型动画也可以作为对玩家的反馈。对于人物模型的动画,则同样配合按键检测。当人物在移动或放置摧毁方块时,通过旋转人物的手,腿模型来实现人物移动时的身体摆动。

1.2.5 实现合适天空盒

分析:在MC中通过天空盒来实现类似蓝天,星空等场景,可以让游戏的画面更加真实和丰富。而实现天空盒需要有包含六个面的天空盒纹理,在将纹理导入并创建纹理立方体后,通过设置绘制天空盒所需的着色器,采样模式等实现天空盒的绘制。

1.2.6 实现第一人称和第三人称视角控制

分析: 第一人称视角可以说是许多游戏中极其重要的部分,可以让玩家有更接近于真实世界的体验。在程序中可使用第一人称摄像机作为玩家的第一人称视角,让摄像机的位置随着按键的控制移动,并记录鼠标的移动实现游戏视角的转动。也就是说实际上我们使用键盘鼠标是在控制一个摄像机。

虽然主要是第一人称的游戏视角,但MC中第三人称视角控制的添加,使得游戏的体验更加丰富。这里要实现第三人称的控制,我们需要在程序中添加一个第三人称摄像机。并在玩家切换到第三人称视角后将摄像机的镜头绑定到人物上。同样检测鼠标的位移和滚轮实现摄像机视角的转动和拉伸。

1.2.7 实现合理的光源

分析:合理的光源不仅是让游戏的场景能被看见,同时也是为了模拟更真实的游戏世界。而在程序中要实现光源,光照。首先需要选用合适的光照模型,并在场景中铺设好光照的方向(如果是有向光源)。然后我们需要在像素着色器中加入光照对像素颜色影响的计算,最终才能得到有光照效果的游戏画面。

1.2.8 随机生成游戏目标(敌人)

分析:在MC中有各种各样的生物,怪物。而增加除玩家人物本身以外的实体生物后可以显著提升游戏的趣味性,挑战性等等。而在这个程序中,我们用简单的几何模型来代替目标实体。同时利用相关函数来随机生成目标的初始位置坐标来实现目标的随机生成。

同时在玩家靠近敌人时敌人会自动跟踪敌人。此处的难点同样在于敌人与玩家和周围游戏场景的碰撞检测。

1.2.9 实现目标血条显示

分析:要实现敌人生命值的显示,并实时朝向玩家。我们可以利用公告板的效果展示敌人的血条。同时当敌人的生命值发生变化后,更新血量公告板上显示的纹理来实现血条显示的更新。

1.2.10 可攻击目标,目标生命值低于0时销毁目标

分析:在MC中打怪无疑是游戏中很重要的一部分。而要实现攻击目标,首先同样是检测鼠标按键的状态。随后的难点就在于玩家按下攻击按键后是否有攻击到目标,这里可以利用射线与敌人包围盒的碰撞来实现检测。最后实时检测目标的生命值,当生命值低于0时销毁(不绘制)该目标。

二、设计思路

该项目是参考Github上x_jun提供的DirectX11框架实现的。框架中提供了最基本初始化、的窗口设置、常用数学库等API。而我就是在此框架上进行修改并增加自己设计的内容。

因为一个游戏最主要就就是游戏中的物体。可以说一个游戏项目中几乎所有代码都是围绕着游戏中不同的物体来写的。所以我决定从不同的游戏物体作为切入点,探讨该项目的设计思路。

在分析完项目需求后,我们可以抽离出三种游戏物体(对象)

下面将分别介绍三种游戏物体以及与它们相关的设计思路

2.1 方块

作为一个MC的基本元素,方块无疑是最核心的游戏物体。所以在项目中我定义了一个继承于已有的 GameObject类的方块类 Block 。用 Block 类来存储方块的实例数据,获取方块的数据信息,实现与方块有关的操作。类中具体内容如下

  • 创建方块:

    这部分的函数主要是用在游戏初始化时,从文件中读取方块的纹理,利用纹理数组来存储所有方块的纹理,并利用在每帧更新的常量缓冲区中存储纹理的索引。而对于的方块实例数据,我使用了C++ STL中的Map容器来存储。之所以使用Map是为了能够实现资料的一对一映射,有助于进行大量方块实例数据中任意方块的准确定位和修改。而当方块被破坏后会生成可拾取的掉落方块。为了便于快速的插入或删除掉落方块实例数据,这里使用List容器来存储掉落方块的实例数据。方块和掉落方块的绘制则是在原先实例绘制函数的基础上进行修改,同时完成方块和掉落方块实例的绘制。

  • 获取方块信息:

    这部分主要是在利用Map容器所提供按Key值进行搜索的方法查找方块实例是否存在等。同时包含在碰撞检测时要用到的包围盒的获取

  • 修改方块:

    这部分主要是和玩家类进行一个对接,当玩家类中触发放置/销毁方块的操作时,实现对方块实例数据集的更新。同时包含被破坏的方块的掉落物品,以及掉落方块相关的旋转,浮空检查和拾取的实现。最后还有改变玩家当前手持方块类型的实现。

2.2 玩家

玩家是游戏中的操控者,也是引起几乎一切游戏变化的起源。所以在项目中我定义了一个Player 类来管理与玩家有关的内容。类中具体内容如下

  • 人物模型:

    这部分我在Player类中又定义了一个 BodyPart类,将人物模型的不同组成部分(身体,手,腿)用一个BodyPart的对象来管理。这有助于实现更个性化的模型动画。

  • 玩家主动触发事件

    这部分函数是主要是作为一个衔接。在GameApp中检测到鼠标按键的指令后,将摄像机等数据传递到玩家类,玩家类中对接收到的信息进行加工,再将数据传递到方块类中完成整一个操作。

  • 玩家被动触发事件

    这部分主要是实现人物移动时与方块的碰撞检测以及玩家浮空状态的检测和更新,以实现重力的效果。

2.3 敌人

敌人是除了玩家以外的会动的实体,因此重点在于实现一些自动的功能。同样我也是定义了一个 Enemy 类来管理敌人的相关活动以及生命值等信息。对于敌人实例,因为目前只设置了3个敌人,所以就用最简单的数组来存储每个敌人实例对象的数据。以下是 Enemy类的具体内容。

  • 模型及其变换:

    包含敌人模型的一些基本变换操作。对于敌人的模型,由于时间限制我决定用简单的圆柱体来代替。

  • 敌人被动触发事件:

    当玩家靠近敌人时,敌人会自动朝向玩家并缓慢向玩家移动。当然,由于敌人也是一个会动的实体,所以在跟踪的过程中也要进行与方块和玩家碰撞的检测,以及检测是否出现浮空的状态。

  • 敌人信息:

    包含敌人血条公告板的显示和更新,,以及用于碰撞检测的包围盒的获取。

三、详细功能实现

3.1 方块相关功能实现

3.1.1 初始化地形

初始化地形的利用定义在GameApp类中的函数 void InitLand(); 来实现,在InitResource()中调用。首先是创建用于构成地形的方块。所以先初始化方块的网格模型,材质

随后就是创建方块纹理数组

其中函数 CreateBlockTextureArrayFromFile 的具体实现如下(仅展示关键部分代码)

  1. 因为我的方块纹理是一整张大的未裁剪的纹理图片,所以创建纹理数组的思路就是,先从文件中读取大张的纹理。

  1. 随后通过定位大纹理中每个方块的纹理来创建一个存放每种方块不同面纹理的纹理数组

(用于方块6个面定位的BlockBoxs数组)

(遍历每一种方块,再遍历数组中每个纹理元素的子资源并利用 CopySubresourceRegion函数将每个面的纹理拷贝到纹理数组 BlockTectures中。其中纹理数组中每6个连续的纹理元素为一种方块的六个面的纹理)

完成了方块纹理数组的创建后,就可以利用 Block类中的SetPosition函数来在游戏世界中的指定位置创建方块实例

(具体地形是通过我已经写好的代码生成的固定地形)

其中,SetPosition函数的本质就是将形参中的坐标和方块ID存入到用Map容器存储的方块实例数据集中

(Map中的实例数据结构体) (以实例方块的坐标作为Map的Key)

至此,场景的初始化已经完成

3.1.2 放置/销毁方块

放置/销毁方块的实现需要结合到Player 类中的GetRayHitPos函数来获取从屏幕中心发射出的射线所碰撞到的方块实例的坐标(如果有射线路径上有实例存在)

具体流程如下

具体实现中几处比较关键的代码:

(射线逐渐增长并检测是否有方块实例存在的实现)

(确定新方块生成在当前方块的那个面的实现)

3.1.3掉落方块的生成、浮空检测、旋转与拾取

掉落方块相关的具体实现流程如下

为了避免同一x,z坐标的掉落方块实例堆叠在一起难以区分,所以利用借助随机数使掉落方块的位置更随机和真实。

(掉落方块生成的的代码)

而玩家靠近后拾取掉落方块通过删除掉落方块实例数据实现,具体代码如下

(掉落方块拾取(删除)的的代码)

3.2 玩家相关功能实现

3.2.1 构建人物模型,并在人物放置/销毁方块时模型身体部位有相应的摆动

创建出BodyPart类的5个实例对象来分别管理人物模型。具体实现流程如下

其中人物的四肢和身体使用不同长度和大小的立方体组合。而四肢摆动的动画通过模型绕模型局部坐标系中一点朝着不同方向旋转来实现。

(人物移动动画的具体实现)

(模型转动的具体实现)

因为人物可能一边移动一边放置/摧毁方块,所以这里右手选择优先播放人物放置/销毁方块的动画。(右手单独动画与移动动画类似,故不展示具体代码)

同时为了,为了让人物整体的模型在游戏世界中发生移动,这里在BodyPart实现一个UpdataPosToCam函数让人物身体部分始终跟随(绑定)第一人称摄像机的移动,同时实现UpdataPos函数让其余身体部分绑定到人物身体局部坐标系的相对位置

(UpdataPos的实现。利用身体模型的世界矩阵的逆矩阵将其它身体部位变换到身体的局部坐标系中,设置其在局部坐标系的位置,在变换回世界坐标系中)

3.2.2 实现跳跃,检测人物浮空状态并实现人物浮空时的下落

由于跳跃的触发时利用键盘上的空格键,但要确保人物已经在空中时不能实现多次连跳,而且人物的跳跃是一个过程而不是一瞬间的,所以这里我利用一个计时器来记录每次跳跃的时长。

(利用计时器实现跳跃的具体实现)

对于浮空状态的检测,我的设计思路如下

(更新人物浮空状态的具体代码实现)

3.2.3 人物移动时与周围方块的碰撞检测

碰撞检测这部分功能,我一开始是想直接利用人物模型的包围盒与周围的方块的包围盒进行碰撞检测,后来仔细一想发现其实根本每必要同时对多个方向进行检测,只需要检测人物当前移动的方向是否有方块实例存在即可,具体实现流程如下

(计算坐标查找实例部分的代码的实现)

同时使用函数 isUpCollosion实现头部碰撞的检测。函数的具体实现与浮空状态的检测类似,只不过改为计算上方的方块坐标并并判断是否有实例方块存在。

3.2.4 实现第一人称视角/第三人称视角的控制

为了能够实现第一人称视角和第三人称视角的实时切换,所以在项目中我同时维护了cam1st和cam3rd两个摄像机。在初始化资源的同时初始化两个摄像机后,用m_pCamera表示当前实际在时候的摄像机,当按下切换按键时切换m_pCamera的指向即可实现视角的切换。

(在第三人称视角时,在用滚轮改变了Distance后,还需要相应更新射线检测的最大距离)

3.3 敌人相关功能实现

3.3.1 模型,浮空状态, 浮空时下落,碰撞检测

由于敌人也是类似于玩家人物的实体,所以在这些同样的功能实现部分,实现的原理基本相同。其中模型是选用了简单的圆柱体来代替敌人模型,浮空状态和与方块,人物碰撞检测相比于人物直接检测实例方块的存在,这里还尝试增加了获取两者的碰撞盒进一步实现真正意义上的碰撞检测。

3.3.2 自动跟踪玩家

在MC中当玩家人物靠近敌对生物时,近距离攻击的敌对生物都会尝试靠近玩家。在这里就简易的还原了敌人靠近玩家的实现,具体实现流程如下

(水平距离的计算)

3.3.3 敌人血条显示

在项目中利用公告板的实现原理实现敌人血量条的显示。具体实现如下

其中,由于敌人是会移动的,所以为了保持血条的位置始终在敌人的正上方,就要实时更新用于记录血条中心点位位置的顶点缓冲区。同时,敌人血条的纹理应该随着敌人当前的血量实时变换,所以也要在绘制前更新常量缓冲区CBChangesEveryObjectDrawing 中用于血条纹理数组采样的索引值。

//构建出公告板矩形的局部坐标系

float3 up = float3(0.0f, 1.0f, 0.0f);

float3 look = g_EyePosW - input[0].PosW;

look.y = 0.0f;

look = normalize(look);

float3 right = cross(up, look);

//计算出公告板矩形的四个顶点

float4 v[4];

float3 center = input[0].PosW;//输入的顶点坐标作为公告板矩形的中心

float halfWidth = 0.5f \* input[0].SizeW.x;

float halfHeight = 0.5f \* input[0].SizeW.y;

v[0] = float4(center + halfWidth \* right - halfHeight \* up, 1.0f);

v[1] = float4(center + halfWidth \* right + halfHeight \* up, 1.0f);

v[2] = float4(center - halfWidth \* right - halfHeight \* up, 1.0f);

v[3] = float4(center - halfWidth \* right + halfHeight \* up, 1.0f);

//(几何着色器中构建局部坐标系及计算公告板矩形部分的代码)

3.3.4 玩家可攻击敌人,当生命值小于0时敌人被销毁

在项目中我将玩家的有效攻击距离等同于敌人探测跟踪玩家的距离,具体实现如下

(玩家攻击敌人实现的部分代码)

最后在每次绘制敌人时,不绘制生命值小于或等于0的敌人来实现敌人的销毁(此处并未实现真正删除敌人的实例数据)

3.4 其它功能实现

3.4.1 绘制天空盒

由于天空盒的本质是使用一个纹理立方体来为作为天空的“景“,同时为了配合MC这一游戏内容,所以我选用了6张在真正MC中的不同角度的游戏截图作为该项目中的天空盒。让程序更有MC的氛围。具体绘制流程如下

3.4.2实现合理的光源

由于我项目中使用的天空盒纹理是一个太阳斜照的纹理图片,所以在设置光源的时候我选择添加4个方向光,其中3个的方向基本和太阳光的照射方向一致,另一个用于背面的补光。最终可以呈现一个跟符合天空纹理的光源效果。

3.4.3 2D文字信息的绘制

在项目中利用Dwrite在屏幕窗口的左上角进行部分游戏信息的显示。如显示当前人物手中的方块类型。由于时间关系,没能完成敌人血条显示的功能,所以这里就通用文字信息的方式显示每个敌人的血量。

同时,在屏幕中心绘制一个文字“十“来实现MC中的光标效果,使玩家更好确定当前选中的具体位置。

四、程序测试

4.1 程序测试截图

(由于文档形式的限制,此处仅展示部分静态游戏效果)

4.1.1初始化游戏场景和天空盒(图1)

(图1)

4.1.2方块的放置与破坏(图2)

(图2)

4.1.3 人物模型(图3)

(图3)

4.1.4 随机生成的敌人(图4)

(图4)

五、难点讨论

5.1 不同方块纹理的实现

实现不同的方块对应不同的纹理,这可以说是MC最基本的要求了,而在刚开始做项目是我就这这个第一步卡了很久,具体的难点有以下几个。

**难点1:**如何实现在一张纹理上定位并拷贝出其中的一部分纹理,并将它们导入纹理数组中?

**解决:**预先用一个数组来存放不同方块每个面的纹理在源纹理上的位置,并结合CopySubresourceRegion函数将指定位置的小块纹理拷贝到纹理数组中。

**难点2:**如何在像素着色器中得到纹理数组的正确索引?

**解决:**巧妙利用系统值SV_PrimitiveID来通过当前绘制的图元ID区分一个立方体六个面的绘制。并利用图元ID选取传入到常量缓冲区中的用于存放6个面纹理数组索引的向量中的值,最终得到当前顶点准确的索引值。

5.2 对任一实例方块的检测和修改

**难点1:**如何对实例数据中任一数据进行快捷的修改?

**解决:**利用STL中Map容器的特性实现任一方块实例数据的快速定位和修改

**难点2:**如何知道玩家当前选中的是哪个实例方块?

**解决:**利用从屏幕中心发射的射线,并在逐渐增加射线长度的同时把射线末端当作一个“探头“去探测射线方向是否有方块实例存在

六、体会总结

6.1 项目总结

这次模拟MC的项目有做的不错的地方。比如玩家人物的控制,放置方块,蓄力挖掘,可攻击的移动目标等等。这些都让整个项目的趣味性得到了很大的提升,也较好的还原了MC中的部分操作和功能。但技术水平仍需不断提升,所以这次模拟MC的项目仍然有不少可以拓展完善的功能。以下列举3个我认为比较重要的可完善功能。

  1. 实现高级的阴影映射,提高画面的真实度。
  2. 借助DirectSound库实现音效和音乐。
  3. 利用柏林噪声算法实现随机场景地形的生成。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.