mooc自学网址:
https://www.icourse163.org/learn/CUC-1450328379?tid=1450742649#/learn/content?type=detail&id=1216695336&sm=1(源自中国传媒大学韩红雷老师的课程和他出版的书籍《《游戏开发程序设计基础》》)
第九章
第九章的小游戏是初级版“坦克大战”,玩法就跟最常见的坦克大战一样。其中用到了枚举的方法来设置方向,用结构体来建立所有实体的参数。
(1)首先新建一个左面应用程序,命名为“tank”。
(2)设置需要用到的全局变量和常量:
enum Dir( UP,DOWN,LEFT,RIGHT}; //利用枚举建立方向键组,方便后面能设置随机方向
typedef struct{ //建立实体结构体,可表示玩家坦克、敌人坦克、炮弹
int x,y; //实体坐标
Dir dir; //实体当前朝向
int v; //实体速度
int s; //实体边长
int b; //是否是炮弹
int p; //是否停止
int e; //是否是敌人
COLORREF c; //颜色
}Entiy;
#define MAX_ENEMY 16 //敌人坦克的最大数量
#define MAX_BULLETS 32 //最多的炮弹数
int nLife=3; //玩家生命值
int nScore=0' //玩家分数
int nBullet=0; //玩家射出的炮弹数量
int nEnemyBullet=0; //敌人坦克射出的炮弹数量
itn nEnemy=0; //当前敌人的数量
int timeStep=20; //定时器间隔
int sz=50; //坦克尺寸
int velf=4; //快速坦克的速率
int vels=2; //慢速坦克的速率
int szb=20; //炮弹尺寸
int velb=6; //炮弹速率
int enemyFirePer=300; //敌人炮弹发射的几率
int enemyDir=200; //敌人坦克改变方向的几率
int bFire=0; //是否处于设计状态
Entity enemys[Max_ENEMY]; //敌人坦克数组
Entity player; //玩家坦克
Entity bullets[MAX_BULLETS]; //玩家炮弹数组
Entity enemyBullets[MAX_BULLETS]; //敌人炮弹数组
int wndWidth=0; //窗口大小
int wndHeight=0;
(3)建立功能函数,包括重置玩家信息、初始化、实体移动操作、射击操作、摧毁操作、实体与实体之间的碰撞、实体与边界的碰撞、更新操作、实体绘制、游戏场景绘制等:
void Restplayer() //重置玩家信息(设置实体信息)
{
player.s=sz; //坦克尺寸
player.b=0; //不是炮弹
player.c=RGB(122,30,0); //坦克颜色
player.dir=UP; //初始朝向
player.v=vels; //设置为慢速坦克速度
player.x=wndWidth/2; //初始位置位于窗口中心
player.y=wndHeight-sz;
player.p=1; //选择停止
player.e=0; //不是敌人
}
void Init()
{
for(nEnemy=0;nEnemy<MAX_ENEMY;nEnemy++) //对所有敌人坦克进行初始化
{
enemys[nEnemy].s=sz; //坦克尺寸
enemys[nEnemy].b=0; //不是炮弹
enemys[nEnemy].e=1; //是敌人
enemys[nEnemy].dir=Dir(UP+rand()%4); //方向设置为随机方向
enemys[nEnemy].v=rand()%2==0?velf:vels; //速度设为随机慢速或快速
enemys[nEnemy].c=enemys[nEnemy].v==velf?RGB(0,122,122):RGB(0,60,30); //颜色设为快或慢对应的颜色
enemys[nEnemy].x=(rand()%3)*(wndWidth-sz)/2+sz/2; //设置坦克坐标
enemys[nEnemy].y=sz;
enemys[nEnemy].p=0; //不是停止状态
}
RestPlayer(); //对玩家坦克进行初始化
}
void Move(Entity*ent,int ts) //实体随时间的运动
{
if(ent->p) //如果实体处于停止状态则不进行操作
return;
switch(ent->dir) //根据各方向进行相应的变化
{
case UP:
ent->y-=ent->v*ts;
break;
case DOWN:
ent->y+=ent->v*ts;
break;
case LEFT:
ent->x-=ent->v*ts;
break;
case RIGHT:
ent->x+=ent->v*ts;
break;
}
}
void Fire(const Entity*ent) //射击操作
{
Entity*pBullets=(ent->e)?enemyBullets:bullets; //区分敌人炮弹和玩家炮弹
int nB=(ent->e)?nEnemyBullet:nBullet; //设置炮弹数量
if(nB>=MAX_BULLETS) //如果超过最大炮弹数则直接返回
return;
(pBullets+nB)->s=szb; //尺寸设置
(pBullets+nB)->b=1; //是炮弹
(pBullets+nB)->e=0; //不是敌人坦克
(pBullets+nB)->c=(ent->e)?RGB(0,0,255):RGB(255,0,0); //设置颜色
(pBullets+nB)->dir=ent->dir; //方向与坦克方向一致
(pBullets+nB)->v=velb; //速度设置为已定的炮弹速度
(pBullets+nB)->x=ent->x; //坐标设置为坦克坐标
(pBullets+nB)->y=ent->y;
(pBullets+nB)->p=0; //不停止
switch(ent->dir) //运动方向设置为坦克方向并持续运动
{
case UP:
(pBullets+nB)->y-=ent->s;
break;
case DOWN:
(pBullets+nB)->y+=ent->s;
break;
case LEFT:
(pBullets+nB)->x-=ent->s;
break;
case RIGHT:
(pBullets+nB)->x+=ent->s;
break;
}
if(ent->e) //根据敌人和玩家的不同对应的增加已发射的炮弹数
nEnemyBullet++;
else
nBullet++;
}
void Destroy(Entity ents[],int n,int *num) //实体摧毁
{
memcpy(ents+n,ents+n+1,sizeof(Entity)*((*num)-1-n)); //利用函数将数组中的第n位向前移动一位
(*num)--;
}
int IsCollider(const Entity* ent1,const Entity* ent2) //实体碰撞判定
{
if(ent1->x+ent1->s/2<=ent2->x-ent2->s/2||ent1->x-ent1->s/2>=ent2->x+ent2->s/2) //x方向左边界和右边
return 0;
if(ent1->y+ent1->s/2<=ent2->y-ent2->s/2||ent1->y-ent1->s/2>=ent2->y+ent2->s/2) //y方向左边界和右边
return 0;
return 1;
}
int WallCollider(Entity* ent) //实体与边界发生碰撞
{
int bc=0; //先设置未发生碰撞
switch(ent->dir) //根据各个方向的不同检测是否发生碰撞
{
case UP:
if((ent->y-ent->s/2)<0){
bc=1;
ent->y=ent->s/2; //方向向上的时候发生碰撞,则将y值始终为边长的一半
}
break;
case DOWN:
if((ent->y+ent->s/2)>wndHeight){
bc=1;
ent->y=wndHeight-ent->s/2;
}
break;
case LEFT:
if((ent->x-ent->s/2)<0){
bc=1;
ent->x=ent->s/2;
}
case RIGHT:
if((ent->x+ent->s/2)>wndWidth){
bc=1;
ent->x=wndWidth-ent->s/2;
}
break;
}
if(bc) //如果发生碰撞
{
if(ent->e) //如果是敌人坦克,就将其方向进行随机变化
ent->dir=Dir((ent->dir+1+rand()%3)%4);
else
ent->p=1; //如果是玩家坦克,就设置为停止
}
return bc; //返回碰撞判断结果
}
void Update(int ts) //更新函数(随着帧数)
{
Enity*ent=NULL; //设置一个指向实体的指针
for(int i=0;i<nEnemy;i++){ //使所有敌人坦克进行移动,并有几率发射炮弹
ent=enemys+i;
Move(ent,ts);
if(rand()%enemyFirePer)==0)
Fire(ent);
}
for(int i=0;i<nBullet;i++){ //使玩家炮弹进行移动
ent=bullets+i;
Move(ent,ts);
}
for(int i=0;i<nEnemyBullet;i++){ //使敌人炮弹进行移动
ent=enemyBullets+i;
Move(ent,ts);
}
Move(&player,ts); //使玩家坦克进行移动
if(bFire) //射击状态设置
{
Fire(&player);
bFire=0;
}
for(int i=0;i<nBullet;i++){ //检查玩家炮弹是否与敌人坦克进行碰撞,如果有接触就去除对应的这两个,且分数加一
for(int j=0;j<nEnemy;j++){
if(IsCollider(&bullets[i],&enemys[j]){
Destroy(bullets,i,&nBullet);
Destroy(enemys,j,*nEnemy);
nScore++;
i--;
j--;
break;
}
}
}
for(int i=0;i<nEnemyBullet;i++){ //检查敌人炮弹和玩家坦克是否进行接触,如果有接触就消除炮弹,重置玩家信息,生命减一
if(IsCollider(&enemyBullets[i],&player))
Destroy(enemyBullets,i,&nEnemyBullet);
ResetPlayer();
nLife--;
i--;
break;
}
}
for(int i=0;i<nEnemy;i++){ //检查玩家坦克和敌人坦克是否有接触,如果有接触就重置玩家信息,生命减一
if(IsCollider(&player,&enemys[i])){
ResetPlayer();
nLife--;
}
}
for(int i=0;i<nEnemy;i++){ //检查敌人坦克是否与边界有接触,如果有接触就随机改变方向
ent=enemys+i;
if(!WallCollider(ent)){
if(rand()%enemyDir==0)
ent->dir=Dir((ent->dir+1+rand()%3)%4);
}
}
for(int i=0;i<nBullet;i++){ //检查玩家炮弹是否与边界有接触,如果有接触就去除
ent=bullets+i;
if(WallCollider(ent)){
Destroy(bullets,i,&nBullet);
i--;
}
}
for(int i=0;i<nEnemyBullet;i++){ //检查敌人炮弹是否与边界相接触,如果有接触就去除
ent=enemyBullets+i;
if(WallCollider(ent)){
Destroy(enemyBullets,i,&nEnemyBullet);
i--;
}
}
WallCollider(&player); //检查玩家坦克与边界情况
}
void DrawEntity(HDC hdc,const Entity*ent)
{
HBRUSH brush; //建立笔刷
brush=CreateSolidBrush(ent->c); //用实体颜色赋给笔刷
RECT rc; //创建矩形框
rc.top=ent->y-ent->s/2; //将矩形框设置为以坐标为中心的正方形
rc.left=ent->x-ent->s/2;
rc.bottom=ent->y-ent->s/2;
rc.right=ent->x+ent->s/2;
FillRect(hdc,&rc,brush); //用笔刷填充矩形框
if(!ent->b){ //如果实体不是炮弹,要根据方向不同绘制对应的炮筒(高度一致,宽度减半)
switch(ent->dir){
case UP:
rc.bottom=rc.top;
rc.top=rc.bottom-ent->s/2;
rc.left=rc.left+ent->s/4;
rc.right=rc.right-ent->s/4;
break;
case DOWN:
rc.top=rc.bottom;
rc.bottom=rc.bottom+ent->s/2;
rc.left=rc.left+ent->s/4;
rc.right=rc.right-ent->s/4;
break;
case LEFT:
rc.right=rc.left;
rc.left=rc.left-ent->s/2;
rc.bottom=rc.bottom-ent->s/4;
rc.top=rc.top+ent->s/4;
break;
case RIGHT:
rc.left=rc.right;
rc.right=rc.right+ent->s/2;
rc.bottom=rc.bottom-ent->s/4;
rc.top=rc.top+ent->s/4;
break;
}
FillRect(hdc,&rc,brush); //用笔刷对新的矩形框进行填充
}
DeleteObject(brush); //删除笔刷
}
void DrawScene(HDC hdc) //场景绘制函数
{
HFONT hf; //字体定义
WCHAR str[32];
long lfHeight;
lfHeight=-MulDiv(16,GetDeviceCaps(hdc,LOGPIXELSY),72); //字体高度设置
hf=CreateFont(lfHeight,0,0,0,0,TRUE,0,0,0,0,0,0,0,L"Times New Roman"); //创建字体
HFONT hfOld=(HFONT)SelectObject(hdc,hf); //将字体应用到环境hdc中
if(nLife<=0){ //当生命值小于0时输出“Game Over”
SetTextColor(hdc,RGB(122,0,0));
TextOut(hdc,wndWidth/2-100,wndHeight/2-40,L"Game Over",9);
SelectObject(hdc,hfOld);
return;
}
SetTextColor(hdc,RGB(100,100,100)); //否则输出对应的生命值和分数
wsprintf(str,L"Life:%d Score:%d",nLife,nScore);
TextOut(hdc,wndWidth/2-100,wndHeight/2-40,str,wcslen(str));
SelectObject(hdc,hfOld); //选择该画笔进行删除
Delete(hf);
Entity* ent=NULL; //新建一个指向实体的指针
for(int i=0;i<nEnemy;i++){ //绘制敌人坦克
ent=enemys+i;
DrawEntity(hdc,ent);
}
for(int i=0;i<nBullet;i++) //绘制玩家炮弹
{
ent=bullets+i;
DrawEntity(hdc,ent);
}
for(int i=0;i<nEnemyBullet;i++){ //绘制敌人炮弹
ent=enemyBullets+i;
DrawEntity(hdc,ent);
}
DrawEntity(hdc,&player); //绘制玩家坦克
}
(4)在主消息循环之前增加初始化设置:
srand(time(NULL));
Init();
(5)消息处理设置:
case WM_CREATE: //设置定时器
SetTimer(hWnd,1,timeStep,NULL);
break;
case WM_TIMER: //定时更新处理
if(wParam==1){
if(nLife>0)
Update(timetep/10);
InvalidateRect(hWnd,NULL,TRUE);
}
break;
case WM_SIZE: //获取窗口尺寸
wndWeigh=LOWORD(lParam);
wndHeight=HIWORD(lParam);
break;
case WM_KEYDOWN: //键盘信息,方向键移动和空格键发射炮弹
{
InvalidateRect(hWnd,NULL,TRUE);
switch(wParam){
case VK_LEFT:
player.dir=LEFT;
player.p=0;
break;
case VK_RIGHT:
player.dir=RIGHT;
player.p=0;
break;
case VK_UP:
player.dir=UP;
player.p=0;
break;
case VK_DOWN:
player.dir=DOWN;
player.p=0;
break;
case VK_SPACE:
bFire=1;
break;
}
}
break;
case WM_ERASEBKGND: //消除擦除操作
break;
(6)绘制操作采用背景更换(和前面程序的一样):
HDC memHDC=CreateCompatibleDC(hdc);
RECT rectClient;
GetClientRect(hWnd,&rectClient);
HBITMAP bmpBuff=CreateCompatibleBitmap(hdc,wndWidth,wndHeight);
HBITMAP pOldBMP=(HBITMAP)SelectObject(memHDC,bmpBuff);
PatBlt(memHDC,0,0,wndWidth,wndHeight,WHITENESS);
DrawScene(memHDC);
Bool tt=BitBlt(hdc,rectClient.left,rectClient.top,wndWidth,wndHeight,memHDC,rectClient.left,rectClient.top,SRCCOPY);
SelectObject(memHDC,pOldBMP);
DeleteObject(bmpBuff);
DeleteDC(memHDC);
EndPaint(hWnd,&ps);