0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

C/C++项目实战:2D射击游戏开发(简易版)

C语言编程学习基地 ? 来源:C语言编程学习基地 ? 2023-01-12 15:57 ? 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

每天一个C语言小项目,提升你的编程能力!

【第一版】

花了一天时间,用 easyx 做了一个小游戏,程序中所有的类函数都是内联函数,大约 300 行。

【第二版】

主要做了代码优化,加强可读性。

同时改了操作方式,玩家和敌人都可以在 x、y 方向上移动,敌人每隔一段时间会随机换向。

游戏运行截图如下:

49f10e52-924c-11ed-bfe3-dac502259ad0.png

操作方式

玩家通过方向键移动,z键射击,左 Shift 进入低速移动模式提高操作精度。

代码说明

关于无阻塞延时,首先,先要 ctime创建一个 clock_t 变量 a,初始化为 clock(),貌似是自从 1970 年到现在的毫秒数。

我们要每隔 0.5 秒执行函数 func() 一次。

那么创建主循环 while(1),调用前用 clock() - a;如果 clock() - a > 500,那么执行 func(),并把 a 重新赋值为 clock()。

如果使用 Sleep(500) 的话,这个循环就只能执行 func 函数了,在此期间什么也做不了。

代码展示:

(直接上源码,大家可以看注释)

/* 作者:STF(QQ:2292683261)*/
#include 
#include 
#include 




void hp_bar();
void show_player();
void show_enemy();
void move_enemy();
void draw_background();
int generate_line();          // 若返回 -1,表示生成线条失败


int create_p_b();            // 创建自机的子弹
int create_e_b();            // 创建敌机的子弹


int destroy_p_b(int index);
int destroy_e_b(int index);        // 删除一个子弹


#define FRAMERATE 20          // 画面刷新的周期(ms)
#define FIRERATE 350          // 射击间隔时间
#define E_FIRERATE 350          // 敌人射击间隔
#define BLEED_TIME 150          // 受伤闪烁时间


#define BACKGROUND 80          // 绘制背景线条的周期


#define MAX_LINES 75          // 最多同屏背景线条数目
#define MAX_PLAYER_BULLETS 40      // 最多同屏自机子弹数目
#define MAX_ENEMY_BULLETS 40      // 最多同屏敌机子弹数目




int player_pos[2] = { 30,30 };            // 自机位置xy
int enemy_bullet[MAX_ENEMY_BULLETS][2];        // 敌人的子弹位置
int player_bullet[MAX_PLAYER_BULLETS][2];      // 自机的子弹位置
int enemy_pos[2] = { 580,240 };            // 敌机位置
bool p_b_slots[MAX_PLAYER_BULLETS] = { false };    // 用于判断 player_bullet 的某个位置是否可用
bool e_b_slots[MAX_ENEMY_BULLETS] = { false };
int number_p_b = 0, number_e_b = 0;          // 记录自机和敌机的子弹数,减少遍历压力


int player_health = 100, enemy_health = 100;


bool isBleeding_p = false, isBleeding_e = false;  // 用于实现命中后的闪烁效果




int background_line[MAX_LINES][3];          // 背景的线条,三个参数分别是 x、y、长度
bool line_slots[MAX_LINES] = { false };
int number_lines = 0;                // 记录背景线条数目




clock_t begin_time = 0;


