C语言如何实现pingpong游戏?

99ANYc3cd6
预计阅读时长 44 分钟
位置: 首页 C语言 正文

我们将使用 Allegro 5 这个跨平台的 C 游戏开发库,它比 SDL 更现代,API 也相对简洁,非常适合这个项目。

c语言pingpong
(图片来源网络,侵删)

第一步:准备工作(安装 Allegro 5)

在开始写代码之前,你需要安装 Allegro 5 库。

Windows (使用 vcpkg): 这是在 Windows 上最推荐的方式。

# 克隆 vcpkg
git clone https://github.com/microsoft/vcpkg.git
cd vcpkg
# 编译 vcpkg
.\bootstrap-vcpkg.bat
# 安装 allegro
.\vcpkg install allegro5
# 设置环境变量 (非常重要!)
# 打开一个新的命令提示符或 PowerShell,或者运行:
.\vcpkg integrate install

macOS (使用 Homebrew):

brew install allegro

Linux (使用包管理器):

c语言pingpong
(图片来源网络,侵删)
# 对于 Debian/Ubuntu
sudo apt-get update
sudo apt-get install liballegro5-dev liballegro-image5-dev liballegro-primitives5-dev liballegro-font5-dev liballegro-ttf5-dev
# 对于 Fedora
sudo dnf install allegro5-devel allegro5-image-devel allegro5-primitives-devel allegro5-font-devel allegro5-ttf-devel

第二步:编写 C 代码

下面是完整的 Ping Pong 游戏代码,代码中包含了详细的注释,解释了每一部分的功能。

我们将创建两个文件:

  1. pingpong.c - 主游戏逻辑
  2. Makefile - 用于编译项目(在 Linux/macOS 上推荐,Windows 也可以用 MinGW 或 MSBuild)

pingpong.c

