使用C++实现贪吃蛇游戏完整指南
前言
贪吃蛇是一款经典的电子游戏,具有简单易懂的游戏规则和令人上瘾的游戏体验。在本篇文章中,我们将使用C++语言从零开始实现一个完整的贪吃蛇游戏。通过这个项目,你将学习到游戏开发的基本概念,包括游戏循环、碰撞检测、用户输入处理等关键技术。
项目背景与目标
贪吃蛇游戏的核心机制非常简单:
- 控制一条蛇在游戏区域内移动
- 吃掉随机出现的食物来增长身体
- 避免撞到墙壁或自己的身体
- 通过吃食物来获得分数
我们的目标是创建一个基于控制台的贪吃蛇游戏,实现以下功能:
- 蛇的移动和控制
- 食物生成和吃食逻辑
- 碰撞检测
- 分数统计
- 游戏结束和重新开始机制
创建项目结构
首先,让我们创建项目的基本结构:
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
#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
#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
#ifndef FOOD_H #define FOOD_H
#include "Snake.h" #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
#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
#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
#include "Game.h" #include <conio.h> #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() { 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; 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
#include "Game.h"
int main() { Game game; game.run(); return 0; }
|
以及CMakeLists.txt文件来构建项目:
CMakeLists.txt
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})
if(WIN32) target_link_libraries(SnakeGame) endif()
|
构建和运行游戏
要构建和运行游戏,请按照以下步骤操作:
- 确保安装了C++编译器(如GCC或Clang)
- 安装CMake构建工具
- 在项目根目录下执行以下命令:
mkdir build cd build cmake .. make ./SnakeGame
SnakeGame.exe
|
游戏功能总结
我们的贪吃蛇游戏实现了以下核心功能:
- 蛇的移动: 蛇会持续向当前方向移动
- 方向控制: 使用W/S/A/D键控制蛇的移动方向
- 食物系统: 随机生成食物,吃到后蛇身增长
- 碰撞检测: 检测蛇头是否撞墙或撞到自己
- 计分系统: 每吃一个食物得10分
- 游戏结束: 碰撞时游戏结束
- 用户界面: 清晰的游戏画面显示蛇、食物和边界
扩展功能建议
如果你想进一步完善游戏,可以考虑添加以下功能:
- 难度递增: 随着得分增加,蛇的移动速度加快
- 障碍物: 在游戏区域内添加不可穿越的障碍
- 特殊食物: 实现不同类型的奖励食物
- 音效: 添加游戏音效
- 存档系统: 保存最高分记录
- 多种界面: 移植到图形界面库如SFML或SDL
跨平台兼容性说明
当前代码主要基于Windows平台的conio.h库实现键盘输入和清屏功能。如果要在Linux或macOS上运行,需要进行以下调整:
- 替换
conio.h为termios.h和unistd.h
- 实现跨平台的键盘输入处理
- 使用
system("clear")替代system("cls")
总结
通过这个项目,我们学会了如何使用C++实现一个完整的游戏。从设计数据结构到实现游戏逻辑,再到用户界面的展示,每一步都体现了面向对象编程的精髓。贪吃蛇游戏虽然简单,但它涵盖了游戏开发的核心概念,是学习游戏编程的理想入门项目。
这个项目展示了如何将复杂的逻辑分解为简单的组件(蛇、食物、游戏管理器),并通过它们的协同工作来实现复杂的游戏功能。希望这个指南对你理解游戏开发有所帮助!