Back to Articles

使用C++实现贪吃蛇游戏完整指南

#C++ #游戏开发 #面向对象编程 #项目实践

使用C++实现贪吃蛇游戏完整指南

前言

贪吃蛇是一款经典的电子游戏,具有简单易懂的游戏规则和令人上瘾的游戏体验。在本篇文章中,我们将使用C++语言从零开始实现一个完整的贪吃蛇游戏。通过这个项目,你将学习到游戏开发的基本概念,包括游戏循环、碰撞检测、用户输入处理等关键技术。

项目背景与目标

贪吃蛇游戏的核心机制非常简单:

  • 控制一条蛇在游戏区域内移动
  • 吃掉随机出现的食物来增长身体
  • 避免撞到墙壁或自己的身体
  • 通过吃食物来获得分数

我们的目标是创建一个基于控制台的贪吃蛇游戏,实现以下功能:

  • 蛇的移动和控制
  • 食物生成和吃食逻辑
  • 碰撞检测
  • 分数统计
  • 游戏结束和重新开始机制

创建项目结构

首先,让我们创建项目的基本结构:

bash
mkdir SnakeGame
cd SnakeGame
mkdir src

我们的项目将包含以下文件:

  • src/main.cpp - 程序入口点
  • src/Snake.h - 蛇类定义
  • src/Snake.cpp - 蛇类实现
  • src/Food.h - 食物类定义
  • src/Food.cpp - 食物类实现
  • src/Game.h - 游戏主类定义
  • src/Game.cpp - 游戏主类实现

数据结构设计

让我们首先设计游戏所需的基本数据结构。我们先创建Snake类:

src/Snake.h

cpp
#ifndef SNAKE_H
#define SNAKE_H

#include <vector>
#include <utility>

enum class Direction {
    UP,
    DOWN,
    LEFT,
    RIGHT
};

struct Position {
    int x;
    int y;
    
    bool operator==(const Position& other) const {
        return x == other.x && y == other.y;
    }
};

class Snake {
private:
    std::vector<Position> body;  // 蛇的身体,每个元素代表一个位置
    Direction currentDirection;  // 当前移动方向
    int width;                   // 游戏区域宽度
    int height;                  // 游戏区域高度

public:
    Snake(int x, int y, int gameWidth, int gameHeight);
    
    void move();                 // 移动蛇
    void changeDirection(Direction newDirection);  // 改变方向
    bool eatFood(const Position& foodPos);         // 吃食物
    bool checkCollision();       // 检查碰撞
    const std::vector<Position>& getBody() const;  // 获取蛇的身体位置
    
    Position getHead() const;    // 获取蛇头位置
    Direction getDirection() const;  // 获取当前方向
};

#endif

src/Snake.cpp

cpp
#include "Snake.h"

Snake::Snake(int x, int y, int gameWidth, int gameHeight) 
    : width(gameWidth), height(gameHeight), currentDirection(Direction::RIGHT) {
    // 初始化蛇,蛇身包含三个部分
    body.push_back({x, y});
    body.push_back({x - 1, y});
    body.push_back({x - 2, y});
}

void Snake::move() {
    // 复制当前蛇头位置
    Position newHead = getHead();
    
    // 根据当前方向更新蛇头位置
    switch(currentDirection) {
        case Direction::UP:
            newHead.y--;
            break;
        case Direction::DOWN:
            newHead.y++;
            break;
        case Direction::LEFT:
            newHead.x--;
            break;
        case Direction::RIGHT:
            newHead.x++;
            break;
    }
    
    // 将新的头部位置插入到蛇身的最前面
    body.insert(body.begin(), newHead);
    
    // 移除最后一个元素(尾巴),除非吃到了食物
    body.pop_back();
}