int main()
{
  initgraph(640, 550, 4);


  srand((unsigned)time(NULL));


  settextcolor(RGB(0, 254, 0));
  settextstyle(30, 0, L"微软雅黑");
  outtextxy(50, 200, L"方向键移动, Z 攻击, 左 Shift 切换低速模式");


  bool win = false, dead = false;


  clock_t firerate = clock();            // 射击控制
  clock_t e_firerate = clock();          // 控制敌机的射击
  clock_t runtime = clock();            // 用于控制画面刷新频率
  clock_t bleed_p = clock(), bleed_e = clock();  // 用于实现受伤闪烁
  clock_t backgroundline_generate = clock();    // 用于生成背景线条
  Sleep(3000);
  BeginBatchDraw();


  bool leftshift = false;


  begin_time = clock();
  while (true)
  {


    if (clock() - runtime >= FRAMERATE)
    {
      runtime = clock();
      cleardevice();
      draw_background();
      hp_bar();// 画血条
      show_player();
      show_enemy();


      int n_p_b = 1, n_e_b = 1;          // 计数,遍历子弹,刷新位置
      int p_b_toprocess = number_p_b, e_b_toprocess = number_e_b;  // 需要处理的子弹数
      for (int i = 0; i < MAX_PLAYER_BULLETS && (n_p_b <= p_b_toprocess || n_e_b <= e_b_toprocess); ++i)
      {
        if (n_p_b <= p_b_toprocess)        // 如果子弹已经处理完就不处理了
        {
          if (p_b_slots[i] == true)
          {
            ++n_p_b;
            player_bullet[i][0] += 3;
            setfillcolor(RGB(150, 180, 210));
            if (player_bullet[i][0] >= 635)
            {
              destroy_p_b(i);  // 到达了屏幕最右端
            }


            // 碰撞检测,两个矩形
            if ((player_bullet[i][0] + 5 >= enemy_pos[0] - 20 && player_bullet[i][0] - 5 <= enemy_pos[0] + 20) && (player_bullet[i][1] - 5 < enemy_pos[1] + 40 && player_bullet[i][1] + 5 > enemy_pos[1] - 40))
              // 击中敌人
            {
              destroy_p_b(i);
              enemy_health -= 8;
              isBleeding_e = true;
              bleed_e = clock();
            }


            fillrectangle(player_bullet[i][0] - 5, player_bullet[i][1] - 5, player_bullet[i][0] + 5, player_bullet[i][1] + 5);    // 画子弹
          }


        }


        if (n_e_b <= e_b_toprocess)        // 敌人的子弹
        {
          if (e_b_slots[i] == true)
          {
            ++n_e_b;
            enemy_bullet[i][0] -= 3;
            setfillcolor(RGB(255, 180, 20));
            if (enemy_bullet[i][0] < 5)
            {
              destroy_e_b(i);
            }


            // 碰撞检测,两个矩形
            if (enemy_bullet[i][0] - 5 < player_pos[0] + 25 && enemy_bullet[i][0] + 5 > player_pos[0] - 25 && enemy_bullet[i][1] - 5 < player_pos[1] + 25 && enemy_bullet[i][1] + 5 > player_pos[1] - 25)
            {
              // 击中自机
              isBleeding_p = true;
              destroy_e_b(i);
              player_health -= 8;
              bleed_p = clock();
            }
            fillrectangle(enemy_bullet[i][0] - 5, enemy_bullet[i][1] - 5, enemy_bullet[i][0] + 5, enemy_bullet[i][1] + 5);
          }


        }
      }


      if (win || dead)
        break;
      FlushBatchDraw();


      move_enemy();


      if (player_health <= 0)
        dead = true;
      if (enemy_health <= 0)
      {
        win = true;
      }


      if (GetAsyncKeyState(VK_LSHIFT) & 0x8000)  // 按住 Shift 减速
      {
        leftshift = true;
      }
      else
      {
        leftshift = false;
      }


      if (GetAsyncKeyState(VK_UP) & 0x8000)
        // 玩家移动
      {
        if (player_pos[1] >= 28)
          if (leftshift)
            player_pos[1] -= 2;        // y 的正方向是向下的
          else
            player_pos[1] -= 5;
      }
      if (clock() - firerate >= FIRERATE && GetAsyncKeyState('Z') & 0x8000)
        // 玩家开火
      {
        firerate = clock();
        create_p_b();
      }
      if (GetAsyncKeyState(VK_DOWN) & 0x8000)
        // 玩家移动
      {
        if (player_pos[1] <= 452)
          if (leftshift)
            player_pos[1] += 2;
          else
            player_pos[1] += 5;
      }
      if (GetAsyncKeyState(VK_LEFT) & 0x8000)
        // 玩家移动
      {
        if (player_pos[0] >= 30)
          if (leftshift)
            player_pos[0] -= 2;
          else
            player_pos[0] -= 5;
      }
      if (GetAsyncKeyState(VK_RIGHT) & 0x8000)
        // 玩家移动
      {
        if (player_pos[0] <= 320)
          if (leftshift)
            player_pos[0] += 2;
          else
            player_pos[0] += 5;
      }




      if (clock() - e_firerate >= E_FIRERATE)
      {
        e_firerate = clock();
        create_e_b();
      }




      if (clock() - bleed_p >= BLEED_TIME)    // 受伤时间结束后关闭受伤闪烁效果
      {
        isBleeding_p = false;
      }


      if (clock() - bleed_e >= BLEED_TIME)    // 受伤时间结束后关闭受伤闪烁效果
      {
        isBleeding_e = false;
      }


      if (clock() - backgroundline_generate >= BACKGROUND)
      {
        backgroundline_generate = clock();
        generate_line();
      }
    }
  }
  if (win)
  {
    settextcolor(RGB(0, 254, 0));
    settextstyle(35, 0, L"黑体");
    outtextxy(150, 200, L"你打败了boss!你赢了!!");
  }
  else
  {
    settextcolor(RGB(254, 0, 0));
    settextstyle(35, 0, L"黑体");
    outtextxy(140, 200, L"你被boss打败了!");
  }
  FlushBatchDraw();
  Sleep(5000);
  EndBatchDraw();
  return 0;
}












