全网最详细用c语言实现植物大战僵尸游戏(下)-1

简介: 全网最详细用c语言实现植物大战僵尸游戏(下)

让大家久等了,各位看官们!


续接上文(上文链接植物大战僵尸(上)),我们完成了有关植物部分设计,接下来设计僵尸部分啦!


九、创建僵尸和更新僵尸数状态

创建全局变量 ,僵尸结构体,里面包括僵尸的位置x,y,僵尸的照片帧,僵尸行走的速度,僵尸所在行,僵尸血量,僵尸状态,在创建一个僵尸结构体数组,相当于僵尸池,里面十个僵尸


struct zm
{
  int x, y;
  int frameIndex;//照片帧序号
  bool use;
  int speed;//僵尸的速度
  int row;
  int blood;
  bool dead;
  bool eating;//正在吃植物
};
struct zm zms[10];//僵尸个数
IMAGE imgzm[22];//存放僵尸图片数组
IMAGE imgzmdead[20];//存放僵尸死亡图片数组
IMAGE imgeat[21];//存放僵尸吃植物图片数组

在初始化gameInit()函数中,加载僵尸图片


//初始化僵尸数据
  memset (zms, 0, sizeof(zms));//初始化僵尸结构体
  for (int i = 0; i < 22; i++)
  {
    sprintf_s(plantname, sizeof(plantname), "res/zm/%d.png",i+1);//将僵尸文件名存储到字符数组
    loadimage(&imgzm[i], plantname);//加载僵尸图片
  }

在updategame()修改游戏数据 函数中设置创建僵尸函数


createzm();//创建僵尸
updatezm();//更新僵尸的状态

接下来我们写创建僵尸函数,首先找到一个没有用过的僵尸,使他处于使用状态,初始位置在屏幕最右边,随机出现在任意一行,僵尸处于正常行走状态


void createzm()
{
  int i = 0;
  int zmmax = sizeof(zms) / sizeof(zms[0]);//僵尸个数
  for (i = 0; i < zmmax && zms[i].use; i++);//寻找没用的僵尸
  if(i < zmmax)//找到可以用的僵尸
  {
    zms[i].use = true;
    zms[i].x = WIN_WIDTH;
        zms[i].row = rand() % 4;
    zms[i].y = 172 + 90 * (zms[i].row);
    zms[i].speed = 1;//一帧走1个像素
    zms[i].frameIndex = 0;
    zms[i].blood = 100;
    zms[i].dead = false;
    zms[i].eating = false;
    }
}


僵尸创建函数就写好了,但是像阳光一样,每调用一次就创建一个僵尸,一瞬间就创建完10个僵尸,如何修改呢?


与创建阳光球一样创造俩个静态变量,使用控制频率技巧。


表示调用50次该函数,才创建第一僵尸,也就是50帧创建一个僵尸,后面每隔100~200帧创建一个僵尸。

void createzm()
{
  static int zmfre = 50;
  static int count = 0;
  count++;
  if (count > zmfre)
  {
    count = 0;
    zmfre = rand() % 100 + 100;
    int i = 0;
    int zmmax = sizeof(zms) / sizeof(zms[0]);//僵尸个数
    for (i = 0; i < zmmax && zms[i].use; i++);//寻找没用的僵尸
    if (i < zmmax)//找到可以用的僵尸
    {
      zms[i].use = true;
      zms[i].frameIndex = 0;
      zms[i].x = WIN_WIDTH;
      zms[i].row = rand() % 4;
      zms[i].y = 172 + 90 * (zms[i].row);
      zms[i].speed = 1;//一帧走1个像素
      zms[i].blood = 100;
      zms[i].dead = false;
      zms[i].eating = false;
    }
  }
}


接下来像做阳光一样,在updategame()修改游戏数据 函数中设置更新僵尸状态函数


createzm();//创建僵尸

僵尸有俩个位置变换:1.更新僵尸图片帧序号 ,2.更新僵尸图片位置


我们先写更新僵尸图片位置,僵尸到达离左边屏幕170个像素,代表输了


