mooc游戏设计基础(自学)9

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);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值