参考
- 《C和C++游戏趣味编程》 童晶
坚持一百秒游戏
游戏主要思路是玩家通过鼠标控制火箭躲避一架UFO和越来越多的反弹子弹
背景显示
利用事先准备的background.png图片,在画面中显示背景图片:
#include <graphics.h>
#include <conio.h>
#define WIDTH 560
#define HEIGHT 800
int main()
{
IMAGE im_bk; // 定义图像对象
loadimage(&im_bk, _T("background.png"));
initgraph(WIDTH, HEIGHT);
putimage(0, 0, &im_bk); // 显示背景图片
_getch();
return 0;
}
显示火箭
同背景显示一样,先定义im_rocket图像对象,然后导入火箭图片rocket.png。但火箭图片带有透明通道,使用putimage()绘制会使透明部分显示为黑色,于是导入EasyXPng.h头文件解决:
子弹
定义子弹类,在类中增加IMAGE图片对象,然后定义一个draw()成员函数显示子弹,并定义一个update()成员函数用于更新子弹的位置、速度:
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;
}
}
};
IMAGE im_bk, im_bullet; // 定义图像对象
Bullet bullet;
void startup()
{
loadimage(&im_bk, _T("background.png"));
loadimage(&im_bullet, _T("bullet.png"));
bullet.x = WIDTH / 2;
bullet.y = HEIGHT / 2;
bullet.vx = 2;
bullet.vy = 2;
bullet.im_bullet = im_bullet;
bullet.radius = im_bullet.getwidth() / 2; // 设置子弹的半径为其图片宽度的一半
initgraph(WIDTH, HEIGHT);
BeginBatchDraw();
}
void show()
{
putimage(0, 0, &im_bk); // 显示背景
bullet.draw(); // 显示子弹
FlushBatchDraw(); // 批量绘制
Sleep(10);
}
void updateWithoutInput()
{
bullet.update();
}
int main()
{
startup();
while (1)
{
show();
updateWithoutInput();
}
return 0;
}
每隔2秒增加一颗子弹
定义数组bullet[]存储现有的子弹,每隔2秒随机初始化一颗新子弹,并更新现有子弹的速度和位置,并显示:
Bullet bullet[MaxBulletNum];
int bulletNum = 0;
void updateWithoutInput()
{
static int lastSecond = 0; // 记录前一次程序运行了多少秒
static int nowSecond = 0; // 记录当前程序运行了多少秒
static clock_t start = clock(); // 记录第一次运行时刻
clock_t now = clock(); // 获得当前时刻
nowSecond = (int(now - start) / CLOCKS_PER_SEC);
if (nowSecond == lastSecond + 2)
{
lastSecond = nowSecond;
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();
}
}
火箭
定义火箭类,与子弹类类似:
class Rocket
{
public:
IMAGE im_rocket;
float x, y; // 火箭坐标
float width, height; // 火箭图片的宽度、高度
void draw()
{
putimagePng(x - width / 2, y - height / 2, &im_rocket);
}
void update(float mx, float my)
{
x = mx;
y = my;
}
};
在startup()中初始化火箭:
loadimage(&im_rocket, _T("rocket.png"));
rocket.im_rocket = im_rocket;
rocket.width = im_rocket.getwidth();
rocket.height = im_rocket.getheight();
在updateWithInput()中,当鼠标移动时,更新火箭的位置:
void updateWithInput()
{
ExMessage e;
while (peekmessage(&e))
{
if (e.message == WM_MOUSEMOVE)
{
rocket.update(e.x, e.y);
}
}
}
碰撞判断与火箭爆炸
在Bullet类中添加成员函数isCollideRocket():
int isCollideRocket(Rocket rocket)
{
float distance_x = fabs(rocket.x - x);
float distance_y = fabs(rocket.y - y);
if (distance_x < rocket.width / 2 && distance_y < rocket.height / 2)
{
return 1;
}
else
{
return 0;
}
}
在火箭类中添加火箭爆炸图像对象,并修改显示火箭的draw():
IMAGE im_blowup;
int islive;
void draw()
{
if (islive > 0)
{
putimagePng(x - width / 2, y - height / 2, &im_rocket);
}
else
{
putimagePng(x - width / 2, y - height / 2, &im_blowup);
}
}
初始化时火箭的islive设为1,在updateWithoutInput()中遍历所有子弹,任一子弹和火箭发生碰撞,则将火箭的islive设为0:
for (int i = 0; i < bulletNum; i++)
{
bullet[i].update();
if (bullet[i].isCollideRocket(rocket))
{
rocket.islive = 0;
bullet[i].x = 5; // 当前子弹移开,防止重复碰撞
bullet[i].y = 5;
break;
}
}
坚持时间与多条生命的显示
在Rocket类添加成员变量liveSecond,记录火箭存活了多少秒,并在draw()中绘制:
int liveSecond;
void draw()
{
if (islive > 0)
{
putimagePng(x - width / 2, y - height / 2, &im_rocket);
}
else
{
putimagePng(x - width / 2, y - height / 2, &im_blowup);
}
TCHAR s[20];
setbkmode(TRANSPARENT);
swprintf_s(s, _T("%d秒"), liveSecond);
settextcolor(WHITE);
settextstyle(40, 0, _T("黑体"));
outtextxy(WIDTH * 0.85, 20, s);
}
然后,添加成员变量life记录火箭的生命数,添加成员函数updateWhenLifeLoss()处理火箭生命减少时的操作,另外,在draw()中实现在窗口左上角显示火箭生命数:
int life;
void updateWithLifeLost()
{
life--;
}
void draw()
{
for (int i = 0; i < life; i++) // 左上角显示life个火箭图片
{
putimagePng(i * width * 0.9, 0, &im_rocket);
}
TCHAR s[20]; // 窗口正上方显示坚持了多少秒
setbkmode(TRANSPARENT);
swprintf_s(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);
}
}
添加音效
导入winmm.lib库以支持对windows多媒体的编程:
#pragma comment(lib, "Winmm.lib")
背景音乐
在startup()中添加代码,重复播放game_music.mp3:
mciSendString(_T("open game_music.mp3 alias bkmusic"), NULL, 0, NULL); // 打开背景音乐
mciSendString(_T("play bkmusic repeat"), NULL, 0, NULL); // 循环播放
爆炸音效
定义播放一次音乐函数:
void PlayMusicOnce(TCHAR fileName[80])
{
TCHAR cmdString1[50];
swprintf_s(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); // 仅播放一次
}
在Rocket()类的updateWhenLifeLost()成员函数中调用:
void updateWithLifeLost()
{
PlayMusicOnce((TCHAR*)_T("explode.mp3"));
life--;
}
添加智能飞碟类
定义新类SmartUFO,继承自子弹类:
class SmartUFO : public Bullet
{
public:
void updateVelforTarget(Rocket targetRocket)
{
float scalar = 1 * rand() / double(RAND_MAX) + 1; // 让飞碟的速度瞄向目标火箭
if (targetRocket.x > x)
{
vx = scalar;
}
else if (targetRocket.x < x)
{
vx = -scalar;
}
if (targetRocket.y > y)
{
vy = scalar;
}
else if (targetRocket.y < y)
{
vy = -scalar;
}
}
};
在startup()中初始化飞碟:
ufo.x = WIDTH / 2;
ufo.y = 10;
ufo.im_bullet = im_UFO;
ufo.radius = im_UFO.getwidth() / 2;
ufo.updateVelforTarget(rocket);
在updateWithoutInput()中,每隔1秒,根据火箭位置设定ufo的速度:
if (nowSecond == lastSecond + 1)
{
ufo.updateVelforTarget(rocket);
}
判断飞碟是否和火箭碰撞,如果碰撞就让火箭减命:
ufo.update();
if (ufo.isCollideRocket(rocket))
{
rocket.updateWithLifeLost();
ufo.x = 5;
ufo.y = 5;
}
完整代码
#include <graphics.h>
#include <conio.h>
#include <time.h>
#include "EasyXPng.h"
#define WIDTH 560
#define HEIGHT 800
#define MaxBulletNum 200
#pragma comment(lib, "Winmm.lib")
void sleep(DWORD ms) // 精确延时函数
{
static DWORD oldtime = GetTickCount();
while (GetTickCount() - oldtime < ms)
{
Sleep(1);
}
oldtime = GetTickCount();
}
void PlayMusicOnce(TCHAR fileName[80])
{
TCHAR cmdString1[50];
swprintf_s(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()
{
for (int i = 0; i < life; i++) // 左上角显示life个火箭图片
{
putimagePng(i * width * 0.9, 0, &im_rocket);
}
TCHAR s[20]; // 窗口正上方显示坚持了多少秒
setbkmode(TRANSPARENT);
swprintf_s(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 updateWithLifeLost()
{
PlayMusicOnce((TCHAR*)_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 = fabs(rocket.x - x);
float distance_y = fabs(rocket.y - y);
if (distance_x < rocket.width / 2 && distance_y < rocket.height / 2)
{
return 1;
}
else
{
return 0;
}
}
};
class SmartUFO : public Bullet
{
public:
void updateVelforTarget(Rocket targetRocket)
{
float scalar = 1 * rand() / double(RAND_MAX) + 1; // 让飞碟的速度瞄向目标火箭
if (targetRocket.x > x)
{
vx = scalar;
}
else if (targetRocket.x < x)
{
vx = -scalar;
}
if (targetRocket.y > y)
{
vy = scalar;
}
else if (targetRocket.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()
{
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"));
rocket.im_rocket = im_rocket;
rocket.im_blowup = im_blowup;
rocket.width = im_rocket.getwidth();
rocket.height = im_rocket.getheight();
rocket.life = 5;
ufo.x = WIDTH / 2;
ufo.y = 10;
ufo.im_bullet = im_UFO;
ufo.radius = im_UFO.getwidth() / 2;
ufo.updateVelforTarget(rocket);
initgraph(WIDTH, HEIGHT);
BeginBatchDraw();
mciSendString(_T("open game_music.mp3 alias bkmusic"), NULL, 0, NULL); // 打开背景音乐
mciSendString(_T("play bkmusic repeat"), NULL, 0, NULL); // 循环播放
}
void show()
{
putimage(0, 0, &im_bk); // 显示背景
for (int i = 0; i < bulletNum; i++)
{
bullet[i].draw(); // 显示子弹
}
rocket.draw();
ufo.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 + 2)
{
lastSecond = nowSecond;
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++;
}
if (nowSecond == lastSecond + 1)
{
ufo.updateVelforTarget(rocket);
}
for (int i = 0; i < bulletNum; i++)
{
bullet[i].update();
if (bullet[i].isCollideRocket(rocket))
{
rocket.updateWithLifeLost();
bullet[i].x = 5; // 当前子弹移开,防止重复碰撞
bullet[i].y = 5;
break;
}
}
ufo.update();
if (ufo.isCollideRocket(rocket))
{
rocket.updateWithLifeLost();
ufo.x = 5;
ufo.y = 5;
}
}
void updateWithInput()
{
if (rocket.life <= 0)
{
return;
}
ExMessage e;
while (peekmessage(&e))
{
if (e.message == WM_MOUSEMOVE)
{
rocket.update(e.x, e.y);
}
}
}
int main()
{
startup();
while (1)
{
show();
updateWithoutInput();
updateWithInput();
}
return 0;
}