void updatezm()//1.更新僵尸图片帧序号 2.更新僵尸图片位置
{
  int zmmax = sizeof(zms) / sizeof(zms[0]);
  //更新僵尸位置
  for (int i = 0; i < zmmax; i++)
  {
    if (zms[i].use)
    {
      zms[i].x = zms[i].x-zms[i].speed  ;
      if (zms[i].x < 170)
      {
        printf("game over\n");//操作台输出文本
        MessageBox(NULL, "over", "over", 0);//图像界面输出消息对话框
        exit(0);//结束
      }
    }
  }
}

0ebbcbbccd529677c17daafda10811cf_45d4d470833240c398a657b2816cba95.jpeg


注:一个MessageBox最多由四个部分组成。 因而,它的Show()方法的重载最多有四个参数,分别是内容(text)、标题(caption)、按钮(buttons)、图标(icon) 。函数的语法如下:


MessageBox.Show(text, caption, buttons, icon);//弹出MessageBox窗口

僵尸走的速度太快了,这里有俩个处理方法:


第一种将zms[i].speed = 1;//一帧走1个像素,zms[i].speed=0.5(俩帧走一个像素);


第二种使用控制频率技巧,表示每3帧才走一个像素;


void updatezm()//1.更新僵尸图片帧序号 2.更新僵尸图片位置
{
static int count = 0;
  count++;
  if (count > 2)
  {
       count=0;
      //更新僵尸图片位置
    }
}

下一步写 更新僵尸图片帧序号


这个比较简单,即更新zms[i].frameIndex 数据


   //更新僵尸图片帧序号
  for (int i = 0; i < zmmax; i++)
  {
    if (zms[i].use)
    {
      zms[i].frameIndex = (zms[i].frameIndex + 1) % 22;
    }
  }

如果觉得僵尸走的太快了,像滑铲,也可以向上面一样使用控制频率技巧

//更新僵尸图片帧序号
  static int count2 = 0;
  count2 ++;
  if (count2 > 1)
  {
    count2 = 0;
    for (int i = 0; i < zmmax; i++)
    {
      if (zms[i].use)
      {
        zms[i].frameIndex = (zms[i].frameIndex + 1) % 22;
      }
    }
  }

接下来,我们需要看效果,进行渲染僵尸图片即可


//渲染僵尸图片
  int zmmax = sizeof(zms) / sizeof(zms[0]);
  for (int i = 0; i < zmmax; i++)
  {
    if (zms[i].use)
    {
      //IMAGE* img = &imgzm[zms[i].frameIndex];
      //IMAGE* img = ((zms[i].dead) ? imgzmdead : imgzm);
      IMAGE* img  = imgzm;
      img = img + zms[i].frameIndex;//指针跳过几个IMAGE数据类型大小
      putimagePNG(zms[i].x, zms[i].y-45,img);
    }
  }


看效果图


6aeeebff8554b4ff47602f2bccd01376_fa35256504eb4bcd90d11d8868841e40.png


十、发射(创建)子弹和更新子弹状态

定义子弹的结构体,子弹有爆炸状态和正常状态子弹,而且此处定义子弹行数是为了和僵尸行数对比,方便实现后面的碰撞情况


//子弹的数据类型
struct bullet
{
  int x, y;
  int row;
  bool use;
  int speed;
  bool blast;//是否爆炸
  int frameIndex;//子弹爆炸的帧图片
};
struct bullet bullets[30];
IMAGE imgbulletnormal;
IMAGE imgbulletblast[4];//子弹爆炸图片数组


然后在初始化函数gameInit()中, 初始化子弹

//初始化子弹数据
  memset(bullets, 0, sizeof(bullets));//初始化子弹结构体
  loadimage(&imgbulletnormal, "res/bullets/bullet_normal.png");//加载子弹图片

在更新游戏数据中创建俩个函数,发射子弹和更新子弹数据

1. shoot();//发射子弹
2. updatebullet();//更新子弹数据

