C语言怎么写小游戏?从零开始做一个贪吃蛇

1. 引言

很多初学者学完 C 语言基础后都会问:C 语言能写小游戏吗?怎么写?

答案是肯定的。C 语言虽然不像 Python、JavaScript 那样有丰富的游戏框架,但它足够底层、足够灵活,完全可以用标准库写出贪吃蛇、扫雷、俄罗斯方块、井字棋等经典小游戏。而且用 C 写游戏,能让你更深刻地理解数据结构、逻辑控制、输入输出这些核心概念。

本文以贪吃蛇为例,带你从零开始,用纯 C 语言(只依赖标准库)写一个能在终端里运行的小游戏。全程手把手,代码可直接复制运行。

2. 准备工作

2.1 开发环境

写 C 语言小游戏,只需要一个 C 编译器即可:

  • Windows:安装 Dev-C++ 或 Visual Studio,也可以使用 MinGW 的 gcc。
  • Linux / macOS:系统自带 gcc,直接终端编译即可。

2.2 验证环境

先写一个最简单的程序,确认编译环境正常:

c 复制代码
#include <stdio.h>

int main() {
    printf("Hello, C Game!\n");
    return 0;
}

编译运行:

bash 复制代码
gcc test.c -o test
./test

能输出 Hello, C Game! 就说明环境没问题。

3. 游戏设计思路

写游戏之前,先想清楚几个问题:

3.1 游戏地图

贪吃蛇在一个固定大小的矩形区域里活动。我们用二维数组(或一维数组模拟)来表示地图:

  • 0 表示空地
  • 1 表示蛇身
  • 2 表示食物

3.2 蛇的表示

蛇是一串连续的格子。我们用两个数组分别记录蛇身每个格子的 x 和 y 坐标,数组下标 0 是蛇头。

3.3 游戏循环

所有小游戏的核心都是游戏循环:

  1. 接收玩家输入(方向键)
  2. 更新游戏状态(蛇移动、吃食物)
  3. 判断游戏结束条件(撞墙、撞自己)
  4. 绘制画面
  5. 短暂延时,控制速度

#mermaid-svg-fTcuDInM5cS4QrnM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fTcuDInM5cS4QrnM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fTcuDInM5cS4QrnM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fTcuDInM5cS4QrnM .error-icon{fill:#552222;}#mermaid-svg-fTcuDInM5cS4QrnM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fTcuDInM5cS4QrnM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fTcuDInM5cS4QrnM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fTcuDInM5cS4QrnM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fTcuDInM5cS4QrnM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fTcuDInM5cS4QrnM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fTcuDInM5cS4QrnM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fTcuDInM5cS4QrnM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fTcuDInM5cS4QrnM .marker.cross{stroke:#333333;}#mermaid-svg-fTcuDInM5cS4QrnM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fTcuDInM5cS4QrnM p{margin:0;}#mermaid-svg-fTcuDInM5cS4QrnM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-fTcuDInM5cS4QrnM .cluster-label text{fill:#333;}#mermaid-svg-fTcuDInM5cS4QrnM .cluster-label span{color:#333;}#mermaid-svg-fTcuDInM5cS4QrnM .cluster-label span p{background-color:transparent;}#mermaid-svg-fTcuDInM5cS4QrnM .label text,#mermaid-svg-fTcuDInM5cS4QrnM span{fill:#333;color:#333;}#mermaid-svg-fTcuDInM5cS4QrnM .node rect,#mermaid-svg-fTcuDInM5cS4QrnM .node circle,#mermaid-svg-fTcuDInM5cS4QrnM .node ellipse,#mermaid-svg-fTcuDInM5cS4QrnM .node polygon,#mermaid-svg-fTcuDInM5cS4QrnM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fTcuDInM5cS4QrnM .rough-node .label text,#mermaid-svg-fTcuDInM5cS4QrnM .node .label text,#mermaid-svg-fTcuDInM5cS4QrnM .image-shape .label,#mermaid-svg-fTcuDInM5cS4QrnM .icon-shape .label{text-anchor:middle;}#mermaid-svg-fTcuDInM5cS4QrnM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fTcuDInM5cS4QrnM .rough-node .label,#mermaid-svg-fTcuDInM5cS4QrnM .node .label,#mermaid-svg-fTcuDInM5cS4QrnM .image-shape .label,#mermaid-svg-fTcuDInM5cS4QrnM .icon-shape .label{text-align:center;}#mermaid-svg-fTcuDInM5cS4QrnM .node.clickable{cursor:pointer;}#mermaid-svg-fTcuDInM5cS4QrnM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fTcuDInM5cS4QrnM .arrowheadPath{fill:#333333;}#mermaid-svg-fTcuDInM5cS4QrnM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fTcuDInM5cS4QrnM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fTcuDInM5cS4QrnM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fTcuDInM5cS4QrnM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fTcuDInM5cS4QrnM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fTcuDInM5cS4QrnM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fTcuDInM5cS4QrnM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fTcuDInM5cS4QrnM .cluster text{fill:#333;}#mermaid-svg-fTcuDInM5cS4QrnM .cluster span{color:#333;}#mermaid-svg-fTcuDInM5cS4QrnM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-fTcuDInM5cS4QrnM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fTcuDInM5cS4QrnM rect.text{fill:none;stroke-width:0;}#mermaid-svg-fTcuDInM5cS4QrnM .icon-shape,#mermaid-svg-fTcuDInM5cS4QrnM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fTcuDInM5cS4QrnM .icon-shape p,#mermaid-svg-fTcuDInM5cS4QrnM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fTcuDInM5cS4QrnM .icon-shape .label rect,#mermaid-svg-fTcuDInM5cS4QrnM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fTcuDInM5cS4QrnM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fTcuDInM5cS4QrnM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fTcuDInM5cS4QrnM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
游戏初始化
接收输入
更新蛇位置
是否吃到食物?
蛇变长 + 生成新食物
蛇正常移动
是否撞墙或撞自己?
游戏结束
绘制画面