void hp_bar()
{
  setlinecolor(RGB(255, 255, 255));
  line(0, 481, 640, 481);                    // 一条分割线
  settextstyle(20, 0, L"黑体");
  outtextxy(10, 485, L"BOSS的生命值:");
  outtextxy(10, 520, L"玩家的生命值:");
  setfillcolor(RGB(0, 255, 1));
  setlinecolor(WHITE);
  rectangle(160, 515, 560, 540);                // 血条外框
  setfillcolor(RGB(0, 255, 1));
  setlinecolor(RGB(255, 255, 255));
  if (player_health > 0)
    fillrectangle(160, 515, 160 + player_health * 4, 540);  // 玩家血条
  setlinecolor(WHITE);
  rectangle(160, 485, 560, 510);                // 敌人血条外框
  setfillcolor(RGB(230, 0, 1));
  setlinecolor(RGB(255, 255, 255));
  if (enemy_health > 0)
    fillrectangle(160, 485, 160 + enemy_health * 4, 510);  // 敌人血条


}


void show_player()
{
  if (isBleeding_p)
    setfillcolor(RGB(255, 0, 0));
  else
    setfillcolor(RGB(150, 180, 210));
  fillrectangle(player_pos[0] - 25, player_pos[1] - 25, player_pos[0] + 25, player_pos[1] + 25);
  setfillcolor(RGB(100, 200, 180));
  fillrectangle(player_pos[0], player_pos[1] + 5, player_pos[0] + 40, player_pos[1] - 5);
}


void show_enemy()
{
  if (isBleeding_e)
    setfillcolor(RGB(255, 0, 0));
  else
    setfillcolor(RGB(0, 130, 125));
  fillrectangle(enemy_pos[0] - 20, enemy_pos[1] - 40, enemy_pos[0] + 20, enemy_pos[1] + 40);
  setfillcolor(RGB(100, 200, 180));
  fillrectangle(enemy_pos[0], enemy_pos[1] + 5, enemy_pos[0] - 40, enemy_pos[1] - 5);
}


void move_enemy()
{
  static bool angle_v;    // 控制敌机的竖直移动方向,true 为向上,到边缘就换向
  static bool angle_h;    // 控制敌机的水平移动方向,true 为向左,到边缘就换向
  static clock_t interval;  // 定时随机换向


  if (clock() - interval >= 2000)
  {
    interval = clock();
    if (rand() % 2)      // 一般的概率换向
      angle_v = !angle_v;
    if (rand() % 2)
      angle_h = !angle_h;
  }


  if (angle_v == true)    // 到了地图边缘就调头
    enemy_pos[1] -= 3;
  else
    enemy_pos[1] += 3;
  if (angle_h == true)
    enemy_pos[0] -= 3;
  else
    enemy_pos[0] += 3;




  if (enemy_pos[1] >= 440)
    angle_v = true;
  else if (enemy_pos[1] <= 40)
    angle_v = false;




  if (enemy_pos[0] >= 580)
    angle_h = true;
  else if (enemy_pos[0] <= 380)
    angle_h = false;


}


void draw_background()
{
  setlinecolor(WHITE);
  int n_b_l = number_lines;    // 待处理线条数目
  for (int i = 0; i < MAX_LINES && (n_b_l > 0); ++i)
  {
    if (line_slots[i] == true)
    {
      if (background_line[i][0] + background_line[i][2] <= 0)    // 说明线条出了屏幕
      {
        --number_lines;
        line_slots[i] = false;
      }
      else
      {
        background_line[i][0] -= 10;        // 线条移动
        line(background_line[i][0], background_line[i][1], background_line[i][0] + background_line[i][2], background_line[i][1]);
      }
      --n_b_l;
    }


  }
}


