实现效果
具体实现
关于对话框
查看信息后按确定或者直接叉掉就行了
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
。。。
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDM_ABOUT:
DialogBox(NULL, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
}
break;
。。。
}
。。。
}
LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return TRUE;
}
break;
}
return FALSE;
}
资源:
关卡
页面通过int pageId;
分为0:跳转单人游戏页面 1:跳转双人游戏页面 2:跳转游戏说明页面 3:跳转制作页面 4:游戏说明页面 5:制作页面 6 7 8:单人游戏页面 9 10 11:双人游戏页面。保存在HBITMAP page[10];
中。
其中0~3的图片:
4的图片:
5的图片:
6 7 8 9 10 11通过背景+绘制得出
加载地图(0:空地;1、2:土墙;3:铁墙;4:草地;5:河):
void State::open_map(int id)
{
char filename[20];
StringCchPrintf(filename, sizeof(filename), TEXT("Map\\map%d.txt"), id);
FILE *fp = fopen(filename, "rt");
for (int i = 0; i < 31; i++)
{
for (int j = 0; j < 50; j++)
{
fscanf(fp, "%d", &map[i][j]);
}
}
fclose(fp);
}
绘制图片
比如绘制子弹:
void Bullet::showBullet(HDC &mhdc, HDC &mmhdc)
{
SelectObject(mmhdc, bulletBmp[0]);
BitBlt(mhdc, x, y, 10, 10, mmhdc, 0, 0, SRCAND);
SelectObject(mmhdc, bulletBmp[1]);
BitBlt(mhdc, x, y, 10, 10, mmhdc, 0, 0, SRCPAINT);
}
使用到了基于三元光栅操作的透明贴图法:
需要原图:需要透明的部分,用纯黑色表示。
掩码图:与原图对应。原图需要透明的部分,用纯白色表示;原图需要显示的部分,用纯黑色表示。
现在用一维数字的形式来讲解光栅操作的原理。假设:
源 图:00 00 00 56 78 9a bc 00 (00 表示透明的部分,其它数字表示显示的部分)
掩码图:ff ff ff 00 00 00 00 ff (ff 表示透明的部分,00 表示显示的部分)
目标图:12 34 12 34 12 34 12 34
执行步骤:
初始目标图:12 34 12 34 12 34 12 34
执行:BitBlt(..., SRCAND); // SRCAND 表示“掩码图 AND 目标图”
目标图变为:12 34 12 00 00 00 00 34
执行:BitBlt(..., SRCPAINT); // SRCPAINT 表示“源图 OR 目标图”
目标图变为:12 34 12 56 78 9a bc 34
按键控制逻辑
除了窗口的消息处理就运行我们编写的游戏循环,界面跳转的按键消息也在窗口中处理(坦克相关按键操作不在窗口中处理)。
control example;
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
。。。
MSG msg;
while (true)
{
// 如果有消息就处理消息 没有消息就处理游戏内容
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
example.gameLoop(hWnd);
}
if (msg.message == WM_QUIT)
{
return msg.wParam;
}
}
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
。。。
case WM_KEYDOWN:
switch (wParam)
{
// 初始界面跳转逻辑
case VK_ESCAPE: example.onEscape(); break;
case VK_UP: example.onUp(); break;
case VK_DOWN: example.onDown(); break;
case VK_RETURN: example.onReturn(); break;
case VK_PRIOR: example.onPrior(); break;
case VK_NEXT: example.onNext(); break;
}
break;
。。。
}
}
处理1p和2p的按键操作(方向和攻击)
void control::gameLoop(HWND hWnd)
{
if (page.isEnterGame() && !state.isStart()) // 判断是否进入游戏页面和静止时间是否结束
{
// 1p
state.setKeyNowTime(0);
if (state.getKeyTimeDifference(0) >= 30 && myTank[0].isSurvive() && state.isPlayerSurvive(0))
{
keyDown(0);
}
// 2p
state.setKeyNowTime(1);
if (page.isDoublePlayer() && state.getKeyTimeDifference(1) >= 30 && myTank[1].isSurvive() && state.isPlayerSurvive(1))
{
keyDown(1);
}
// 若两个坦克的血都小于等于0则gameover
if (!state.isPlayerSurvive(0) && !state.isPlayerSurvive(1))
{
state.gameOver();
}
}
if (state.getTimeDifference() >= 30)
{
myPaint(hWnd); // 刷新画面
}
}
void control::keyDown(int belong)
{
if (state.isNotBeingGame())
{
return;
}
// 方向键
for (int i = 0; i < 4; ++i)
{
if (GetKeyState(THEPLAYERBUTTON[belong][i]) < 0)
{
myTank[belong].setD(i);
if (meet(0, myTank))
{
moveMyTank(i, belong);
}
}
}
// 攻击键
if (GetKeyState(THEPLAYERBUTTON[belong][4]) < 0)
{
myTank[belong].setBulletNowTime();
if (myTank[belong].getBulletInterval() >= 400)
{
createBullet(myTank[belong].getD(), belong, myTank[belong].getBkind());
mciSendString(TEXT("play Sound\\Gunfire.wav"), NULL, 0, 0);
}
}
state.setKeyPreTime(belong);
}
敌人运动逻辑
在主游戏循环中:
void control::gameLoop(HWND hWnd)
{
。。。
if (state.getTimeDifference() >= 30)
{
myPaint(hWnd); // 刷新画面
}
}
绘制每一帧的时候使用双缓冲
void control::myPaint(HWND hWnd)
{
hdc = GetDC(hWnd); // 得到窗口DC
mhdc = CreateCompatibleDC(hdc); // 创建内存DC 绘制总画面使用
mmhdc = CreateCompatibleDC(hdc); // 创建内存DC 给素材图片使用
HBITMAP bmp = CreateCompatibleBitmap(hdc, 1300, 680); // 创建兼容性位图 1015 680
SelectObject(mhdc, bmp); // 将DC和位图绑定在一起
if (!page.isEnterGame()) // 若没有进入游戏界面
{
ChooseInterface();
}
else // 进入游戏界面
{
effect.showBackground(mhdc, mmhdc);
waitStartAndEndTime();
drawAll();
BitBlt(hdc, 0, 0, 1300, 680, mhdc, 0, 0, SRCCOPY); // 将内存DC传递到窗口DC
state.setPreTime();
}
DeleteObject(bmp);
DeleteDC(mmhdc);
DeleteDC(mhdc);
DeleteDC(hdc);
}
还在正常游戏的时候敌人会一直移动
void control::waitStartAndEndTime()
{
if (!state.isStart()) // 游戏开始时间已过
{
if (!state.isBeingStatic()) // 敌人没有被静止
{
moveEnemy();
}
else
{
state.redeceTiming();
}
}
else
{
state.reduceStartTime();
}
if (!state.isEnd()) // 游戏结束时间已过
{
if (state.isNotGetEndTime())
{
if (page.isOnGameThr())
{
state.Win(); // 若这一关为一个玩家或两个玩家的第三关则win
}
else
{
page.addPageId();
init();
}
}
}
else
{
state.reduceEndTime();
}
}
一共四个敌人,发射子弹和移动都是随机的
void control::moveEnemy()
{
for (int i = 0; i < 5; i++)
{
if (enemy[i].getBlood() <= 0)
{
continue;
}
enemy[i].setBulletNowTime();
if (rand() % 40 < 5 && enemy[i].getBulletInterval() >= 400) // 敌人可以发射子弹
{
createBullet(enemy[i].getD(), i + 2, enemy[i].getBkind());
}
if (meet(i, enemy))
{
enemy[i].move();
}
else // 若不可以走要么拐弯,要么发射子弹破坏墙继续走
{
enemy[i].setBulletNowTime();
if (enemy[i].getBulletInterval() >= 400)
{
createBullet(enemy[i].getD(), i + 2, enemy[i].getBkind());
}
else
{
enemy[i].setD(rand() % 4);
}
}
getProp(i + 2, enemy[i].getX(), enemy[i].getY()); // 获得道具
}
}
子弹运动逻辑
在主游戏循环中:
void control::gameLoop(HWND hWnd)
{
。。。
if (state.getTimeDifference() >= 30)
{
myPaint(hWnd); // 刷新画面
}
}
void control::myPaint(HWND hWnd)
{
。。。
else // 进入游戏界面
{
。。。
drawAll();
。。。
}
。。。
}
void control::drawAll()
{
bulletHit();
。。。
}
void control::bulletHit()
{
drawAllFire();
int x, y;
// 打到基地
for (int i = 0; i < 500; i++)
{
if (bullet[i].getF())
{
switch (bullet[i].getD())
{
case TORIGHT: if (isHitBase(bullet[i].getY() / 20, (bullet[i].getX() + 10) / 20)) return;
case TODOWN: if (isHitBase((bullet[i].getY() + 10) / 20, bullet[i].getX() / 20)) return;
case TOLEFT: if (isHitBase(bullet[i].getY() / 20, (bullet[i].getX() - 10) / 20)) return;
}
}
}
// 打墙
for (int i = 0; i < 500; i++)
{
if (bullet[i].getF() && bullet[i].getKind() != 1)
{
if (bullet[i].getD() == TOUP)
{
x = (bullet[i].getY() - 10) / 20;
y = bullet[i].getX() / 20;
if (!state.isClearing(x, y) && (bullet[i].getY() - 10) / 20 == bullet[i].getY() / 20)
{
hitWall(i, x, y, TOUP);
}
}
else if (bullet[i].getD() == TORIGHT)
{
x = bullet[i].getY() / 20;
y = (bullet[i].getX() + 10) / 20;
if (!state.isClearing(x, y) && (bullet[i].getX() + 10) / 20 == bullet[i].getX() / 20)
{
hitWall(i, x, y, TORIGHT);
}
}
else if (bullet[i].getD() == TODOWN)
{
x = (bullet[i].getY() + 10) / 20;
y = bullet[i].getX() / 20;
if (!state.isClearing(x, y) && (bullet[i].getY() + 10) / 20 == bullet[i].getY() / 20)
{
hitWall(i, x, y, TODOWN);
}
}
else
{
x = bullet[i].getY() / 20;
y = (bullet[i].getX() - 10) / 20;
if (!state.isClearing(x, y) && (bullet[i].getX() - 10) / 20 == bullet[i].getX() / 20)
{
hitWall(i, x, y, TOLEFT);
}
}
}
}
// 打到敌人坦克
for (int i = 0; i < 500; i++)
{
if (bullet[i].getF() && bullet[i].getBelong() <= 1)
{
for (int j = 0; j < 5; j++)
{
hitTank(i, j, enemy);
}
}
}
// 打到自己的坦克
for (int i = 0; i < 500; i++)
{
if (bullet[i].getF() && bullet[i].getBelong() > 1)
{
for (int j = 0; j < 2; j++)
{
if (i == 1 && !page.isDoublePlayer())
{
continue;
}
hitTank(i, j, myTank);
}
}
}
}
链接
百度云链接:https://pan.baidu.com/s/12qiFSPWLiwC16N6uY49ENg
提取码:a5mq