我的家園

          我的家園

          本系列文章由zhmxy555編寫,轉載請注明出處。 文章鏈接  http://blog.csdn.net/zhmxy555/article/details/7447864

          作者:毛星云    郵箱: happylifemxy@qq.com    歡迎郵件交流編程心得



          這節筆記的主要內容是介紹一個完整的回合制游戲demo,而這個demo里面主要突出了游戲里AI的各種思考與行為的方式.這樣的通過計算機角色本身的判斷思考,然后產生對應行為的AI稱作行為型游戲AI。

          如果對AI基礎不太了解的朋友,請移步:


          【Visual C++】游戲開發筆記十五 游戲人工智能(一) 運動型游戲AI


          首先,我們來了解這種行為型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/5
          else
                 //進行閃電鏈攻擊概率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	 = 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,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日

          作者:zhmxy555 發表于2012-4-11 3:10:45 原文鏈接
          閱讀:7242 評論:96 查看評論

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 新和县| 潮州市| 林芝县| 石柱| 彭阳县| 三亚市| 滨州市| 平塘县| 阜阳市| 云梦县| 贵州省| 芒康县| 岳池县| 安康市| 巫山县| 西充县| 金塔县| 连山| 溧阳市| 叶城县| 梧州市| 昌邑市| 玉屏| 台江县| 南康市| 紫阳县| 沛县| 筠连县| 宁河县| 德安县| 陇川县| 霍邱县| 遂宁市| 永新县| 儋州市| 棋牌| 石城县| 鹤峰县| 定结县| 腾冲县| 香格里拉县|