void Snake::changeDirection(Direction newDirection) {
    // 防止蛇向相反方向移动
    if ((currentDirection == Direction::UP && newDirection == Direction::DOWN) ||
        (currentDirection == Direction::DOWN && newDirection == Direction::UP) ||
        (currentDirection == Direction::LEFT && newDirection == Direction::RIGHT) ||
        (currentDirection == Direction::RIGHT && newDirection == Direction::LEFT)) {
        return;  // 忽略相反方向的输入
    }
    
    currentDirection = newDirection;
}

bool Snake::eatFood(const Position& foodPos) {
    // 检查蛇头是否与食物位置重合
    if (getHead() == foodPos) {
        // 如果吃到食物,不移除尾巴,这样蛇就变长了
        Position newHead = getHead();
        
        switch(currentDirection) {
            case Direction::UP:
                newHead.y--;
                break;
            case Direction::DOWN:
                newHead.y++;
                break;
            case Direction::LEFT:
                newHead.x--;
                break;
            case Direction::RIGHT:
                newHead.x++;
                break;
        }
        
        body.insert(body.begin(), newHead);
        return true;
    }
    return false;
}

bool Snake::checkCollision() {
    Position head = getHead();
    
    // 检查是否撞到墙壁
    if (head.x < 0 || head.x >= width || head.y < 0 || head.y >= height) {
        return true;
    }
    
    // 检查是否撞到自己,跳过蛇头,从蛇身第二个部分开始检查
    for (size_t i = 1; i < body.size(); ++i) {
        if (head == body[i]) {
            return true;
        }
    }
    
    return false;
}

const std::vector<Position>& Snake::getBody() const {
    return body;
}

Position Snake::getHead() const {
    if (!body.empty()) {
        return body[0];
    }
    return {-1, -1};  // 错误情况
}

Direction Snake::getDirection() const {
    return currentDirection;
}

现在让我们创建食物类:

src/Food.h

cpp
#ifndef FOOD_H
#define FOOD_H

#include "Snake.h"  // 需要Position结构
#include <random>

class Food {
private:
    Position position;  // 食物位置
    int width;          // 游戏区域宽度
    int height;         // 游戏区域高度
    std::random_device rd;
    std::mt19937 gen;
    std::uniform_int_distribution<> xDist;
    std::uniform_int_distribution<> yDist;

public:
    Food(int gameWidth, int gameHeight);
    
    Position getPosition() const;  // 获取食物位置
    void generate(const std::vector<Position>& snakeBody);  // 生成新食物,确保不在蛇身上
    void setPosition(int x, int y);  // 设置食物位置
};

#endif

src/Food.cpp

cpp
#include "Food.h"
#include <algorithm>

Food::Food(int gameWidth, int gameHeight) 
    : width(gameWidth), height(gameHeight), gen(rd()) {
    xDist = std::uniform_int_distribution<>(0, width - 1);
    yDist = std::uniform_int_distribution<>(0, height - 1);
    
    // 初始位置随机生成
    position.x = xDist(gen);
    position.y = yDist(gen);
}

Position Food::getPosition() const {
    return position;
}

void Food::generate(const std::vector<Position>& snakeBody) {
    bool validPosition = false;
    
    // 确保食物不会生成在蛇身上
    while (!validPosition) {
        position.x = xDist(gen);
        position.y = yDist(gen);
        
        // 检查新位置是否与蛇身重合
        validPosition = true;
        for (const auto& segment : snakeBody) {
            if (position == segment) {
                validPosition = false;
                break;
            }
        }
    }
}

void Food::setPosition(int x, int y) {
    position.x = x;
    position.y = y;
}

现在让我们创建游戏主类:

src/Game.h

cpp
#ifndef GAME_H
#define GAME_H

#include "Snake.h"
#include "Food.h"
#include <iostream>
#include <chrono>
#include <thread>

class Game {
private:
    static const int WIDTH = 20;   // 游戏区域宽度
    static const int HEIGHT = 20;  // 游戏区域高度
    static const int DELAY = 200;  // 游戏延迟(毫秒)
    
    Snake snake;
    Food food;
    bool gameOver;
    int score;

public:
    Game();
    
