Visual C++游戏编程基础之行为型AI游戏

1、设计目标

1.创建一个回合制游戏,角色设定怪物与玩家,并在下方显示它们的生命值;canvas

2.在背景图上贴上攻击标志,鼠标点击该标志时,玩家向怪物发动 攻击,并在左下角输出怪物的状态信息;函数

3.玩家回合结束后,轮到怪物,根据怪物的HP来决定要选择哪一种攻击方式或者逃跑或重伤而死;ui

4.当某一方HP为0时,贴上游戏结束标志;spa

2、基本思路

1.初始化过程当中,将背景图、怪物及其HP、玩家及其HP、攻击标志贴到mdc,最后在贴到hdc;设计

2.单击左键,WndProc函数处理该消息,获取光标位置判断是否在攻击标志范围内,有就将attack设为true;code

3.接着回到MyPaint函数,玩家开始攻击,用f来表示贴图的次数,将攻击图像贴在怪物身上持续6个画面,而后f更新到第10个画对象

   面时,下降怪物的HP,并把伤害输出到左下角的文本框,并检查怪物的状态;blog

4.当f更新到第15个画面时,此次是怪物的回合,设定若怪物HP>20,有90%概率发动普通攻击,10%的概率发动魔法攻击,HP游戏

   低于20就有5种状况:普攻、魔攻、全力攻、回血、逃跑,各有20%概率;it

5.f更新到26个画面时,根据怪物的选择进行贴图,持续5个画面,当f更新到30时,减小玩家的HP并输出其状态到左下角,或者

   因血量过低逃跑或死亡;

6.一个回合结束,f从新设为0,等待下一回合,直到over=true,触发游戏结束标志;

3、效果

4、代码以下

#include "stdafx.h"
#include <stdio.h>

//结构体:储存怪物或玩家的状态信息
struct chr
{
	int		nHp;//目前生命值
	int		fHp;//最大生命值
	int		lv;//等级
	int		w;//加权值
	int		kind;//怪物的行为代号
};

//声明全局变量
HINSTANCE hInst;
HBITMAP	bg,sheep,girl,skill,slash,magic,recover,game;//skill--攻击命令图;slash--普通攻击图;magic--怪物魔法攻击图;recover--怪物恢复魔法图;game--游戏结束图
HDC		hdc,mdc,bufdc;
HWND	hWnd;
DWORD	tPre,tNow;
int		pNum,f,txtNum;//pNum--玩家跑动图编号;f--每一回合攻击更新的画面数;TxtNum--记录目前所要显示消息的总数
bool	attack,over;//attack记录玩家是否点击攻击命令;over一方生命值为0时,设为true
chr		player,monster;
char	text[5][100];//存储对战时要显示的消息

ATOM				MyRegisterClass(HINSTANCE hInstance);
BOOL				InitInstance(HINSTANCE, int);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);
void				MyPaint(HDC hdc);
void				MsgInsert(char*);//****新增要显示的消息到‘text’缓冲区,当显示的消息数目达到显示的上限数目时,删除最早的消息*********************************

void				CheckDie(int hp,bool player);//每次攻击以后进行判断
void				MyPaint1(HDC hdc,LPARAM lParam);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
	MSG msg;

	MyRegisterClass(hInstance);

	if (!InitInstance (hInstance, nCmdShow)) 
	{
		return FALSE;
	}

    while( msg.message!=WM_QUIT )
    {
        if( PeekMessage( &msg, NULL, 0,0 ,PM_REMOVE) )
        {
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
		else
		{
			tNow = GetTickCount();
			if(tNow-tPre >= 40)
				MyPaint(hdc);
		}
    }

	return msg.wParam;
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX); 
	wcex.style			= CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
	wcex.lpfnWndProc	= (WNDPROC)WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= NULL;
	wcex.hCursor		= NULL;
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName	= NULL;
	wcex.lpszClassName	= "canvas";
	wcex.hIconSm		= NULL;

	return RegisterClassEx(&wcex);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
	HBITMAP bmp;
	hInst = hInstance;

	hWnd = CreateWindow("canvas", "绘图窗口" , WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

	if (!hWnd)
	{
		return FALSE;
	}

	MoveWindow(hWnd,100,100,640,520,true);
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	hdc = GetDC(hWnd);
	mdc = CreateCompatibleDC(hdc);
	bufdc = CreateCompatibleDC(hdc);

	bmp = CreateCompatibleBitmap(hdc,640,480);
	SelectObject(mdc,bmp);

	bg = (HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,640,480,LR_LOADFROMFILE);
	sheep = (HBITMAP)LoadImage(NULL,"sheep.bmp",IMAGE_BITMAP,133,220,LR_LOADFROMFILE);
	girl = (HBITMAP)LoadImage(NULL,"girl.bmp",IMAGE_BITMAP,480,148,LR_LOADFROMFILE);
	skill = (HBITMAP)LoadImage(NULL,"skill.bmp",IMAGE_BITMAP,74,60,LR_LOADFROMFILE);
	slash = (HBITMAP)LoadImage(NULL,"slash.bmp",IMAGE_BITMAP,196,162,LR_LOADFROMFILE);
	magic = (HBITMAP)LoadImage(NULL,"magic.bmp",IMAGE_BITMAP,200,100,LR_LOADFROMFILE);
	recover = (HBITMAP)LoadImage(NULL,"recover.bmp",IMAGE_BITMAP,300,150,LR_LOADFROMFILE);
	game = (HBITMAP)LoadImage(NULL,"over.bmp",IMAGE_BITMAP,289,74,LR_LOADFROMFILE);

	player.nHp = player.fHp = 50;
	player.lv = 2;
	player.w  = 4;//设定攻击伤害加权值					

	monster.nHp = monster.fHp = 30;
	monster.lv = 1;						
	monster.w = 1;						

	txtNum = 0;//显示消息数目		

	MyPaint(hdc);

	return TRUE;
}

