在本章中我们的任务是赋予游戏中对抗对象的生命值,以便游戏中的打斗更有真
实性。具体实现方法是在上一章的程序中修改加入对应功能。
我们规定,在人与动物相遇打斗时,生命值man[q].smz随对方的攻击力而减少。当
对象生命值减少到一定程度时,我们就认为这个对象生命终结了。对比动画的表现有
一个对象倒地的过程,倒地后的残体还应该慢慢的化解,到最后消失。下面我们在程
序中加入这种过程的算法。这些算法多数是在原来的程序中加入的,所以我们就按修
改程序的顺序来讲解。
根据游戏角色动作序列有;
JCDZ zjdz[6]={ 0,5, 40,10, 120,10, 200,10, 280,10, 360,5};//人
//0 站, 1 走, 2 刺, 3 劈, 4 倒地 5 残体化解
JCDZ zjdw[6]={ 0,5, 40,10, 120,10, 200,10, 280,10, 360,5};//兽
//0 站, 1 走, 2 跑, 3 斗, 4 倒地, 5 残体化解
man[q].zs=4为对象倒地;man[q].zs=5为对象残体化解;我们设man[q].zs>5为对象残
体化解完毕。
现在我们假定某个对象man[q].smz的生命值完了,从对象已倒地man[q].zs>4开始看
相关程序的修改。
对象显示函数中有对象的动作变换计数,我们在这里加上对象倒地后残体化解的
动作变换过程(见加黑部分)。
void game::Alphasetobj(int q,int a)//对象显示
{ if(a==1&&man[q].lb==2) return;
1 if(man[q].zs>5){Delete(q);return;} //删除对象返回
⋯⋯
//-----------------------------------生命值,对象倒地后残体化解的动作变换过程
2 if(man[q].zd==1) setxtx(q,x,y); //对象正在打斗时,调显示生命值函数
3 if(man[q].zs>4)//对象倒地
4 {man[q].smz=man[q].smz-1; //对象倒地后,smz 用于残体化解计数
5 if(man[q].smz<-200){man[q].smz=0;man[q].p++;}//残体化解过程
}
else
//------------------------------------
man[q].p++; //下一动作
if(man[q].p>=man[q].m1) {bianfw(q);} //本动作完成,进行方位转换
}
对象显示函数加入部分注释:
第 1行对象残体化解完毕man[q].zs>5,删除q对象返回。
第 2行对象正在打时,调显示生命值函数setxtx(q,x,y),显示对象的生命值。
第 3行对象已倒地执行第4、5行,否则执行正常的动作变换。
第 4行对象倒地后,smz用于残体化解计数,减缓残体化解过程。
第 5行,残体化解过程。
在这里我们新加入了两个函数 :删除对象、显示生命值。
1.新加删除对象函数
这是一个典型的在数组中删除一个结点的算法。
//**************************************************
// Delete(int q) //删除
// 删除选 中的q 对象。
//**************************************************
void gmedit::Delete(int q) //删除
1{ if(gno==0||rs<2) return;
2 int n=man[q].jisu
3 for(int i=0;i<rs;i++)
4 if(man[i].jisu==n)
5 {for (int j=i;j<rs-1;j++) man[j]=man[j+1];//删除n 对象
6 rs--; //对象数减1
7 return;
}
}
第3、4行通过循环查找选中的对象。
第 5行删除对象数组man[]中第j个单元。
这是一个典型的数组单元删除算法, 被删单元后面的数据都前移一个单元。原理
见以下图示。
图12-2
第6行rs--;对象数减一。
第 7行删除完毕后返回。
2.新加显示生命值函数
这个函数用于动物在打斗时显示其生命值和攻击力 。采用的是字符显示的方法。
字符串“*” 的多少表示攻击力,字符底色表示生命值。man[q].smz为对象q的生命值,
Font 为字体, 在前面已创建为4 号impact体。
///////////////////////////////////////////
//setxtx(int q,int x,int y)显示生命值函数
// 在[x,y]位置显示当前对象q 的生命值和攻击力
// 生命值用颜色表示;按赤、橙、黄、绿、蓝、青、紫、从低到高表示。
// 攻击力用星表示;星越多攻击力越高。
///////////////////////////////////////////
void game::setxtx(int q,int x,int y)//显示生命值函数
{ if(man[q].lb==0) return; //是人返回
CString cc;
COLORREF col; //颜色
if(smz[1][man[q].js]>8) {cc="●●●●●";goto aa;} //攻击力最大
if(smz[1][man[q].js]>6) {cc="●●●●";goto aa;}
if(smz[1][man[q].js]>4) {cc="●●●";goto aa;}
if(smz[1][man[q].js]>2) {cc="●●";goto aa;}
cc="● "; //攻击力最小
aa: if(man[q].smz>=60) {col=RGB(0x0,0xff,0xff);gotobb;} //紫生命值大
if(man[q].smz>=50) {col=RGB(0x0,0x88,0xff);goto bb;}
if(man[q].smz>=40) {col=RGB(0x0,0x00,0xff);goto bb;}
if(man[q].smz>=30) {col=RGB(0x0,0xff,0x88);goto bb;}
if(man[q].smz>=20) {col=RGB(0xff,0xff,0x0);goto bb;}
if(man[q].smz>=10) {col=RGB(0xff,0x88,0x0);goto bb;}
col=RGB(0xff,0x0,0x0); //赤生命值小
bb: HDC hdc; //设备环境句柄
if (DXSBack1→GetDC(&hdc) != DD_OK) return; //打开DXSBack1 面
CDC* dc = CDC::FromHandle (hdc);
CFont *pOldFont;
pOldFont=dc→SelectObject(&Font); //设置字体4 号impact
体
dc→SetBkColor(col); //字底色
dc→SetTextColor(RGB(0x0,0x0,0x0)); //字色
dc→TextOut(x+SCRW+w/2,y+SCRH-10,cc,lstrlen(cc));//写攻击力字串
dc→SelectObject(pOldFont); //恢复原来的字体
CDC::DeleteTempMap( );
DXSBack1→ReleaseDC(hdc); //关闭DXSBack1 面
}
对象生命值结束后的倒地和残体化解都是动画的变换过程; 所以在设置活动对象
初值函数中,对倒地和残体化解应该有单独的处理。
void game::setman(int q)//设置活动对象初值
1{ if(man[q].zs>3){man[q].zs++;goto aa;} //倒地后,转为下一动作。
2 if(man[q].smz>-1&&man[q].smz<1){man[q].zs=4;} //生命值以完,转为倒地。
aa: int a=400;
⋯⋯⋯
}
所加的第 2 行是生命值小于1时,对象的动作转为倒地。加上大于-1的判断是
在对象倒地后,man[q].smz用于它用。
在活动对象的移动函数中,应该把倒地和残体化解的过程屏蔽,死了的对象不能
再动了嘛。
void game::manmove(int i)//活动对象的移动
{ if(man[i].smz<1) return; //生命值小于正常返回,不要移
动。
⋯⋯
}
同理,对象倒地和残体化解也不做方位转换了,所以也应该在方位转换函数中将
它屏蔽。但是倒地和残体化解的动画过程还有,所以设置活动对象初值setman(q)还是
要的。
void game::bianfw(int q)//方位转换
{ if(man[q].smz<1)
{setman(q);return;} //对象倒地以后不做方位转换
⋯⋯
}
不同的动物其生命值、攻击力也不同。我们用一个全局定义的二维数组来表示。
unsigned short smz[2][7]={ 60, 50, 30, 20,40, 10, 15, //生命值
10, 8, 6, 3, 5, 1, 2 };//攻击力
//兽[ 0豹 1 狼 2 猪 3 鹿 4 马 5 雀 6 羊 ]
siz[n][m] n=0为生命值,n=1为攻击力
m 为不同的动物m{0豹1狼2猪3鹿4马5雀6羊}
例如:siz[0][2]表示猪的生命值;siz[1][2]表示猪的攻击力。
人有一个初始的生命值,我们允许人的生命值可以用其它方式增加。人还有一个
初始的攻击力我们称为经验值,人的经验值可以在猎物中增加。我们还可以设置人的
金钱、猎物拥有量等。
在 game.h中我们定义一维数组来表示人的生命值等的拥有量
short int mansmz[5]; //0生命1经验2金钱3猎物4
并在 game.cpp 中init()程序初始化时赋与初值。
mansmz[0]=200; //生命
mansmz[1]=5; //经验
mansmz[2]=10; //金钱
mansmz[3]=20; //猎物
mansmz[4]=0; //其它
我们是在调入地图中对人和兽设置生命值初值的。每个对象调入后分别对人或动
物设定生命值,生命值的大小取至以上定义。
另外我们还要在场景中添加一些道具(如钱、药瓶等等),添加的数量和位置是随
机的。
void game::loadmap(CString name)//调地图
{ //打开地图文件
for (i=0;i<rs;i++)
{//调各对象
if(man[i].lb==0) man[i].smz=mansmz[0]; //设人生命值
if(man[i].lb==1)man[i].smz=smz[0][man[i].js]; //设兽生命值
if(man[i].lb<2) setman(man[i].jisu); //设置活动对象初值
if(getpic(i,0)==FALSE) return;
man[i].w=w; man[i].h=h; //对象的尺寸
}
fclose(f);
Insert() //添加物品
}
//**************************************************
// Insert() //添加物品
// 在man[]中顺序加入物品,加入后对象数rs 加1。
//**************************************************
void game::Insert() //添加物品
{ int a=rand()%100+5; //随机产生物品数
for (int i=0;i<a;i++)
{if(rs>SU_LIANG-2) return;//AfxMessageBox("对象数超出!");
int i=rs;
man[i].jisu=i+1; //序号
man[i].lb=3; //类别:0 人1 兽2 景3 物
man[i].p=rand()%4; //静物图形号
man[i].xix=man[i].x0=rand()%(WIDTH*SCRP0-10); //随机产生位置x
man[i].xiy=man[i].y0=rand()%(HEIGHT*SCRP0-10); //随机产生位置y
if(getpic(i,0)==FALSE) return;
man[i].w=w; man[i].h=h; //对象的尺寸
rs++; //对象数加1
}
}
在我们这个游戏中,对象打斗时生命值的减少和人在打斗中经验值的增加,还有
人拾物的过程,都是在类文件“game_寻路.cpp” 的角色碰撞函数中实现的。以下我们
来看这些功能的实现方法。
在角色碰撞函数中我们加入了4个功能。
A.屏蔽死了的动物
第 1行动物生命值完返回。
B.屏蔽人已倒地
第 2行人已倒地返回。
C.人拾物的过程。
第 3行人见物,注意人见物的程序表述方式(man[i].lb==0&&man[q].lb==3)。
第 4~9行根据物品 man[q].p 分别改变人的拥有物mansmz[]大小。
第 10行删除物品。东西被检了,当然应该不见了,所以应该删除它。
D.打斗中生命值的改变。
第 11行在一个打斗回合时改变对象生命值。
第 12行人的生命值按相斗的动物攻击力减少。
第13行人的生命值校正。
第 14行动物的生命值按人的攻击力减少。
第 15行动物的生命值校正。
第 16~17行根据相斗的动物攻击力增加人的经验值。
void gamepro::lookit(int i)//角色碰撞
{ if(man[i].lb>=2) return;
1 if(man[i].smz<1) return; //动物生命值完
{for(int q=0;q<rs;q++)
{⋯⋯
2 if(man[q].lb==0&&man[q].zs>3)return; // 人已倒
if(abs(x)<man[q].w*4/3 &&abs(y)<man[q].h*4/3) //相遇
{⋯⋯
3 if(man[i].lb==0&&man[q].lb==3) //人见物
4 {if(man[q].p==0) man[i].smz=200; //生命
5 if(man[q].p==1) mansmz[1]=mansmz[1]+10; //经验
6 if(man[q].p==2) mansmz[2]=mansmz[2]+10; //金钱1
7 if(man[q].p==3) mansmz[2]=mansmz[2]+20; //金钱2
8 if(man[q].p==4) mansmz[2]=mansmz[2]+50; //金钱5
9 if(man[q].p==5)mansmz[3]=mansmz[3]+smz[0][man[q].js];//食物
10 Delete(q); //删除物品
11 return;
}
if(man[q].lb>=1) continue; //是兽看见人
if(man[i].lb!=man[q].lb) //不同类
{⋯⋯
//兽停下来
//双方面对面
//================================生命值
11 if(man[q].p==man[q].m1-5)
12{man[q].smz=man[q].smz-smz[1][man[i].js];//兽对人攻击力
13 if(man[q].smz<0) man[q].smz=0; //生命值校正
14 man[i].smz=man[i].smz-mansmz[1]; //人对兽攻击力
15 if(man[i].smz<0) man[i].smz=0; //生命值校正
16 da=da+smz[1][man[i].js]; //长经验值计数
17 if(da>50) {mansmz[1]=mansmz[1]+1;da=0;}//长经验值
}//==================================
if(inscreer(i)) [发声] //在显示区?
break;
} //end if 不同类
} //end if 相遇
} //end for
} //end if 动物生命值完
}
还有一项重要的修改,就是在原来调入、读取资源压缩包的地方加入物品压缩包。
常数定义.h中加入
#define WBUF 10 //物最大数
Game.h 中加入
CFile sfile,rfile,jfile,wfile; //压缩资源包的文件指针
BYTE * stmp,*rtmp,*jtmp,*wtmp; //压缩的内存变量
int wbufadd[10]; //物的资源指针
Game.cpp 的调压缩资源包中加入
void gamepro::loaddata()//调压缩资源包
{ ⋯⋯
// A.
cc=dir+"物.dar"; //读物品的数据指针
f=fopen(cc,"r");
if(f==NULL) return;
fscanf(f,"%d",&len);
for(i=0;i<len;i++)
fscanf(f,"%d,%d,%d",&wbufadd[i],&j,&j);//读图形的位置
fclose(f);
⋯⋯
// B.
cc=dir+"物.gam"; //读物品的压缩包
if( !wfile.Open(cc, CFile::modeRead, NULL ) )return;
⋯⋯
}
Game.cpp 的调图片中加入
BOOL game::getpic(int q,int a)//调图片
{ ⋯⋯
if(man[q].lb==3) //是调物品
{if(p>WBUF-1) return FALSE;
len=wbufadd[p+1]-wbufadd[p]; //取数据块长度
tmp=(BYTE *)new BYTE[len];
Memcpy0(tmp,wtmp+wbufadd[p],len);
}
⋯⋯
}
加入生命值后,在主程序中还要做一点修改。在“真的打呀Dlg.cpp”的OnTimer(UINT
nIDEvent)时钟函数中,加入人牺牲后的结束游戏的处理,还要新增加一个在主界面显示
人的生命值、经验值的函数。
在时钟函数的显示信息段加入人牺牲后的结束游戏的处理和在主界面显示生命
值、经验值,金钱、食物等信息的代码。
void CMyDlg::OnTimer(UINT nIDEvent) //时钟函数
{ if(nIDEvent==1) //动画刷屏
{ ⋯⋯
}
if(nIDEvent==2) //显示信息
{char cc[255],c1[255];
int q=m_game.mann;
1 if(m_game.man[q].smz<-5) //人倒,开始计时。
2 {KillTimer(1);KillTimer(2);
3 MCIWndStop(hMCI);
4 AfxMessageBox("你牺牲了! 游戏结束。");
5 OnOK();
}
6 setsmz(m_game.man[q].smz,0);
7 setsmz(m_game.mansmz[1],1);
8 sprintf(cc,"金钱:%d",m_game.mansmz[2]);
9 SetDlgItemText(IDC_STATIC9,cc);
10 sprintf(cc,"食物:%d",m_game.mansmz[3]);
11 SetDlgItemText(IDC_STATIC10,cc);
⋯⋯
}
⋯⋯
}
第1~5行是人牺牲后的结束游戏的处理。
第 6行显示人的生命值。
第 7行显示人的经验值。
第 8~11行显示人的金钱、食物等信息。
12-6-2 新加主界面上显示生命值函数
这是一个最简单的生命值图形显示方式。读者可以在此基础上改成更生动好看的
方式。
void CMyDlg::setsmz(int h,int a)
{ CClientDC dc(this); //取客户区设备环境
1 int x,y;
2 int s=m_game.mansmz[0];
3 char cc[255];
4 CBrush Brush(RGB(120,120,130));
5 dc.SelectObject(Brush);
6 if(a==0) // 在主界面左下方显示生命值图
示
7 {x=3;y=500;
8 dc.PatBlt(x,y,40,50,PATCOPY);
9 CBrush Brush(RGB(200,0,0));
10 dc.SelectObject(Brush);
11 dc.PatBlt(x,y+51-h*50/s,40,h*50/s,PATCOPY);
12 sprintf(cc,"生命:%d",h);
13 SetDlgItemText(IDC_STATIC7,cc);
}
14 if(a==1) // 在主界面右下方显示经验值图示
15 {x=600;y=500;
16 dc.PatBlt(x,y,40,50,PATCOPY);
17 CBrush Brush(RGB(0,0,200));
18 dc.SelectObject(Brush);
19 dc.PatBlt(x,y+50-h*50/20,40,h*50/20,PATCOPY);
20 sprintf(cc,"经验:%d",h);
21 SetDlgItemText(IDC_STATIC8,cc);
}
第1行 定义显示的x,y坐标。
第 2行 取人的生命值。
第 4行 定义灰色刷子。
第 5行 置当前显示设备环境为灰色
第 6行a=0时第7~13行在主界面左下方显示生命值图示
第 7行 设显示坐标。
第 8行 在当前显示设备显一个40×50的灰色矩形。
第 9行 定义红色刷子
第 10行置当前显示设备环境为红色
第 11行在当前显示设备显一个表示当前生命值的红色矩形。
第 12、13行在IDC_STATIC7上显示生命数值。
第 14~21行在主界面右下方显示经验值图示。
到此,我们的游戏中生命值和有关的修改基本完成。现在再进入游戏中可要小心
了,这下可是“要命” 的了;当然你可以拾红色的药瓶为你加生命值⋯⋯。有关游戏
情节设计和玩法设计就留给读者自己去发挥了。
在这一章里,我们学习了以下知识和方法。
1.对象的新属性:生命值、攻击力。
2.对象生命值的显示。
3.打斗时生命值的改变。
4.在场景中添加物品。