#include <allegro5/allegro.h>
#include <allegro5/allegro_primitives.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include <allegro5/allegro_image.h>
#include <stdio.h>
#include <stdbool.h>
// 定义常量
const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;
const int PADDLE_WIDTH = 20;
const int PADDLE_HEIGHT = 100;
const int BALL_SIZE = 20;
const int PADDLE_SPEED = 7;
const int INITIAL_BALL_SPEED = 5;
// 定义方向枚举
typedef enum {
    UP,
    DOWN,
    LEFT,
    RIGHT
} Direction;
// 游戏对象结构体
typedef struct {
    float x, y;
    float dx, dy; // 速度
    int width, height;
} GameObject;
// 初始化游戏对象
void init_game_object(GameObject* obj, float x, float y, int width, int height, float dx, float dy) {
    obj->x = x;
    obj->y = y;
    obj->dx = dx;
    obj->dy = dy;
    obj->width = width;
    obj->height = height;
}
// 更新游戏对象位置
void update_game_object(GameObject* obj) {
    obj->x += obj->dx;
    obj->y += obj->dy;
}
// 检测碰撞
bool check_collision(const GameObject* a, const GameObject* b) {
    return a->x < b->x + b->width &&
           a->x + a->width > b->x &&
           a->y < b->y + b->height &&
           a->y + a->height > b->y;
}
int main(int argc, char** argv) {
    // 1. 初始化 Allegro
    if (!al_init()) {
        fprintf(stderr, "Failed to initialize Allegro!\n");
        return -1;
    }
    // 2. 初始化 Allegro 的模块
    al_init_primitives_addon();
    al_init_font_addon();
    al_init_ttf_addon();
    al_init_image_addon();
    // 3. 创建显示设备
    ALLEGRO_DISPLAY* display = al_create_display(SCREEN_WIDTH, SCREEN_HEIGHT);
    if (!display) {
        fprintf(stderr, "Failed to create display!\n");
        return -1;
    }
    al_set_window_title(display, "C Ping Pong");
    // 4. 创建定时器
    ALLEGRO_TIMER* timer = al_create_timer(1.0 / 60.0); // 60 FPS
    if (!timer) {
        fprintf(stderr, "Failed to create timer!\n");
        return -1;
    }
    // 5. 创建事件队列
    ALLEGRO_EVENT_QUEUE* event_queue = al_create_event_queue();
    if (!event_queue) {
        fprintf(stderr, "Failed to create event queue!\n");
        return -1;
    }
    // 6. 注册事件源
    al_register_event_source(event_queue, al_get_display_event_source(display));
    al_register_event_source(event_queue, al_get_timer_event_source(timer));
    // 7. 加载资源
    ALLEGRO_FONT* font = al_load_ttf_font("data/DejaVuSans.ttf", 36, 0);
    if (!font) {
        // 如果找不到字体,尝试使用默认字体
        font = al_load_font("data/arial.ttf", 36, 0);
        if (!font) {
            fprintf(stderr, "Could not load font. Make sure 'data/DejaVuSans.ttf' exists.\n");
            return -1;
        }
    }
    // 8. 初始化游戏对象
    GameObject left_paddle, right_paddle, ball;
    init_game_object(&left_paddle, 30, SCREEN_HEIGHT / 2 - PADDLE_HEIGHT / 2, PADDLE_WIDTH, PADDLE_HEIGHT, 0, 0);
    init_game_object(&right_paddle, SCREEN_WIDTH - 30 - PADDLE_WIDTH, SCREEN_HEIGHT / 2 - PADDLE_HEIGHT / 2, PADDLE_WIDTH, PADDLE_HEIGHT, 0, 0);
    init_game_object(&ball, SCREEN_WIDTH / 2 - BALL_SIZE / 2, SCREEN_HEIGHT / 2 - BALL_SIZE / 2, BALL_SIZE, BALL_SIZE, INITIAL_BALL_SPEED, INITIAL_BALL_SPEED);
    // 游戏状态变量
    bool keys[ALLEGRO_KEY_MAX] = { false };
    bool redraw = true;
    bool game_over = false;
    int left_score = 0;
    int right_score = 0;
    // 9. 游戏主循环
    al_start_timer(timer);
    while (!game_over) {
        ALLEGRO_EVENT event;
        al_wait_for_event(event_queue, &event);
        // 处理事件
        if (event.type == ALLEGRO_EVENT_TIMER) {
            // 60 FPS 的游戏逻辑更新
            redraw = true;
            // --- 左侧挡板控制 (W/S) ---
            if (keys[ALLEGRO_KEY_W] && left_paddle.y > 0) {
                left_paddle.y -= PADDLE_SPEED;
            }
            if (keys[ALLEGRO_KEY_S] && left_paddle.y < SCREEN_HEIGHT - left_paddle.height) {
                left_paddle.y += PADDLE_SPEED;
            }
            // --- 右侧挡板控制 (上/下箭头) ---
            if (keys[ALLEGRO_KEY_UP] && right_paddle.y > 0) {
                right_paddle.y -= PADDLE_SPEED;
            }
            if (keys[ALLEGRO_KEY_DOWN] && right_paddle.y < SCREEN_HEIGHT - right_paddle.height) {
                right_paddle.y += PADDLE_SPEED;
            }
            // --- 球的移动和碰撞检测 ---
            update_game_object(&ball);
            // 球与上下边界碰撞
            if (ball.y <= 0 || ball.y + ball.height >= SCREEN_HEIGHT) {
                ball.dy = -ball.dy;
            }
            // 球与左挡板碰撞
            if (check_collision(&ball, &left_paddle)) {
                ball.dx = -ball.dx;
                // 可以根据击球位置调整Y方向速度,增加游戏性
                // 简单示例:根据击中挡板的位置改变dy
                float relative_intersect_y = (left_paddle.y + (left_paddle.height / 2)) - (ball.y + (BALL_SIZE / 2));
                float normalized_intersect_y = relative_intersect_y / (left_paddle.height / 2);
                ball.dy = -normalized_intersect_y * 5; // 调整这个系数可以改变反弹角度
            }
            // 球与右挡板碰撞
            if (check_collision(&ball, &right_paddle)) {
                ball.dx = -ball.dx;
                float relative_intersect_y = (right_paddle.y + (right_paddle.height / 2)) - (ball.y + (BALL_SIZE / 2));
                float normalized_intersect_y = relative_intersect_y / (right_paddle.height / 2);
                ball.dy = -normalized_intersect_y * 5;
            }
            // 球出界,得分
            if (ball.x < 0) {
                right_score++;
                init_game_object(&ball, SCREEN_WIDTH / 2 - BALL_SIZE / 2, SCREEN_HEIGHT / 2 - BALL_SIZE / 2, BALL_SIZE, BALL_SIZE, INITIAL_BALL_SPEED, INITIAL_BALL_SPEED);
            }
            if (ball.x > SCREEN_WIDTH) {
                left_score++;
                init_game_object(&ball, SCREEN_WIDTH / 2 - BALL_SIZE / 2, SCREEN_HEIGHT / 2 - BALL_SIZE / 2, BALL_SIZE, BALL_SIZE, -INITIAL_BALL_SPEED, INITIAL_BALL_SPEED);
            }
            // 检查游戏结束 (例如先得到5分)
            if (left_score >= 5 || right_score >= 5) {
                game_over = true;
            }
        } else if (event.type == ALLEGRO_EVENT_KEY_DOWN) {
            keys[event.keyboard.keycode] = true;
        } else if (event.type == ALLEGRO_EVENT_KEY_UP) {
            keys[event.keyboard.keycode] = false;
        } else if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
            break;
        }
        // 渲染
        if (redraw && al_is_event_queue_empty(event_queue)) {
            redraw = false;
            al_clear_to_color(al_map_rgb(0, 0, 0)); // 清屏为黑色
            // 绘制中线
            al_draw_line(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT, al_map_rgb(255, 255, 255), 2);
            // 绘制挡板
            al_draw_filled_rectangle(left_paddle.x, left_paddle.y, left_paddle.x + left_paddle.width, left_paddle.y + left_paddle.height, al_map_rgb(255, 255, 255));
            al_draw_filled_rectangle(right_paddle.x, right_paddle.y, right_paddle.x + right_paddle.width, right_paddle.y + right_paddle.height, al_map_rgb(255, 255, 255));
            // 绘制球
            al_draw_filled_rectangle(ball.x, ball.y, ball.x + ball.width, ball.y + ball.height, al_map_rgb(255, 255, 255));
            // 绘制分数
            al_draw_textf(font, al_map_rgb(255, 255, 255), SCREEN_WIDTH / 4, 50, 0, "%d", left_score);
            al_draw_textf(font, al_map_rgb(255, 255, 255), 3 * SCREEN_WIDTH / 4, 50, ALLEGRO_ALIGN_CENTER, "%d", right_score);
            // 游戏结束提示
            if (game_over) {
                const char* winner = left_score >= 5 ? "Left Player Wins!" : "Right Player Wins!";
                al_draw_text(font, al_map_rgb(255, 255, 0), SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, ALLEGRO_ALIGN_CENTER, winner);
                al_draw_text(font, al_map_rgb(255, 255, 255), SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2 + 50, ALLEGRO_ALIGN_CENTER, "Press ESC to exit.");
            }
            al_flip_display(); // 更新显示
        }
    }
    // 10. 清理资源
    al_destroy_font(font);
    al_destroy_event_queue(event_queue);
    al_destroy_timer(timer);
    al_destroy_display(display);
    return 0;
}