    void run();                    // 运行游戏主循环
    void update();                 // 更新游戏状态
    void render();                 // 渲染游戏画面
    void handleInput();            // 处理用户输入
    void initGame();               // 初始化游戏
    void resetGame();              // 重置游戏
    bool isGameOver() const;       // 检查游戏是否结束
    void displayGameInfo();        // 显示游戏信息
};

#endif

src/Game.cpp

cpp
#include "Game.h"
#include <conio.h>  // Windows平台的键盘输入处理
#include <windows.h> // 用于清屏

Game::Game() : snake(10, 10, WIDTH, HEIGHT), food(WIDTH, HEIGHT), gameOver(false), score(0) {
    food.generate(snake.getBody());
}

void Game::run() {
    initGame();
    
    while (!gameOver) {
        render();
        handleInput();
        update();
        
        // 控制游戏速度
        std::this_thread::sleep_for(std::chrono::milliseconds(DELAY));
    }
    
    std::cout << "\n游戏结束!最终得分: " << score << std::endl;
    std::cout << "按任意键退出..." << std::endl;
    _getch();
}

void Game::update() {
    // 移动蛇
    snake.move();
    
    // 检查是否吃到食物
    if (snake.eatFood(food.getPosition())) {
        score += 10;  // 吃到食物加分
        food.generate(snake.getBody());  // 生成新食物
    }
    
    // 检查碰撞
    if (snake.checkCollision()) {
        gameOver = true;
    }
}

void Game::render() {
    // 清屏(Windows系统)
    system("cls");
    
    // 创建游戏区域显示
    char display[HEIGHT][WIDTH];
    
    // 初始化显示区域
    for (int y = 0; y < HEIGHT; y++) {
        for (int x = 0; x < WIDTH; x++) {
            display[y][x] = ' ';
        }
    }
    
    // 绘制蛇
    auto snakeBody = snake.getBody();
    for (size_t i = 0; i < snakeBody.size(); i++) {
        Position pos = snakeBody[i];
        if (pos.x >= 0 && pos.x < WIDTH && pos.y >= 0 && pos.y < HEIGHT) {
            if (i == 0) {
                display[pos.y][pos.x] = 'O';  // 蛇头
            } else {
                display[pos.y][pos.x] = 'o';  // 蛇身
            }
        }
    }
    
    // 绘制食物
    Position foodPos = food.getPosition();
    if (foodPos.x >= 0 && foodPos.x < WIDTH && foodPos.y >= 0 && foodPos.y < HEIGHT) {
        display[foodPos.y][foodPos.x] = '*';
    }
    
    // 显示边框
    std::cout << "+";
    for (int x = 0; x < WIDTH; x++) {
        std::cout << "-";
    }
    std::cout << "+" << std::endl;
    
    // 显示游戏区域
    for (int y = 0; y < HEIGHT; y++) {
        std::cout << "|";
        for (int x = 0; x < WIDTH; x++) {
            std::cout << display[y][x];
        }
        std::cout << "|" << std::endl;
    }
    
    // 显示底部边框
    std::cout << "+";
    for (int x = 0; x < WIDTH; x++) {
        std::cout << "-";
    }
    std::cout << "+" << std::endl;
    
    // 显示游戏信息
    displayGameInfo();
}

void Game::handleInput() {
    if (_kbhit()) {
        char key = _getch();
        switch (key) {
            case 'w':
            case 'W':
                snake.changeDirection(Direction::UP);
                break;
            case 's':
            case 'S':
                snake.changeDirection(Direction::DOWN);
                break;
            case 'a':
            case 'A':
                snake.changeDirection(Direction::LEFT);
                break;
            case 'd':
            case 'D':
                snake.changeDirection(Direction::RIGHT);
                break;
            case 'q':
            case 'Q':
                gameOver = true;  // 按Q键退出游戏
                break;
        }
    }
}

