【Visual C++】游戏开发笔记十六 讲解一个完整的回合制游戏demo

               


本系列文章由zhmxy555编写,转载请注明出处。

文章链接  http://blog.csdn.net/zhmxy555/article/details/7447864

作者:毛星云    邮箱: [email protected]  


这节笔记的主要内容是介绍一个完整的回合制游戏demo,而这个demo里面主要突出了游戏里AI的各种思考与行为的方式.这样的通过计算机角色本身的判断思考,然后产生对应行为的AI称作行为型游戏AI。

如果对AI基础不太了解的朋友,请移步:


【Visual C++】游戏开发笔记十五 游戏人工智能(一) 运动型游戏AI

扫描二维码关注公众号,回复: 5114239 查看本文章


首先,我们来了解这种行为型AI的设计方法。

游戏程序中计算机角色的思考与行为,实际上是对各种不同事件进行分析思考,然后根据不同的情况作出相应的反应。但如何对发生的条件进行判断,并作出相应的反应呢?

对,我们可以利用“if-else”条件句以及“switch-case”语句这类的判断式来完成。

通常情况下,设计此类AI,会涉及到连串的条件判断句,简单数学运算,及一些数据结构的知识。



下面我们就来具体讲解这个demo涉及到的一些知识点:




一、AI怪物攻击与思考方式设计



例如今天我们要展示的这个回合制游戏demo里的AI,就有如下几种行为

(1)利爪攻击

(2)闪电链攻击

(3)致命一击

(4)使用梅肯斯姆回复生命值

(5)逃跑


那么我们可以根据以上设计的怪物行为,设计以下一段算法,用来模拟怪物对战时的思考与行为的方式:

