首页 技术 正文
技术 2022年11月20日
0 收藏 939 点赞 3,569 浏览 11520 个字

本系列文章由@二货梦想家张程 所写,转载请注明出处。

作者:ZeeCoder  微博链接:http://weibo.com/zc463717263

我的邮箱:michealfloyd@126.com   欢迎大家发邮件来和我交流编程心得

you are what you read!与大家共勉!

————————————————-分割线:ZeeCoder——————————————–

本笔记借鉴了经典回合制游戏大作—梦幻西游的战斗场景和音效。

回合制游戏主要利用行为性AI,通常都依靠连续的if….else…以及switch…case…语句来判断和运算,或者利用一些数据结构的概念

一、计算机角色的思考和行为

在本游戏demo中,巡游天神(计算机)的行为主要有以下:

1.普通攻击

2.泰山压顶

3.地狱烈火

4.回血

5.逃跑

那么在游戏编程设计中,我们可以定义一个int型kind(表示攻击类型),然后通过switch…case语句来做出相应判断

if (s_monster.nHp > 200)//当怪物血量小于200时{if (rand()%3 != 1){//普通攻击,2/3几率}else{//进行泰山压顶攻击,1/3几率}}else{switch(rand()%5){case 0://使用普通攻击break;case 1://使用泰山魔法攻击break;case 2://使用地狱烈火break;case 3://回血break;case 4://逃跑break;}}

二、玩家角色攻击和行为

本游戏中玩家为剑侠客,他主要有两个技能加一个暴击:

1、剑击

2、雷电诀

3、暴击(十分之一概率)

判断剑侠客释放哪个技能主要通过鼠标左键单击。

case WM_LBUTTONDOWN:if (!attack_1 && !attack_2)  //当没有按下攻击按钮{x = LOWORD(lParam);//获取游戏鼠标坐标y = HIWORD(lParam);if ( x >= 80 && y >= 150 && x <=  113 && y <= 183 )//判断是否按下了剑击按钮{attack_1 = true ;attack_2 = false ;}if ( x >= 80 && y >= 200 && x <=  113 && y <= 233 )//判断是否按下了雷神诀按钮{attack_1 = false ;attack_2 = true ;}}

玩家按下技能按钮后,需要判断行为。

if (attack_1 && !attack_2) //释放剑击技能{//添加行为}else if (!attack_1 && attack_2)//释放雷击技能{//添加行为}if ( frame ==10)//第十个画面统计怪物收到的伤害值并显示{if ( 4 == rand()%10)//百分之十几率释放暴击技能{//添加行为}else{if (!attack_1 && attack_2) //释放雷击技能{//添加行为}else if (attack_1 && !attack_2)//释放剑击技能{//添加行为} }}

三、下面来看整个demo的完整代码。

1、为了偷懒使用了TransparentBlt进行透明贴图,效果感觉和bitblt差不多。可能是我P图没P好的原因吧。

2、本demo利用msi类,添加了战斗音效。效果还是很酷炫的,代码也不叫简单。

