上个文章里面,我们分别控制胶囊体和摄像机确实比较麻烦,接下来,我将创建一个可以自由移动的模型对象,然后让摄像机跟随其移动和旋转。我们使用VS2019来新建一个项目“D3D_10_Follow”。首先,我们还是引入之前封装好的“InputClass.h”和“InputClass.cpp”两个文件。接下来,就是我们的自由移动摄像机类“CameraClass.h”和“CameraClass.cpp”文件。但是,这个文件需要做一点调整。首先是“CameraClass.h”代码如下:
#pragma once
#include "main.h"
// 跟随摄像机
class CameraClass {
public:
D3DXVECTOR3 camera_pos; // 摄像机位置
D3DXVECTOR3 camera_target; // 摄像机观察点
D3DXVECTOR3 camera_up; // 向上方向
D3DXVECTOR3 camera_right; // 向右方向
D3DXVECTOR3 camera_look; // 观察点方向
public:
// 构造方法
CameraClass();
// 有参构造函数
CameraClass(D3DXVECTOR3 target, float distance);
// 析构方法
~CameraClass(){};
// 前后移动
void frontBackMove(float step, D3DXVECTOR3 front);
// 左右移动
void leftRightMove(float step);
// 上下移动
void upDownMove(float step);
// 围绕 _up 方向旋转,也就是左右旋转(摇头)
void leftRightRotate(float angle);
// 围绕 _right 方向旋转,也就是上下旋转(点头)
void upDownRotate(float angle);
// 围绕 _look 方向旋转,相当于侧头
void sideRotate(float angle);
// 计算取景变换矩阵
void getViewMatrix(D3DXMATRIX* V);
};
变化有三点。第一,增加了摄像机观察点。第二,构造函数的参数是观察点和间距。第三,就是前后移动方法的参数增加了front向量,也就是说,摄像机前后移动不是根据观察点的方向,而是跟随目标的前方向。接下来就是实现“CameraClass.cpp”的内容:
#include "CameraClass.h"
// 跟随摄像机: 默认构造方法
CameraClass::CameraClass() {
camera_pos = { 0.0f, 0.0f, -10.0f };
camera_target = { 0.0f, 0.0f, 0.0f };
camera_up = { 0.0f, 1.0f, 0.0f };
camera_right = { 1.0f, 0.0f, 0.0f };
camera_look = { 0.0f, 0.0f, 1.0f };
}
// 跟随摄像机:有参构造函数
CameraClass::CameraClass(D3DXVECTOR3 target, float distance) {
// 设置摄像机目标点
camera_target.x = target.x;
camera_target.y = target.y;
camera_target.z = target.z;
// 设置摄像机位置
camera_pos.x = target.x;
camera_pos.y = target.y + distance;
camera_pos.z = target.z - distance;
// 初始化向量
camera_up = { 0.0f, 1.0f, 0.0f };
camera_right = { 1.0f, 0.0f, 0.0f };
// 观察点位置减摄像机位置,得到观察方向向量
camera_look = camera_target - camera_pos;
// 规范化 camera_look 向量
D3DXVec3Normalize(&camera_look, &camera_look);
};
// 跟随摄像机: 前后移动
void CameraClass::frontBackMove(float step, D3DXVECTOR3 front) {
// 注意:摄像机按照跟随模型的 front 方向移动
camera_pos += front * step;
camera_target += front * step;
// 摄像机不跟随的话,就按照自己的 camera_look 方向移动
//camera_pos += camera_look * step;
//camera_target += camera_look * step;
}
// 跟随摄像机: 左右移动
void CameraClass::leftRightMove(float step) {
camera_pos += camera_right * step;
camera_target += camera_right * step;
}
// 跟随摄像机: 上下移动
void CameraClass::upDownMove(float step) {
camera_pos.y += step;
camera_target.y += step;
}
// 跟随摄像机: 围绕 _up 方向旋转,也就是左右旋转(摇头)
void CameraClass::leftRightRotate(float angle) {
// 创建一个绕 Y 轴向上方向旋转 angle 角度的 R 矩阵
D3DXMATRIX rotationY;
D3DXVECTOR3 Up = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
D3DXMatrixRotationAxis(&rotationY, &Up, angle);
// 让 camera_right 向量和 camera_look 向量绕 Up 方向旋转 angle 个角度
D3DXVec3TransformCoord(&camera_right, &camera_right, &rotationY);
D3DXVec3TransformCoord(&camera_look, &camera_look, &rotationY);
// 更新一下观察点,摄像机跟随模型旋转的话,观察点是不变的,变的是摄像机的位置
//camera_target = camera_look * D3DXVec3Length(&camera_pos);
// camera_look 相反向量
D3DXVECTOR3 look = camera_pos - camera_target;
// 让 camera_look 相反向量围绕 Up 方向旋转 angle 个角度
D3DXVECTOR3 temp;
D3DXVec3TransformCoord(&temp, &look, &rotationY);
// 更新摄像机位置
camera_pos = camera_target + temp;
}
// 跟随摄像机: 围绕 _right 方向旋转,也就是上下旋转(点头)
void CameraClass::upDownRotate(float angle) {
D3DXMATRIX M;
D3DXMatrixRotationAxis(&M, &camera_right, angle);
D3DXVec3TransformCoord(&camera_up, &camera_up, &M);
D3DXVec3TransformCoord(&camera_look, &camera_look, &M);
// 这里应该同步修改摄像机观察点,这样会让观察点偏离跟随模型,最好不能这么做
// 因此,我们还是要调整摄像机的位置
//camera_target = camera_look * D3DXVec3Length(&camera_pos);
// camera_look 相反向量
D3DXVECTOR3 look = camera_pos - camera_target;
// 让 camera_look 相反向量围绕 Up 方向旋转 angle 个角度
D3DXVECTOR3 temp;
D3DXVec3TransformCoord(&temp, &look, &M);
// 更新摄像机位置
camera_pos = camera_target + temp;
}
// 跟随摄像机: 围绕 _look 方向旋转,相当于侧头(歪头)
void CameraClass::sideRotate(float angle) {
D3DXMATRIX M;
D3DXMatrixRotationAxis(&M, &camera_look, angle);
D3DXVec3TransformCoord(&camera_right, &camera_right, &M);
D3DXVec3TransformCoord(&camera_up, &camera_up, &M);
}
// 跟随摄像机: 计算取景变换矩阵
void CameraClass::getViewMatrix(D3DXMATRIX* V) {
// 三个方向向量正交单位化
D3DXVec3Normalize(&camera_look, &camera_look);
D3DXVec3Cross(&camera_up, &camera_look, &camera_right);
D3DXVec3Normalize(&camera_up, &camera_up);
D3DXVec3Cross(&camera_right, &camera_up, &camera_look);
D3DXVec3Normalize(&camera_right, &camera_right);
// 取景变换
float x = -D3DXVec3Dot(&camera_right, &camera_pos);
float y = -D3DXVec3Dot(&camera_up, &camera_pos);
float z = -D3DXVec3Dot(&camera_look, &camera_pos);
// 取景变换矩阵
(*V)(0, 0) = camera_right.x; (*V)(0, 1) = camera_up.x; (*V)(0, 2) = camera_look.x; (*V)(0, 3) = 0.0f;
(*V)(1, 0) = camera_right.y; (*V)(1, 1) = camera_up.y; (*V)(1, 2) = camera_look.y; (*V)(1, 3) = 0.0f;
(*V)(2, 0) = camera_right.z; (*V)(2, 1) = camera_up.z; (*V)(2, 2) = camera_look.z; (*V)(2, 3) = 0.0f;
(*V)(3, 0) = x; (*V)(3, 1) = y; (*V)(3, 2) = z;
(*V)(3, 3) = 1.0f;
}
在这个文件中,代码还是比较复杂的。在构造函数中,我们确定了目标点,也就是观察点,也是跟随的目标。然后让摄像机位于目标点的后上方。有了观察点和位置点,就能计算观察方向向量了。其他两个向量也给了默认值。移动操作基本就是让位置和观察点一起变动。旋转的有些复杂了。例如左右旋转,我们是让摄像机沿着世界坐标系Y轴进行旋转,同时调整摄像机的位置坐标。大家可以实景想想一下,当我们角色左右旋转的时候,摄像机应该如何变动才能让我们的观察的范围同步。其余的方法不在介绍。取景变换方法没有变动。
完成了跟随摄像机的封装,我们开始对玩家进行封装,首先玩家是一个模型,因此我们需要将之前封装好的“MeshClass.h”和“MeshClass.cpp”两个文件复制过来。完成这个基础类的封装后,我们就可以继承MeshClass来完成自由移动的玩家类了,首先我们创建“PlayerClass.h”和“PlayerClass.cpp”两个文件,首先是“PlayerClass.h”文件内容:
#pragma once
#include "MeshClass.h"
#include "CameraClass.h"
// 玩家类,继承 MeshClass 类
class PlayerClass : public MeshClass {
public:
D3DXVECTOR3 _pos; // 玩家位置
D3DXVECTOR3 _up; // 向上的方向
D3DXVECTOR3 _right; // 向右的方向
D3DXVECTOR3 _front; // 向前的方向
CameraClass* _camera; // 跟随的摄像机
public:
// 构造方法
PlayerClass();
PlayerClass(LPDIRECT3DDEVICE9 device, const wchar_t* dir, const wchar_t* file, D3DXVECTOR3 pos);
// 析构方法
~PlayerClass() {}
// 设置摄像机类
void setCamera(CameraClass* camera);
// 计算世界变换矩阵
void getWorldMatrix(D3DXMATRIX* V);
/******************************************************/
// 左右移动(_right轴方向)
void move(float step);
// 前进后退(_front轴方向)
void forward(float step);
// 上下飞行(_up轴方向)
void fly(float step);
/******************************************************/
// 摇头,左右旋转(_up轴旋转)
void shake(float angle);
// 点头,上下旋转(_right轴旋转)
void nod(float angle);
// 歪头,侧歪旋转(_front轴旋转)
void roll(float angle);
};
其实这个类的实现,和自由移动的摄像机非常相似。我们声明三个方向向量以及位置信息。然后围绕三个方向做移动和旋转的操作。只不过,最后我们要生成的是世界变换矩阵,而不是摄像机的取景变换矩阵,两者是不一样的。接下来就是实现“PlayerClass.cpp”的内容:
#include "PlayerClass.h"
// 玩家类:构造方法
PlayerClass::PlayerClass() {};
// 玩家类:有参构造方法
PlayerClass::PlayerClass(LPDIRECT3DDEVICE9 device, const wchar_t* dir, const wchar_t* file, D3DXVECTOR3 pos) : MeshClass(device, dir, file) {
_pos = pos;
_up = { 0.0f, 1.0f, 0.0f };
_right = { 1.0f, 0.0f, 0.0f };
_front = { 0.0f, 0.0f, 1.0f };
};
// 玩家类:设置摄像机类
void PlayerClass::setCamera(CameraClass* camera) {
_camera = camera;
};
// 玩家类:计算世界变换矩阵
void PlayerClass::getWorldMatrix(D3DXMATRIX* V) {
// _front, _up, _right正交单位化
D3DXVec3Normalize(&_front, &_front);
D3DXVec3Cross(&_up, &_front, &_right);
D3DXVec3Normalize(&_up, &_up);
D3DXVec3Cross(&_right, &_up, &_front);
D3DXVec3Normalize(&_right, &_right);
// 世界矩阵
(*V)(0, 0) = _right.x; (*V)(0, 1) = _right.y; (*V)(0, 2) = _right.z; (*V)(0, 3) = 0.0f;
(*V)(1, 0) = _up.x; (*V)(1, 1) = _up.y; (*V)(1, 2) = _up.z; (*V)(1, 3) = 0.0f;
(*V)(2, 0) = _front.x; (*V)(2, 1) = _front.y; (*V)(2, 2) = _front.z; (*V)(2, 3) = 0.0f;
(*V)(3, 0) = _pos.x; (*V)(3, 1) = _pos.y; (*V)(3, 2) = _pos.z; (*V)(3, 3) = 1.0f;
};
// 玩家类:左右移动(_right轴方向)
void PlayerClass::move(float step) {
_pos += _right * step;
if (_camera != NULL) _camera->leftRightMove(step);
};
// 玩家类:前进后退(_front轴方向)
void PlayerClass::forward(float step) {
_pos += _front * step;
if (_camera != NULL) _camera->frontBackMove(step, _front);
};
// 玩家类:上下飞行(_up轴方向)
void PlayerClass::fly(float step) {
_pos.y += step;
if (_camera != NULL) _camera->upDownMove(step);
};
// 玩家类:摇头,左右旋转(_up轴旋转)
void PlayerClass::shake(float angle) {
D3DXMATRIX T;
D3DXMatrixRotationAxis(&T, &_up, angle);
D3DXVec3TransformCoord(&_right, &_right, &T);
D3DXVec3TransformCoord(&_front, &_front, &T);
if (_camera != NULL) _camera->leftRightRotate(angle);
};
// 玩家类:点头,上下旋转(_right轴旋转),不建议使用此方法
void PlayerClass::nod(float angle) {
D3DXMATRIX T;
D3DXMatrixRotationAxis(&T, &_right, angle);
D3DXVec3TransformCoord(&_up, &_up, &T);
D3DXVec3TransformCoord(&_front, &_front, &T);
if (_camera != NULL) _camera->upDownRotate(angle);
};
// 玩家类:歪头,侧歪旋转(_front轴旋转),不建议使用此方法
void PlayerClass::roll(float angle) {
D3DXMATRIX T;
D3DXMatrixRotationAxis(&T, &_front, angle);
D3DXVec3TransformCoord(&_right, &_right, &T);
D3DXVec3TransformCoord(&_up, &_up, &T);
if (_camera != NULL) _camera->sideRotate(angle);
};
由于完成过自由摄像机的封装,因此这个玩家类就比较容易理解。复杂的在于玩家的移动和旋转后,要同步跟随摄像机的移动和旋转。关于如何计算世界变换矩阵,也不需要大家理解。接下来,就是我们的主文件“main.cpp”的内容,首先是全局变量的声明:
// 引入头文件
#include "main.h"
#include "MeshClass.h"
#include "InputClass.h"
#include "CameraClass.h"
#include "PlayerClass.h"
// Direct3D设备指针对象
LPDIRECT3DDEVICE9 D3DDevice = NULL;
// 输入类
InputClass* input;
// 摄像机类
CameraClass* camera;
// 玩家类(立方体)
PlayerClass* player;
// 草地模型
MeshClass* land;
然后就是我们的initScene函数,代码如下:
// 初始化输入
input = new InputClass(D3DDevice, hwnd, hInstance);
// 初始化草地模型
land = new MeshClass(D3DDevice, L"asset/", L"bg.X");
land->init();
// 初始化玩家模型
D3DXVECTOR3 pos(0.0f, 0.0f, 0.0f);
player = new PlayerClass(D3DDevice, L"asset/", L"cube.X", pos);
player->init();
// 初始化摄像机
camera = new CameraClass(pos, 1.0f);
// 摄像机给到玩家
player->setCamera(camera);
// 线性纹理
D3DDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
D3DDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
// 初始化投影变换
initProjection();
// 初始化光照
initLight();
需要注意的是,这里的草地模型,并不是我们之前创建的地形类,它只是一个没有高度的平面草地而已。initProjection函数和initLight函数还是按照上个项目中代码不变。接下来,就是renderScene函数,代码如下:
// 绘制草地
D3DXMATRIX matrix;
D3DXMatrixTranslation(&matrix, 0.0f, 0.0f, 0.0f);
D3DDevice->SetTransform(D3DTS_WORLD, &matrix);
land->render();
// 绘制玩家
D3DXMATRIX worldMatrix;
player->getWorldMatrix(&worldMatrix);
D3DDevice->SetTransform(D3DTS_WORLD, &worldMatrix);
player->render();
// 调整取景变换矩阵
D3DXMATRIX viewMatrix;
camera->getViewMatrix(&viewMatrix);
D3DDevice->SetTransform(D3DTS_VIEW, &viewMatrix);
运行代码效果如下:
接下来就是我们的update方法,我们使用“WASD”来左右前后平移立方体模型,使用“QE”来左右旋转,使用“CV”来上下平移模型。代码如下:
// 获取键盘信息
input->update();
char key = input->key[0];
// 按键判断
switch (key) {
case 'A':
// 向左移动
player->move(-0.02f);
break;
case 'D':
// 向右移动
player->move(0.02f);
break;
case 'W':
// 向前移动
player->forward(0.02f);
break;
case 'S':
// 向后移动
player->forward(-0.02f);
break;
case 'Q':
// 向左旋转
player->shake(-0.02f);
break;
case 'E':
// 向右旋转
player->shake(0.02f);
break;
case 'C':
// 向上移动
player->fly(0.02f);
break;
case 'V':
// 向下移动
player->fly(-0.02f);
break;
}
运行后效果如下:
最后,我们要讲一个天空盒。它的实现非常简单,它就是一个缺少底部的立方体而已。说白了,就是使用它来模拟周围的天空。我们使用VS2019新建项目“D3D_10_Skybox”。首先我们创建“SkyboxClass.h”和“SkyboxClass.cpp”两个文件,其中“SkyboxClass.h”文件内容如下:
#pragma once
#include "main.h"
// 为天空盒类定义一个FVF灵活顶点格式
struct D3D_SKYBOX_DATA_VERTEX { float x, y, z, u, v; };
// 定义带纹理的顶点类型
#define D3D_SKYBOX_FVF_VERTEX D3DFVF_XYZ | D3DFVF_TEX1
// 天空盒类
class SkyBoxClass {
private:
LPDIRECT3DDEVICE9 D3DDevice; // D3D设备对象
LPDIRECT3DVERTEXBUFFER9 vertexBuffer; // 顶点缓存对象
LPDIRECT3DTEXTURE9 textureArray[5]; // 5个纹理贴图
float length; // 天空盒边长
public:
// 构造函数
SkyBoxClass(LPDIRECT3DDEVICE9 device, float len);
// 析构函数
~SkyBoxClass();
// 初始化天空盒
void init();
// 加载天空盒纹理贴图
void load(const wchar_t* front, const wchar_t* back, const wchar_t* left, const wchar_t* right, const wchar_t* top);
// 渲染天空盒
void render();
};
天空盒的实现原理还是四边形纹理,我们需要构建一个前后左右,有上无下的5个四边形纹理。“SkyboxClass.cpp”如下:
#include "SkyBoxClass.h"
// 天空盒类: 构造函数
SkyBoxClass::SkyBoxClass(LPDIRECT3DDEVICE9 device, float len) {
length = len;
D3DDevice = device;
vertexBuffer = NULL;
for (int i = 0; i < 5; i++) textureArray[i] = NULL;
}
// 天空盒类: 初始化天空盒
void SkyBoxClass::init() {
// 创建顶点缓存对象,20个顶点数据大小
D3DDevice->CreateVertexBuffer(20 * sizeof(D3D_SKYBOX_DATA_VERTEX), 0, D3D_SKYBOX_FVF_VERTEX, D3DPOOL_DEFAULT, &vertexBuffer, 0);
// 顶点数组数据
D3D_SKYBOX_DATA_VERTEX vertices[] = {
// 前面的四个顶点
{ -length / 2, 0.0f, length / 2, 0.0f, 1.0f, },
{ -length / 2, length / 2, length / 2, 0.0f, 0.0f, },
{ length / 2, 0.0f, length / 2, 1.0f, 1.0f, },
{ length / 2, length / 2, length / 2, 1.0f, 0.0f, },
// 背面的四个顶点
{ length / 2, 0.0f, -length / 2, 0.0f, 1.0f, },
{ length / 2, length / 2, -length / 2, 0.0f, 0.0f, },
{ -length / 2, 0.0f, -length / 2, 1.0f, 1.0f, },
{ -length / 2, length / 2, -length / 2, 1.0f, 0.0f, },
// 左面的四个顶点
{ -length / 2, 0.0f, -length / 2, 0.0f, 1.0f, },
{ -length / 2, length / 2, -length / 2, 0.0f, 0.0f, },
{ -length / 2, 0.0f, length / 2, 1.0f, 1.0f, },
{ -length / 2, length / 2, length / 2, 1.0f, 0.0f, },
// 右面的四个顶点
{ length / 2, 0.0f, length / 2, 0.0f, 1.0f, },
{ length / 2, length / 2, length / 2, 0.0f, 0.0f, },
{ length / 2, 0.0f, -length / 2, 1.0f, 1.0f, },
{ length / 2, length / 2, -length / 2, 1.0f, 0.0f, },
// 上面的四个顶点
{ length / 2, length / 2, -length / 2, 1.0f, 0.0f, },
{ length / 2, length / 2, length / 2, 1.0f, 1.0f, },
{ -length / 2, length / 2, -length / 2, 0.0f, 0.0f, },
{ -length / 2, length / 2, length / 2, 0.0f, 1.0f, },
};
// 填充顶点数据
void* ptr;
vertexBuffer->Lock(0, 0, (void**)&ptr, 0);
memcpy(ptr, vertices, sizeof(vertices));
vertexBuffer->Unlock();
}
// 天空盒类: 加载天空盒纹理贴图
void SkyBoxClass::load(const wchar_t* front, const wchar_t* back, const wchar_t* left, const wchar_t* right, const wchar_t* top) {
D3DXCreateTextureFromFile(D3DDevice, front, &textureArray[0]); // 前面
D3DXCreateTextureFromFile(D3DDevice, back, &textureArray[1]); // 后面
D3DXCreateTextureFromFile(D3DDevice, left, &textureArray[2]); // 左面
D3DXCreateTextureFromFile(D3DDevice, right, &textureArray[3]); // 右面
D3DXCreateTextureFromFile(D3DDevice, top, &textureArray[4]); // 上面
}
// 天空盒类: 渲染天空盒
void SkyBoxClass::render() {
// 将纹理颜色混合的第一个参数的颜色值用于输出
D3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
// 纹理颜色混合的第一个参数的值就取纹理颜色值
D3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
// 绘制5个四边形
D3DDevice->SetStreamSource(0, vertexBuffer, 0, sizeof(D3D_SKYBOX_DATA_VERTEX));
D3DDevice->SetFVF(D3D_SKYBOX_FVF_VERTEX);
for (int i = 0; i < 5; i++) {
D3DDevice->SetTexture(0, textureArray[i]);
D3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, i * 4, 2);
}
}
// 天空盒类: 析构函数
SkyBoxClass::~SkyBoxClass() {
SAFE_RELEASE(vertexBuffer);
for (int i = 0; i < 5; i++) SAFE_RELEASE(textureArray[i]);
}
这个四边形纹理的绘制方式与我们之前不太一样。它使用的是D3DPT_TRIANGLESTRIP方式,也就是使用连续的四个顶点来绘制一个四边形,其实本质也是两个三角形组成一个四边形,只不过三角形的顶点顺序不同而已。天空盒封装完毕之后,我们就开始创建InputClass封装类,直接复制我们之前的代码即可。然后即使跟随摄像机类CameraClass,直接复制我们之前的代码即可。这里需要注意一点,由于我们使用3D人物动画模型的原因,在CameraClass的构造函数中,初始化方向向量的时候,要反转一下。
// 初始化向量
camera_up = { 0.0f, -1.0f, 0.0f };
camera_right = { -1.0f, 0.0f, 0.0f };
这样做的原因是3D人物动画模型制作完导出后的朝向问题,大家可以实景想想一下。如果你的人物模型的正面是Z轴正方向或是负方向的话,肯定是不一样的。这样导致你跟随摄像机的方向要调整。当然,这个不是重点。接下来就是MeshClass类的引入,还是之前的旧代码。然后就是我们之前封装的地形类TerrainClass的引入。然后就是我们关于骨骼动画的CAllocateHierarchy和SkinMesh的引入。最后即使我们的玩家类,它需要继承SkinMesh,然后让摄像机跟随移动。这个父类的改变,其实对PlayerClass的影响不是很大。以上所有的类都构建完毕后,就是主文件“main.cpp”的内容,首先是全局变量的声明:
// Direct3D设备指针对象
LPDIRECT3DDEVICE9 D3DDevice = NULL;
// 天空盒
SkyBoxClass* skybox;
// 草地模型(地形)
TerrainClass* land;
// 输入类
InputClass* input;
// 摄像机类
CameraClass* camera;
// 玩家类
float step;
float height;
PlayerClass* player;
然后就是我们的initScene函数,代码如下:
// 初始化天空盒
skybox = new SkyBoxClass(D3DDevice, 10000.0f);
skybox->init();
skybox->load(L"asset/front.jpg", L"asset/back.jpg", L"asset/left.jpg", L"asset/right.jpg", L"asset/top.jpg");
// 草地模型边长为10000,分段为100,因此每两个顶点间隔就是100
float interval = 100.0f;
// 初始化草地模型
land = new TerrainClass(D3DDevice, L"asset/", L"ground.X", interval);
// 初始化输入
input = new InputClass(D3DDevice, hwnd, hInstance);
// 初始化玩家模型
step = 5.0f;
height = 250.0f;
D3DXVECTOR3 pos(3000.0f, height, 3000.0f);
player = new PlayerClass(D3DDevice, L"tiny/", L"tiny.x", pos);
player->isPlay = false;
player->startTime = (float)timeGetTime();
// 初始化摄像机
camera = new CameraClass(pos, 1000.0f);
// 摄像机给到玩家
player->setCamera(camera);
// 线性纹理
D3DDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
D3DDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
// 初始化投影变换
initProjection();
// 初始化光照
initLight();
接下来就是renderScene函数,代码如下:
// 渲染天空盒
D3DXMATRIX matrix;
D3DXMatrixTranslation(&matrix, 0.0f, 0.0f, 0.0f);
D3DDevice->SetTransform(D3DTS_WORLD, &matrix);
skybox->render();
// 绘制草地模型
land->render();
// 绘制玩家
D3DXMATRIX worldMatrix;
player->getWorldMatrix(&worldMatrix);
player->render(worldMatrix);
// 调整取景变换矩阵
D3DXMATRIX viewMatrix;
camera->getViewMatrix(&viewMatrix);
D3DDevice->SetTransform(D3DTS_VIEW, &viewMatrix);
我们先运行效果:
接下来,就是我们的update函数,我们使用键盘操控模型移动且播放动画。代码如下:
// 获取键盘信息
input->update();
char key = input->key[0];
// 按键判断
switch (key) {
case 'W':
// 向前移动
player->forward(-step);
// 根据地形高度设置模型y轴位置
player->_pos.y = land->getTerrainHeight(player->_pos.x, player->_pos.z) + height;
// 播放动画
player->isPlay = true;
break;
case 'S':
// 向后移动
player->forward(step);
// 根据地形高度设置模型y轴位置
player->_pos.y = land->getTerrainHeight(player->_pos.x, player->_pos.z) + height;
// 播放动画
player->isPlay = true;
break;
case 'Q':
// 向左旋转
player->shake(-0.02f);
break;
case 'E':
// 向右旋转
player->shake(0.02f);
break;
case 'F':
// 向上旋转
player->nod(0.02f);
break;
case 'G':
// 向下旋转
player->nod(-0.02f);
break;
default:
// 停止动画
player->isPlay = false;
break;
}
运行代码,我们使用“W”向前移动模型,使用“QE”来旋转模型,移动过程中伴随动画。
可以看到,我们的地形也起作用了。到这里,我们关于DirectX的基础课程就讲解全部完毕了。该课程主要是让大家了解游戏开发中的一些基础理论知识。如果能够看懂附带的DirectX项目源码,则能够加深对这些理论知识的掌握。
本课程的所有代码案例下载地址:
备注:这是我们游戏开发系列教程的第二个课程,这个课程主要使用C++语言和DirectX来讲解游戏开发中的一些基础理论知识。学习目标主要依理解理论知识为主,附带的C++代码能够看懂且运行成功即可,不要求我们使用DirectX来开发游戏。课程中如果有一些错误的地方,请大家留言指正,感激不尽!