本章我们将编写坚持一百秒游戏,玩家通过鼠标控制火箭躲避一架UFO和越来越多的反弹子弹,效果如图所示。
首先学习图片的导入和显示,并利用结构体实现一颗反弹的子弹;然后学习面向对象编程的知识,利用类和对象实现新版本的子弹;接着添加火箭类,并实现子弹和火箭的碰撞检测、坚持时间和火箭多条生命的显示,学习如何添加音乐音效;最后学习继承的概念,在子弹类的基础上快速实现智能飞碟类。
知乎视频 www.zhihu.com讲解视频:
知乎视频 www.zhihu.com最终代码:
#include <graphics.h>
#include <conio.h>
#include <time.h>
#include "EasyXPng.h" // 用于显示带透明通道的png图片
// 引用 Windows Multimedia API
#pragma comment(lib,"Winmm.lib")
#define WIDTH 560 // 画面宽度
#define HEIGHT 800 // 画面高度
#define MaxBulletNum 200 // 最多子弹个数
void sleep(DWORD ms) // 精确延时函数
{
static DWORD oldtime = GetTickCount();
while(GetTickCount() - oldtime < ms)
Sleep(1);
oldtime = GetTickCount();
}
void PlayMusicOnce(TCHAR fileName[80]) // 播放一次音乐函数
{
TCHAR cmdString1[50];
_stprintf(cmdString1, _T("open %s alias tmpmusic"), fileName); // 生成命令字符串
mciSendString(_T("close tmpmusic"), NULL, 0, NULL); // 先把前面一次的音乐关闭
mciSendString(cmdString1, NULL, 0, NULL); // 打开音乐
mciSendString(_T("play tmpmusic"), NULL, 0, NULL); // 仅播放一次
}
class Rocket // 定义火箭类
{
public:
IMAGE im_rocket; // 火箭图像
IMAGE im_blowup; // 爆炸图像
float x,y; // 火箭坐标
float width,height; // 火箭图片的宽度、高度
int liveSecond; // 火箭存活了多长时间
int life; // 火箭有几条命
void draw() // 显示火箭相关信息
{
// 窗口左上角显示life个火箭图片,表示火箭生命数
for (int i=0;i<life;i++)
putimagePng(i*width*0.9,0,&im_rocket);
// 窗口正上方显示坚持了多少秒
TCHAR s[20];
setbkmode(TRANSPARENT); // 文字字体透明
_stprintf(s, _T("%d秒"), liveSecond);
settextcolor(WHITE); // 设定文字颜色
settextstyle(40, 0, _T("黑体"));// 设定文字大小、样式
outtextxy(WIDTH*0.85, 20, s); // 输出文字内容
if (life>0) // 根据有命没命,显示不同的图片
putimagePng(x - width/2,y-height/2,&im_rocket); // 游戏中显示火箭图片
else
putimagePng(x - width/2,y-height/2,&im_blowup); // 游戏中显示爆炸图片
}
void update(float mx,float my) // 根据输入的坐标更新火箭的位置
{
x = mx;
y = my;
}
void updateWhenLifeLost() // 当火箭减命时执行的操作
{
PlayMusicOnce(_T("explode.mp3")); // 播放一次爆炸音效
life --; // 生命减少
}
};
class Bullet // 定义子弹类
{
public:
IMAGE im_bullet; // 子弹图像
float x,y; // 子弹坐标
float vx,vy; // 子弹速度
float radius; // 接近球体的子弹半径大小
void draw()// 显示子弹
{
putimagePng(x - radius,y-radius,&im_bullet);
}
void update() // 更新子弹的位置、速度
{
x += vx;
y += vy;
if (x<=0 || x>=WIDTH)
vx = -vx;
if (y<=0 || y>=HEIGHT)
vy = -vy;
}
int isCollideRocket(Rocket rocket) // 判断子弹是否和火箭碰撞
{
float distance_x = abs(rocket.x - x);
float distance_y = abs(rocket.y - y);
if ( distance_x < rocket.width/2 && distance_y < rocket.height/2 )
return 1; // 发生碰撞返回1
else
return 0; // 不碰撞返回0
}
};
class SmartUFO: public Bullet // 智能飞碟类,由Bullet类派生出来
{
public:
void updateVelforTarge(Rocket targetRocket) // 让飞碟的速度瞄向目标火箭
{
float scalar = 1*rand()/double(RAND_MAX) + 1; // 速度大小有一定的随机性
if (targetRocket.x>x) // 目标在飞碟左边,飞碟x方向速度向右
vx = scalar;
else if (targetRocket.x<x) // 目标在飞碟右边,飞碟x方向速度向左
vx = -scalar;
if (targetRocket.y>y) // 目标在飞碟下方,飞碟y方向速度向下
vy = scalar;
else if (targetRocket.y<y) // 目标在飞碟上方,飞碟y方向速度向上
vy = -scalar;
}
};
IMAGE im_bk,im_bullet,im_rocket,im_blowup,im_UFO; // 定义图像对象
Bullet bullet[MaxBulletNum]; // 定义子弹对象数组
Rocket rocket; // 定义火箭对象
SmartUFO ufo; // 定义飞碟对象
int bulletNum = 0; // 已有子弹的个数
void startup() // 初始化函数
{
mciSendString(_T("open game_music.mp3 alias bkmusic"), NULL, 0, NULL);//打开背景音乐
mciSendString(_T("play bkmusic repeat"), NULL, 0, NULL); // 循环播放
srand(time(0)); // 初始化随机种子
loadimage(&im_bk, _T("background.png")); // 导入背景图片
loadimage(&im_bullet, _T("bullet.png")); // 导入子弹图片
loadimage(&im_rocket, _T("rocket.png")); // 导入火箭图片
loadimage(&im_blowup, _T("blowup.png")); // 导入爆炸图片
loadimage(&im_UFO, _T("ufo.png")); // 导入飞碟图片
// 对飞碟的一些成员变量初始化
ufo.x = WIDTH/2; // 设置飞碟位置
ufo.y = 10;
ufo.im_bullet = im_UFO; // 设置飞碟图片
ufo.radius = im_UFO.getwidth()/2; // 设置飞碟半径大小
ufo.updateVelforTarge(rocket); // 更新飞碟的速度
// 对rocket一些成员变量初始化
rocket.im_rocket = im_rocket; // 设置火箭图片
rocket.im_blowup = im_blowup; // 设置火箭爆炸图片
rocket.width = im_rocket.getwidth(); // 设置火箭宽度
rocket.height = im_rocket.getheight(); // 设置火箭高度
rocket.life = 5; // 火箭初始5条命
initgraph(WIDTH,HEIGHT); // 新开一个画面
BeginBatchDraw(); // 开始批量绘制
}
void show() // 绘制函数
{
putimage(0, 0, &im_bk); // 显示背景
ufo.draw(); // 显示飞碟
for (int i=0;i<bulletNum;i++)
bullet[i].draw(); // 显示已有的子弹
rocket.draw(); // 显示火箭及相关信息
FlushBatchDraw(); // 批量绘制
sleep(10); // 暂停
}
void updateWithoutInput() // 和输入无关的更新
{
if (rocket.life<=0) // 火箭没有命了,不处理
return; // 直接返回
static int lastSecond = 0; // 记录前一次程序运行了多少秒
static int nowSecond = 0; // 记录当前程序运行了多少秒
static clock_t start = clock(); // 记录第一次运行时刻
clock_t now = clock(); // 获得当前时刻
// 计算程序目前一共运行了多少秒
nowSecond =( int(now - start) / CLOCKS_PER_SEC);
rocket.liveSecond = nowSecond; // 火箭生成了多少秒赋值
if (nowSecond==lastSecond+1) // 时间过了1秒钟,更新下飞碟的速度
ufo.updateVelforTarge(rocket); // ufo速度方向瞄准火箭
if (nowSecond==lastSecond+2) // 时间过了2秒钟,新增一颗子弹
{
lastSecond = nowSecond; // 更新下lastSecond变量
// 如果没有超出最大子弹数目的限制,增加一颗新的子弹
if (bulletNum<MaxBulletNum)
{
bullet[bulletNum].x = WIDTH/2; // 子弹初始位置
bullet[bulletNum].y = 10;
float angle = (rand()/double(RAND_MAX)-0.5)*0.9*PI;
float scalar = 2*rand()/double(RAND_MAX) + 2;
bullet[bulletNum].vx = scalar*sin(angle); // 子弹随机速度
bullet[bulletNum].vy = scalar*cos(angle);
bullet[bulletNum].im_bullet = im_bullet; // 设置子弹图像
bullet[bulletNum].radius = im_bullet.getwidth()/2; // 子弹半径为图片宽度一半
}
bulletNum++; // 子弹数目加一
}
for (int i=0;i<bulletNum;i++) // 对所有已有的子弹
{
bullet[i].update(); // 更新子弹的位置、速度
if (bullet[i].isCollideRocket(rocket)) // 判断子弹是否和火箭碰撞
{
rocket.updateWhenLifeLost(); // 火箭减命相关操作
bullet[i].x = 5; // 当前子弹移开,防止重复碰撞
bullet[i].y = 5;
break; // 火箭已炸,不用再和其他子弹比较了
}
}
ufo.update(); // 更新飞碟的位置、速度
if (ufo.isCollideRocket(rocket)) // 判断飞碟是否和火箭碰撞
{
rocket.updateWhenLifeLost(); // 当火箭减命时执行的操作
ufo.x = 5; // 当前飞碟移开,防止重复碰撞
ufo.y = 5;
}
}
void updateWithInput() // 和输入相关的更新
{
if (rocket.life<=0) // 火箭没有命了,不处理
return; // 直接返回
MOUSEMSG m; // 定义鼠标消息
while (MouseHit()) // 检测当前是否有鼠标消息
{
m = GetMouseMsg();
if(m.uMsg == WM_MOUSEMOVE) // 到鼠标移动时
rocket.update(m.x,m.y); // 火箭的位置等于鼠标所在的位置
}
}
int main() // 主函数
{
startup(); // 初始化
while (1) // 重复运行
{
show(); // 绘制
updateWithoutInput(); // 和输入无关的更新
updateWithInput(); // 和输入相关的更新
}
return 0;
}