void MyPaint(HDC hdc)
{
	char str[100];
	int i,damage;

	//贴上背景图
	SelectObject(bufdc,bg);
	BitBlt(mdc,0,0,640,480,bufdc,0,0,SRCCOPY);

	//显示对战消息
	for(i=0;i<txtNum;i++)
		TextOut(mdc,0,360+i*20,text[i],strlen(text[i]));

	//贴上怪物
	if(monster.nHp>0)
	{
		SelectObject(bufdc,sheep);
		BitBlt(mdc,70,180,133,110,bufdc,0,110,SRCAND);
		BitBlt(mdc,70,180,133,110,bufdc,0,0,SRCPAINT);
		sprintf(str,"%d / %d",monster.nHp,monster.fHp);
		TextOut(mdc,100,320,str,strlen(str));
	}

	//贴上玩家
	if(player.nHp>0)
	{
		SelectObject(bufdc,girl);
		BitBlt(mdc,500,200,60,74,bufdc,pNum*60,74,SRCAND);
		BitBlt(mdc,500,200,60,74,bufdc,pNum*60,0,SRCPAINT);
		sprintf(str,"%d / %d",player.nHp,player.fHp);
		TextOut(mdc,510,320,str,strlen(str));
	}

	if(over)				//贴上游戏结束标志
	{
		SelectObject(bufdc,game);
		BitBlt(mdc,200,200,289,37,bufdc,0,37,SRCAND);
		BitBlt(mdc,200,200,289,37,bufdc,0,0,SRCPAINT);
	}
	else if(!attack)		//贴上攻击标志
	{
		SelectObject(bufdc,skill);
		BitBlt(mdc,500,350,74,30,bufdc,0,30,SRCAND);
		BitBlt(mdc,500,350,74,30,bufdc,0,0,SRCPAINT);
	}	
	else
	{
		f++;  //递增变量,每回合开始重设为0;     

		//玩家按下攻击键时,玩家可看到6个画面(5~10)的攻击暂留效果
		if(f>=5 && f<=10)
		{
			SelectObject(bufdc,slash);
			BitBlt(mdc,100,160,98,162,bufdc,98,0,SRCAND);
			BitBlt(mdc,100,160,98,162,bufdc,0,0,SRCPAINT);//在怪物身上贴上攻击特效

			//第10个画面的时候,减小怪物的生命值
			if(f == 10)
			{
				damage = rand()%10 + player.lv*player.w;
				monster.nHp -= (int)damage;
					
				sprintf(str,"    怪物生命值减小%d HP    ",damage);
				MsgInsert(str);//text里的内容是从这里传递过去的

				CheckDie(monster.nHp,false);//检查怪物是否还活着;flase表示要判断的对象是monster
			}
		}

		srand(tPre);//产生随机数,若int固定,那么在rand下,它是伪随机数,要产生真正的随机数必须是个时刻变化的量

		//回合开始后的第15个画面,判断怪物要采起何种行动
		if(f == 15)
		{
			if(monster.nHp > 20)				
			{
				if(rand()%10 != 1)
					monster.kind = 0;//90%的概率进行普通攻击
				else
					monster.kind = 1;//10%的概率进行魔法攻击
			}
			else								
			{
				switch(rand()%5)
				{
					case 0:						//普通攻击
						monster.kind = 0;
						break;
					case 1:						//魔法攻击
						monster.kind = 1;
						break;
					case 2:						//全力攻击
						monster.kind = 2;
						break;
					case 3:						//补血
						monster.kind = 3;
						break;
					case 4:						//逃跑
						monster.kind = 4;
						break;
				}
			}
		}

		//第26~30个画面间,对怪物所做出的行为进行贴图操做
		if(f>=26  && f<=30)
		{
			switch(monster.kind)
			{
				case 0:							//普通攻击
					SelectObject(bufdc,slash);
					BitBlt(mdc,480,150,98,162,bufdc,98,0,SRCAND);
					BitBlt(mdc,480,150,98,162,bufdc,0,0,SRCPAINT);

					//到第30个画面,对玩家HP减掉普通攻击的部分
					if(f == 30)
					{
						damage = rand()%10 + monster.lv*monster.w;
						player.nHp -= (int)damage;
						
						sprintf(str,"    玩家生命值减小 %d HP    ",damage);
						MsgInsert(str);

						CheckDie(player.nHp,true);
					}
					break;
				case 1:							//魔法攻击
					SelectObject(bufdc,magic);
					BitBlt(mdc,480,190,100,100,bufdc,100,0,SRCAND);
					BitBlt(mdc,480,190,100,100,bufdc,0,0,SRCPAINT);

					//到第30个画面,对玩家HP减掉魔法攻击的部分
					if(f == 30)
					{
						damage = rand()%10 + 3*monster.w;
						player.nHp -= (int)damage;	
						
						sprintf(str,"    玩家的生命值减小%d HP    ",damage);
						MsgInsert(str);

						CheckDie(player.nHp,true);
					}
					break;
				case 2:							
					SelectObject(bufdc,slash);
					BitBlt(mdc,480,150,98,162,bufdc,98,0,SRCAND);
					BitBlt(mdc,480,150,98,162,bufdc,0,0,SRCPAINT);

					//到第30个画面,对玩家HP减掉全力攻击的部分
					if(f == 30)
					{
						damage = rand()%10 + monster.lv*monster.w*5;
						player.nHp -= (int)damage;
						
						sprintf(str,"    玩家的生命值减小 %d HP    ",damage);
						MsgInsert(str);

						CheckDie(player.nHp,true);
					}
					break;
				case 3:							//回血
					SelectObject(bufdc,recover);
					BitBlt(mdc,60,160,150,150,bufdc,150,0,SRCAND);
					BitBlt(mdc,60,160,150,150,bufdc,0,0,SRCPAINT);

					//更新到第30个画面的时候,怪物回血
					if(f == 30)
					{
						monster.nHp += 30;
						
						sprintf(str,"    怪兽回血值为30    ",damage);
						MsgInsert(str);
					}
					break;
				case 4:
					//更新到第30个画面,逃跑
					if(f == 30)
					{
						if(rand()%3 == 1)	//2/3的概率怪兽死亡,游戏结束
						{
							over = true;
							monster.nHp = 0;

							sprintf(str,"    怪兽重伤而死!    ");
							MsgInsert(str);
						}
						else
						{
							sprintf(str,"    怪兽已经逃跑!    ");
							MsgInsert(str);
						}
					}
					break;
			}
		}

		if(f == 30)			//攻击回合结束,准备下一个回合
		{
			attack = false;
			f = 0;
		}
	}

	BitBlt(hdc,0,0,640,480,mdc,0,0,SRCCOPY);

	tPre = GetTickCount();

	pNum++;
	if(pNum == 8)
		pNum = 0;
}

