【源码+注释】纯C++小游戏开发之射击小球游戏

【源码+注释】纯C++小游戏开发之射击小球游戏

      • 一、项目前置配置
      • 二、项目源码
        • [1. game.h文件](#1. game.h文件)
        • [2. game.cpp文件](#2. game.cpp文件)
        • [3. main.cpp文件](#3. main.cpp文件)
      • 三、运行结果
        • [1. 游戏开始界面](#1. 游戏开始界面)
        • [2. 游戏结束界面(倒计时结束后)](#2. 游戏结束界面(倒计时结束后))

灵感来源:《三角洲行动》第8赛季中,MP7的电玩高手2枪皮中的检视小游戏

一、项目前置配置

开发平台:DevC++

图形库:EasyX库
https://easyx.cn/easyx

二、项目源码

项目结构:

1. game.h文件
cpp 复制代码
#ifndef GAME_H
#define GAME_H

#include <windows.h>
#include <graphics.h>
#include <conio.h>
#include <time.h>
#include <cmath>
#include <string>
#include <algorithm>  // 添加这个头文件以使用min() 
using namespace std;

// 颜色定义 - 紫色主题
const COLORREF DEEP_PURPLE = RGB(75, 0, 130);       // 深紫色
const COLORREF INDIGO_PURPLE = RGB(111, 45, 168);  // 靛紫色
const COLORREF VIOLET = RGB(138, 43, 226);         // 紫罗兰
const COLORREF MEDIUM_PURPLE = RGB(147, 112, 219); // 中紫色
const COLORREF AMETHYST = RGB(153, 102, 204);      // 兰紫色
const COLORREF LIGHT_PURPLE = RGB(186, 85, 211);   // 浅紫色
const COLORREF PINK_PURPLE = RGB(218, 112, 214);   // 粉紫色
const COLORREF LAVENDER = RGB(230, 230, 250);      // 薰衣草色(高光用)

const COLORREF DARK_BG = RGB(20, 15, 35);          // 深紫色背景
const COLORREF GRID_COLOR = RGB(45, 40, 70);       // 紫色网格线
const COLORREF UI_PURPLE = RGB(120, 80, 180);      // UI紫色

// 圆球结构体
struct Ball {
    float x, y;           // 位置
    float speed;          // 移动速度
    float radius;         // 半径
    COLORREF color;       // 颜色
    bool active;          // 是否活跃
    int direction;        // 1:从左到右, -1:从右到左
    float trail[5][2];    // 轨迹点   存储圆球最近5个位置的历史坐标
    int trailIndex;       // 轨迹索引    指示下一个要更新的轨迹位置    取值范围:0-4,循环使用
};

// 游戏类
class Game {
private:
    // 游戏状态
    int score;                    // 得分
    int targetsHit;               // 击中数量
    int timeLeft;                 // 剩余时间(秒)
    bool gameActive;              // 游戏是否进行中
    DWORD startTime;              // 游戏开始时间
    DWORD lastFrameTime;          // 上一帧的时间
    
    // 游戏配置
    static const int MAX_BALLS = 20; // 小球数量 
    static const int SCREEN_WIDTH = 1024;
    static const int SCREEN_HEIGHT = 768;
    static const int GAME_DURATION = 60; // 游戏时长60秒
    
    // 紫色颜色数组
    static const int PURPLE_COLORS_COUNT = 7;
    COLORREF purpleColors[PURPLE_COLORS_COUNT];
    
    // 圆球数组
    Ball balls[MAX_BALLS];
    
    // 私有方法
    void init();                     // 初始化游戏
    void initBall(Ball& ball);       // 初始化单个圆球
    void update();                   // 更新游戏状态
    void render();                   // 渲染游戏画面
    void handleInput();              // 处理用户输入
    void reset();                    // 重置游戏
    
    // 辅助方法
    void updateBall(Ball& ball);     // 更新圆球位置
    void drawBall(const Ball& ball); // 绘制圆球
    void drawGameUI();               // 绘制游戏UI
    void drawGameOverScreen();       // 绘制游戏结束界面
    void drawMP7Model();             // 绘制MP7模型
    bool checkBallHit(Ball& ball, int mouseX, int mouseY); // 检查击中

public:
    Game();                          // 构造函数
    ~Game();                         // 析构函数
    void run();                      // 运行游戏主循环
};

#endif // GAME_H
2. game.cpp文件
csharp 复制代码
#include "game.h"
using namespace std;
// 构造函数
Game::Game() {
    // 初始化随机数种子
    srand(time(NULL));
    
    // 初始化紫色颜色数组
    purpleColors[0] = DEEP_PURPLE;
    purpleColors[1] = INDIGO_PURPLE;
    purpleColors[2] = VIOLET;
    purpleColors[3] = MEDIUM_PURPLE;
    purpleColors[4] = AMETHYST;
    purpleColors[5] = LIGHT_PURPLE;
    purpleColors[6] = PINK_PURPLE;
    
    // 初始化图形窗口
    initgraph(SCREEN_WIDTH, SCREEN_HEIGHT);
    setbkcolor(DARK_BG);
    cleardevice();
    
    // 初始化游戏状态
    reset();
}

// 析构函数
Game::~Game() {
    // 关闭图形窗口
    closegraph();
}

// 重置游戏状态
void Game::reset() {
    score = 0;
    targetsHit = 0; // 击中数量 
    timeLeft = GAME_DURATION; // 将剩余时间(timeLeft)设置为游戏时长(GAME_DURATION,即60秒) 
    gameActive = true; // 表示游戏进行中 
    startTime = GetTickCount();  // 记录游戏开始时间 
    lastFrameTime = GetTickCount(); // 记录上一帧的时间,用于控制帧率
    // GetTickCount():返回系统启动到现在的毫秒数  
    
    // 初始化所有圆球
    for (int i = 0; i < MAX_BALLS; i++) { //  MAX_BALLS :20
        initBall(balls[i]);
        // 让圆球在不同时间出现
        balls[i].x = (balls[i].direction == 1) ? -balls[i].radius - i * 30 : SCREEN_WIDTH + balls[i].radius + i * 30;
        // 如果圆球方向为1(从左向右),则圆球的初始 x 坐标为:负的圆球半径减去 i*30 
		// 如果圆球方向为-1(从右向左),则圆球的初始 x 坐标为:屏幕宽度加上圆球半径再加上 i*30 
		// 这样每个圆球从屏幕左侧(右侧)更远的位置开始,使得它们不会同时出现。 
    }
}

// 初始化单个圆球
void Game::initBall(Ball& ball) { // 通过引用传递,直接修改传入的圆球对象 
    ball.radius = 15 + rand() % 20;  // 随机半径15-35
    ball.speed = 0.5f + (rand() % 100) / 200.0f;  // 随机速度 0.5-1.0
    
    // 随机选择紫色颜色
    int colorIndex = rand() % PURPLE_COLORS_COUNT;
    ball.color = purpleColors[colorIndex];
    
    // 随机选择出现边(0:左边, 1:右边)
    if (rand() % 2 == 0) {
        // 从左边出现
        ball.x = -ball.radius; // 设置圆球的初始 x 坐标为负的半径,这样圆球刚好在屏幕左侧外 
        ball.direction = 1; // 方向为1,表示向右移动 
    } else {
        // 从右边出现
        ball.x = SCREEN_WIDTH + ball.radius; // 设置圆球的初始 x 坐标为屏幕宽度加上半径,这样圆球刚好在屏幕右侧外 
        ball.direction = -1; // 方向为-1,表示向左移动 
    }
    
    // 随机垂直位置
    ball.y = 100 + rand() % (SCREEN_HEIGHT - 200); // 是在屏幕中间区域(上下各留100像素的边界)随机出现 
    
    ball.active = true;
    ball.trailIndex = 0; // 表示轨迹数组的第一个位置 
    
    // 初始化轨迹
    // 将轨迹数组中的5个点都初始化为圆球的初始位置
	// 这样在圆球刚开始移动时,轨迹点都是重叠的,绘制时不会出现拖尾,直到圆球移动并更新轨迹点
    for (int i = 0; i < 5; i++) {
        ball.trail[i][0] = ball.x;
        ball.trail[i][1] = ball.y;
    }
}

// 更新圆球位置
void Game::updateBall(Ball& ball) {
    if (!ball.active) return;
    
    // 更新位置
    // 根据圆球的速度和方向更新其 x 坐标        
	// 如果方向为1(向右),则 x 增加;方向为-1(向左),则 x 减少 
    ball.x += ball.speed * ball.direction;
    
    // 更新轨迹
    ball.trail[ball.trailIndex][0] = ball.x;
    ball.trail[ball.trailIndex][1] = ball.y;
    // trailIndex 会在0到4之间循环        这样下一次更新轨迹时,会覆盖最旧的轨迹点,实现循环缓冲区
    ball.trailIndex = (ball.trailIndex + 1) % 5;
    
    // 检查是否移出屏幕
    if ((ball.direction == 1 && ball.x > SCREEN_WIDTH + ball.radius) || // 方向从左往右,移到屏幕右侧 
        (ball.direction == -1 && ball.x < -ball.radius)) { 
        ball.active = false;
    }
}

// 绘制圆球(带轨迹效果)
void Game::drawBall(const Ball& ball) {
    if (!ball.active) return;
    
    // 绘制轨迹(渐隐效果)
    for (int i = 0; i < 5; i++) {
        int idx = (ball.trailIndex + i) % 5; // 每个i值对应一个轨迹点 
        
        float alpha = 1.0f - (i * 0.2f); // 计算当前轨迹点的透明度系数(渐隐效果) 
        /*
		1.0f 表示完全不透明
		i * 0.2f 随着i增加而增加

		计算结果:
		i=0: alpha = 1.0 - 0.0 = 1.0 (100%不透明)
		i=1: alpha = 1.0 - 0.2 = 0.8 (80%不透明)	
		i=2: alpha = 1.0 - 0.4 = 0.6 (60%不透明)	
		i=3: alpha = 1.0 - 0.6 = 0.4 (40%不透明)	
		i=4: alpha = 1.0 - 0.8 = 0.2 (20%不透明)
		*/ 
        
        // 计算轨迹颜色
        int r = GetRValue(ball.color);
        int g = GetGValue(ball.color);
        int b = GetBValue(ball.color);
        
        // 紫色轨迹颜色(稍微淡一点)
        // 修复:使用min并确保两个参数都是int类型
        int trailR = min(255, static_cast<int>(r + 50 * (1 - alpha)));
        int trailG = min(255, static_cast<int>(g + 30 * (1 - alpha)));
        int trailB = min(255, static_cast<int>(b + 50 * (1 - alpha)));
        
        COLORREF trailColor = RGB(trailR, trailG, trailB);
        
        // 绘制轨迹点
        setfillcolor(trailColor);
        solidcircle(ball.trail[idx][0], ball.trail[idx][1], ball.radius * 0.3f * alpha);
        /* 
		solidcircle(轨迹点的x坐标, 轨迹点的y坐标, 轨迹点半径)
		       
		ball.radius:圆球主体半径(例如20像素)
		0.3f:轨迹点大小为主体的30%
		* alpha:越旧的轨迹点越小
		*/
    }
    
    // 绘制圆球主体
    setfillcolor(ball.color);
    solidcircle(ball.x, ball.y, ball.radius);
    
    // 绘制圆球高光(使用薰衣草色)
    setfillcolor(LAVENDER);
    solidcircle(
        ball.x - ball.radius * 0.3f,
        ball.y - ball.radius * 0.3f,
        ball.radius * 0.4f
    );
    
    // 添加紫色光晕效果
    setlinecolor(RGB(
        min(255, GetRValue(ball.color) + 50),
        min(255, GetGValue(ball.color) + 30),
        min(255, GetBValue(ball.color) + 50)
    ));
    circle(ball.x, ball.y, ball.radius + 2);
}

// 绘制MP7枪械文字标识
void Game::drawMP7Model() {
    settextcolor(LAVENDER);
    settextstyle(16, 0, "Arial");
    outtextxy(SCREEN_WIDTH - 190, SCREEN_HEIGHT - 120, "MP7 - 电玩高手S2");
}

// 绘制游戏UI
void Game::drawGameUI() {
    // 绘制顶部状态栏背景(半透明紫色)
    setfillcolor(RGB(60, 45, 100));
    solidrectangle(0, 0, SCREEN_WIDTH, 60);
    
    // 绘制剩余时间
    char timeText[50];
    sprintf(timeText, "剩余时间: %02d:%02d", timeLeft / 60, timeLeft % 60);
    
    settextcolor(LAVENDER);
    settextstyle(24, 0, "Arial");
    int textWidth = textwidth(timeText);
    outtextxy((SCREEN_WIDTH - textWidth) / 2, 20, timeText);
    
    // 绘制击败数量
    char scoreText[50];
    sprintf(scoreText, "击败: %d", targetsHit);
    
    settextcolor(LIGHT_PURPLE);
    settextstyle(28, 0, "Arial");
    outtextxy(50, 18, scoreText);
    
    // 绘制分数
    char totalText[50];
    sprintf(totalText, "得分: %d", score);
    outtextxy(SCREEN_WIDTH - 150, 18, totalText);
}

// 绘制游戏结束界面
void Game::drawGameOverScreen() {
    // 深紫色半透明覆盖层
    setfillcolor(RGB(30, 20, 60));
    solidrectangle(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
    
    // 绘制紫色网格线
    setlinecolor(RGB(70, 55, 110));
    for (int x = 0; x < SCREEN_WIDTH; x += 40) { // 每隔40,绘制竖线 
        line(x, 0, x, SCREEN_HEIGHT);
    }
    for (int y = 0; y < SCREEN_HEIGHT; y += 40) { // 每隔40,绘制横线 
        line(0, y, SCREEN_WIDTH, y);
    }
    
    // 游戏结束标题
    settextcolor(LIGHT_PURPLE);
    settextstyle(48, 0, "Arial");
    const char* title = "游戏结束";
    int titleWidth = textwidth(title);
    outtextxy((SCREEN_WIDTH - titleWidth) / 2, SCREEN_HEIGHT / 3, title);
    
    // 最终击败数量
    char finalScore[100];
    sprintf(finalScore, "最终击败数量: %d", targetsHit);
    
    settextcolor(LAVENDER);
    settextstyle(36, 0, "Arial");
    int scoreWidth = textwidth(finalScore);
    outtextxy((SCREEN_WIDTH - scoreWidth) / 2, SCREEN_HEIGHT / 2, finalScore);
    
    // 最终分数
    char totalScore[100];
    sprintf(totalScore, "最终得分: %d", score);
    outtextxy((SCREEN_WIDTH - scoreWidth) / 2, SCREEN_HEIGHT / 2 + 50, totalScore);
    
    // 重新开始提示
    settextcolor(MEDIUM_PURPLE);
    settextstyle(24, 0, "Arial");
    const char* restartMsg = "按R键重新开始,按ESC键退出";
    int msgWidth = textwidth(restartMsg);
    outtextxy((SCREEN_WIDTH - msgWidth) / 2, SCREEN_HEIGHT * 2 / 3, restartMsg);
    
    // 添加紫色装饰边框
    setlinecolor(PINK_PURPLE);
    rectangle(50, SCREEN_HEIGHT / 3 - 30, SCREEN_WIDTH - 50, SCREEN_HEIGHT * 2 / 3 + 80);
    rectangle(55, SCREEN_HEIGHT / 3 - 25, SCREEN_WIDTH - 55, SCREEN_HEIGHT * 2 / 3 + 75);
}

// 检查鼠标是否击中圆球
bool Game::checkBallHit(Ball& ball, int mouseX, int mouseY) {
    if (!ball.active) return false;
    
    // 计算鼠标与圆心的距离
    float dx = mouseX - ball.x;
    float dy = mouseY - ball.y;
    float distance = sqrt(dx * dx + dy * dy);
    
    // 如果距离小于半径,则击中
    if (distance <= ball.radius) {
        ball.active = false;
        
        // 击中特效:在击中位置绘制一个紫色光晕
        setfillcolor(RGB(
            min(255, GetRValue(ball.color) + 80),
            min(255, GetGValue(ball.color) + 50),
            min(255, GetBValue(ball.color) + 80)
        ));
        solidcircle(mouseX, mouseY, 25);
        
        return true;
    }
    
    return false;
}

// 处理用户输入
void Game::handleInput() {
    // 键盘输入
    if (kbhit()) {
        int key = getch();
        if (key == 27) { // ESC键退出
            exit(0);
        }
        if (!gameActive && (key == 'r' || key == 'R')) {
            reset();
        }
    }
    
    // 鼠标输入
    if (MouseHit()) {
        MOUSEMSG mmsg = GetMouseMsg();
        
        if (mmsg.uMsg == WM_LBUTTONDOWN && gameActive) {
            // 检查是否击中任何圆球
            for (int i = 0; i < MAX_BALLS; i++) {
                if (checkBallHit(balls[i], mmsg.x, mmsg.y)) {
                    targetsHit++;
                    // 大圆球更多分
                    score += 10 + (int)(balls[i].radius / 5);
                    
                    // 播放击中提示(视觉反馈)
                    settextcolor(PINK_PURPLE);
                    settextstyle(20, 0, "Arial");
                    char hitText[20];
                    sprintf(hitText, "+%d", 10 + (int)(balls[i].radius / 5));
                    outtextxy(mmsg.x + 20, mmsg.y - 20, hitText);
                }
            }
        }
    }
}

// 更新游戏状态
void Game::update() {
    // 更新游戏时间
    if (gameActive) {
        DWORD currentTime = GetTickCount();
        DWORD elapsedSeconds = (currentTime - startTime) / 1000;
        timeLeft = GAME_DURATION - elapsedSeconds;
        
        if (timeLeft <= 0) {
            timeLeft = 0;
            gameActive = false;
        }
    }
    
    // 更新圆球
    for (int i = 0; i < MAX_BALLS; i++) {
        if (gameActive) {
            updateBall(balls[i]);
            
            // 如果圆球不活跃且游戏进行中,重新初始化
            if (!balls[i].active && gameActive) {
                // 随机决定是否生成新圆球
                if (rand() % 50 == 0) { // 大约每50帧生成一个新圆球
                    initBall(balls[i]);
                }
            }
        }
    }
}

// 渲染游戏画面
void Game::render() {
    // 清除屏幕
    cleardevice();
    
    // 绘制紫色渐变背景(从深紫到中紫)
    for (int y = 0; y < SCREEN_HEIGHT; y++) {
        int r = 20 + (y * 15 / SCREEN_HEIGHT);
        int g = 15 + (y * 10 / SCREEN_HEIGHT);
        int b = 35 + (y * 20 / SCREEN_HEIGHT);
        setlinecolor(RGB(r, g, b));
        line(0, y, SCREEN_WIDTH, y);
    }
    
    // 绘制网格背景(紫色)
    setlinecolor(GRID_COLOR);
    for (int x = 0; x < SCREEN_WIDTH; x += 40) {
        line(x, 0, x, SCREEN_HEIGHT);
    }
    for (int y = 0; y < SCREEN_HEIGHT; y += 40) {
        line(0, y, SCREEN_WIDTH, y);
    }
    
    // 绘制圆球
    for (int i = 0; i < MAX_BALLS; i++) {
        drawBall(balls[i]);
    }
    
    // 绘制游戏UI
    drawGameUI();
    
    // 绘制MP7的机械文字 
    drawMP7Model();
    
    // 如果游戏结束,显示结束界面
    if (!gameActive) {
        drawGameOverScreen();
    }
}

// 运行游戏主循环
void Game::run() {
    while (true) {
        // 处理输入
        handleInput();
        
        // 更新游戏状态
        update();
        
        // 渲染游戏画面
        render();
        
        // 控制帧率
        DWORD currentTime = GetTickCount();
        DWORD frameTime = currentTime - lastFrameTime;
        if (frameTime < 16) { // 约60FPS
            Sleep(16 - frameTime);
        }
        lastFrameTime = GetTickCount();
        
        // 刷新显示
        FlushBatchDraw();
    }
}
3. main.cpp文件
cpp 复制代码
#include "game.h"

int main() {
    // 创建游戏对象
    Game game;
    // 运行游戏
    game.run();
    
    return 0;
}

三、运行结果

玩法介绍:在倒计时结束前,鼠标点击小球,击败次数+1,得分根据小球的大小而增加不同分值。

1. 游戏开始界面
2. 游戏结束界面(倒计时结束后)
相关推荐
ujainu2 小时前
Flutter + OpenHarmony 游戏开发进阶:主菜单架构与历史最高分持久化
flutter·游戏·架构·openharmony
云栖梦泽2 小时前
易语言开发从入门到精通:补充篇·网络编程进阶+实用爬虫开发·API集成·代理IP配置·异步请求·防封禁优化
开发语言
java1234_小锋3 小时前
Java高频面试题:SpringBoot为什么要禁止循环依赖?
java·开发语言·面试
铅笔侠_小龙虾3 小时前
Flutter Demo
开发语言·javascript·flutter
2501_944525543 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 账户详情页面
android·java·开发语言·前端·javascript·flutter
福大大架构师每日一题3 小时前
ComfyUI v0.11.1正式发布:新增开发者专属节点支持、API节点强化、Python 3.14兼容性更新等全方位优化!
开发语言·python
wangdaoyin20103 小时前
若依vue2前后端分离集成flowable
开发语言·前端·javascript
John_ToDebug3 小时前
浏览器内核崩溃深度分析:从 MiniDump 堆栈到 BindOnce UAF 机制(未完待续...)
c++·chrome·windows
向阳开的夏天4 小时前
麒麟V10源码编译QT5.6.3 (x86 & arm64)
开发语言·qt