游戏: flappy bird(草稿版)
游戏素材
- 在网上找了几张素材图片
- 将图片转成bmp格式
- 对图片进行一些处理,方便后面操作
代码部分
先放图:
大体来说,用到的函数就是这些
代码详解
#include
#pragma comment(lib,"winmm.lib") //调用PlaySound函数所需库文件
#pragma comment(lib,"Msimg32.lib") //添加使用TransparentBlt函数所需的库文件
#define WINDOW_WIDTH 450 //为窗口宽度定义的宏,以方便在此处修改窗口宽度
#define WINDOW_HEIGHT 650 //为窗口高度定义的宏,以方便在此处修改窗口高度
#define TUBE_WIDTH 52 //管道的宽度为52像素
#define TUBE_HEIGHT 320 //管道的高度为320像素
#define BIRD_WIDTH 48 //鸟的宽度
#define BIRD_HEIGHT 48 //鸟的高度
#define BIRD_X 100 //鸟的X坐标
#define GRAVITY 2 //重力加速度
#define PASSWAY 130 //管道中间距离130
这部分就是进行一些库的包括,以及宏的定义
int tube_x[3] = { WINDOW_WIDTH ,WINDOW_WIDTH + 167,WINDOW_WIDTH + 167 * 2 }, i; //管道左上角的水平位置
int randomNumber[3] = { 0 }, loopNumber = 0, score = 0, rgb[3] = {0};
int bird_y = 250, birdSpeed = 0;
HDC g_hdc = NULL, g_mdc = NULL, g_bufdc = NULL; //全局变量环境句柄和全局内存DC句柄
DWORD g_tPre = 0, g_tNow = 0, g_tGra = 0, g_tStart = 0,g_tRGB=0;
HBITMAP g_hBackground = NULL, g_hGround = NULL, g_hTubeDown = NULL, g_hTubeUp = NULL, g_hBird = NULL;
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);//窗口过程函数
BOOL Game_Init(HWND hwnd); //在此函数中进行资源的初始化
VOID Game_Paint(HWND hwnd); //在此函数中进行绘图代码的书写
BOOL Game_CleanUp(HWND hwnd); //在此函数中进行资源的清理
声明变量和函数
WinMain函数内部
PlaySound("GameMedia\\8bitMusic.wav", NULL, SND_FILENAME | SND_ASYNC);
播放背景音乐
WNDCLASSEX wndClass = { 0 };
wndClass.cbSize = sizeof(WNDCLASSEX);
wndClass.style = CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc = WndProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = hInstance;
wndClass.hIcon = (HICON)::LoadImage(NULL, "icon.ico", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE);
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = "myWindow";
窗口类的设计
if (!RegisterClassEx(&wndClass)) //注册窗口
{
return -1;
}
窗口类的注册
HWND hwnd = CreateWindow("myWindow", "GameBy1zhou",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,
WINDOW_HEIGHT, NULL, NULL, hInstance, NULL); //创建窗口
窗口类的创建
MoveWindow(hwnd, 250, 80, WINDOW_WIDTH, WINDOW_HEIGHT, true);
ShowWindow(hwnd, nShowCmd);
UpdateWindow(hwnd);
窗口的显示、更新
if (!Game_Init(hwnd))
{
MessageBox(hwnd, "资源初始化失败", "ERROR", 0); //使用MessageBox函数,创建一个消息窗口
return FALSE;
}
游戏的初始化
MSG msg = { 0 }; //定义并初始化msg
while (msg.message != WM_QUIT) //使用while循环,如果消息不是WM_QUIT消息,就继续循环
{
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) //查看应用程序消息队列,有消息时将队列中的消息派发出去。
{
TranslateMessage(&msg); //将虚拟键消息转换为字符消息
DispatchMessage(&msg); //分发一个消息给窗口程序。
}
else
{
g_tNow = GetTickCount(); //获取当前系统时间
if (g_tNow - g_tPre >= 20) //当此次循环运行与上次绘图时间相差0.02秒时再进行重绘操作
{
Game_Paint(hwnd);
}
}
}
消息循环(这里我用的是PeekMessage,方便游戏画面的刷新)
UnregisterClass("myWindow", wndClass.hInstance); //程序准备结束,注销窗口类
窗口类的注销
窗口过程函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_KEYUP:
switch (wParam)
{
case VK_UP:
birdSpeed = -3;
break;
}
case WM_KEYDOWN:
switch (wParam)
{
case VK_ESCAPE:
Game_CleanUp(hwnd);
DestroyWindow(hwnd);
PostQuitMessage(0);
break;
}
break;
case WM_DESTROY:
Game_CleanUp(hwnd);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
Game_Init函数
srand((unsigned)timeGetTime());
for (i = 0; i < 3; i++)
{
randomNumber[i] = 80 + rand() % 240;
}
管道长度的随机产生
HBITMAP bmp;
g_hdc = GetDC(hwnd);
g_mdc = CreateCompatibleDC(g_hdc); //内存DC
g_bufdc = CreateCompatibleDC(g_hdc); //缓存DC
bmp = CreateCompatibleBitmap(g_hdc, WINDOW_WIDTH, WINDOW_HEIGHT);
SelectObject(g_mdc, bmp);
三缓存(先把资源放进缓存DC中,然后在贴到内存DC上,所有资源全部贴好后,最后在直接把内存DC的画面直接贴到HDC)
g_hBackground = (HBITMAP)LoadImage(NULL, "GameMedia\\background.bmp", IMAGE_BITMAP, WINDOW_WIDTH, WINDOW_HEIGHT, LR_LOADFROMFILE);
g_hGround = (HBITMAP)LoadImage(NULL, "GameMedia\\ground.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
g_hTubeDown = (HBITMAP)LoadImage(NULL, "GameMedia\\tubeDown.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
g_hTubeUp = (HBITMAP)LoadImage(NULL, "GameMedia\\tubeUp.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
g_hBird = (HBITMAP)LoadImage(NULL, "GameMedia\\bird.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
加载游戏资源
HFONT hFont;
hFont = CreateFont(20, 0, 0, 0, 700, 0, 0, 0, GB2312_CHARSET, 0, 0, 0, 0, TEXT("微软雅黑"));
SelectObject(g_mdc, hFont);
SetBkMode(g_mdc, TRANSPARENT);
创建字体
Game_Paint函数
(贴图部分我用的是透明色彩法 TransparentBlt )
SelectObject(g_bufdc, g_hBackground);
BitBlt(g_mdc, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, g_bufdc, 0, 0, SRCCOPY);
SelectObject(g_bufdc, g_hGround);
BitBlt(g_mdc, 0, 530, 225, 120, g_bufdc, 0, 0, SRCCOPY);
BitBlt(g_mdc, 225, 530, 225, 120, g_bufdc, 0, 0, SRCCOPY);
先贴上背景和地面(地面的素材图片长度不够,贴了两次)
for (i = 0; i < 3; i++)
{
SelectObject(g_bufdc, g_hTubeDown);
TransparentBlt(g_mdc, tube_x[i], 0, TUBE_WIDTH, randomNumber[i], g_bufdc, 0, TUBE_HEIGHT- randomNumber[i], TUBE_WIDTH, randomNumber[i], RGB(255, 255, 255));
SelectObject(g_bufdc, g_hTubeUp);
TransparentBlt(g_mdc, tube_x[i], 130 + randomNumber[i], TUBE_WIDTH, 400 - randomNumber[i], g_bufdc, 0, 0, TUBE_WIDTH, 400 - randomNumber[i], RGB(255, 255, 255));
if (BIRD_X >= tube_x[i] - BIRD_WIDTH + 10 && BIRD_X <= tube_x[i] + TUBE_WIDTH - 10)
{
//这里的加10、减10是因为图片本身边缘有一定的空隙,所以通过这样来使判断更精确一点 =_=
if (bird_y <= randomNumber[i] - 10 || bird_y >= randomNumber[i] + PASSWAY - BIRD_HEIGHT + 10)
{
char result[100];
PlaySound(NULL, NULL, SND_FILENAME); //结束播放音乐
wsprintf(result, "GAME OVER!\n你的分数是:%d", score);
MessageBox(hwnd, result, "游戏结束", 0);
Game_CleanUp(hwnd);
DestroyWindow(hwnd);
PostQuitMessage(0);
}
}
if (bird_y >= 530 - BIRD_HEIGHT + 10)
{
char result[100];
PlaySound(NULL, NULL, SND_FILENAME);
wsprintf(result, "GAME OVER!\n你的分数是:%d", score);
MessageBox(hwnd, result, "游戏结束", 0);
Game_CleanUp(hwnd);
DestroyWindow(hwnd);
PostQuitMessage(0);
}
贴上管道、碰撞检测以及游戏结束相应的处理
if (tube_x[i] > -52)
{
tube_x[i] -= 1;
}
else if (tube_x[i] <= -52)
{
tube_x[i] = WINDOW_WIDTH;
randomNumber[i] =80 + rand() % 240;
}
管道的重复生成
g_tNow = GetTickCount();
if (g_tNow - g_tGra >= 500)
{
birdSpeed += GRAVITY;
g_tGra = GetTickCount();
}
重力的模拟
SelectObject(g_bufdc, g_hBird);
TransparentBlt(g_mdc, BIRD_X, bird_y, BIRD_WIDTH, BIRD_HEIGHT, g_bufdc, BIRD_WIDTH*loopNumber, 0, BIRD_WIDTH, BIRD_HEIGHT, RGB(0, 0, 0));
if (loopNumber < 2)
{
loopNumber += 1;
}
else
{
loopNumber = 0;
}
bird_y += birdSpeed;
贴上小鸟
if (GetTickCount() - g_tRGB >= 200) //控制颜色的刷新时间
{
for (i = 0; i < 3; i++)
{
rgb[i] = rand() % 255;
}
g_tRGB = GetTickCount();
}
if (BIRD_X == tube_x[1]+TUBE_WIDTH || BIRD_X == tube_x[2] + TUBE_WIDTH || BIRD_X == tube_x[0] + TUBE_WIDTH)
{
score++;
}
char str[3][10];
wsprintf(str[0], "得");
wsprintf(str[1], "分");
wsprintf(str[2], "%d", score);
SetTextColor(g_mdc, RGB(rgb[0], rgb[1], rgb[2]));
TextOut(g_mdc, 20, 20, str[0], strlen(str[0]));
SetTextColor(g_mdc, RGB(rgb[1], rgb[2], rgb[0]));
TextOut(g_mdc, 40, 20, str[1], strlen(str[1]));
SetTextColor(g_mdc, RGB(rgb[2], rgb[1], rgb[0]));
TextOut(g_mdc, 60, 20, str[2], strlen(str[2]));
得分的实时输出
BitBlt(g_hdc, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, g_mdc, 0, 0, SRCCOPY);
将g_mdc的内容全部贴到g_hdc中去,将画面显示在窗口中
Game_CleanUp函数
DeleteObject(g_hBackground);
DeleteObject(g_hBird);
DeleteObject(g_hGround);
DeleteObject(g_hTubeDown);
DeleteObject(g_hTubeUp);
ReleaseDC(hwnd, g_hdc);
DeleteDC(g_mdc);
DeleteDC(g_bufdc);
这里总结一下就是,如果是自己创建的,就需要删除;如果是跟系统要的,就需要释放
总结
- win32窗口化编程
- windows系统的消息机制
- GDI