第三步:创建 Makefile(用于编译)

pingpong.c 同级目录下,创建一个名为 Makefile 的文件,内容如下:

# 编译器
CC = gcc
# 编译选项
CFLAGS = -O2 -Wall
# Allegro 5 的头文件和库路径
# 如果你使用了 vcpkg,这些路径通常是正确的
# 如果不是,你需要手动修改它们
INC = -I/vcpkg/installed/x64-windows/include
LIB_PATH = -L/vcpkg/installed/x64-windows/lib
LIBS = -lallegro -lallegro_primitives -lallegro_font -lallegro_ttf -lallegro_image -lm -ldinput8 -ldxguid -ldxerr8 -d3dx9d -lws2_32 -lwinmm -lstdc++ -lgdi32
# 目标文件
TARGET = pingpong.exe
# 源文件
SRC = pingpong.c
# 默认目标
all: $(TARGET)
# 链接规则
$(TARGET): $(SRC)
    $(CC) $(CFLAGS) $(INC) -o $@ $< $(LIB_PATH) $(LIBS)
# 清理生成的文件
clean:
    rm -f $(TARGET)
.PHONY: all clean

注意:

c语言pingpong
(图片来源网络,侵删)
  • 这个 Makefile 是为 Windows (vcpkg) 环境准备的,如果你在 Linux 或 macOS 上,LIB_PATHLIBS 需要相应调整。
  • Linux/macOS 上的 LIBS 可能是 -lallegro -lallegro_primitives -lallegro_font -lallegro_ttf -lallegro_image -lm
  • TARGET 在 Linux/macOS 上通常是 pingpong 而不是 pingpong.exe