设计发射子弹函数,怎么设计呢?


遍历4行8列植物数组,如果有豌豆并且有僵尸,就发射子弹,怎么该行判断有僵尸呢?上面僵尸结构体中的行数就有用了,如果该行有僵尸且僵尸走到一段距离时,则该行设置为1,表示该行有僵尸。


如何发射子弹,找一个没有被使用的子弹,对该子弹结构体赋值,对于子弹的初始行数,根据豌豆植物和僵尸所在行进行设计,子弹的坐标根据植物坐标进行调整设计在豌豆喷嘴处,为了不让子弹瞬间被用完,用上面常用的控制频率技巧。代码如下


void  shoot()
{
  int lines[4] = { 0 };
  int zmcount = sizeof(zms) / sizeof(zms[0]);
  int dangerx = 750;
  int bulletmax = sizeof(bullets) / sizeof(bullets[0]);
  for (int i = 0; i < zmcount; i++)
  {
    if (zms[i].use && zms[i].x < dangerx)
    {
      lines[zms[i].row] = 1;//该行有僵尸设为1
    }
  }
  for (int i = 0; i < 4; i++)
  {
    for (int j = 0; j < 8; j++)
    {
      if (map[i][j].type == PEA + 1 && lines[i])
      {
        static int count = 0;
        count++;
        if (count > 10)
        {
          count = 0;
          int k = 0;
          for (k = 0; k < bulletmax && bullets[k].use; k++);
          if (k < bulletmax)
          {
            bullets[k].use = true;
            bullets[k].row = i;
            bullets[k].speed = 9;
            int zwx = 261 + 81 * j;//植物的坐标
            int zwy = 167 + 91 * i + 14;
            bullets[k].x = zwx +
                  imgplant[map[i][j].type - 1][0]->getwidth() - 10;//子弹的坐标
            bullets[k].y = zwy + 5;
            bullets[k].blast = false;
            bullets[k].frameIndex = 0;
          }
        }
      }
    }
  }
}


然后我们设计  updatebullet()更新子弹数据函数。


这个函数设计比较简单,就是更新每帧子弹位置变换的数据,一旦子弹到达屏幕最右边,则回收子弹


void  updatebullet()
{
  int countmax = sizeof(bullets) / sizeof(bullets[0]);
  for (int i = 0; i < countmax; i++)
  {
    if (bullets[i].use)
    {
      bullets[i].x = bullets[i].x + bullets[i].speed;
      if (bullets[i].x > WIN_WIDTH)
      {
        bullets[i].use = false;
      }
    }
  }
}

然后渲染子弹


//渲染子弹
  int bulletmax = sizeof(bullets) / sizeof(bullets[0]);
    for (int i = 0; i < bulletmax; i++)
    {
      if (bullets[i].use)
      {
        putimagePNG(bullets[i].x, bullets[i].y, &imgbulletnormal);
      }
   }


十一、实现子弹和僵尸碰撞效果(子弹击中僵尸)

到目前为止已经设计好了子弹运动和僵尸运动,接下来应该处理子弹和僵尸碰撞效果了,我们创建僵尸结构体定义了初始100滴血量,死亡状态和死亡图片数组,在创建子弹结构体时,设置了爆炸状态,以及爆炸图片数组。


第一步,将初始化(加载)子弹爆炸图片和僵尸死亡图片


注意:子弹爆炸效果实现通过逐步放大碎片子弹,展现爆炸效果

//初始化爆炸子弹数据
  loadimage(&imgbulletblast[3], "res/bullets/bullet_blast.png");//爆炸子弹数组的最后一张图片为源爆炸图片
  for (int i = 0; i < 3; i++)
  {
    float k = (i + 1) * 0.2;
    loadimage(&imgbulletblast[i], "res/bullets/bullet_blast.png",
      imgbulletblast[3].getwidth() * k, imgbulletblast[3].getheight() * k, true);//等比例缩小爆炸图片
  }
  //初始化僵尸死亡图片
  for (int i = 0; i < 20; i++)
  {
    sprintf_s(plantname, sizeof(plantname), "res/zm_dead/%d.png", i + 1);
    loadimage(&imgzmdead[i], plantname);
  }

