世人独爱之,重复感知。
——Lrice
设计内容
结合大一学习的《C++面向对象程序设计》课程内容,实现一个简单的控制台游戏程序,游戏名称为贪食蛇。贪食蛇是一款十分经典的休闲游戏,玩家可以通过方向按键操控一条虚拟蛇的移动来吞下食物,蛇吞下食物后会获得成长(身体长度变长),在有限的画面空间内,蛇头若触碰到墙壁或者蛇自身则游戏结束。
系统环境
硬件环境
CPU:Intel(R) Core(TM) i5-9300H CPU
内存:8.0 GB
Wi-Fi:Intel® Wireless-AC 9560 160MHz
磁盘:BC501 NVMe SK hynix 512GB
GPU0:Intel® UHD Graphics 630
GPU1:NVIDIA GeForce GTX 1650
软件环境
Visual Studio Community 2019 16.7.2版本
设计思想描述
C++中的类具有封装、继承、多态的特性,利用其特性可以进行具体的对象模拟实现,将看似浩大的工程分解成一个个小部分。我在该课程设计中将整个游戏分解成三个类,一个类为角色主体,也就是贪食蛇类,一个类用来掌控蛇的移动,还有一个类统筹控制整个游戏进行。形成以下项目文件(本次课程设计采用多文件结构进行编程,包含头文件卫士):
移动操控类:
(1):BasicClass.h
(2):BasicClass.cpp
主体蛇类:
(3):CSnake.h
(4):CSnake.cpp
游戏全局类:
(5):GameManager.h
(6):GameManager.cpp
主程序:
(7):main.cpp
本次课程设计包含的库有<time.h><iostream> <Windows.h><conio.h>。
程序结构
Basic Class类
1. class BasicClass
2. {
3. private:
4. KeyState _keyState;
5. public:
6. BasicClass();
7. //修改光标位置
8. void Gotoxy(int x, int y);
9. //设置按键状态
10. void SetKeyState(KeyState keyState);
11. //获取按键状态
12. KeyState GetKeyState();
13. //判断方向按键
14. KeyState GetKeyDown();
15. };
首先,利用枚举来表示方向按键的五种状态(KeyState),便于处理,也是作为唯一的数据成员来记录实时的按键状态。除了构造函数,Gotoxy函数可以修改光标的位置,首先获取标准输出设备句柄,再指定窗体内的具体位置,其中调用了Windows Api函数中的SetConsoleCursorPosition函数。这个类中还有获取、设置、判断按键方向状态的函数。其中GetKeyState函数被用作监听键盘的指令输入,从而控制蛇的移动,通过_kbhit函数实现对键盘敲击的检测。
CSnake类
1. class CSnake
2. {
3. private:
4. int _len; // 贪吃蛇长度
5. Position _pos[SIZE]; //身体节点位置
6. public:
7. CSnake();
8. //获取长度
9. int GetLen();
10. //添加节点
11. bool AddPosition(Position pos);
12. //修改节点位置
13. void SetPosition(int index,Position pos);
14. //获取节点位置
15. Position GetPosition(int index);
16. };
蛇是这个游戏的主要对象,它的数据成员有_len(身体长度)、_pos[](身体结点位置),CSnake类不具备把蛇完整呈现在控制台上的功能,它包含的是蛇对象的特性。由于在游戏机制下蛇每吃到食物身体都会变长,所以便它的身体看作由一个个节点组成,当它吃到食物(头部触碰到食物)时则会获得成长,但如果超过了节点上限则不会获得成长。蛇是一直处在一个移动状态,所以采用一个坐标数组来管理蛇的整体。该类的成员函数都与蛇身节点有关,可以获取长度、添加节点、修改节点位置和获取节点位置。
GameManager类
1. class GameManager
2. {
3. private:
4. BasicClass *_basic;
5. CSnake *_snake;
6. int _score; //分数
7. Position _foodPos; //食物位置
8. GameState _gameState; //游戏状态
9.
10. //初始化函数
11. void Init();
12. //绘制背景
13. void DrawMap();
14. //绘制蛇身
15. void DrawSnake();
16. //创建食物位置
17. void CreateFood();
18. //移动蛇身位置
19. void MoveSnake();
20. //设置游戏状态
21. void SetGameState();
22.
23. public:
24. GameManager(BasicClass *basic);
25. //更新函数
26. bool Update();
27. };
GameManager类是这个游戏的全局控制类,它的数据成员包含CSnake对象指针、BasicClass对象指针、游戏获得分数、食物位置和游戏状态,其中游戏状态利用枚举分为两种情况:游戏正常进行和游戏结束。
1. void GameManager::Init()
2. {
3. //初始化数据
4. srand((int)time(0));
5. _snake = new CSnake();
6. Position pos;
7. pos.x = COLUMN / 2 - 1;
8. pos.y = ROW / 2;
9. _snake->AddPosition(pos);
10. pos.x = COLUMN / 2;
11. _snake->AddPosition(pos);
12. pos.x = COLUMN / 2 + 1;
13. _snake->AddPosition(pos);
14. _score = 0;
15. _basic->SetKeyState(KeyState_LEFT);
16. _gameState = GameState_Playing;
17. DrawMap();
18. CreateFood();
19. DrawSnake();
20. }
以上为游戏的初始化函数,首先以当前时间对应的int值为随机序列起点,这样每次运行程序,由于起点不同才可以得到不同的随机数,接着以窗口中心创建蛇身的三个节点,游戏状态进入运行状态,基础分数设置为0,最后就是游戏区域、食物、蛇身的绘制。绘制函数都是利用cout函数进行字符的输出,其中代表蛇身的字符为"■",代表食物的字符为"*"。食物的创建函数中利用到了rand函数来达到随机生成的效果。
1. bool GameManager::Update()
2. {
3. //更新蛇身
4. MoveSnake();
5. //延迟250,即移动速度
6. Sleep(250);
7. //判断是否游戏结束
8. SetGameState();
9. if (_gameState == GameState_GameOver)
10. {
11. _basic->Gotoxy(COLUMN / 2, ROW / 2);
12. cout << "游戏结束";
13. _basic->Gotoxy(COLUMN / 2, ROW / 2+1);
14. cout << "游戏得分:" << _score;
15. _basic->Gotoxy(0, ROW + 6);
16. return false;
17. }
18. return true;
19. }
整个游戏的正常运行以及蛇移动在视觉上的实现是通过不断调用Update函数完成的。首先进行蛇身的移动,分为以下这些步骤。若蛇身超出长度,则需要抹除尾巴一格。
主程序
1. int main()
2. {
3. BasicClass *basic = new BasicClass();
4. GameManager *manager = new GameManager(basic);
5. //循环游戏更新
6. while (true)
7. {
8. //监听按键
9. basic->GetKeyDown();
10. if (!manager->Update())
11. break;
12. }
13. return 0;
14. }
主程序中将BasicClass和GameManager的对象实体化,利用while循环来维持程序的运行。循环体中不断进行按键(指令)的监听并调用Update函数更新游戏状态。
程序测试案例
收获与体会
这是我第一次使用书本之外的内容设计程序,也是第一次进行小游戏类的设计。在决定做这个主题后,我在CSDN技术博客中学习到了很多此类游戏的设计思路和一些相关库函数的使用方法,也不断尝试让自己设计的程序更加标准化,从类与函数的命名开始。在编写过程中为了使自身更加清楚也不断地添加程序注释。整个项目并没有特别特别多而繁琐的代码,但我觉得这个项目中涉及的一些思想,各个功能的实现与学习新内容的应用才是我最大的收获。我感觉程序就是用户为了自身与他人的体验从而利用计算机特性来设计的。
在程序编写的过程中,我不断回忆着C++的相关知识,也重新翻开了课本来确认一些模糊的概念。调试程序的过程是煎熬的,一个字符大小处理不当都会出现许多问题,经验不足的我则需要大量的时间来调试。本次课程设计完成后,我对面向对象的概念有了更深的领会,意识到大大小小的游戏、各类应用都可以分为一个个基础的模块、对象来实现,也明白了积累的重要性。