4. 完整代码实现

下面就是完整的贪吃蛇代码。为了简单易懂,我们使用 wasd 控制方向,按 q 退出。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>   // Windows 下用 _kbhit 和 _getch
#include <windows.h> // Windows 下用 Sleep

#define WIDTH  20
#define HEIGHT 15

// 蛇身坐标
int snakeX[100], snakeY[100];
int snakeLen = 3;

// 食物坐标
int foodX, foodY;

// 当前方向:0上 1下 2左 3右
int dir = 3;

// 游戏是否结束
int gameOver = 0;

// 初始化游戏
void initGame() {
    // 蛇初始位置:在中间,水平向右
    snakeX[0] = WIDTH / 2;
    snakeY[0] = HEIGHT / 2;
    snakeX[1] = snakeX[0] - 1;
    snakeY[1] = snakeY[0];
    snakeX[2] = snakeX[0] - 2;
    snakeY[2] = snakeY[0];

    // 生成第一个食物
    foodX = rand() % WIDTH;
    foodY = rand() % HEIGHT;
}

// 生成新食物(避开蛇身)
void generateFood() {
    int onSnake;
    do {
        foodX = rand() % WIDTH;
        foodY = rand() % HEIGHT;
        onSnake = 0;
        for (int i = 0; i < snakeLen; i++) {
            if (snakeX[i] == foodX && snakeY[i] == foodY) {
                onSnake = 1;
                break;
            }
        }
    } while (onSnake);
}

// 绘制画面
void draw() {
    system("cls"); // 清屏

    // 上边框
    for (int i = 0; i < WIDTH + 2; i++) printf("#");
    printf("\n");

    for (int y = 0; y < HEIGHT; y++) {
        for (int x = 0; x < WIDTH; x++) {
            if (x == 0) printf("#"); // 左边框

            int isSnake = 0;
            for (int i = 0; i < snakeLen; i++) {
                if (snakeX[i] == x && snakeY[i] == y) {
                    isSnake = 1;
                    break;
                }
            }

            if (isSnake) {
                printf("O");
            } else if (foodX == x && foodY == y) {
                printf("*");
            } else {
                printf(" ");
            }

            if (x == WIDTH - 1) printf("#"); // 右边框
        }
        printf("\n");
    }

    // 下边框
    for (int i = 0; i < WIDTH + 2; i++) printf("#");
    printf("\n");

    printf("得分: %d\n", (snakeLen - 3) * 10);
    printf("方向: W上 S下 A左 D右 | Q退出\n");
}

// 读取输入
void input() {
    if (_kbhit()) {
        char ch = _getch();
        switch (ch) {
            case 'w': case 'W': if (dir != 1) dir = 0; break;
            case 's': case 'S': if (dir != 0) dir = 1; break;
            case 'a': case 'A': if (dir != 3) dir = 2; break;
            case 'd': case 'D': if (dir != 2) dir = 3; break;
            case 'q': case 'Q': gameOver = 1; break;
        }
    }
}

// 更新游戏逻辑
void update() {
    // 新蛇头位置
    int newX = snakeX[0];
    int newY = snakeY[0];

    switch (dir) {
        case 0: newY--; break;
        case 1: newY++; break;
        case 2: newX--; break;
        case 3: newX++; break;
    }

    // 撞墙判断
    if (newX < 0 || newX >= WIDTH || newY < 0 || newY >= HEIGHT) {
        gameOver = 1;
        return;
    }

    // 撞自己判断(新蛇头是否碰到身体)
    for (int i = 0; i < snakeLen; i++) {
        if (snakeX[i] == newX && snakeY[i] == newY) {
            gameOver = 1;
            return;
        }
    }

    // 吃到食物
    int ateFood = (newX == foodX && newY == foodY);

    // 蛇身整体前移
    for (int i = snakeLen - 1; i > 0; i--) {
        snakeX[i] = snakeX[i - 1];
        snakeY[i] = snakeY[i - 1];
    }
    snakeX[0] = newX;
    snakeY[0] = newY;

    // 吃到食物则变长并生成新食物
    if (ateFood) {
        snakeLen++;
        generateFood();
    }
}

