本章将展示如何创建一个在游戏中使用的通用背景类。
本章内容包括:
- 背景的4种类型
- 如何向游戏引擎添加背景支持
- 如何与动画子动画一起使用动画背景,以便模拟一个星际太空
了解游戏背景的类型
下面是4种主要的背景类型:
- 纯色背景
- 图像背景
- 动画背景
- 滚动背景
纯色背景
纯色背景是只有一种纯色的背景,在游戏中较少使用。例如,如果在开发一个足球游戏时,创建一个纯绿色背景来表示球场上的草是可以的,但是使用一个带草状纹理的图像效果会好得多,并且这不需要更多的开发工作。
纯色背景很有用的一种情况是在测试游戏时。例如,如果有一个包含复杂背景图像的游戏,子画面很容易混在一起,这时在一个简单背景上查看它们会更容易调整这些子画面。通过使用一个对比度强的纯色背景来临时替换背景图像,就可以很容易地了解子画面的情况并修改它们存在的问题。
图像背景
图像背景比纯色背景进了一步,它们使用一个位图图像而不是一种纯色来代表游戏的背景。读者已经在前面开发的游戏中使用了图像背景,因此希望读者能够了解它们的用途。
使用图像背景的主要工作是创建图像本身,从编程的角度来说,游戏引擎中的Bitmap 位图类已经处理了使用图像背景的大多数工作。
使用图像背景的问题在于,它们是静态的,这意味着它们不会移动。例如在 游戏编程入门(7):使用子画面动画移动对象 的Planets实例中使用的银河背景,如果银河能够有一点旋转,还不时能产生一颗流星,那么这个背景则会更真实。改进图像背景的一种方法是使用动画背景。
动画背景
动画背景是随着时间以某种方式改变其外观的背景,这有一点类似于动画子画面。不过,动画背景不一定涉及一系列的帧图像。创建通过自定义代码实现动画效果的动画背景是完全可能的。
实现动画背景的关键是提供某种机制来更新和绘制背景。
滚动背景
滚动背景是一个图像或者一组图形对象在屏幕上飘动或者滚动。理解滚动背景的最佳方式是想象一个比游戏屏幕大得多的背景,这就意味着游戏屏幕提供了查看一部分背景的视图。要想查看背景的其他部分,则必须将视图滚动到另一部分。
上面这幅图片,显示了游戏屏幕只显示一个更大背景的一部分。这种背景在冒险游戏中应用的非常多,玩家控制一个角色在很大的虚拟世界中走动。正如读者可能想到的,开发滚动背景要比开发其他类型的背景复杂的多,这是因为他们涉及多得多的游戏逻辑。例如,背景必须在玩家移动角色时响应,更不用说还必须移动子画面,使他们看起来是与背景一起滚动的。不仅如此,滚动背景还经常必须能够环绕,这样角色就不会碰到边界。
在2D游戏中经常使用的一种很有趣的滚动背景是视差滚动背景,这是一种以不同滚动速度滚动的背景,视差滚动还需要使用多层背景图像。例如前景中的建筑物、中景中的树木以及背景中的山脉。其思想是通过以不同的速度移动各个图像来给人一种深度的错觉。因此,因为山脉离的最远,所以它们的移动是最慢的,树木的移动则要快一点,而建筑物的移动是最快的。这模拟了在真实世界中我们经过不同距离的物体时观察到的移动效果。
向游戏引擎添加背景支持
目前我们还不打算创建滚动背景,所以现在我们只需要在游戏引擎中结合3种背景:纯色背景、图像背景以及动画背景。很容易在一个单独的类中实现前两种背景,但第三种背景(动画背景)需要它自己的一个自定义类。因此,我们将创建两个新的类,它们将作为游戏引擎的一部分。
第一个类是一个基本的背景类,包括纯色背景和图像背景,而第二个类包含一个特定的动画背景类型,使用闪烁的星星显示一个布满星星的夜空。布满星星的背景足以展示如何创建一个动画背景。
创建基本的背景类(纯色背景和图像背景)
在Background 类中包括了支持纯色背景和图像背景的基本背景类。Background 类很灵活,可以使用这一个类来创建两种背景,对于要创建哪一种背景,是由使用的构造函数决定的。
下面是Backgound 类的代码:
class Background
{
protected:
// 成员变量
int m_iWidth, m_iHeight;//背景宽度和高度
COLORREF m_crColor; //背景颜色
Bitmap* m_pBitmap; //背景位图
public:
// 构造函数/析构函数
Background(int iWidth, int iHeight, COLORREF crColor);//纯色背景
Background(Bitmap* pBitmap); //图像背景
virtual ~Background();
// 常规方法
virtual void Update(); //更新背景外观
virtual void Draw(HDC hDC); //绘制背景
// 访问方法
int GetWidth()
{
return m_iWidth;
};
int GetHeight()
{
return m_iHeight;
};
};
下面看看Background( ) 构造函数和析构函数的具体代码:
// 创建纯色背景
Background::Background(int iWidth, int iHeight, COLORREF crColor)
{
// 初始化成员变量
m_iWidth = iWidth;
m_iHeight = iHeight;
m_crColor = crColor;
m_pBitmap = NULL;
}
// 创建位图背景
Background::Background(Bitmap* pBitmap)
{
// 初始化成员变量
m_crColor = 0;
m_pBitmap = pBitmap;
m_iWidth = pBitmap->GetWidth();
m_iHeight = pBitmap->GetHeight();
}
Background::~Background()
{
}
Background 的Update( )和Draw( )方法如下所示:
//-----------------------------------------------------------------
// Background 常规方法
//-----------------------------------------------------------------
// 更新背景
void Background::Update()
{
// 因为基本背景没有使用动画,所以不做任何事情
}
// 绘制背景
void Background::Draw(HDC hDC)
{
if (m_pBitmap != NULL) //位图背景
m_pBitmap->Draw(hDC, 0, 0);
else //纯色背景(用纯色画刷绘制一个矩形)
{
RECT rect = { 0, 0, m_iWidth, m_iHeight };
HBRUSH hBrush = CreateSolidBrush(m_crColor);
FillRect(hDC, &rect, hBrush);
DeleteObject(hBrush);
}
}
创建动画背景类
我们创建一个特定的动画背景类,StarryBackground 类,这个类表示一个星空的动画背景。
StarryBackground 类是从 Background 类派生的,这意味着它继承了Background 类的成员变量和方法。不过StarryBackground 类还需要它自己的构造函数和自己的Update( )和Draw( )方法。StarryBackground 类还增加了一些新的成员变量,用来管理背景中闪烁的星星。
StarryBackground 类定义如下:
//-----------------------------------------------------------------
// Starry Background 星空动画背景类
//-----------------------------------------------------------------
class StarryBackground : Background
{
protected:
// 成员变量
int m_iNumStars; //星星数量
int m_iTwinkleDelay; //星星闪烁的频率,延迟越长,闪烁越慢
POINT m_ptStars[100]; //各个星星的位置
COLORREF m_crStarColors[100];//各个星星的颜色
public:
// 构造函数/析构函数
StarryBackground(int iWidth, int iHeight, int iNumStars = 100,
int iTwinkleDelay = 50);
virtual ~StarryBackground();
// 常规方法
virtual void Update();
virtual void Draw(HDC hDC);
};
StarryBackground 类的构造函数和析构函数如下所示:
//-----------------------------------------------------------------
// StarryBackground 构造函数/析构函数
//-----------------------------------------------------------------
StarryBackground::StarryBackground(int iWidth, int iHeight, int iNumStars,
int iTwinkleDelay) : Background(iWidth, iHeight, 0)
{
// 初始化成员变量
m_iNumStars = min(iNumStars, 100);
m_iTwinkleDelay = iTwinkleDelay;
// 创建星星
for (int i = 0; i < iNumStars; i++)
{
m_ptStars[i].x = rand() % iWidth;
m_ptStars[i].y = rand() % iHeight;
m_crStarColors[i] = RGB(128, 128, 128);
}
}
StarryBackground::~StarryBackground()
{
}
StarryBackground的Update( )和Draw( )方法如下:
//-----------------------------------------------------------------
// StarryBackground 常规方法
//-----------------------------------------------------------------
void StarryBackground::Update()
{
// 随机更改星星的灰度,使之闪烁
int iRGB;
for (int i = 0; i < m_iNumStars; i++)
if ((rand() % m_iTwinkleDelay) == 0)
{
iRGB = rand() % 256;
m_crStarColors[i] = RGB(iRGB, iRGB, iRGB); //只要红、蓝、绿三个颜色相等,星星就是灰色的
}
}
void StarryBackground::Draw(HDC hDC)
{
// 绘制纯黑色背景
RECT rect = { 0, 0, m_iWidth, m_iHeight };
HBRUSH hBrush = CreateSolidBrush(RGB(0, 0, 0));
FillRect(hDC, &rect, hBrush);
DeleteObject(hBrush);
// 绘制星星
for (int i = 0; i < m_iNumStars; i++)
SetPixel(hDC, m_ptStars[i].x, m_ptStars[i].y, m_crStarColors[i]);
}
StarryBackground 类的Draw( )方法用来绘制背景,然后绘制各个星星。黑色背景是调用FillRect( )绘制的。后面则用循环,通过调用SetPixel( )来绘制每一刻单独的星星。
SetPixel( )是一个Win32 函数,它将单个像素设定为指定的颜色。
开发Roids 示例
Roids 程序模拟了一个行星太空,在一个动画星星背景上显示几个动画行星子画面。
注意:若出现编译错误,请在项目设置->连接->对象/库模块中 加入 msimg32.lib winmm.lib
Roids 目录结构和效果图
Roids 目录结构:
Roids 效果图:
(行星动态的滚动,背景星星闪烁)
编写游戏代码
Roids.h
#pragma once
//-----------------------------------------------------------------
// 包含文件
//-----------------------------------------------------------------
#include <windows.h>
#include "Resource.h"
#include "GameEngine.h"
#include "Bitmap.h"
#include "Sprite.h"
#include "Background.h"
//-----------------------------------------------------------------
// 全局变量
//-----------------------------------------------------------------
HINSTANCE g_hInstance; //程序实例句柄
GameEngine* g_pGame; //游戏引擎指针
HDC g_hOffscreenDC; //屏幕外设备环境
HBITMAP g_hOffscreenBitmap;//屏幕外位图
Bitmap* g_pAsteroidBitmap; //行星位图(用于创建动画子画面)
StarryBackground* g_pBackground; //星空动画背景
GameStart( )
// 开始游戏
void GameStart(HWND hWindow)
{
// 生成随机数生成种子
srand(GetTickCount());
// 创建屏幕外设备环境和位图
g_hOffscreenDC = CreateCompatibleDC(GetDC(hWindow));
g_hOffscreenBitmap = CreateCompatibleBitmap(GetDC(hWindow),
g_pGame->GetWidth(), g_pGame->GetHeight());
SelectObject(g_hOffscreenDC, g_hOffscreenBitmap);
// 创建并加载行星位图
HDC hDC = GetDC(hWindow);
g_pAsteroidBitmap = new Bitmap(hDC, IDB_ASTEROID, g_hInstance);
// 创建布满星星的背景
g_pBackground = new StarryBackground(500, 400);
// 创建行星子画面
RECT rcBounds = { 0, 0, 500, 400 };
Sprite* pSprite;
pSprite = new Sprite(g_pAsteroidBitmap, rcBounds, BA_WRAP);
pSprite->SetNumFrames(14);
pSprite->SetFrameDelay(1);
pSprite->SetPosition(250, 200);
pSprite->SetVelocity(-3, 1);
g_pGame->AddSprite(pSprite);
pSprite = new Sprite(g_pAsteroidBitmap, rcBounds, BA_WRAP);
pSprite->SetNumFrames(14);
pSprite->SetFrameDelay(2);
pSprite->SetPosition(250, 200);
pSprite->SetVelocity(3, -2);
g_pGame->AddSprite(pSprite);
pSprite = new Sprite(g_pAsteroidBitmap, rcBounds, BA_WRAP);
pSprite->SetNumFrames(14);
pSprite->SetFrameDelay(3);
pSprite->SetPosition(250, 200);
pSprite->SetVelocity(-2, -4);
g_pGame->AddSprite(pSprite);
}
注意,各个子画面的帧数设置为14,表示在行星的图像中存储了14个帧图像。此外,各个子画面的帧延迟设置也是不同的,这样行星看起来就是以不同的速度翻滚。
GamePaint( )
// 绘制游戏
void GamePaint(HDC hDC)
{
// 绘制背景
g_pBackground->Draw(hDC);
// 绘制子画面
g_pGame->DrawSprites(hDC);
}
GameCycle( )
// 游戏循环
void GameCycle()
{
// 更新背景
g_pBackground->Update();
// 更新子画面
g_pGame->UpdateSprites();
// 获得用于重新绘制游戏的设备环境
HWND hWindow = g_pGame->GetWindow();
HDC hDC = GetDC(hWindow);
// 在屏幕外设备环境上绘制游戏
GamePaint(g_hOffscreenDC);
// 将屏幕外位图块传送到游戏屏幕
BitBlt(hDC, 0, 0, g_pGame->GetWidth(), g_pGame->GetHeight(),
g_hOffscreenDC, 0, 0, SRCCOPY);
// 清理
ReleaseDC(hWindow, hDC);
}