【Visual C++】游戏开发笔记十六 讲解一个完整的回合制游戏demo
文章转载自:http://blog.csdn.net/zhmxy555/article/details/7447864
本系列文章由zhmxy555编写,转载请注明出处。
作者:毛星云 邮箱: [email protected] 欢迎邮件交流编程心得
这节笔记的主要内容是介绍一个完整的回合制游戏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)
- {
- if(rand()%5!= 1)
-
- else
-
- }
- else
- {
- switch(rand()%5)
- {
- case 0:
- break;
- case 1:
- break;
- case 2:
- break;
- case 3:
- break;
- case 4:
- if(1== rand()%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)
- {
- 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);
-
- 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);
- 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);
- MyPaint(hdc);
- return TRUE;
- }
-
-
-
- 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);
-
-
- }
- else
- {
- f++;
-
- 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);
-
- if(f == 10)
- {
- if (4==rand()%5)
- {
- 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);
-
- if(f == 15)
- {
- if(monster.nHp > 20)
- {
- if(rand()%5 != 1)
- monster.kind = 0;
- else
- monster.kind = 1;
- }
- else
- {
- 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;
- }
- }
- }
-
- 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);
-
- 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);
-
- 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);
-
- 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);
-
- if(f == 30)
- {
- monster.nHp += 30;
- sprintf(str,"怪物使用梅肯斯姆...恢复了30点生命值",damage);
- MsgInsert(str);
- }
- break;
- case 4:
-
- if(f == 30)
- {
- if(1== rand()%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)
- PostQuitMessage(0);
- break;
- case WM_LBUTTONDOWN:
- if(!attack)
- {
- x = LOWORD(lParam);
- y = HIWORD(lParam);
- 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日