本系列文章由zhmxy555編寫,轉載請註明出處。
http://qianmo.blog.51cto.com/5127279/875577
作者:毛星雲 郵箱: [email protected] 歡迎郵件交流編程心得
我們常常聽聞AI(Artificial Intelligence人工智能)這個名詞,比如Dota裏面的AI地圖。寫這篇文章的時候,最新版的Dota AI是6.72f,估計過幾天6.73的AI也要出來了。很多Dota玩家喜歡玩AI地圖練練感覺和補刀,可以這樣說,Dota 地圖成功的加入了AI元素,是近幾年Dota風靡全球不可缺少的因素之一。
一、知識點講解
那麼,到底什麼是AI呢?首先我們來了解一下人工智能(AI)的具體定義。“人工智能”(Artificial Intelligence)簡稱AI。它是研究、開發用於模擬、延伸和擴展人的智能的理論、方法、技術及應用系統的一門新的技術科學。人工智能研究如何用計算機去模擬、延伸和擴展人的智能;如何把計算機用得更聰明;如何設計和建造具有高智能水平的計算機應用系統;如何設計和製造更聰明的計算機以及智能水平更高的智能計算機等。人工智能是計算機科學的一個分支,人工智能是計算機科學技術的前沿科技領域。人工智能與計算機軟件有密切的關係。一方面,各種人工智能應用系統都要用計算機軟件去實現,另一方面,許多聰明的計算機軟件也應用了人工智能的理論方法和技術。
而我們要講解的遊戲人工智能,只是淵博的人工智能領域裏面的冰山一角。我們不會用到那些類似於神經網絡,基因算法,模糊邏輯等複雜的人工智能理論,我們只需利用自己本身的思考模式去賦予遊戲中角色判斷的能力,來進行某些特定的行爲。
今天我們主角是運動型的AI,下面就開始正題吧。
凡是在遊戲中會移動的物體,幾乎都涉及到了運動型的遊戲AI,例如遊戲中怪物的追逐或者躲避玩家和遊戲中NPC角色的移動都是移動型AI的例子。
<1>追逐移動
下面我們以移動型AI裏的追逐移動型AI來作爲例子講解。
追逐移動一般是通過控制一角色朝某一目標接近來實現,簡單點說,就是兩個物體的空間座標相互接近。比如我們要設計一個怪物追逐玩家的遊戲,只要在每次進行貼圖時,將怪物坐在座標與玩家角色所在的座標進行比較,自增或者自減怪物X,Y軸上的貼圖座標,就可產生追逐移動的效果。下面就是一個典型的怪物追逐外加的移動AI算法,其中“梟獸X”、“梟獸Y”,“幻影刺客X”,“幻影刺客Y”分別用來表示怪物及玩家在X與Y軸上的貼圖座標。
【算法1】
- If(梟獸X>幻影刺客X)
- 梟獸X--;
- else
- 梟獸X++;
- If(梟獸Y<幻影刺客Y)
- 梟獸Y++;
- else
- 梟獸Y--;
下面我們再來看一個例子,這段算法是以上面的【算法1】爲核心代碼,賦予了怪物更多的“思考”空間。追逐移動的怪物會按照自身生命值的多寡來決定是否進行追逐,每次計算下次的位置座標時,也只有二分之三的機率能正確地朝向玩家,以其中以“梟獸HP”來表示怪物當前的生命值。
【算法2】]
- If(梟獸HP>200) //生命值大於200時才追
- (
- P=rand()%3; //取隨機數除以3的餘數
- If(p!=1) //餘數不爲1時進行追逐
- {
- If(梟獸X>幻影刺客X)
- 梟獸X--;
- else
- 梟獸X++;
- If(梟獸Y<幻影刺客Y)
- 梟獸Y++;
- else
- 梟獸Y--;
- }
- else
- 梟獸HP+=5 //怪物不動,自動補5點血
- )
這樣的怪物就比較有靈性了,要繼續創造出更聰明的AI,只要繼續完善代碼,寫出更多的功能就行了。
<2>躲避移動
其實躲避移動和追逐移動的算法差不多,就是把++的地方和--對調就行了,讓怪物與人物的空間座標相互遠離。
具體代碼如下:
【算法3】
- If(梟獸X>幻影刺客X)
- 梟獸X++;
- else
- 梟獸X--;
- If(梟獸Y<幻影刺客Y)
- 梟獸Y--;
- else
- 梟獸Y++;
二、在實例中將知識融會貫通
依舊,我們看一個實例,來將本節的知識融會貫通.
這是一個小鳥追小女孩的場景,我們需要用鍵盤的【↑】【↓】【←】【→】鍵來躲避小鳥的追擊,具體鍵盤輸入消息的知識點還
不太瞭解的朋友,請移步筆記十二,這裏給出鏈接:
【Visual C++】遊戲開發筆記十二 遊戲輸入消息處理(一) 鍵盤消息處理
下面依舊是貼圖詳細註釋的源代碼:
- #include "stdafx.h"
- #include <stdio.h>
- //全局變量聲明
- HINSTANCE hInst;
- HBITMAP girl[4],bg,bird;
- HDC hdc,mdc,bufdc;
- HWND hWnd;
- DWORD tPre,tNow,nowX,nowY;
- POINT p[3]; //用於記錄3只小鳥的貼圖座標
- int num,dir,x,y; //x,y變量爲人物貼圖座標,dir爲人物移動方向,這裏我們中以0,1,2,3代表人物上,下,左,右方向上的移動:num爲連續貼圖中的小圖編號
- //全局函數聲明
- ATOM MyRegisterClass(HINSTANCE hInstance);
- BOOL InitInstance(HINSTANCE, int);
- LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
- void MyPaint(HDC hdc);
- //****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;
- 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,480,true);
- ShowWindow(hWnd, nCmdShow);
- UpdateWindow(hWnd);
- hdc = GetDC(hWnd);
- mdc = CreateCompatibleDC(hdc);
- bufdc = CreateCompatibleDC(hdc);
- //建立空的位圖並置入mdc中
- bmp = CreateCompatibleBitmap(hdc,640,480);
- SelectObject(mdc,bmp);
- //設定人物貼圖初始位置和移動方向
- x = 300;
- y = 250;
- dir = 0;
- num = 0;
- nowX = 300;
- nowY = 300;
- //載入各連續移動位圖及背景圖
- girl[0] = (HBITMAP)LoadImage(NULL,"girl0.bmp",IMAGE_BITMAP,440,148,LR_LOADFROMFILE);
- girl[1] = (HBITMAP)LoadImage(NULL,"girl1.bmp",IMAGE_BITMAP,424,154,LR_LOADFROMFILE);
- girl[2] = (HBITMAP)LoadImage(NULL,"girl2.bmp",IMAGE_BITMAP,480,148,LR_LOADFROMFILE);
- girl[3] = (HBITMAP)LoadImage(NULL,"girl3.bmp",IMAGE_BITMAP,480,148,LR_LOADFROMFILE);
- bg = (HBITMAP)LoadImage(NULL,"bg.bmp",IMAGE_BITMAP,640,480,LR_LOADFROMFILE);\
- bird = (HBITMAP)LoadImage(NULL,"bird.bmp",IMAGE_BITMAP,122,122,LR_LOADFROMFILE);
- p[0].x = 30;
- p[0].y = 100;
- p[1].x = 250;
- p[1].y = 250;
- p[2].x = 500;
- p[2].y = 400;
- MyPaint(hdc);
- return TRUE;
- }
- //****自定義繪圖函數*********************************
- // 1.人物貼圖座標修正及窗口貼圖
- //進行AI行爲判斷並貼圖
- void MyPaint(HDC hdc)
- {
- int w,h,i;
- //先在mdc中貼上背景圖
- SelectObject(bufdc,bg);
- BitBlt(mdc,0,0,640,480,bufdc,0,0,SRCCOPY);
- //按照目前的移動方向取出對應人物的連續走動圖,並確定截取人物圖的寬度與高度
- SelectObject(bufdc,girl[dir]);
- switch(dir)
- {
- case 0:
- w = 55;
- h = 74;
- break;
- case 1:
- w = 53;
- h = 77;
- break;
- case 2:
- w = 60;
- h = 74;
- break;
- case 3:
- w = 60;
- h = 74;
- break;
- }
- //按照目前的X,Y的值在mdc上進行透明貼圖,然後顯示在窗口畫面上
- BitBlt(mdc,x,y,w,h,bufdc,num*w,h,SRCAND);
- BitBlt(mdc,x,y,w,h,bufdc,num*w,0,SRCPAINT);
- //貼出鳥的圖片
- SelectObject(bufdc,bird);
- for(i=0;i<3;i++)
- {
- if(rand()%3 != 1) //有2/3機率進行追蹤
- {
- if(p[i].y > y-16)
- p[i].y -= 5;
- else
- p[i].y += 5;
- if(p[i].x > x-25)
- p[i].x -= 5;
- else
- p[i].x += 5;
- }
- if(p[i].x > x-25) //判斷小鳥的移動方向,從而選擇合適的位圖朝向
- {
- BitBlt(mdc,p[i].x,p[i].y,61,61,bufdc,61,61,SRCAND);
- BitBlt(mdc,p[i].x,p[i].y,61,61,bufdc,0,61,SRCPAINT);
- }
- else
- {
- BitBlt(mdc,p[i].x,p[i].y,61,61,bufdc,61,0,SRCAND);
- BitBlt(mdc,p[i].x,p[i].y,61,61,bufdc,0,0,SRCPAINT);
- }
- }
- BitBlt(hdc,0,0,640,480,mdc,0,0,SRCCOPY);
- tPre = GetTickCount(); //記錄此次繪圖時間
- num++;
- if(num == 8)
- num = 0;
- }
- //****消息處理函數***********************************
- // 1.按下【Esc】鍵結束程序
- // 2.按下方向鍵重設貼圖座標
- LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
- {
- switch (message)
- {
- case WM_KEYDOWN: //按下鍵盤消息
- //判斷按鍵的虛擬鍵碼
- switch (wParam)
- {
- case VK_ESCAPE: //按下【Esc】鍵
- PostQuitMessage( 0 ); //結束程序
- break;
- case VK_UP: //按下【↑】鍵
- //先按照目前的移動方向來進行貼圖座標修正,並加入人物往上移動的量(每次按下一次按鍵移動10個單位),來決定人物貼圖座標的X與Y值,接着判斷座標是否超出窗口區域,若有則再次修正
- switch(dir)
- {
- case 0:
- y -= 10;
- break;
- case 1:
- x -= 1;
- y -= 8;
- break;
- case 2:
- x += 2;
- y -= 10;
- break;
- case 3:
- x += 2;
- y -= 10;
- break;
- }
- if(y < 0)
- y = 0;
- dir = 0;
- break;
- case VK_DOWN: //按下【↓】鍵
- switch(dir)
- {
- case 0:
- x += 1;
- y += 8;
- break;
- case 1:
- y += 10;
- break;
- case 2:
- x += 3;
- y += 6;
- break;
- case 3:
- x += 3;
- y += 6;
- break;
- }
- if(y > 375)
- y = 375;
- dir = 1;
- break;
- case VK_LEFT: //按下【←】鍵
- switch(dir)
- {
- case 0:
- x -= 12;
- break;
- case 1:
- x -= 13;
- y += 4;
- break;
- case 2:
- x -= 10;
- break;
- case 3:
- x -= 10;
- break;
- }
- if(x < 0)
- x = 0;
- dir = 2;
- break;
- case VK_RIGHT: //按下【→】鍵
- switch(dir)
- {
- case 0:
- x += 8;
- break;
- case 1:
- x += 7;
- y += 4;
- break;
- case 2:
- x += 10;
- break;
- case 3:
- x += 10;
- break;
- }
- if(x > 575)
- x = 575;
- dir = 3;
- break;
- }
- break;
- case WM_DESTROY: //窗口結束消息
- int i;
- DeleteDC(mdc);
- DeleteDC(bufdc);
- for(i=0;i<4;i++)
- DeleteObject(girl[i]);
- DeleteObject(bg);
- ReleaseDC(hWnd,hdc);
- PostQuitMessage(0);
- break;
- default: //其他消息
- return DefWindowProc(hWnd, message, wParam, lParam);
- }
- return 0;
- }
運行截圖如下:
以及
運行這個小遊戲,我們要用鍵盤的【↑】【↓】【←】【→】鍵來躲避小鳥的追擊,小鳥則會不斷向人物靠近。
之前小鳥閃爍的bug已經修復,感謝 lghui1 的指出,我是大意了,將背景圖在hdc上繪製了兩次。
已經下載源代碼的朋友,請將177行的 BitBlt(hdc,0,0,640,480,mdc,0,0,SRCCOPY); 刪去即可。
本節筆記到這裏就結束了,由於近期在做一個純flash的網站,更新速度和評論的回覆都不像往常那麼及時,希望大家能夠體諒。
本節筆記的源代碼請點擊這裏下載: 【Visual C++】Code_Note_15
感謝一直支持【Visual C++】遊戲開發筆記系列專欄的朋友們,也請大家繼續關注我的專欄,我一有時間就會把自己的學習心得,覺得比較好的知識點寫出來和大家一起分享。
精通遊戲開發的路還很長很長,非常希望能和大家一起交流,共同學習,共同進步。
大家看過後覺得值得一看的話,可以頂一下這篇文章,你們的支持是我繼續寫下去的動力~
如果文章中有什麼疏漏的地方,也請大家指正。也希望大家可以多留言來和我探討編程相關的問題。
最後,謝謝你們一直的支持~~~
——————————淺墨於2012年4月7日