maltadirk 发表于 2023-2-1 21:05

C/C++项目实战:2D射击游戏开发!(简易版) 440 行源码 ...

每天一个C语言小项目,提升你的编程能力!
【第一版】
花了一天时间,用 easyx 做了一个小游戏,程序中所有的类函数都是内联函数,大约 300 行。
【第二版】
主要做了代码优化,加强可读性。
同时改了操作方式,玩家和敌人都可以在 x、y 方向上移动,敌人每隔一段时间会随机换向。
游戏运行截图如下:


操作方式
玩家通过方向键移动,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 <easyx.h>
#include <cstdlib>
#include <ctime>


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 = { 30,30 };                                                // 自机位置xy
int enemy_bullet;                                // 敌人的子弹位置
int player_bullet;                        // 自机的子弹位置
int enemy_pos = { 580,240 };                                                // 敌机位置
bool p_b_slots = { false };                // 用于判断 player_bullet 的某个位置是否可用
bool e_b_slots = { 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;                                        // 背景的线条,三个参数分别是 x、y、长度
bool line_slots = { 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 == true)
                                        {
                                                ++n_p_b;
                                                player_bullet += 3;
                                                setfillcolor(RGB(150, 180, 210));
                                                if (player_bullet >= 635)
                                                {
                                                        destroy_p_b(i);        // 到达了屏幕最右端
                                                }

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

                                                fillrectangle(player_bullet - 5, player_bullet - 5, player_bullet + 5, player_bullet + 5);                // 画子弹
                                        }

                                }

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

                                                // 碰撞检测,两个矩形
                                                if (enemy_bullet - 5 < player_pos + 25 && enemy_bullet + 5 > player_pos - 25 && enemy_bullet - 5 < player_pos + 25 && enemy_bullet + 5 > player_pos - 25)
                                                {
                                                        // 击中自机
                                                        isBleeding_p = true;
                                                        destroy_e_b(i);
                                                        player_health -= 8;
                                                        bleed_p = clock();
                                                }
                                                fillrectangle(enemy_bullet - 5, enemy_bullet - 5, enemy_bullet + 5, enemy_bullet + 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 >= 28)
                                        if (leftshift)
                                                player_pos -= 2;                                // y 的正方向是向下的
                                        else
                                                player_pos -= 5;
                        }
                        if (clock() - firerate >= FIRERATE && GetAsyncKeyState('Z') & 0x8000)
                                // 玩家开火
                        {
                                firerate = clock();
                                create_p_b();
                        }
                        if (GetAsyncKeyState(VK_DOWN) & 0x8000)
                                // 玩家移动
                        {
                                if (player_pos <= 452)
                                        if (leftshift)
                                                player_pos += 2;
                                        else
                                                player_pos += 5;
                        }
                        if (GetAsyncKeyState(VK_LEFT) & 0x8000)
                                // 玩家移动
                        {
                                if (player_pos >= 30)
                                        if (leftshift)
                                                player_pos -= 2;
                                        else
                                                player_pos -= 5;
                        }
                        if (GetAsyncKeyState(VK_RIGHT) & 0x8000)
                                // 玩家移动
                        {
                                if (player_pos <= 320)
                                        if (leftshift)
                                                player_pos += 2;
                                        else
                                                player_pos += 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 - 25, player_pos - 25, player_pos + 25, player_pos + 25);
        setfillcolor(RGB(100, 200, 180));
        fillrectangle(player_pos, player_pos + 5, player_pos + 40, player_pos - 5);
}

void show_enemy()
{
        if (isBleeding_e)
                setfillcolor(RGB(255, 0, 0));
        else
                setfillcolor(RGB(0, 130, 125));
        fillrectangle(enemy_pos - 20, enemy_pos - 40, enemy_pos + 20, enemy_pos + 40);
        setfillcolor(RGB(100, 200, 180));
        fillrectangle(enemy_pos, enemy_pos + 5, enemy_pos - 40, enemy_pos - 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 -= 3;
        else
                enemy_pos += 3;
        if (angle_h == true)
                enemy_pos -= 3;
        else
                enemy_pos += 3;


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


        if (enemy_pos >= 580)
                angle_h = true;
        else if (enemy_pos <= 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 == true)
                {
                        if (background_line + background_line <= 0)                // 说明线条出了屏幕
                        {
                                --number_lines;
                                line_slots = false;
                        }
                        else
                        {
                                background_line -= 10;                                // 线条移动
                                line(background_line, background_line, background_line + background_line, background_line);
                        }
                        --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 == false)
                {
                        line_slots = true;
                        background_line = 640;                                // 线条出现于屏幕最右边
                        background_line = rand() % 480;                // 线条高度随机
                        background_line = 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 == false)
                {
                        p_b_slots = true;
                        player_bullet = player_pos + 45;
                        player_bullet = player_pos;        // 创建子弹
                        ++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 == false)
                {
                        e_b_slots = true;
                        enemy_bullet = enemy_pos - 45;
                        enemy_bullet = enemy_pos;                // 创建子弹
                        ++number_e_b;
                        break;
                }
        }
        return 0;
}

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

int destroy_e_b(int index)
{
        if (index > MAX_ENEMY_BULLETS - 1)
                return -2;
        if (e_b_slots == false)
                return -1;
        e_b_slots = false;
        --number_e_b;
        return 0;
}
大家赶紧去动手试试吧!
此外,我也给大家分享我收集的其他资源,从最零基础开始的教程到C语言C++项目案例,帮助大家在学习C语言的道路上披荆斩棘!
C语言C++编程学习视频分享:




整理分享(多年学习的源码、项目实战视频、项目笔记,基础入门教程)最重要的是你可以在群里面交流提问编程问题哦!
欢迎转行和学习编程的伙伴,利用更多的资料学习成长比自己琢磨更快哦!
【C语言/C++/数据结构与算法编程学习】
页: [1]
查看完整版本: C/C++项目实战:2D射击游戏开发!(简易版) 440 行源码 ...