//****新增要显示的消息到‘text’缓冲区,当显示的消息数目达到显示的上限数目时,删除最早的消息*********************************
void MsgInsert(char* str)
{
	if(txtNum < 5)
	{
		sprintf(text[txtNum],str);
		txtNum++;
	}
	else
	{
		for(int i=0;i<txtNum;i++)//有5行的话,各行依次往上前进一行,最新的消息放在最后一行;
			sprintf(text[i],text[i+1]);

		sprintf(text[4],str);
	}
}

//****检查玩家的生存状况*************************
void CheckDie(int hp,bool player)
{
	char str[100];

	if(hp <= 0)
	{
		over = true;
		if(player)
		{
			sprintf(str,"    玩家战败!    ");
			MsgInsert(str);
		}
		else
		{
			sprintf(str,"    怪物战败!    ");
			MsgInsert(str);
		}
	}
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int x,y;

	switch (message)
	{
		case WM_KEYDOWN:				
			if(wParam==VK_ESCAPE)		
				PostQuitMessage(0);
			break;
		case WM_LBUTTONDOWN:			//检测攻击范围
			if(!attack)
			{
				x = LOWORD(lParam);		
				y = HIWORD(lParam);		
			
				if(x >= 500 && x <= 574 && y >= 350 && y <= 380)
					attack = true;
			}
			break;
		case WM_DESTROY:				
			DeleteDC(mdc);
			DeleteDC(bufdc);
			DeleteObject(bg);
			DeleteObject(sheep);
			DeleteObject(girl);
			DeleteObject(skill);
			DeleteObject(slash);
			DeleteObject(magic);
			DeleteObject(recover);
			DeleteObject(game);

			ReleaseDC(hWnd,hdc);

			PostQuitMessage(0);
			break;
		default:						
			return DefWindowProc(hWnd, message, wParam, lParam);
   }
   return 0;
}