if(monster.nHp > 20//生命值大于20{if(rand()%5!= 1)            //进行利爪攻击概率4/5else       //进行闪电链攻击概率1/5}else  //生命值小于20{switch(rand()%5){case 0//利爪攻击break;case 1//释放闪电链break;case 2//致命一击break;case 3//使用梅肯斯姆回复  ;break;case 4//逃跑if(1== rand()%3 )     //逃跑成功几率1/3//逃跑成功else//逃跑失败break;}}


这段代码中,利用if-else判断式判断怪物生命值,然后怪物有4/5的几率释放普通的利爪攻击,有1/5的几率释放闪电链魔法攻击,当怪物重伤生命值小于20点时,也有一定的几率逃跑。

以上的利用“if-else”、“switch”语句,使计算机角色进行事件情况判断,然后写出相应的动作实现代码,这就是行为型游戏AI

设计的核心精神。





二,玩家角色攻击方式设计


然后我们再来设计一下玩家的攻击技能。

今天放出的这个demo里我给人物设定了两个技能,一个主动的普通攻击技能“无敌斩”,伤害计算公式为damage = rand()%10 + player.lv*player.w(player.lv为角色等级,player.w为攻击系数)。

而被动技能为可以有一定几率打出4倍暴击伤害的“恩赐解脱”,这个技能是Dota里面幻影刺客的大招(呵呵,浅墨玩dota时可是超级幻刺控~~)。

其实暴击的实现方式很简单,就是用if条件句进行概率的判断(浅墨在这里利用4==rand( )%5来设定暴击概率为20%),如果判断成功就将“倍率x普通攻击”作为damage的值。

(哈哈,浅墨专门找到了Dota里面这两个技能的图标以及用到了这个demo里面,具体效果图在下面)


下面贴出实现人物技能的代码:

if (4==rand()%5)                   // 20%几率触发幻影刺客的大招,恩赐解脱,4倍暴击伤害{damage = 4*(rand()%10 + player.lv*player.w);monster.nHp -= (int)damage;sprintf(str,"恩赐解脱触发,这下牛逼了,4倍暴击...对怪物照成了%d点伤害",damage);} else{damage = rand()%10 + player.lv*player.w;monster.nHp -= (int)damage;sprintf(str,"玩家使用了无敌斩,伤害一般般...对怪物照成了%d点伤害",damage);}




三、完整的回合制游戏源代码


基础部分就讲解完了,下面就贴出注释详细的,完整的回合制游戏demo的代码吧:

#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,skillult,slash,magic,recover,game;HDC  hdc,mdc,bufdc;HWND hWnd;DWORD tPre,tNow;int  pNum,f,txtNum;bool attack,over;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*);void    CheckDie(int hp,bool player);//****WinMain函数,程序入口点函数**************************************int APIENTRY WinMain(HINSTANCE hInstance,                     HINSTANCE hPrevInstance,                     LPSTR     lpCmdLine,                     int       nCmdShow){ MSG msg; MyRegisterClass(hInstance); //初始化   if (!InitInstance (hInstance, nCmdShow))  {  return FALSE; } //消息循环 GetMessage(&msg,NULL,NULL,NULL);            //初始化msg        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  = NULLreturn 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,10,10,640,510,true); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); hdc = GetDC(hWnd); mdc = CreateCompatibleDC(hdc); bufdc = CreateCompatibleDC(hdc); bmp = CreateCompatibleBitmap(hdc,640,510); SelectObject(mdc,bmp); bg = (HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,640,510,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,50,50,LR_LOADFROMFILE); skillult = (HBITMAP)LoadImage(NULL,"skillult.bmp",IMAGE_BITMAP,50,50,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 = 120//设定怪物角色生命值及上限 monster.lv = 1;      //设定怪物角色等级 monster.w = 1;      //设定攻击伤害加权值 txtNum = 0;  //显示消息数目 SetBkMode(mdc, TRANSPARENT);    //设置TextOut背景透明  MyPaint(hdc); return TRUE;}//****自定义绘图函数*********************************// 1.画面贴图与对战消息显示// 2.怪物行为判断及各项数据处理与计算void MyPaint(HDC hdc)char str[100]; int i,damage; //贴上背景图 SelectObject(bufdc,bg); BitBlt(mdc,0,0,640,510,bufdc,0,0,SRCCOPY); //显示对战消息     for(i=0;i<txtNum;i++)  TextOut(mdc,0,360+i*18,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,50,50,bufdc,0,0,SRCCOPY);  SelectObject(bufdc,skillult);  BitBlt(mdc,430,350,50,50,bufdc,0,0,SRCCOPY);  //BitBlt(mdc,500,350,74,30,bufdc,0,30,SRCAND);  //BitBlt(mdc,500,350,74,30,bufdc,0,0,SRCPAINT); }  else {  f++;  //第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)   {    if (4==rand()%5)                   // 20%几率触发幻影刺客的大招,恩赐解脱,4倍暴击伤害    {     damage = 4*(rand()%10 + player.lv*player.w);     monster.nHp -= (int)damage;     sprintf(str,"恩赐解脱触发,这下牛逼了,4倍暴击...对怪物照成了%d点伤害",damage);    }     else    {     damage = rand()%10 + player.lv*player.w;     monster.nHp -= (int)damage;     sprintf(str,"玩家使用了无敌斩,伤害一般般...对怪物照成了%d点伤害",damage);    }        MsgInsert(str);    CheckDie(monster.nHp,false);   }  }  srand(tPre);  //第15个画面时判断怪物进行哪项动作  if(f == 15)  {   if(monster.nHp > 20)    //生命值大于20   {    if(rand()%5 != 1)          //进行利爪攻击概率4/5     monster.kind = 0;    else                       //进行闪电链攻击概率1/5     monster.kind = 1;       }   else        //生命值小于20   {    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个画面时计算玩家受伤害程度并加入显示消息     if(f == 30)     {      damage = rand()%10 + monster.lv*monster.w;      player.nHp -= (int)damage;            sprintf(str,"怪物利爪攻击...对玩家照成 %d 点伤害",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个画面时计算玩家受伤害程度并加入显示消息     if(f == 30)     {      damage = rand()%10 + 3*monster.w;      player.nHp -= (int)damage;             sprintf(str,"怪物释放闪电链...对玩家照成 %d 点伤害",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个画面时计算玩家受伤害程度并加入显示消息     if(f == 30)     {      damage = rand()%10 + monster.lv*monster.w*5;      player.nHp -= (int)damage;            sprintf(str,"怪物致命一击...对玩家照成 %d 点伤害.",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(1== rand()%3//逃跑几率1/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,510,mdc,0,0,SRCCOPY); tPre = GetTickCount(); pNum++; if(pNum == 8)  pNum = 0;}//****新增的对战消息函数********************************void MsgInsert(char* str)if(txtNum < 5) {  sprintf(text[txtNum],str);  txtNum++; } else {  for(int i=0;i<txtNum;i++)   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)  //按下Esc键    PostQuitMessage(0);   break;  case WM_LBUTTONDOWN:   //鼠标左键消息   if(!attack)   {    x = LOWORD(lParam);  //X坐标    y = HIWORD(lParam);  //Y坐标       if(x >= 500 && x <= 550 && y >= 350 && y <= 400)     attack = true;   }   break;  case WM_DESTROY:    //窗口结束消息   DeleteDC(mdc);   DeleteDC(bufdc);   DeleteObject(bg);   DeleteObject(sheep);   DeleteObject(girl);   DeleteObject(skill);   DeleteObject(skillult);   DeleteObject(slash);   DeleteObject(magic);   DeleteObject(recover);   DeleteObject(game);   ReleaseDC(hWnd,hdc);   PostQuitMessage(0);   break;  default:      //默认消息   return DefWindowProc(hWnd, message, wParam, lParam);   }   return 0;}



每一回合开始的时候,我们点击画面上“无敌斩”的技能图标,就可以进行攻击,对怪物造成伤害,人品好的话,还可以触发强力被动技能“恩赐解脱”,对怪物造成4倍暴击伤害,这里我们设定的暴击概率为20%



浅墨在截图的时候,人品挺好的,恩赐解脱的暴击概率为20%,但是浅墨的4次攻击里,有3次都打出了“恩赐解脱”的暴击效果,直接果断地把这只小绵羊带走了,呵呵。


下面就是游戏运行的截图:


游戏开始



第一刀就出暴击了,48点伤害



运气不错,又一刀暴击,68点伤害



最后一刀又出了暴击,小绵羊被“秒杀”,游戏结束




我们还可以调节怪物等级,怪物攻击加权值,怪物血量上限以及玩家角色等级,玩家角色攻击加权值,玩家角色血量上限来让游戏更具挑战性。


当然,我们也可以增加更多的代码,来使怪物的思考与行动方式更具真实性和多样性,来使玩家的技能更加丰富。



这个回合制游戏demo可以说是目前市场上回合制游戏的本体,《仙剑奇侠传》(三代以前的,三代及以后的仙剑都是进度条模式了),《梦幻西游》《问道》等经典的回合制游戏,无非就是在这种风格的demo基础上,写更多的代码,丰富内容而已,或为游戏引擎的核心代码。


最后浅墨再提一点,以结束这篇笔记,其实就是为了给大家提供一些实现思路:


可以在这个demo的基础上,增加剧情,世界观,游戏地图,等级系统,经验值系统,宠物系统,道具系统,符文系统,五行相克系

统,天气系统等,让这个回合制游戏更加有趣更加吸引人。

而这些系统,我在以后的笔记里面会尽量全部涵盖进行讲解的,希望大家继续关注我的博客。




本节笔记到这里就结束了。


本节笔记的源代码请点击这里下载:  【Visual C++】Note_Code_16



感谢一直支持【Visual C++】游戏开发笔记系列专栏的朋友们,也请大家继续关注我的专栏,我一有时间就会把自己的学习心得,觉得比较好的知识点写出来和大家一起分享。

精通游戏开发的路还很长很长,非常希望能和大家一起交流,共同学习,共同进步。

大家看过后觉得值得一看的话,可以顶一下这篇文章,你们的支持是我继续写下去的动力~

如果文章中有什么疏漏的地方,也请大家指正。也希望大家可以多留言来和我探讨编程相关的问题。

最后,谢谢你们一直的支持~~~


——————————浅墨于2012年4月10日


------------------------------------------------------------------------------------------------------------------------------

浅墨历时一年为游戏编程爱好者锻造的著作《逐梦旅程:Windows游戏编程之从零开始》

如果你喜欢浅墨写的【Visual C++】游戏开发系列博客文章,那么你一定会爱上这本书。

这是浅墨专门为热爱游戏编程的朋友们写的入门级游戏编程宝典。



------------------------------------------------------------------------------------------------------------------------------


           

再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

猜你喜欢

转载自blog.csdn.net/yttyffggh/article/details/86681078
今日推荐