第二步,改变帧序号,改变子弹爆炸的帧序号和僵尸死亡的帧序号,在更新子弹数据函数和更新僵尸状态函数中进行编写,这里的就会引出一个问题,什么时候更新子弹爆炸和僵尸死亡图片帧序号,在判断子弹和僵尸碰撞时改变,那么先写一个检测子弹和僵尸碰撞检测函数,这个函数属于数据更改,放在更新数据函数中

void updategame()
{
collisioncheck();//碰撞检测
}

之所以在碰撞检测函数中封装子弹和僵尸碰撞检测函数,考虑到后面还有僵尸吃植物的情况

void  collisioncheck()
{
  checkbullet_to_zm();//子弹对僵尸的碰撞检测
}

首先,先找一个出现且没有爆炸的子弹,若子弹在的该行有正常行走的僵尸,且子弹在僵尸胸前到背后区间位置时,则子弹速变为0,开始进入爆炸状态,僵尸被击中扣除5点血量,直到僵尸血量为0,僵尸停下来,状态变成死亡状态


void checkbullet_to_zm();;//子弹对僵尸的碰撞检测
{
  int bulletmax = sizeof(bullets) / sizeof(bullets[0]);
  int zmmax = sizeof(zms) / sizeof(zms[0]);
  for (int i = 0; i < bulletmax; i++)
  {
    if (bullets[i].use == false || bullets[i].blast)
      continue;           //若子弹没出现或发生爆炸,则不执行后面检测,执行下一个子弹检测
    for (int j = 0; j < zmmax; j++)
    {
      if (zms[j].use == false)   //跳过没出现的僵尸
        continue;
      int x1 = zms[j].x + 80;
      int x2 = zms[j].x + 110;
      if (zms[j].dead == false && bullets[i].row == zms[j].row && bullets[i].x > x1 && bullets[i].x < x2)//发生检测碰撞,死了不检测
      {
        PlaySound("res/audio/peacrush1.wav", NULL, SND_FILENAME | SND_ASYNC);
        //mciSendString("play res/audio/splat2.mp3", 0, 0, 0);
        zms[j].blood = zms[j].blood - 5;
        bullets[i].blast = true;
        bullets[i].speed = 0;
        //检测血量是否为0(在碰撞时方便检测)
        if (zms[j].blood <= 0)
        {
          zms[j].dead = true;
          zms[j].speed = 0;
          zms[j].frameIndex = 0;
        }
        break;//这颗子弹检测结束,不用和下一个僵尸检测
      }
    }
  }
}


第三步,通过碰撞检测后,僵尸和子弹状态发生变化,此时以子弹和僵尸碰撞后的状态为条件来进行判断何时发生子弹爆炸和僵尸死亡图片的帧序号变化


在updatebullet()中更改子弹爆炸帧序号


void  updatebullet()
{
  int countmax = sizeof(bullets) / sizeof(bullets[0]);
  for (int i = 0; i < countmax; i++)
  {
    if (bullets[i].use)
    {
      bullets[i].x = bullets[i].x + bullets[i].speed;
      if (bullets[i].x > WIN_WIDTH)
      {
        bullets[i].use = false;//子弹到边界回收
      }
      //子弹碰撞时bullets[i].blast设为真,子弹碰撞后状态为爆炸状态
      if (bullets[i].blast)
      {
        bullets[i].frameIndex++;//如果子弹碰撞,则帧序号改变
        if (bullets[i].frameIndex >= 4)
        {
        bullets[i].use = false;//爆炸结束回收子弹
        }
      }
    }
  }
}


在updatezm()中更新僵尸死亡图片帧序号