int generate_line()
{
  if (number_lines >= MAX_LINES)
    return -1;
  ++number_lines;
  for (int i = 0; i < MAX_LINES; ++i)
  {
    if (line_slots[i] == false)
    {
      line_slots[i] = true;
      background_line[i][0] = 640;        // 线条出现于屏幕最右边
      background_line[i][1] = rand() % 480;    // 线条高度随机
      background_line[i][2] = 10 + rand() % 50;  // 线条长度随机在 10-50 像素之间


      break;
    }
  }
  return 0;
}


int create_p_b()
{
  if (number_p_b > MAX_PLAYER_BULLETS)      // 空间不够
    return -1;
  for (int i = 0; i < MAX_PLAYER_BULLETS; ++i)  // 搜索 slots,寻找空位
  {
    if (p_b_slots[i] == false)
    {
      p_b_slots[i] = true;
      player_bullet[i][0] = player_pos[0] + 45;
      player_bullet[i][1] = player_pos[1];  // 创建子弹
      ++number_p_b;
      break;
    }
  }
  return 0;
}


int create_e_b()
{
  if (number_e_b > MAX_ENEMY_BULLETS)        // 空间不够
    return -1;
  for (int i = 0; i < MAX_ENEMY_BULLETS; ++i)    // 搜索 slots,寻找空位
  {
    if (e_b_slots[i] == false)
    {
      e_b_slots[i] = true;
      enemy_bullet[i][0] = enemy_pos[0] - 45;
      enemy_bullet[i][1] = enemy_pos[1];    // 创建子弹
      ++number_e_b;
      break;
    }
  }
  return 0;
}


int destroy_p_b(int index)
{
  if (index > MAX_PLAYER_BULLETS - 1)
    return -2;
  if (p_b_slots[index] == false)
    return -1;
  p_b_slots[index] = false;
  --number_p_b;
  return 0;
}


int destroy_e_b(int index)
{
  if (index > MAX_ENEMY_BULLETS - 1)
    return -2;
  if (e_b_slots[index] == false)
    return -1;
  e_b_slots[index] = false;
  --number_e_b;
  return 0;
}

大家赶紧去动手试试吧!

此外,我也给大家分享我收集的其他资源,从最零基础开始的教程到C语言C++项目案例,帮助大家在学习C语言的道路上披荆斩棘!

审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • C语言
    +关注

    关注

    181

    文章

    7633

    浏览量

    142592
  • C++
    C++
    +关注

    关注

    22

    文章

    2119

    浏览量

    75784

原文标题:C/C++项目实战:2D射击游戏开发(简易版),440 行源码分享来啦~

