win32 坦克大战

实现效果

在这里插入图片描述

具体实现

关于对话框

查看信息后按确定或者直接叉掉就行了

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 0000 表示透明的部分,其它数字表示显示的部分)
掩码图: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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值