纪念我的第一篇博客~
《算法原理》这堂课要结课了,这是通过这个课第一次接触C++,所以就用MFC设计一个小游戏(游戏可玩性还可以更丰富,主要是考试多加没时间,就没有加那么多,有兴趣的可以在此基础上丰富我的代码)
上截图:
大致内容就是这样,接下来简单讲下怎么实现的:
1、新建工程:
需要Visual Studio2019
(注意要选择MFC的组件)
接下来建工程:
2、代码实现
(1)边框样式设置:
在MainFrm.cpp中找到对应部分修改成如下代码:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
//if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
// !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
//{
// TRACE0("未能创建工具栏\n");
// return -1; // 未能创建
//}
if (!m_wndStatusBar.Create(this))
{
TRACE0("未能创建状态栏\n");
return -1; // 未能创建
}
m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT));
// TODO: 如果不需要可停靠工具栏,则删除这三行
/*m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);*/
SetMenu(NULL);
return 0;
}
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: 在此处通过修改
// CREATESTRUCT cs 来修改窗口类或样式
cs.style = WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX; //去除窗口拖拉、最大化属性
cs.cx = 800; //设定窗口宽度
cs.cy = 800; //设定窗口高度
return TRUE;
}
(2)画图代码:
一、双缓冲技术画图:
我们知道,图像是由一个个像素点组成,如果像画出一个图像也就是把图像的每个点都画出来,但那种画法不能一次性将完整的图像呈现给你,所以我们就采用双缓冲技术画图,代码如下:【画图代码必须加在OnDraw()代码里】
(说明:有些凭空出现的变量是在h文件中声明,最后会有所有的h和cpp代码)
CDC* cDC;
cDC = this->GetDC(); //获得当前窗口的DC
GetClientRect(&m_client); //获得窗口的尺寸
//
加入我们要绘制的代码
//创建缓冲DC
m_cacheDC.CreateCompatibleDC(NULL);
m_cacheCBitmap.CreateCompatibleBitmap(cDC, m_client.Width(), m_client.Height());
m_cacheDC.SelectObject(&m_cacheCBitmap);
《-----------如下是画图代码-------------》
(待会加...)
《-----------如上是画图代码-------------》
//在绘制完图后,使窗口区有效
ValidateRect(&m_client);
//释放缓冲DC
m_cacheDC.DeleteDC();
//释放对象
m_cacheCBitmap.DeleteObject();
//释放窗口DC
ReleaseDC(cDC);
大家可以在PreCreateWindow()函数里加载一个CImage备用
CImage pictrue;
pictrue.Load(CString("图片的途径"))
最后在画图代码中使用BitBlt将图片加载
《-----------如下是画图代码-------------》
cDC->BitBlt(0, 0, m_client.Width(), m_client.Height(), &m_cacheDC, 0, 0, SRCCOPY);
《-----------如上是画图代码-------------》
二、定时器+人物动画
定时器是实现图像动画的前提,所以大致给大家写个流程:
项目-》类向导-》消息-》WM_TIMER和WM_CREATE
然后粘贴代码:
void CMyMFCGameView::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
switch (nIDEvent)
{
case TIMER_PAINT:OnPaint(); break; //若是重绘定时器,就执行OnPaint函数
case TIMER_HEROMOVE: //控制人物移动的定时器
{
MyHero.frame++; //每次到了间隔时间就将图片换为下一帧
if (MyHero.frame == 4) //到最后了再重头开始
MyHero.frame = 0;
Monster.frame++; //每次到了间隔时间就将图片换为下一帧
if (Monster.frame == 4) //到最后了再重头开始
Monster.frame = 0;
}
break;
case TIMER_HEROATTACK: //控制人物移动的定时器
{
HeroAttack.frame++; //每次到了间隔时间就将图片换为下一帧
if (HeroAttack.frame == 4) //到最后了再重头开始
HeroAttack.frame = 0;
MonsterAttack.frame++; //每次到了间隔时间就将图片换为下一帧
if (MonsterAttack.frame == 4) //到最后了再重头开始
{
MonsterAttack.frame = 0;
}
}
break;
case TIMER_ARROW:
switch (MyHero.direct)
{
case DOWN:
Arrow.y += 5;
break;
case LEFT:
Arrow.x -= 5;
break;
case RIGHT:
Arrow.x += 5;
break;
case UP:
Arrow.y -= 5;
break;
default:
break;
}
break;
}
}
int CMyMFCGameView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: 在此添加您专用的创建代码
//创建一个10毫秒产生一次消息的定时器
SetTimer(TIMER_PAINT, 10, NULL);
//创建人物行走动画定时器
SetTimer(TIMER_HEROMOVE, 100, NULL);
//创建人物攻击动画定时器
SetTimer(TIMER_HEROATTACK, 100, NULL);
//创建弓箭攻击定时器
SetTimer(TIMER_ARROW, 5, NULL);
return 0;
}
然后加载人物图像(最好是像这样的图片)
MyHero.hero.Draw(m_cacheDC, MyHero.x, MyHero.y, 80, 100, MyHero.frame * 80, MyHero.direct * 100, 80, 100);
三、人物按键
这里是人物移动按键的大致流程:
项目-》类向导-》消息-》WM_KEYUP和WM_KEYDOWN
然后粘贴代码:
void CMyMFCGameView::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
switch (nChar)
{
case 'j':
case 'J':
click = 0;
fight = FALSE;
break;
}
}
void CMyMFCGameView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
//nChar表示按下的键值
switch (nChar)
{
case 'd': //游戏中按下的键当然应该不区分大小写了
case 'D':
if (fight == FALSE)
{
MyHero.direct = RIGHT;
MyHero.x += 10;
Arrow.x = MyHero.x;
if (MyHero.x > 720)
{
MyHero.x = 720;
}
}
break;
case 'a':
case 'A':
if (fight == FALSE)
{
MyHero.direct = LEFT;
MyHero.x -= 10;
Arrow.x = MyHero.x;
if (MyHero.x < -15)
{
MyHero.x = -15;
}
}
break;
case 'w':
case 'W':
if (fight == FALSE)
{
MyHero.direct = UP;
MyHero.y -= 10;
Arrow.y = MyHero.y;
if (MyHero.y < -15)
{
MyHero.y = -15;
}
}
break;
case 's':
case 'S':
if (fight == FALSE)
{
MyHero.direct = DOWN;
MyHero.y += 10;
Arrow.y = MyHero.y;
if (MyHero.y > 650)
{
MyHero.y = 650;
}
}
break;
case 'j':
case 'J':
if (click == 0)
{
Arrow.y = MyHero.y;
Arrow.x = MyHero.x;
fight = TRUE;
}
click++;
break;
}
}
最后讲下怎么检测碰撞和人物AI:
一、简单怪物运动AI
相比于应用软件,游戏软件中包含的知识太多了,每一块拿出来都可以写一本厚厚的书,比如怪物AI这一部分。AI:Artificial Intelligence,即人工智能,人工智能研究如何用计算机去模拟、延伸和扩展人的智能;如何设计和制造更聪明的计算机以及智能水平更高的智能计算机等等。游戏人工智能博大精深,雾央也只是了解很少的一部分,本教程也只是面向游戏初学者,所以那些高深的基因算法、神经网络、蚂蚁算法等有待大家以后自己去探索。
本文将要实现的怪物运动AI是:怪物主动追逐人物。
首先,怪物追逐人物,在游戏中表现的效果就是两者距离不断靠近。归结到算法就是
if(Monster.x<Hero.x)
Monster.x++;
else
Monster.x--;
if(Monster.y<Hero.y)
Monster.y++;
else
Monster.y--;
二、碰撞判定
关于碰撞检测,雾央又得说这也是游戏中很复杂的一块了。越复杂的算法得到的精度当然也就越高,带来的就是运行速度的下降。在一般的游戏中,为了速度上的考虑,都会采用近似的算法。
精确的算法中比较简单的一种就是通过像素颜色进行,在下一节中雾央将会进行详解。这里先介绍一下近似算法。
近似算法一般都是根据物体的形状来进行,包括外接圆,外接矩形等判定方法。有些物体不太规则,可能还会对物体进行分段,每一段是一个小矩形,采用组合起来进行判定的方法。
对于我们来说,外接矩形方法就是一种非常简单,精度又不太差的方法,因为我们的人物形状很接近矩形。因此我们的人物与怪物碰撞检测就转化为了判断两个矩形碰撞的问题。
下面是两个矩形刚好碰撞的情况:
仔细观察,我们便可以得出碰撞时满足的条件:
假定黑色矩形固定,那么碰撞时,蓝色矩形的中心在红色方框内部。
那么,我们只需要时刻检查蓝色矩形的中心位置是否在红色方框内部即可
MyHero.Xcenter = MyHero.x + MyHero.width / 2;
MyHero.Ycenter = MyHero.y + MyHero.height / 2;
Monster.Xcenter = Monster.x + Monster.width / 2;
Monster.Ycenter = Monster.y + Monster.height / 2;
Arrow.Xcenter = Arrow.x + Arrow.width / 2; //这里
Arrow.Ycenter = Arrow.y + Arrow.height / 2;
if (Monster.Xcenter< MyHero.Xcenter + (MyHero.width / 2 + Monster.width / 2) &&
Monster.Xcenter> MyHero.Xcenter - (MyHero.width / 2 + Monster.width / 2) &&
Monster.Ycenter< MyHero.Ycenter + (MyHero.height / 2 + Monster.height / 2) &&
Monster.Ycenter> MyHero.Ycenter - (MyHero.height / 2 + Monster.height / 2)) {
Monster_fight = TRUE;
live--;
}
else
Monster_fight = FALSE;
if (Monster.Xcenter< Arrow.Xcenter + (Arrow.width / 2 + Monster.width / 2) &&
Monster.Xcenter> Arrow.Xcenter - (Arrow.width / 2 + Monster.width / 2) &&
Monster.Ycenter< Arrow.Ycenter + (Arrow.height / 2 + Monster.height / 2) &&
Monster.Ycenter> Arrow.Ycenter - (Arrow.height / 2 + Monster.height / 2)) {
live2--;
}
大致的核心思想就将这么多,我把一些源代码分享给大家,我也会加一个说明文件,大家可以自己实践一下。
链接:https://pan.baidu.com/s/19DehZRb9n6IEJkjf314NNw
提取码:8699