花了近一个月的时间,学习了一下VC++深入详解,做了个贪吃蛇小游戏,虽然有很多的瑕疵,但还是想分享一下战果。
说一下思路:
①我们要绘制图形,我们可以用CClientDC(客户区设备上下文,与特定的窗口关联)声明一个dc对象作图。
当然这里我们要画蛇的身体和食物,设置一个食物类和蛇身体结构体
②我们如何让图形动起来?SetTimer创建一个计时器,KillTimer()用来销毁计时器。
③向哪个方向移动? 蛇头的方向
④食物产生在何处?什么时间产生?
产生在客户区,当一个食物被吃掉以后,产生下一个食物
食物产生在客户区,客户区的大小如何获取?(GetClientRect(&rect)可以获取)
当蛇身可以移动,食物可以产生,吃掉食物,蛇身可以增加,贪吃蛇基本框架就做好了!
实现步骤:
①结构体的创建
struct Food
{
int fx;
int fy;
BOOL IsFood;
};//食物结构体
struct Snake
{
int sx;
int sy;
char Direction;
int sLength;
};//蛇身结构体
struct ClientSize
{
int width;
int Height;
};// 客户区的大小
还需要声明3个全局结构体变量
ClientSize clientsize;
Food food;
Snake snake[200];
添加成员函数InitSnake();并在OnInitDialog()中调用该函数
BOOL C贪吃蛇Dlg::OnInitDialog()
{
... ...
// TODO: 在此添加额外的初始化代码
InitSnake();
}
void C贪吃蛇Dlg::InitSnake() /************************************初始化贪吃蛇****************/
{
snake[0].sx = 10;
snake[0].sy = 10;
snake[1].sx = 9;
snake[1].sy = 10;
snake[2].sx = 8;
snake[2].sy = 10;
snake[0].sLength = 3;
snake[0].Direction = 'R';
//SetTimer(1, 200, NULL);
}
void C贪吃蛇Dlg::DrawSnake()/********************************绘制贪吃蛇*******************/
{
CClientDC dc(this);
CBrush brush(RGB(255,0,0));
for (int i = 0; i < snake[0].sLength; i++)
{
//CString str;
//str.Format("snake[%d].sx = %d, snake[%d].sy = %d", i, snake[i].sx, i, snake[0].sy);
//MessageBox(str);
dc.FillRect(CRect(snake[i].sx * 15, snake[i].sy * 15,
(snake[i].sx + 1) * 15, (snake[i].sy + 1) * 15), &brush);
}
CBrush brush1(RGB(0, 255, 0));
dc.FillRect(CRect(food.fx*15,food.fy*15,(food.fx+1)*15,(food.fy+1)*15),&brush1);
}
③获取客户区的大小
IDC_MOVE_SIZE是我添加的一个图片控件的ID,目的是让贪吃蛇在这个区域移动。当然,由于控件的大小与客户区的绘制无关,我们可以在OnInitDialog中调用该函数
void C贪吃蛇Dlg::GetClientSize()
{
CRect clientrect;
GetDlgItem(IDC_MOVE_SIZE)->GetClientRect(clientrect);
clientsize.width = clientrect.Width();
clientsize.Height = clientrect.Height();
clientsize.Height /= 15;
clientsize.width /= 15;
}
④取得客户区的大小以后,我们就可以绘制食物,让食物在客户区中产生
void C贪吃蛇Dlg::MakeFood()/*************************产生食物************************/
{
srand((unsigned int)(time(NULL)));
food.fx = rand() % clientsize.width;
food.fy = rand() % clientsize.Height;
food.IsFood = TRUE;
}
void C贪吃蛇Dlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
CString str;
if (GetDlgItemText(IDC_BUTTON1, str), str == "暂停")
{
SetTimer(1, 200, NULL);
SetDlgItemText(IDC_BUTTON1, "开始");
KillTimer(1);
}
else
{
SetDlgItemText(IDC_BUTTON1, "暂停");
SetTimer(1, 200, NULL);
}
}
void C贪吃蛇Dlg::OnTimer(UINT_PTR nIDEvent)/******************时间响应函数*************************/
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
ChangeCoordinate();
if ('R' == snake[0].Direction) //向右拐
snake[0].sx++;
if ('L' == snake[0].Direction) // 向左拐
snake[0].sx--;
if ('U' == snake[0].Direction) // 向上拐
snake[0].sy--;
if ('D' == snake[0].Direction) // 向下拐
snake[0].sy++;
Invalidate();
EatFood();
IsDied();
CDialogEx::OnTimer(nIDEvent);
}
⑦让贪吃蛇的状态虽我们鼠标控制,我们要时时的改变其状态
void C贪吃蛇Dlg::ChangeCoordinate()/*******************改变坐标***********************/
{
for (int i = snake[0].sLength-1; i >0; i-- )
{
snake[i].sx = snake[i - 1].sx;
snake[i].sy = snake[i - 1].sy;
snake[i].Direction = snake[i - 1].Direction;
}
}
⑧判断食物是否被吃掉,添加EatFood()函数,如果吃掉,则生成新的食物,如果没有,判断是否到达30的倍数,是的话,升一级
void C贪吃蛇Dlg::EatFood()/***************************判断食物是否被吃掉********************************/
{
if (food.fx == snake[0].sx && food.fy == snake[0].sy)
{
KillTimer(1);
SetTimer(1, 200, NULL);
snake[snake[0].sLength].sx = snake[snake[0].sLength - 1].sx;
snake[snake[0].sLength].sy = snake[snake[0].sLength - 1].sy;
snake[0].sLength++;
ShowGrade();
MakeFood();
}
if (snake[0].sLength % 30 == 0)//判断是否升级
{
UpdateData();
m_order++;
UpdateData(FALSE);
}
}
void C贪吃蛇Dlg::IsDied()
{
if (snake[0].sx < 0 || snake[0].sy < 0 ||
snake[0].sx >= clientsize.width || snake[0].sy >= clientsize.Height)
{
KillTimer(1);
MessageBox("游戏结束");
}
}
⑩每当吃掉一个食物,我们要判断我们当前的得分情况,增加一个ShowGrade()
void C贪吃蛇Dlg::ShowGrade()/**********************显示成绩************************/
{
char str[5];
int grade;
GetDlgItemText(IDC_EDIT_GRADE, str, 10);
grade = atoi(str);
grade++;
_itoa_s(grade, str, 10);
m_grade.SetWindowText(str);
}
我们这儿可以注意到,我们的得分和升级中有两种想在对话框中显示,这儿有两种不同的方法,主要是我们对每个控件所关联的成员变量的类型不同
等级,我们关联的是int类型,而成绩我们关联的是是CEdit类型
11、做到这,如果没有问题的话,程序应该可以运行了。但是我们会发现焦点不在我们的绘制贪吃蛇的区域,而可能在“开始”,或者其他的编辑框中,我们无法调用我们的方向键,这里,我们要调用一个虚函数PreTranslateMessage(MSG* pMsg)。 MFC 消息控制流最具特色的地方是CWnd类的虚拟函数PreTranslateMessage(),通过重载这个函数,可以改变MFC的消息控制流程,甚至可以作一个全新的控制流出来。只有穿过消息队列的消息才受PreTranslateMessage()影响,采用SendMessage()或其他类似的方式向窗口直接发送的而不经过消息队列的消息根本不会理睬PreTranslateMessage()的存在。BOOL C贪吃蛇Dlg::PreTranslateMessage(MSG* pMsg)
{
// TODO: 在此添加专用代码和/或调用基类
if (pMsg->message == WM_KEYDOWN)
{
switch (pMsg->wParam)
{
case VK_UP:
if (snake[0].Direction != 'D')
snake[0].Direction = 'U';
break;
case VK_DOWN:
if (snake[0].Direction != 'U')
snake[0].Direction = 'D';
break;
case VK_LEFT:
if (snake[0].Direction != 'R')
snake[0].Direction = 'L';
break;
case VK_RIGHT:
if (snake[0].Direction != 'L')
snake[0].Direction = 'R';
break;
default:
break;
}
}
return CDialogEx::PreTranslateMessage(pMsg);
}
12、如果想让我们的图形界面更加的美观,我们可以添加一个位图
方法:资源视图---Bitmap鼠标右键---添加资源---Bitmap---导入
选择自己的位图
void C贪吃蛇Dlg::OnPaint()
{
... ...
else
{
CDialogEx::OnPaint();
CRect rect;
GetClientRect(&rect);
CDC *pDC = GetDC();
CDC memdc;
memdc.CreateCompatibleDC(pDC);
CBitmap bitmap;
//从资源中载入位图
bitmap.LoadBitmap(IDB_BITMAP2);
memdc.SelectObject(bitmap);
pDC->StretchBlt(0, 0, clientsize.width*15,clientsize.Height*15, &memdc, 0, 0,
290, 290, SRCCOPY);
}
DrawSnake();
}
完结。
程序中有很多地方都没有完成的很好,最近比较忙,就不再进行完善
自己发现的不足之处:
①咬到自己程序无法终结,一次循环,判断头节点是否等于身上的某个节点即可
②老是闪屏,这个应该要用到双向缓冲技术
③食物可能出现在自己身上,解决方法同①
源代码下载