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

(图片来源网络,侵删)
第一步:准备工作(安装 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 (使用包管理器):

(图片来源网络,侵删)
# 对于 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 游戏代码,代码中包含了详细的注释,解释了每一部分的功能。
我们将创建两个文件:
pingpong.c- 主游戏逻辑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
注意:

(图片来源网络,侵删)
- 这个
Makefile是为 Windows (vcpkg) 环境准备的,如果你在 Linux 或 macOS 上,LIB_PATH和LIBS需要相应调整。 - Linux/macOS 上的
LIBS可能是-lallegro -lallegro_primitives -lallegro_font -lallegro_ttf -lallegro_image -lm。 TARGET在 Linux/macOS 上通常是pingpong而不是pingpong.exe。
第四步:编译和运行
使用 Makefile (推荐)
-
准备字体文件: Allegro 的 TTF 字体加载器需要一个字体文件,你可以从网上下载一个免费的 TrueType 字体(
DejaVuSans.ttf),然后在你的项目目录下创建一个名为data的文件夹,并将字体文件放进去。pingpong_project/ ├── data/ │ └── DejaVuSans.ttf ├── pingpong.c └── Makefile -
编译: 在终端中,进入
pingpong_project目录,运行make命令。make
如果一切顺利,它会生成
pingpong.exe文件。 -
运行:
./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键向下。 - 右侧玩家: (上箭头) 向上, (下箭头) 向下。
- 关闭窗口可以退出游戏。
- 左侧玩家:
-
代码结构解析:
- 初始化: 首先调用
al_init(),然后初始化我们需要的 Allegro 模块(图形、字体、图像等)。 - 核心对象: 使用
GameObject结构体来封装挡板和球的所有属性(位置、大小、速度),这使得代码更整洁。 - 事件队列: 这是 Allegro 游戏编程的核心,我们创建一个事件队列,并将显示设备和定时器的事件源注册到其中。
al_wait_for_event会阻塞程序,直到有事件发生(比如按键、定时器“滴答”一声、窗口被关闭)。 - 游戏循环:
- 逻辑更新: 当
ALLEGRO_EVENT_TIMER事件发生时,意味着我们到了更新游戏状态的时候(每秒60次),我们根据按键状态移动挡板,更新球的位置,并检测碰撞。 - 碰撞检测:
check_collision函数使用了非常经典的 AABB(Axis-Aligned Bounding Box,轴对齐包围盒)碰撞检测算法,简单高效。 - 渲染: 当
redraw标志为true且事件队列为空时,进行渲染,这确保了渲染只在需要时发生,并且不会在处理事件时被中断,避免了画面撕裂。al_clear_to_color清屏,al_draw_...系列函数负责绘制图形和文字,al_flip_display将缓冲区的内容显示到屏幕上。
- 逻辑更新: 当
- 资源管理: 游戏结束时,使用
al_destroy_...系列函数释放所有分配的资源,这是一个好习惯。
- 初始化: 首先调用
这个项目为你提供了一个坚实的基础,你可以在此基础上进行扩展,
- 添加音效。
- 实现难度递增(球速变快)。
- 添加菜单界面。
- 让 AI 控制一个挡板。
- 使用图片代替简单的矩形来绘制游戏元素。