//更新僵尸图片帧序号
  static int count2 = 0;
  count2++;
  if (count2 > 1)
  {
    count2 = 0;
    for (int i = 0; i < zmmax; i++)
    {
      if (zms[i].use)
      {
        if (zms[i].dead)//僵尸与子弹碰撞后,僵尸变为死亡状态
        {
          zms[i].frameIndex++;
          if (zms[i].frameIndex >= 20)
          {
            zms[i].use = false;//变灰烬结束,回收僵尸
          }
        }
        else//正常行走状态
        {
          zms[i].frameIndex = (zms[i].frameIndex + 1) % 22 ;//从下标1开始,因初始化下标0
        }
      }
    }
  }


最后对碰撞后的子弹和僵尸图片进行渲染,对原来的僵尸和子弹图片的渲染函数进行修改。


在updatewindow()进行渲染动作


//渲染僵尸图片
  int zmmax = sizeof(zms) / sizeof(zms[0]);
  for (int i = 0; i < zmmax; i++)
  {
    if (zms[i].use)
    {
      //IMAGE* img = &imgzm[zms[i].frameIndex];
      //IMAGE* img = ((zms[i].dead) ? imgzmdead : imgzm);
      IMAGE* img = NULL;
      if (zms[i].dead)
      {
        img = imgzmdead;
      }
      else
      {
        img = imgzm;
      }
      img = img + zms[i].frameIndex;//指针跳过几个IMAGE数据类型大小
      putimagePNG(zms[i].x, zms[i].y-45,img);
    }
  }
  //渲染子弹
  int bulletmax = sizeof(bullets) / sizeof(bullets[0]);
    for (int i = 0; i < bulletmax; i++)
    {
      if (bullets[i].use)
      { 
        if (bullets[i].blast)
        {
          IMAGE* img = &imgbulletblast[bullets[i].frameIndex];
          putimagePNG(bullets[i].x, bullets[i].y, img);
        }
        else
        {
          putimagePNG(bullets[i].x, bullets[i].y, &imgbulletnormal);
        }
      }
   }


运行一下看一下效果

214b7912043651098b0e97f378bb59f5_999a9c8a503a4eb9aab375da34ae2a13.png

相关文章
|
5天前
|
算法 C语言 C++
【C语言实战项目】三子棋游戏
【C语言实战项目】三子棋游戏
33 1
|
5天前
|
程序员 C语言
【C语言实战项目】猜数字游戏
【C语言实战项目】猜数字游戏
32 0
【C语言实战项目】猜数字游戏
|
5天前
|
C语言
关于使用C语言编写一个简单的猜数字游戏
关于使用C语言编写一个简单的猜数字游戏
26 0
|
5天前
|
算法 C语言
【C语言】三子棋游戏实现代码
【C语言】三子棋游戏实现代码
【C语言】三子棋游戏实现代码
|
5天前
|
C语言
C语言实战演练之游戏框架
C语言实战演练之游戏框架
|
5天前
|
存储 算法 C语言
【C 言专栏】用 C 语言开发游戏的实践
【5月更文挑战第5天】本文探讨了使用C语言开发游戏的实践,包括选择适合的游戏类型(如贪吃蛇、俄罗斯方块),设计游戏框架、图形界面和逻辑,以及音效添加。文章还强调了性能优化、测试调试、跨平台挑战及未来发展趋势。对于热衷于C语言的开发者,这是一次挑战与乐趣并存的探索之旅。
【C 言专栏】用 C 语言开发游戏的实践
|
5天前
|
C语言
以c语言为基础实现的简易扫雷游戏(游戏代码附在文章最后,如有需要请自取)
以c语言为基础实现的简易扫雷游戏(游戏代码附在文章最后,如有需要请自取)
52 1
|
5天前
|
人工智能 机器人 测试技术
【C/C++】C语言 21点桌牌游戏 (源码) 【独一无二】
【C/C++】C语言 21点桌牌游戏 (源码) 【独一无二】
|
5天前
|
编译器 定位技术 C语言
【C语言实战项目】扫雷游戏
【C语言实战项目】扫雷游戏
28 0
|
5天前
|
C语言
C语言中的文本冒险游戏
C语言中的文本冒险游戏
24 0