您的位置:首页 > 编程语言 > C语言/C++

【Visual C++】游戏编程学习笔记之九:回合制游戏demo(剑侠客VS巡游天神)

2015-03-20 22:21 323 查看
本系列文章由@二货梦想家张程 所写,转载请注明出处。

作者: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;

//函数声明
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
void GameRun(HDC hdc);
void MsgInsert(TCHAR* str);
void CheckDie(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); //初始化msg
while (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/3
mciSendString(L"open Audio\\stick.mp3 alias stick", NULL, 0, NULL);//播放音效
mciSendString(L"play stick ", NULL, 0, NULL);
}
else
{
s_monster.kind = 1;//进行泰山压顶攻击,1/3
mciSendString(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++】游戏编程学习笔记九


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