第四步:编译和运行

使用 Makefile (推荐)

  1. 准备字体文件: Allegro 的 TTF 字体加载器需要一个字体文件,你可以从网上下载一个免费的 TrueType 字体(DejaVuSans.ttf),然后在你的项目目录下创建一个名为 data 的文件夹,并将字体文件放进去。

    pingpong_project/
    ├── data/
    │   └── DejaVuSans.ttf
    ├── pingpong.c
    └── Makefile
  2. 编译: 在终端中,进入 pingpong_project 目录,运行 make 命令。

    make

    如果一切顺利,它会生成 pingpong.exe 文件。

  3. 运行:

    ./pingpong.exe

手动编译 (GCC)

如果你不想用 Makefile,可以直接用 GCC 命令编译,确保你已经安装了 Allegro 5 并且环境变量配置正确。

# Linux/macOS 示例
gcc pingpong.c -o pingpong `allegro-config --libs --cflags` -lm
# Windows (MinGW) 示例 (路径需要根据你的vcpkg安装位置修改)
gcc pingpong.c -o pingpong.exe -I/vcpkg/installed/x64-windows/include -L/vcpkg/installed/x64-windows/lib -lallegro -lallegro_primitives -lallegro_font -lallegro_ttf -lallegro_image -lm -ldinput8 -ldxguid -ldxerr8 -d3dx9d -lws2_32 -lwinmm -lstdc++ -lgdi32

游戏玩法和代码解析

  • 控制:

    • 左侧玩家: W 键向上,S 键向下。
    • 右侧玩家: (上箭头) 向上, (下箭头) 向下。
    • 关闭窗口可以退出游戏。
  • 代码结构解析:

    1. 初始化: 首先调用 al_init(),然后初始化我们需要的 Allegro 模块(图形、字体、图像等)。
    2. 核心对象: 使用 GameObject 结构体来封装挡板和球的所有属性(位置、大小、速度),这使得代码更整洁。
    3. 事件队列: 这是 Allegro 游戏编程的核心,我们创建一个事件队列,并将显示设备和定时器的事件源注册到其中。al_wait_for_event 会阻塞程序,直到有事件发生(比如按键、定时器“滴答”一声、窗口被关闭)。
    4. 游戏循环:
      • 逻辑更新: 当 ALLEGRO_EVENT_TIMER 事件发生时,意味着我们到了更新游戏状态的时候(每秒60次),我们根据按键状态移动挡板,更新球的位置,并检测碰撞。
      • 碰撞检测: check_collision 函数使用了非常经典的 AABB(Axis-Aligned Bounding Box,轴对齐包围盒)碰撞检测算法,简单高效。
      • 渲染: 当 redraw 标志为 true 且事件队列为空时,进行渲染,这确保了渲染只在需要时发生,并且不会在处理事件时被中断,避免了画面撕裂。al_clear_to_color 清屏,al_draw_... 系列函数负责绘制图形和文字,al_flip_display 将缓冲区的内容显示到屏幕上。
    5. 资源管理: 游戏结束时,使用 al_destroy_... 系列函数释放所有分配的资源,这是一个好习惯。

这个项目为你提供了一个坚实的基础,你可以在此基础上进行扩展,

  • 添加音效。
  • 实现难度递增(球速变快)。
  • 添加菜单界面。
  • 让 AI 控制一个挡板。
  • 使用图片代替简单的矩形来绘制游戏元素。
-- 展开阅读全文 --
头像
dede系统环境搭建如何快速配置成功?
« 上一篇 04-13
指针究竟指向何方?
下一篇 » 04-13

相关文章

取消
微信二维码
支付宝二维码

目录[+]