#include "stdafx.h"#include "GameDemo.h"#define MAX_LOADSTRING 100#pragma comment (lib , "msimg32.lib")#include <Mmsystem.h>#pragma comment(lib, "Winmm.lib")//定义结构struct state{int nHp;//目前生命值int mHp;//最大生命值int lv;//怪物等级int Weight;//加权值int kind;//怪物的行为代号};//全局变量HINSTANCE hInst;HBITMAP bg , h_monster , h_player , skill1 , skill2 , sword , thunder, stick ,hill ,blaze  ,recover,gameover,baoji;HDC hdc , mdc , bufdc;HWND hWnd;DWORD tNow , tPre;state s_player , s_monster;bool attack_1,attack_2 , over ;TCHAR text[5][100];int pNum , mNum ,frame , textNum;//函数声明ATOMMyRegisterClass(HINSTANCE hInstance);BOOLInitInstance(HINSTANCE, int);LRESULT CALLBACKWndProc(HWND, UINT, WPARAM, LPARAM);INT_PTR CALLBACKAbout(HWND, UINT, WPARAM, LPARAM);voidGameRun(HDC hdc);voidMsgInsert(TCHAR* str);voidCheckDie(int hp , bool player);//***************************WinMain主函数**********************************int APIENTRY _tWinMain(HINSTANCE hInstance,                     HINSTANCE hPrevInstance,                     LPTSTR    lpCmdLine,                     int       nCmdShow){MSG msg;//调用窗口类函数MyRegisterClass(hInstance);//初始化if (!InitInstance (hInstance, nCmdShow)){return FALSE;}//消息循环GetMessage(&msg,NULL,NULL,NULL);  //初始化msgwhile (msg.message != WM_QUIT){if ( PeekMessage( &msg , NULL ,0 ,0 ,PM_REMOVE))//PM_REMOVE消息从队列里除掉{TranslateMessage(&msg);DispatchMessage(&msg);}else{tNow = GetTickCount();//获取当前时间if (tNow - tPre >= 100)//实现游戏循环{GameRun(hdc);//循环贴图}}}return (int) msg.wParam;}//***************************窗口类函数**********************************ATOM MyRegisterClass(HINSTANCE hInstance){WNDCLASSEX wcex;wcex.cbSize = sizeof(WNDCLASSEX);wcex.style= CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;wcex.lpfnWndProc= WndProc;wcex.cbClsExtra= 0;wcex.cbWndExtra= 0;wcex.hInstance= hInstance;wcex.hIcon= NULL;wcex.hCursor= NULL;wcex.hbrBackground= (HBRUSH)(COLOR_WINDOW+1);wcex.lpszMenuName= MAKEINTRESOURCE(IDC_GAMEDEMO);wcex.lpszClassName= _T("Game");wcex.hIconSm= NULL;return RegisterClassEx(&wcex);}//***************************初始化函数**********************************//实现功能:加载位图并给各变量设定初始值BOOL InitInstance(HINSTANCE hInstance, int nCmdShow){   HBITMAP bmp;   hInst = hInstance; // Store instance handle in our global variable   hWnd = CreateWindow(_T("Game"), _T("致经典--梦幻西游"), 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);   bmp = CreateCompatibleBitmap(hdc , 640 , 480);   SelectObject(mdc , bmp);   //载入位图   bg=(HBITMAP)LoadImage(NULL , _T("Res\\bg.bmp") , IMAGE_BITMAP ,  640 ,480 , LR_LOADFROMFILE);//背景图   h_monster=(HBITMAP)LoadImage(NULL , _T("Res\\monster.bmp") , IMAGE_BITMAP ,  640 , 144 , LR_LOADFROMFILE);//怪物图   h_player=(HBITMAP)LoadImage(NULL , _T("Res\\player.bmp") , IMAGE_BITMAP ,  448 , 91 , LR_LOADFROMFILE);//玩家图   skill1=(HBITMAP)LoadImage(NULL , _T("Res\\skill1.bmp") , IMAGE_BITMAP ,  33 ,33 , LR_LOADFROMFILE);//技能1   skill2=(HBITMAP)LoadImage(NULL , _T("Res\\skill2.bmp") , IMAGE_BITMAP ,  33 ,33 , LR_LOADFROMFILE);//技能2   baoji        =   (HBITMAP)LoadImage(NULL , _T("Res\\baoji.bmp") , IMAGE_BITMAP ,  160 ,84 , LR_LOADFROMFILE);//暴击   sword=(HBITMAP)LoadImage(NULL , _T("Res\\sword.bmp") , IMAGE_BITMAP ,  200 ,102 , LR_LOADFROMFILE);//玩家使用剑击   thunder=(HBITMAP)LoadImage(NULL , _T("Res\\thunder.bmp") , IMAGE_BITMAP ,  160 ,210 , LR_LOADFROMFILE);//雷击效果图   stick=(HBITMAP)LoadImage(NULL , _T("Res\\stick.bmp") , IMAGE_BITMAP ,  1100 ,720 , LR_LOADFROMFILE);//巡游棍击   blaze=(HBITMAP)LoadImage(NULL , _T("Res\\blaze.bmp") , IMAGE_BITMAP ,  88 ,79 , LR_LOADFROMFILE);//火焰效果图   hill=(HBITMAP)LoadImage(NULL , _T("Res\\hill.bmp") , IMAGE_BITMAP ,  80 ,64 , LR_LOADFROMFILE);//泰山压顶   //recover=(HBITMAP)LoadImage(NULL , _T("Res\\recover.bmp") , IMAGE_BITMAP ,  1100 ,720 , LR_LOADFROMFILE);//回血效果图   gameover=(HBITMAP)LoadImage(NULL , _T("Res\\gameover.bmp") , IMAGE_BITMAP ,  400 ,100 , LR_LOADFROMFILE);//游戏结束   s_player.nHp = s_player.mHp = 130;   s_player.lv = 3 ;   s_player.Weight = 5 ;   s_monster.nHp = s_monster.mHp = 400;   s_monster.lv = 1;   s_monster.Weight = 1;   textNum = 0 ;   SetBkMode(mdc , TRANSPARENT); //设置TextOut背景透明   GameRun(hdc);   return TRUE;}//***************************消息响应函数**********************************// 判断玩家是否使用攻击命令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_1 && !attack_2)  //当没有按下攻击按钮{x = LOWORD(lParam);y = HIWORD(lParam);if ( x >= 80 && y >= 150 && x <=  113 && y <= 183 )//判断是否按下了剑击按钮{attack_1 = true ;attack_2 = false ;mciSendString(L"open Audio\\sword.mp3 alias sword", NULL, 0, NULL);mciSendString(L"play sword ", NULL, 0, NULL);}if ( x >= 80 && y >= 200 && x <=  113 && y <= 233 )//判断是否按下了雷击按钮{attack_1 = false ;attack_2 = true ;mciSendString(L"open Audio\\thunder.mp3 alias thunder", NULL, 0, NULL);mciSendString(L"play thunder ", NULL, 0, NULL);}}break;case WM_DESTROY:DeleteDC(mdc);DeleteDC(bufdc);PostQuitMessage(0);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}return 0;}//********************************自定义游戏回合开始函数***********************//实现功能://1.贴战斗实时战况消息//2.怪物及玩家行为判断及血量等各项数据的计算void GameRun(HDC hdc){TCHAR str[100] ;memset(str , 0 ,sizeof(TCHAR)*100);int i , damage;//播放背景音乐mciSendString(L"open Audio\\bgm.mp3 alias bgm", NULL, 0, NULL);mciSendString(L"play bgm repeat", NULL, 0, NULL);//贴上背景图SelectObject(bufdc , bg);BitBlt(mdc , 0 , 0 , 640 , 480 , bufdc , 0 , 0 ,SRCCOPY);//显示战况消息for (i = 0 ; i < 6 ; i++){TextOut(mdc , 0 , 300 + i*18, text[i] , _tcslen(text[i]));}//贴上怪物图if (s_monster.nHp > 0)//如果怪物没死{SelectObject(bufdc , h_monster);// BitBlt(mdc,700,250,78,94,bufdc,mNum*78,94,SRCAND);// BitBlt(mdc,700,250,78,94,bufdc,mNum*78,0,SRCPAINT);TransparentBlt(mdc , 400 , 100 ,160 , 144 , bufdc , mNum*160 , 0 ,160 , 144 , RGB(62,91,167));_stprintf_s(str , TEXT("%d / %d") , s_monster.nHp , s_monster.mHp);SetTextColor(mdc , RGB(0,135,0));TextOut(mdc , 430 , 90 , str , _tcslen(str));}//贴上玩家图if (s_player.nHp > 0){SelectObject(bufdc , h_player);// BitBlt(mdc,200,200,53,137,bufdc,pNum*53,137,SRCAND);// BitBlt(mdc,200,200,53,137,bufdc,pNum*53,0,SRCPAINT);TransparentBlt(mdc , 130 , 135 , 56 , 91 , bufdc , pNum*56 , 0 ,56 , 91 , RGB(62,91,167));//透明贴图_stprintf_s(str , TEXT("%d / %d") , s_player.nHp , s_player.mHp);TextOut(mdc , 130 , 120 , str , _tcslen(str));}//贴上游戏结束图画if (over){SelectObject(bufdc , gameover);TransparentBlt(mdc , 120 , 130 , 400 , 100 , bufdc , 0 , 0 ,400 , 100 , RGB(255,255,255));//透明贴图}//贴上技能图else if (!attack_1 && !attack_2)//没有攻击命令{SelectObject(bufdc , skill1);BitBlt(mdc , 80 , 150 , 33 , 33 , bufdc , 0 , 0 , SRCCOPY);SelectObject(bufdc , skill2);BitBlt(mdc , 80 , 200 , 33 , 33 , bufdc , 0 , 0 , SRCCOPY);}else{frame++;//第五到十个画面显示玩家攻击状态if ( frame >=5 && frame <= 10){if (attack_1 && !attack_2) //释放剑击技能{SelectObject(bufdc , sword);mciSendString(L"close sword ", NULL, 0, NULL);//剑击效果图TransparentBlt(mdc , 200 , 130 , 200 , 102 , bufdc , 0 , 0 ,200 , 102 , RGB(255,0,0));//透明贴图}else if (!attack_1 && attack_2)//释放雷击技能{SelectObject(bufdc , thunder);mciSendString(L"close thunder ", NULL, 0, NULL);//雷击效果图TransparentBlt(mdc , 400 , 80 , 160 , 210 , bufdc , 0 , 0 ,160 , 210 , RGB(255,255,255));//透明贴图}if ( frame ==10)//第十个画面统计怪物收到的伤害值并显示{if ( 4 == rand()%10)//百分之十几率释放暴击技能{damage = 4*(rand()%10 + s_player.lv * s_player.Weight);s_monster.nHp -= (int)damage;//贴上暴击效果图_stprintf_s(str , TEXT("剑侠客使出浑身力气,4倍暴击。。。!!巡游天兵血量瞬间下降%d") , damage);}else{if (!attack_1 && attack_2) //释放雷击技能{damage = 1.5*(rand()%10 + s_player.lv * s_player.Weight);s_monster.nHp -= (int)damage;_stprintf_s(str , TEXT("剑侠客释放雷击技能,巡游天兵被从天而降的雷劈掉了%d伤害") , damage);}else if (attack_1 && !attack_2)//释放剑击技能{damage = rand()%10 + s_player.lv * s_player.Weight;s_monster.nHp -= (int)damage;_stprintf_s(str , TEXT("剑侠客一剑刺向巡游天兵,巡游天兵躲闪不及,生命值减少%d") , damage);} }MsgInsert(str);CheckDie(s_monster.nHp , false); }}srand(tPre);//使得每次产生的随机数不一样//第十五个画面判断怪物进行哪项动作if (frame == 15){if (s_monster.nHp > 200){if (rand()%3 != 1){s_monster.kind = 0; //普通攻击,2/3mciSendString(L"open Audio\\stick.mp3 alias stick", NULL, 0, NULL);//播放音效mciSendString(L"play stick ", NULL, 0, NULL);}else{s_monster.kind = 1;//进行泰山压顶攻击,1/3mciSendString(L"open Audio\\hill.mp3 alias hill", NULL, 0, NULL);//播放音效mciSendString(L"play hill ", NULL, 0, NULL);}}else{switch(rand()%5){case 0://使用普通攻击s_monster.kind = 0 ;break;case 1://使用泰山魔法攻击s_monster.kind = 1 ;break;case 2://使用地狱烈火mciSendString(L"open Audio\\blaze.mp3 alias blaze", NULL, 0, NULL);//播放音效mciSendString(L"play blaze ", NULL, 0, NULL);s_monster.kind = 2 ;break;case 3://回血s_monster.kind = 3 ;break;case 4://逃跑s_monster.kind = 4 ;break;}}}if ( frame >= 26 && frame <= 30)//第26~30个画面显示巡游攻击状态动画{switch (s_monster.kind){case 0://使用普通攻击SelectObject(bufdc , stick);mciSendString(L"close stick ", NULL, 0, NULL);//第30个画面时计算玩家受到的伤害程序并显示消息if (frame == 30){damage = rand()%10 + s_monster.lv*s_monster.Weight;s_player.nHp -= (int)damage;_stprintf_s(str , TEXT("巡游一棍打向剑侠客,正中额头,伤害-%d") , damage);MsgInsert(str);CheckDie(s_player.nHp , true);}break;case 1://使用泰山压顶攻击SelectObject(bufdc , hill);mciSendString(L"close hill ", NULL, 0, NULL);TransparentBlt(mdc , 120 , 135 , 80 , 64 , bufdc , 0 , 0 ,80 , 64 , RGB(0,0,0));//透明贴图//第30个画面时计算玩家受到的伤害程序并显示消息if (frame == 30){damage = 1.5*(rand()%10 + s_monster.lv*s_monster.Weight);s_player.nHp -= (int)damage;_stprintf_s(str , TEXT("巡游怒喝一声,泰山压顶!!呀哈哈哈!,剑侠客血量-%d") , damage);MsgInsert(str);CheckDie(s_player.nHp , true);}break;case 2://使用地狱烈火SelectObject(bufdc , blaze);mciSendString(L"close blaze ", NULL, 0, NULL);TransparentBlt(mdc , 120 , 135 , 88 , 79 , bufdc , 0 , 0 ,88 , 79 , RGB(0,0,0));//透明贴图//第30个画面时计算玩家受到的伤害程序并显示消息if (frame == 30){damage = 3*(rand()%10 + s_monster.lv*s_monster.Weight);s_player.nHp -= (int)damage;_stprintf_s(str , TEXT("巡游口里念着咒语,地狱烈火!剑侠客在熊熊烈火中痛不欲生,伤害-%d") , damage);MsgInsert(str);CheckDie(s_player.nHp , true);}break;case 3://回血//SelectObject(bufdc , recover);//第30个画面时计算玩家受到的伤害程序并显示消息if (frame == 30){s_monster.nHp += 50;_stprintf_s(str , TEXT("普渡众生,巡游回复了50点生命值") );MsgInsert(str);}break;case 4://逃跑//第30个画面时计算玩家受到的伤害程序并显示消息if (frame == 30){if (1 == rand()%3){over = true;s_monster.nHp = 0;_stprintf_s(str , TEXT("巡游落荒而逃....逃跑成功") );MsgInsert(str);}else{_stprintf_s(str , TEXT("巡游落荒而逃....逃跑失败") );MsgInsert(str);}}break;}}if (frame == 30)//第30个画面回合结束{attack_1 = attack_2 = false;frame = 0;}}BitBlt(hdc , 0 ,0 ,640 ,480 ,mdc , 0 , 0 ,SRCCOPY);tPre = GetTickCount();  //动画循环变量pNum++;mNum++;if(pNum == 8)       pNum = 0;if (mNum == 4)   mNum = 0;}//对战消息显示函数void MsgInsert(TCHAR* str){if (textNum < 5){_stprintf_s(text[textNum] , str);textNum ++;}else{for (int i = 0 ; i  < textNum ; i++){_stprintf_s(text[i],text[i+1]);}_stprintf_s(text[4] , str);}}void CheckDie(int hp , bool player){TCHAR str[100] ;memset(str , 0 ,sizeof(TCHAR)*100);if (hp <= 0){over = true;if (player){_stprintf_s(str,TEXT("胜败乃兵家常事,少侠请重新来过....."));MsgInsert(str);}else{_stprintf_s(str,TEXT("恭喜少侠,你赢了,巡游被你打死了...."));MsgInsert(str);}}}

四、效果图

笔记九over~~这个游戏demo算是比较完整的梦幻西游一个回合的再现,包括:游戏音效,战斗动画,战斗情况显示等。搜集素材比较费力,做一个游戏还真是不容易啊。~希望在游戏编程上奋斗的人能够耐得住性子,尽量把自己的游戏做得完美~与大家共勉~!!

—end

本笔记配套代码已上传,希望大家能够下载下来,运行,音效和动画还是挺好的。

下载地址:【Visual C++】游戏编程学习笔记九



相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:8,992
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,506
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,349
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,134
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,767
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,844