void Game::initGame() {
    std::cout << "欢迎来到贪吃蛇游戏!" << std::endl;
    std::cout << "使用 W/A/S/D 控制蛇的移动方向" << std::endl;
    std::cout << "吃到食物(*)可以增长蛇身并获得分数" << std::endl;
    std::cout << "按任意键开始游戏..." << std::endl;
    _getch();
}

void Game::resetGame() {
    snake = Snake(10, 10, WIDTH, HEIGHT);
    food = Food(WIDTH, HEIGHT);
    gameOver = false;
    score = 0;
    food.generate(snake.getBody());
}

bool Game::isGameOver() const {
    return gameOver;
}

void Game::displayGameInfo() {
    std::cout << "得分: " << score << std::endl;
    std::cout << "控制: W(上) S(下) A(左) D(右) Q(退出)" << std::endl;
}

最后,让我们创建主函数文件:

src/main.cpp

cpp
#include "Game.h"

int main() {
    Game game;
    game.run();
    
    return 0;
}

以及CMakeLists.txt文件来构建项目:

CMakeLists.txt

cmake
cmake_minimum_required(VERSION 3.10)
project(SnakeGame)

set(CMAKE_CXX_STANDARD 11)

# 源文件
set(SOURCES
    src/main.cpp
    src/Snake.cpp
    src/Food.cpp
    src/Game.cpp
)

# 头文件
set(HEADERS
    src/Snake.h
    src/Food.h
    src/Game.h
)

add_executable(SnakeGame ${SOURCES} ${HEADERS})

# 针对Windows平台的设置
if(WIN32)
    target_link_libraries(SnakeGame)
endif()

构建和运行游戏

要构建和运行游戏,请按照以下步骤操作:

  1. 确保安装了C++编译器(如GCC或Clang)
  2. 安装CMake构建工具
  3. 在项目根目录下执行以下命令:
bash
mkdir build
cd build
cmake ..
make
./SnakeGame  # Linux/macOS
# 或
SnakeGame.exe  # Windows

游戏功能总结

我们的贪吃蛇游戏实现了以下核心功能:

  1. 蛇的移动: 蛇会持续向当前方向移动
  2. 方向控制: 使用W/S/A/D键控制蛇的移动方向
  3. 食物系统: 随机生成食物,吃到后蛇身增长
  4. 碰撞检测: 检测蛇头是否撞墙或撞到自己
  5. 计分系统: 每吃一个食物得10分
  6. 游戏结束: 碰撞时游戏结束
  7. 用户界面: 清晰的游戏画面显示蛇、食物和边界

扩展功能建议

如果你想进一步完善游戏,可以考虑添加以下功能:

  1. 难度递增: 随着得分增加,蛇的移动速度加快
  2. 障碍物: 在游戏区域内添加不可穿越的障碍
  3. 特殊食物: 实现不同类型的奖励食物
  4. 音效: 添加游戏音效
  5. 存档系统: 保存最高分记录
  6. 多种界面: 移植到图形界面库如SFML或SDL

跨平台兼容性说明

当前代码主要基于Windows平台的conio.h库实现键盘输入和清屏功能。如果要在Linux或macOS上运行,需要进行以下调整:

  1. 替换conio.htermios.hunistd.h
  2. 实现跨平台的键盘输入处理
  3. 使用system("clear")替代system("cls")

总结

通过这个项目,我们学会了如何使用C++实现一个完整的游戏。从设计数据结构到实现游戏逻辑,再到用户界面的展示,每一步都体现了面向对象编程的精髓。贪吃蛇游戏虽然简单,但它涵盖了游戏开发的核心概念,是学习游戏编程的理想入门项目。

这个项目展示了如何将复杂的逻辑分解为简单的组件(蛇、食物、游戏管理器),并通过它们的协同工作来实现复杂的游戏功能。希望这个指南对你理解游戏开发有所帮助!


CC BY-NC 4.02025 © Chiway Wang
Feeds (RSS)