一、项目概述
贪吃蛇是一款经典的益智游戏,也是学习C语言编程的绝佳实践项目。本项目将详细讲解如何使用C语言从零开始实现一个功能完整的贪吃蛇游戏,包含音效系统、彩色界面、游戏逻辑控制,并最终封装成可执行应用程序。
1.1 项目特色
- 完整游戏功能:支持蛇的移动、转向、吃食物、碰撞检测等核心功能
- 彩色界面:使用Windows API实现多彩的游戏界面
- 音效系统:包含背景音乐和事件音效
- 可执行封装:最终编译成exe文件,可在任意Windows电脑运行
- 模块化设计:代码结构清晰,便于理解和扩展
1.2 开发环境
- 编译器:GCC/MinGW或Visual Studio
- 操作系统:Windows 10及以上
- 依赖库:Windows API(系统自带)
二、核心数据结构设计
2.1 坐标点结构体
// 坐标点结构体
typedef struct {
int x;
int y;
} Point;
2.2 方向枚举
// 方向枚举
typedef enum {
UP = 1,
DOWN,
LEFT,
RIGHT
} Direction;
2.3 游戏状态结构体
// 游戏状态结构体
typedef struct {
Point snake[100]; // 蛇身数组,最大长度100
int snake_len; // 当前蛇身长度
Point food; // 食物坐标
Direction dir; // 当前移动方向
int score; // 游戏分数
int game_over; // 游戏结束标志
int speed; // 游戏速度
} GameState;
三、游戏界面与颜色设置
3.1 控制台颜色控制
方法一:使用system命令(简单但效率低)
#include <stdlib.h>
// 设置控制台颜色(背景色+前景色)
void setColor(int bg, int fg) {
char cmd[20];
sprintf(cmd, "color %X%X", bg, fg);
system(cmd);
}
// 颜色代码表
// 0=黑色 1=蓝色 2=绿色 3=湖蓝色 4=红色 5=紫色 6=黄色 7=白色
// 8=灰色 9=淡蓝色 A=淡绿色 B=淡浅绿色 C=淡红色 D=淡紫色 E=淡黄色 F=亮白色
方法二:使用Windows API(推荐,效率高)
#include <windows.h>
// 设置控制台文本属性
void setConsoleColor(WORD color) {
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hConsole, color);
}
// 颜色组合示例
// 红色文字:FOREGROUND_RED | FOREGROUND_INTENSITY
// 绿色文字:FOREGROUND_GREEN | FOREGROUND_INTENSITY
// 黄色文字:FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY
// 白色文字:FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY
// 红色背景:BACKGROUND_RED
// 绿色背景:BACKGROUND_GREEN
3.2 光标控制函数
// 设置光标位置
void setCursorPosition(int x, int y) {
COORD pos;
pos.X = x;
pos.Y = y;
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(hConsole, pos);
}
// 隐藏光标
void hideCursor() {
CONSOLE_CURSOR_INFO cursorInfo;
cursorInfo.dwSize = 1;
cursorInfo.bVisible = FALSE;
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorInfo(hConsole, &cursorInfo);
}
3.3 游戏界面绘制
// 绘制游戏界面
void drawGame(GameState* game) {
system("cls"); // 清屏
// 绘制顶部信息栏
setConsoleColor(FOREGROUND_GREEN | FOREGROUND_INTENSITY);
printf("=== 贪吃蛇大作战 ===\n");
printf("分数: %d | 长度: %d\n", game->score, game->snake_len);
printf("使用WASD控制方向,按Q退出游戏\n\n");
// 绘制游戏区域
for (int y = 0; y < HEIGHT; y++) {
for (int x = 0; x < WIDTH; x++) {
// 绘制食物
if (x == game->food.x && y == game->food.y) {
setConsoleColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
printf("◆");
}
// 绘制蛇身
else {
int is_snake = 0;
for (int i = 0; i < game->snake_len; i++) {
if (game->snake[i].x == x && game->snake[i].y == y) {
if (i == 0) {
setConsoleColor(FOREGROUND_GREEN | FOREGROUND_INTENSITY);
printf("●"); // 蛇头
} else {
setConsoleColor(FOREGROUND_GREEN);
printf("○"); // 蛇身
}
is_snake = 1;
break;
}
}
// 绘制空地
if (!is_snake) {
setConsoleColor(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);
printf("·");
}
}
}
printf("\n");
}
// 恢复默认颜色
setConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
}
四、音效系统实现
4.1 Windows音效函数
#include <windows.h>
// 播放简单音效(蜂鸣器)
void playBeep(int frequency, int duration) {
Beep(frequency, duration);
}
// 播放吃食物音效
void playEatSound() {
Beep(523, 100); // C5音,持续100ms
}
// 播放游戏结束音效
void playGameOverSound() {
Beep(392, 200); // G4
Beep(349, 200); // F4
Beep(330, 400); // E4
}
// 播放碰撞音效
void playCollisionSound() {
Beep(262, 150); // C4
}
4.2 使用mciSendString播放音频文件
#include <windows.h>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")
// 播放背景音乐
void playBackgroundMusic() {
mciSendString("open background.mp3 alias bgm", NULL, 0, NULL);
mciSendString("play bgm repeat", NULL, 0, NULL);
}
// 播放音效
void playSoundEffect(const char* filename) {
char cmd[256];
sprintf(cmd, "open %s alias sound", filename);
mciSendString(cmd, NULL, 0, NULL);
mciSendString("play sound", NULL, 0, NULL);
}
// 停止背景音乐
void stopBackgroundMusic() {
mciSendString("stop bgm", NULL, 0, NULL);
mciSendString("close bgm", NULL, 0, NULL);
}
五、游戏核心逻辑实现
5.1 游戏初始化
// 游戏初始化
void initGame(GameState* game) {
// 初始化蛇身(水平排列3个节点)
game->snake[0].x = 10; game->snake[0].y = 10; // 蛇头
game->snake[1].x = 9; game->snake[1].y = 10; // 蛇身
game->snake[2].x = 8; game->snake[2].y = 10; // 蛇尾
game->snake_len = 3;
// 初始方向向右
game->dir = RIGHT;
// 初始分数
game->score = 0;
// 游戏未结束
game->game_over = 0;
// 游戏速度(毫秒)
game->speed = 200;
// 生成食物
generateFood(game);
// 隐藏光标
hideCursor();
// 设置控制台大小
system("mode con cols=60 lines=32");
// 设置控制台标题
system("title 贪吃蛇游戏");
}
5.2 食物生成
// 生成食物
void generateFood(GameState* game) {
srand(time(NULL));
do {
game->food.x = rand() % WIDTH;
game->food.y = rand() % HEIGHT;
} while (checkFoodOnSnake(game)); // 确保食物不在蛇身上
}
// 检查食物是否在蛇身上
int checkFoodOnSnake(GameState* game) {
for (int i = 0; i < game->snake_len; i++) {
if (game->snake[i].x == game->food.x && game->snake[i].y == game->food.y) {
return 1;
}
}
return 0;
}
5.3 蛇的移动算法(核心)
// 移动蛇
void moveSnake(GameState* game) {
// 保存蛇尾位置(用于吃到食物时增长)
Point tail = game->snake[game->snake_len - 1];
// 蛇身向前移动(从尾部向头部)
for (int i = game->snake_len - 1; i > 0; i--) {
game->snake[i] = game->snake[i - 1];
}
// 根据方向移动蛇头
switch (game->dir) {
case UP: game->snake[0].y--; break;
case DOWN: game->snake[0].y++; break;
case LEFT: game->snake[0].x--; break;
case RIGHT: game->snake[0].x++; break;
}
// 检查是否吃到食物
if (game->snake[0].x == game->food.x && game->snake[0].y == game->food.y) {
// 吃到食物,蛇身增长
game->snake[game->snake_len] = tail;
game->snake_len++;
// 增加分数
game->score += 10;
// 播放吃食物音效
playEatSound();
// 生成新食物
generateFood(game);
}
}
5.4 碰撞检测
// 碰撞检测
int checkCollision(GameState* game) {
Point head = game->snake[0];
// 检查边界碰撞
if (head.x < 0 || head.x >= WIDTH ||
head.y < 0 || head.y >= HEIGHT) {
return 1;
}
// 检查自身碰撞(从第2节开始检查,避免与头部比较)
for (int i = 1; i < game->snake_len; i++) {
if (head.x == game->snake[i].x && head.y == game->snake[i].y) {
return 1;
}
}
return 0;
}
5.5 键盘输入处理
// 处理键盘输入
void handleInput(GameState* game) {
if (_kbhit()) { // 检查是否有按键按下
char key = _getch();
switch (key) {
case 'w': case 'W':
if (game->dir != DOWN) game->dir = UP;
break;
case 's': case 'S':
if (game->dir != UP) game->dir = DOWN;
break;
case 'a': case 'A':
if (game->dir != RIGHT) game->dir = LEFT;
break;
case 'd': case 'D':
if (game->dir != LEFT) game->dir = RIGHT;
break;
case 'q': case 'Q':
game->game_over = 1;
break;
case ' ': // 空格键暂停
while (_getch() != ' ') {} // 等待再次按下空格
break;
}
}
}
六、游戏主循环
// 游戏主循环
void gameLoop(GameState* game) {
// 播放背景音乐
playBackgroundMusic();
while (!game->game_over) {
// 绘制游戏界面
drawGame(game);
// 处理键盘输入
handleInput(game);
// 移动蛇
moveSnake(game);
// 检查碰撞
if (checkCollision(game)) {
game->game_over = 1;
playGameOverSound();
break;
}
// 控制游戏速度
Sleep(game->speed);
}
// 游戏结束处理
gameOver(game);
}
6.1 游戏结束处理
// 游戏结束
void gameOver(GameState* game) {
system("cls");
setConsoleColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
printf("============================\n");
printf(" 游戏结束!\n");
printf(" 最终分数: %d\n", game->score);
printf(" 蛇身长度: %d\n", game->snake_len);
printf("============================\n\n");
setConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
printf("按任意键退出游戏...");
_getch();
// 停止背景音乐
stopBackgroundMusic();
}
七、完整代码整合
7.1 头文件(snake.h)
#ifndef SNAKE_H
#define SNAKE_H
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <conio.h>
#include <time.h>
// 游戏区域大小
#define WIDTH 20
#define HEIGHT 15
// 坐标点结构体
typedef struct {
int x;
int y;
} Point;
// 方向枚举
typedef enum {
UP = 1,
DOWN,
LEFT,
RIGHT
} Direction;
// 游戏状态结构体
typedef struct {
Point snake[100]; // 蛇身数组
int snake_len; // 当前蛇身长度
Point food; // 食物坐标
Direction dir; // 当前移动方向
int score; // 游戏分数
int game_over; // 游戏结束标志
int speed; // 游戏速度
} GameState;
// 函数声明
void initGame(GameState* game);
void drawGame(GameState* game);
void moveSnake(GameState* game);
int checkCollision(GameState* game);
void generateFood(GameState* game);
void handleInput(GameState* game);
void gameLoop(GameState* game);
void gameOver(GameState* game);
void setConsoleColor(WORD color);
void hideCursor();
void playEatSound();
void playGameOverSound();
void playBackgroundMusic();
void stopBackgroundMusic();
#endif
7.2 主程序文件(main.c)
#include "snake.h"
int main() {
// 设置随机数种子
srand(time(NULL));
// 创建游戏状态
GameState game;
// 初始化游戏
initGame(&game);
// 开始游戏循环
gameLoop(&game);
return 0;
}
7.3 游戏逻辑实现(game.c)
#include "snake.h"
// 游戏初始化
void initGame(GameState* game) {
// 初始化蛇身
game->snake[0].x = 10; game->snake[0].y = 10;
game->snake[1].x = 9; game->snake[1].y = 10;
game->snake[2].x = 8; game->snake[2].y = 10;
game->snake_len = 3;
game->dir = RIGHT;
game->score = 0;
game->game_over = 0;
game->speed = 200;
generateFood(game);
hideCursor();
system("mode con cols=60 lines=32");
system("title 贪吃蛇游戏");
}
// 生成食物
void generateFood(GameState* game) {
do {
game->food.x = rand() % WIDTH;
game->food.y = rand() % HEIGHT;
} while (checkFoodOnSnake(game));
}
// 检查食物是否在蛇身上
int checkFoodOnSnake(GameState* game) {
for (int i = 0; i < game->snake_len; i++) {
if (game->snake[i].x == game->food.x && game->snake[i].y == game->food.y) {
return 1;
}
}
return 0;
}
// 移动蛇
void moveSnake(GameState* game) {
Point tail = game->snake[game->snake_len - 1];
for (int i = game->snake_len - 1; i > 0; i--) {
game->snake[i] = game->snake[i - 1];
}
switch (game->dir) {
case UP: game->snake[0].y--; break;
case DOWN: game->snake[0].y++; break;
case LEFT: game->snake[0].x--; break;
case RIGHT: game->snake[0].x++; break;
}
if (game->snake[0].x == game->food.x && game->snake[0].y == game->food.y) {
game->snake[game->snake_len] = tail;
game->snake_len++;
game->score += 10;
playEatSound();
generateFood(game);
}
}
// 碰撞检测
int checkCollision(GameState* game) {
Point head = game->snake[0];
if (head.x < 0 || head.x >= WIDTH ||
head.y < 0 || head.y >= HEIGHT) {
return 1;
}
for (int i = 1; i < game->snake_len; i++) {
if (head.x == game->snake[i].x && head.y == game->snake[i].y) {
return 1;
}
}
return 0;
}
// 处理键盘输入
void handleInput(GameState* game) {
if (_kbhit()) {
char key = _getch();
switch (key) {
case 'w': case 'W':
if (game->dir != DOWN) game->dir = UP;
break;
case 's': case 'S':
if (game->dir != UP) game->dir = DOWN;
break;
case 'a': case 'A':
if (game->dir != RIGHT) game->dir = LEFT;
break;
case 'd': case 'D':
if (game->dir != LEFT) game->dir = RIGHT;
break;
case 'q': case 'Q':
game->game_over = 1;
break;
case ' ':
while (_getch() != ' ') {}
break;
}
}
}
// 游戏主循环
void gameLoop(GameState* game) {
playBackgroundMusic();
while (!game->game_over) {
drawGame(game);
handleInput(game);
moveSnake(game);
if (checkCollision(game)) {
game->game_over = 1;
playGameOverSound();
break;
}
Sleep(game->speed);
}
gameOver(game);
}
// 游戏结束
void gameOver(GameState* game) {
system("cls");
setConsoleColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
printf("============================\n");
printf(" 游戏结束!\n");
printf(" 最终分数: %d\n", game->score);
printf(" 蛇身长度: %d\n", game->snake_len);
printf("============================\n\n");
setConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
printf("按任意键退出游戏...");
_getch();
stopBackgroundMusic();
}
7.4 界面与音效实现(ui.c)
#include "snake.h"
// 设置控制台文本属性
void setConsoleColor(WORD color) {
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hConsole, color);
}
// 隐藏光标
void hideCursor() {
CONSOLE_CURSOR_INFO cursorInfo;
cursorInfo.dwSize = 1;
cursorInfo.bVisible = FALSE;
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorInfo(hConsole, &cursorInfo);
}
// 绘制游戏界面
void drawGame(GameState* game) {
system("cls");
// 顶部信息栏
setConsoleColor(FOREGROUND_GREEN | FOREGROUND_INTENSITY);
printf("=== 贪吃蛇大作战 ===\n");
printf("分数: %d | 长度: %d\n", game->score, game->snake_len);
printf("使用WASD控制方向,按Q退出游戏\n\n");
// 游戏区域
for (int y = 0; y < HEIGHT; y++) {
for (int x = 0; x < WIDTH; x++) {
if (x == game->food.x && y == game->food.y) {
setConsoleColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
printf("◆");
} else {
int is_snake = 0;
for (int i = 0; i < game->snake_len; i++) {
if (game->snake[i].x == x && game->snake[i].y == y) {
if (i == 0) {
setConsoleColor(FOREGROUND_GREEN | FOREGROUND_INTENSITY);
printf("●");
} else {
setConsoleColor(FOREGROUND_GREEN);
printf("○");
}
is_snake = 1;
break;
}
}
if (!is_snake) {
setConsoleColor(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);
printf("·");
}
}
}
printf("\n");
}
setConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
}
// 播放吃食物音效
void playEatSound() {
Beep(523, 100);
}
// 播放游戏结束音效
void playGameOverSound() {
Beep(392, 200);
Beep(349, 200);
Beep(330, 400);
}
// 播放背景音乐
void playBackgroundMusic() {
// 使用mciSendString播放MP3文件
// 需要准备background.mp3文件
mciSendString("open background.mp3 alias bgm", NULL, 0, NULL);
mciSendString("play bgm repeat", NULL, 0, NULL);
}
// 停止背景音乐
void stopBackgroundMusic() {
mciSendString("stop bgm", NULL, 0, NULL);
mciSendString("close bgm", NULL, 0, NULL);
}
八、编译与封装
8.1 使用GCC编译
# 编译所有源文件
gcc main.c game.c ui.c -o snake.exe -lwinmm
# 参数说明:
# -o snake.exe:指定输出文件名
# -lwinmm:链接Windows多媒体库(用于音效)
8.2 使用Visual Studio编译
- 创建新的C++控制台项目
- 将上述4个文件添加到项目中
- 在项目属性中,添加链接库
winmm.lib - 编译运行
8.3 制作安装包
使用Inno Setup等工具创建安装程序:
- 下载并安装Inno Setup
- 创建新的脚本文件
- 添加snake.exe和background.mp3到安装包
- 编译生成setup.exe
九、功能扩展建议
9.1 难度分级
// 根据分数调整游戏速度
void adjustDifficulty(GameState* game) {
if (game->score >= 100 && game->score < 200) {
game->speed = 150; // 中等难度
} else if (game->score >= 200) {
game->speed = 100; // 困难难度
}
}
9.2 高分记录
// 保存最高分到文件
void saveHighScore(int score) {
FILE* file = fopen("highscore.txt", "w");
if (file) {
fprintf(file, "%d", score);
fclose(file);
}
}
// 读取最高分
int loadHighScore() {
FILE* file = fopen("highscore.txt", "r");
int highscore = 0;
if (file) {
fscanf(file, "%d", &highscore);
fclose(file);
}
return highscore;
}
9.3 游戏暂停功能
// 在handleInput函数中添加暂停功能
case 'p': case 'P':
setConsoleColor(FOREGROUND_YELLOW);
printf("\n游戏已暂停,按P继续...");
while (_getch() != 'p' && _getch() != 'P') {}
system("cls");
break;
十、常见问题与解决方案
10.1 编译错误
问题:undefined reference to __imp_Beep等链接错误
解决:添加链接选项-lwinmm
问题:无法找到conio.h
解决:使用Windows系统,或使用MinGW编译器
10.2 运行问题
问题:控制台闪烁严重
解决:使用双缓冲技术,或减少system(“cls”)调用频率
问题:音效无法播放
解决:确保系统支持Beep函数,或使用mciSendString播放MP3文件
10.3 性能优化
- 减少system(“cls”)调用,使用局部刷新
- 使用双缓冲技术避免闪烁
- 优化碰撞检测算法
十一、总结
通过本项目的学习,您已经掌握了:
- C语言游戏开发的基本框架
- Windows API控制台编程技巧
- 音效系统的实现方法
- 游戏逻辑的核心算法
- 项目编译与封装技术
贪吃蛇游戏虽然简单,但涵盖了游戏开发的核心要素:状态管理、输入处理、碰撞检测、界面渲染等。希望这个项目能为您进一步学习游戏开发打下坚实的基础。
注意:在实际开发中,建议使用更专业的游戏引擎(如SDL、SFML等)进行复杂游戏开发,控制台游戏更适合学习和练习基础算法。
若内容若侵犯到您的权益,请发送邮件至:platform_service@jienda.com我们将第一时间处理!
所有资源仅限于参考和学习,版权归JienDa作者所有,更多请访问JienDa首页。