文章出处:【微信号:cyuyanxuexi,微信公众号:C语言编程学习基地】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    基于DE1-SOC开发板的太空射击游戏

    今天继续常春藤名校之一——康奈尔大学的FPGA课程ECE 5760典型案例分享:基于DE1-SOC开发板的太空射击游戏
    的头像 发表于 08-04 10:47 ?1196次阅读
    基于DE1-SOC<b class='flag-5'>开发</b>板的太空<b class='flag-5'>射击</b><b class='flag-5'>游戏</b>

    主流的 MCU 开发语言为什么是 C 而不是 C++

    在单片机的地界儿里,C语言稳坐中军帐,C++想分杯羹?难喽。咱电子工程师天天跟那针尖大的内存空间较劲,C++那些花里胡哨的玩意儿,在这儿真玩不转。先说内存这道坎儿。您当stm32f4的256kRAM
    的头像 发表于 05-21 10:33 ?550次阅读
    主流的 MCU <b class='flag-5'>开发</b>语言为什么是 <b class='flag-5'>C</b> 而不是 <b class='flag-5'>C++</b>?

    FN2-24D24C3N FN2-24D24C3N

    电子发烧友网为你提供AIPULNION(AIPULNION)FN2-24D24C3N相关产品参数、数据手册,更有FN2-24D24C3N的引脚图、接线图、封装手册、中文资料、英文资料,FN2-24D24C3N真值表,FN
    发表于 03-19 18:49
    FN<b class='flag-5'>2-24D24C</b>3N FN<b class='flag-5'>2-24D24C</b>3N

    FN2-24D15C FN2-24D15C

    电子发烧友网为你提供AIPULNION(AIPULNION)FN2-24D15C相关产品参数、数据手册,更有FN2-24D15C的引脚图、接线图、封装手册、中文资料、英文资料,FN2-24D15C真值表,FN
    发表于 03-19 18:46
    FN<b class='flag-5'>2-24D15C</b> FN<b class='flag-5'>2-24D15C</b>

    FN2-24D15C3 FN2-24D15C3

    电子发烧友网为你提供AIPULNION(AIPULNION)FN2-24D15C3相关产品参数、数据手册,更有FN2-24D15C3的引脚图、接线图、封装手册、中文资料、英文资料,FN2-24D15C3真值表,FN
    发表于 03-19 18:46
    FN<b class='flag-5'>2-24D15C</b>3 FN<b class='flag-5'>2-24D15C</b>3

    C++学到什么程度可以找工作?

    C++学到什么程度可以找工作?要使用C++找到工作,特别是作为软件开发人员或相关职位,通常需要掌握以下几个方面: 1. **语言基础**:你需要对C++的核心概念有扎实的理解,包括但不
    发表于 03-13 10:19

    STM8/STM32 products有2D marking和没有2D marking的工艺有差别吗?

    请教下,STM8/STM32 products 有2D marking 和没有2D marking的工艺有差别吗?同一程序在使用时有2D标识的不能用。
    发表于 03-07 07:21

    源代码加密、源代码防泄漏c/c++与git服务器开发环境

    源代码加密对于很多研发性单位来说是至关重要的,当然每家企业的业务需求不同所用的开发环境及开发语言也不尽相同,今天主要来讲一下c++及git开发环境的源代码防泄密保护方案。企业源代码泄密
    的头像 发表于 02-12 15:26 ?609次阅读
    源代码加密、源代码防泄漏<b class='flag-5'>c</b>/<b class='flag-5'>c++</b>与git服务器<b class='flag-5'>开发</b>环境

    基于OpenHarmony标准系统的C++公共基础类库案例:ThreadPoll

    。每个线程每秒打印1段字符串,10秒后停止。2、基础知识C++公共基础类库为标准系统提供了一些常用的C++开发工具类,包括:文件、路径、字符串相关操作的能力增强接口
    的头像 发表于 02-10 18:09 ?445次阅读
    基于OpenHarmony标准系统的<b class='flag-5'>C++</b>公共基础类库案例:ThreadPoll

    Spire.XLS for C++组件说明

    开发人员可以快速地在 C++ 平台上完成对 Excel 的各种编程操作,如根据模板创建新的 Excel 文档,编辑现有 Excel 文档,以及对 Excel 文档进行转换。 Spire.XLS
    的头像 发表于 01-14 09:40 ?753次阅读
    Spire.XLS for <b class='flag-5'>C++</b>组件说明

    AKI跨语言调用库神助攻C/C++代码迁移至HarmonyOS NEXT

    ,真正做到所“键”即所得。 这一创新框架的出现,正是为了解决开发者在迁移C/C++项目到HarmonyOS NEXT时面临的核心痛点。传统的NAPI接口调用复杂,学习成本高,
    发表于 01-02 17:08

    同样是函数,在CC++中有什么区别

    ,即使没有数据返回,也得写 void。 第二个函数名。 C语言的函数名绝对不能重名,除了用上 weak 这样的黑科技。同一个项目中,函数重名就会提示重复定义。 C++因为函数重载的存在,函数名可以相同,只要参数有区别就行。这两个
    的头像 发表于 11-29 10:25 ?1004次阅读

    C语言和C++中结构体的区别

    同样是结构体,看看在C语言和C++中有什么区别?
    的头像 发表于 10-30 15:11 ?864次阅读

    C7000优化C/C++编译器

    电子发烧友网站提供《C7000优化C/C++编译器.pdf》资料免费下载
    发表于 10-30 09:45 ?0次下载
    <b class='flag-5'>C</b>7000优化<b class='flag-5'>C</b>/<b class='flag-5'>C++</b>编译器

    Allegro推出2D霍尔效应速度和方向锁存器

    对于天窗顶电机位置,升降门电机,车窗升降电机和电子驻车制动(EPB)系统等应用,透过使用单个2D磁性速度和方向传感器,可以简化系统设计和生产,减少BOM,并提高系统安全性。APS12625和APS12626系列2D霍尔效应2D
    的头像 发表于 09-27 09:58 ?843次阅读