int main() {
    srand((unsigned)time(NULL));
    initGame();

    while (!gameOver) {
        draw();
        input();
        update();
        Sleep(150); // 控制速度,单位毫秒
    }

    printf("\n游戏结束!最终得分: %d\n", (snakeLen - 3) * 10);
    return 0;
}

注意:上面的代码使用了 <conio.h> 和 <windows.h>,这是 Windows 专属 的头文件。如果你在 Linux / macOS 上运行,需要改用 <unistd.h> 的 usleep() 和终端原始模式来读取按键,见下文「跨平台改造」。

5. 代码讲解

5.1 全局变量

我们用全局变量保存蛇、食物、方向等状态,这样各个函数都能直接访问,代码更简洁:

  • snakeX[] / snakeY[]:蛇身每个格子的坐标,snakeX[0] 是蛇头。
  • snakeLen:蛇的长度。
  • foodX / foodY:食物坐标。
  • dir:当前移动方向。
  • gameOver:游戏结束标志。

5.2 游戏循环

main() 里的 while (!gameOver) 就是游戏循环,每一轮做四件事:

  1. draw() 绘制画面
  2. input() 读取按键
  3. update() 更新逻辑
  4. Sleep(150) 延时控制速度

5.3 蛇的移动

蛇移动的本质是:蛇头向前走一格,身体每个格子跟上前面一格 。我们用循环把 snakeX[i] = snakeX[i-1] 实现这个「跟上」的动作。

5.4 碰撞检测

  • 撞墙:新蛇头坐标超出地图边界。
  • 撞自己:新蛇头坐标与身体任意一格重合。

这两种情况都让 gameOver = 1,游戏结束。

6. 跨平台改造(Linux / macOS)

如果你在 Linux 或 macOS 上,上面的代码不能直接编译。需要做两处替换:

6.1 替换头文件

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>   // usleep
#include <termios.h>  // 终端原始模式
#include <fcntl.h>    // fcntl

6.2 替换按键读取和延时

c 复制代码
// 非阻塞读取按键
int kbhit() {
    struct termios oldt, newt;
    int ch;
    int oldf;

    tcgetattr(STDIN_FILENO, &oldt);
    newt = oldt;
    newt.c_lflag &= ~(ICANON | ECHO);
    tcsetattr(STDIN_FILENO, TCSANOW, &newt);
    oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
    fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);

    ch = getchar();

    tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
    fcntl(STDIN_FILENO, F_SETFL, oldf);

    if (ch != EOF) {
        ungetc(ch, stdin);
        return 1;
    }
    return 0;
}

然后把 _kbhit() 换成 kbhit(),_getch() 换成 getchar(),Sleep(150) 换成 usleep(150000)(单位是微秒,150000 微秒 = 150 毫秒)。

7. 扩展与进阶

贪吃蛇只是起点,你可以在此基础上做很多扩展:

7.1 增加难度

  • 每吃一个食物,Sleep 的时间减少一点,蛇越跑越快。
  • 增加障碍物,让地图更复杂。

7.2 增加计分与关卡

  • 用文件保存最高分,下次启动时读取。
  • 达到一定分数进入下一关,地图变大或速度变快。

7.3 换一个游戏

掌握了游戏循环的思路,你可以尝试写:

  • 井字棋:双人对战,用二维数组判断胜负。
  • 扫雷:用二维数组 + 随机布雷 + 递归展开。
  • 俄罗斯方块:用二维数组表示方块,实现旋转和消行。

8. 总结

用 C 语言写小游戏并不难,核心就三件事:

  1. 用数据结构表示游戏状态(数组、结构体)
  2. 用游戏循环驱动逻辑(输入 → 更新 → 绘制)
  3. 用碰撞检测判断结束(边界、自身、目标)

贪吃蛇这个项目麻雀虽小五脏俱全,非常适合作为 C 语言的练手项目。建议你亲手敲一遍代码,再试着加一两个自己的功能,收获会更大。

祝你写出第一个属于自己的 C 语言小游戏!

相关推荐
Chen—LSN1 小时前
数据结构——拿捏复杂度
java·c语言·开发语言·数据结构·经验分享·笔记·算法
Chen_harmony1 小时前
六、排序详解
c语言·数据结构·算法·排序算法
阿钱真强道1 小时前
25 嵌入式操作系统 | 设备面板:用 Qt 控件把“设备“画出来(本节不接串口)
开发语言·qt·flask·架构设计·协议契约
ym hyd 1112 小时前
宝宝早教服务系统源码 Java+SpringBoot+Vue3 前后分离
java·开发语言·spring boot
看浪的路人2 小时前
第7讲:实时告警与自动化响应
开发语言·后端·golang
她说彩礼65万2 小时前
C语言 单引号与双引号
java·服务器·c语言
dtq04242 小时前
数据结构 - 树的概念
c语言·数据结构·学习·算法
“AI国潮设计-小江”2 小时前
Python实战 | SDXL批量生成“潮汕英歌舞”国潮甜品IP,附核心Prompt与商用授权思路
开发语言·人工智能·python·prompt·aigc
山下梅子酒2252 小时前
洛谷-入门-B2075
c语言