这个小游戏截取自本人的计算机图形学课程设计,其中有一个关于二维场景的绘制任务,我把主要精力放在了制作这个贪吃蛇小游戏上,但由于该程序还有其他内容的显示,整体代码量较大,所以这里就只展示贪吃蛇游戏的实现原理。
完整代码见以下链接,包含二维和三维场景的完整绘制:
中国地质大学(武汉)计算机图形学课程设计-C++文档类资源-CSDN文库https://download.csdn.net/download/qq_58010729/85418717?spm=1001.2014.3001.5503
程序运行后,右键鼠标,选择“snake”可以开启贪吃蛇游戏。在原先的坐标系体系下会出现一条模拟的贪吃蛇,而且会在随机的某个地方产生一个黑色方块,代表“食物”:
贪吃蛇游戏初始界面按下P键可以开始游戏,这时贪吃蛇就会自己开始移动,再次按P键则会暂停。通过方向键(这里是W—上,S—下,A—左,D—右)可以控制移动方向;每当头部触碰到食物时,贪吃蛇长度就会增加:
随着食物的摄取,贪吃蛇长度变长如果撞到了边界,则游戏会结束。这时控制台上会显示得分,该得分的含义即为吃到了几个食物:
控制台显示最终得分程序部分,在graphics函数的DrawScene场景下有sanke的判断接口:
1. if (snake) {
2. clear = false;
3. snakestart();
4. }
进入snakestart后,会有一些的新的窗口设置和回调函数:
1. void snakestart() {
2. glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
3. glutDisplayFunc(display);
4. glutKeyboardFunc(keyboard);
5. glutTimerFunc(100, TimerFunc, 0);
6. initializeGame();
7. }
例如在display函数中,主要的绘制在这实现,包括贪吃蛇身体的绘制和食物的随机出现与绘制:
1. void display()
2. {
3. glClear(GL_COLOR_BUFFER_BIT);
4. glMatrixMode(GL_PROJECTION);
5. glLoadIdentity();
6. gluOrtho2D(0, 40, 0, 40);
7. drawText("Press p to start game or pause", 25, 35, 0.0, 0.0, 0.0);
8. drawText("See more in the console", 25, 34, 0.0, 0.0, 0.0);
9. glColor3f(0.0f, 0.0f, 0.0f);
10. glBegin(GL_LINES);
11. glVertex2f(0, 20.0f);
12. glVertex2f(40, 20.0f);
13. glVertex2f(20.0f, 0);
14. glVertex2f(20.0f, 40);
15. glEnd();
16. glMatrixMode(GL_MODELVIEW);
17. drawSnake();
18. drawFood();
19. glutSwapBuffers();
20. }
回到刚才的snakestart,里面的glutTimerFunc设置了刷新频率,回调函数设置如下,这里实际上就是以定期的刷屏实现了贪吃蛇的移动效果:
1. void TimerFunc(int val)
2. if (!paused)
3. {
4. moveSnake(direction);
5. }
6. glutTimerFunc(100, TimerFunc, 0);//以后会再来一次
可以看到,只要不是在暂停状态下,函数就会实际调用。执行结束后,由于moveSnake里有glutPostRedisplay,会执行display。由于上一行的glutTimerFunc,会继续执行TimerFunc。
我们知道glutMainLoop的作用是将处理函数按照某种顺序执行,没有新事件则会保持等待。在有glutTimerFunc时,使用glutMainLoop并不是使diplay无限调用(可以用cout验证),只有在状态改变时(如鼠标点击)才会调用display,但即使没有调用,还是会使得窗口一直保持显示而不会退出。加入glutTimerFunc后,相当于设立了一个计时器,如果在里面加入glutPostRedisplay,则会定时调用display。glutTimerFunc执行与display并行,即计时不会等到diplay完成后才开始,如果diplay未完成则等待(可以在TimerFunc里插入cout验证)。
关于贪吃蛇游戏,还有最重要的一点是撞墙算法和吃到食物的。撞墙即代表游戏结束,吃到食物则会使身体长度+1:
1. if (snake_body[0].first <= 0 || snake_body[0].first >= 40 - 1 || snake_body[0].second <= 0 || snake_body[0].second >= 40 - 1)
2. {
3. cout << "由于撞到了边界,游戏结束了" << endl;
4. cout << "您的最终得分为: " << score << endl;
5. exit(0);
6. }
7. int grow = 0;
8. if (snake_body[0].first + delX == food_pos[0] && snake_body[0].second + delY == food_pos[1])
9. {
10. grow = 1;
11. score++;
12. foodAvailable = 0;
13. }
14. snake_body.push_front({ snake_body[0].first + delX,snake_body[0].second + delY });
15. if (!grow)
16. {
17. snake_body.pop_back();
18. }
除此之外还有一些比较重要的算法,例如随机在屏幕中某一位置生成食物的算法。该算法的重点是随机生成,使用rand函数随机生成x、y坐标后,将其赋给食物数组food_pos,后续再调用专门的食物绘制函数food_texture进行绘制,达到了封装的效果:
1. void drawFood()
2. {
3. if (!foodAvailable)
4. {
5. int fx = rand() % (40 - 2) + 1;
6. int fy = rand() % (40 - 2) + 1;
7. int overlap = 1;
8. while (overlap)// make sure no overlap of food with snake body
9. {
10. for (auto part : snake_body)
11. {
12. if (part.first == fx && part.second == fy)
13. {
14. fx = rand() % 40 + 1, fy = rand() % 40 + 1;
15. break;
16. }
17. }
18. overlap = 0;
19. food_pos[0] = fx;
20. food_pos[1] = fy;
21. }
22. }
23. foodAvailable = 1;
24. glColor3f(0, 0, 0);
25. food_texture(food_pos[0], food_pos[1]);
26.
27. }
28. void food_texture(int x, int y)
29. {
30. glBegin(GL_POLYGON);
31. glVertex2f(x, y);
32. glVertex2f(x, y + 1);
33. glVertex2f(x + 1, y + 1);
34. glVertex2f(x + 1, y);
35. glEnd();
36. }
大家对上述程序有疑问的话可以私信询问。