源码下载:点击下载
记得初学编程时,学习的第一个游戏就是贪吃蛇游戏,碍于技术有限,只能在一旁欣赏大神的代码,很疑惑是怎么做到控制蛇的移动和吃食物的,而且别人的程序思路也不是那么容易弄懂的,直到今天也是略知一二,且容我在程序说明中细细道来。
首先,蛇必须有身体,这里依然用的是Easy-X来做蛇身体和地图(障碍物)的绘制。
都是用小正方形来表示。程序中body.h和body.cpp做蛇身体控制部分。Snake.h和Snake.cpp是游戏逻辑控制。
在蛇的每一节身体使用链表的方式进行管理,其中在蛇移动的过程中,很容易发现,在蛇的移动的过程中,看上去只有头和尾的身体节点发生变化,所以我认为,在游戏过程中,没有必要在每一步的移动过程中都重绘一下整个蛇的身体,我们只需要在蛇的前进方向上的第一的身体节点前新绘制一个一个头,并把原来的头作为现在蛇靠最近头的一节身体,最后把原来的尾巴擦掉,把之前靠近尾巴的节身体作为现在的尾巴即可。总之,这样就只进行了蛇一节身体的绘制和擦除工作,这对程序界面响应是有很大帮助的。
当蛇吃到事物后,蛇的身体自动的增长一节但是,这一节身体在蛇身完全通过事物的坐标后才出现。
另外在产生蛇所吃的事物上,使用随机函数产生事物的有效的食物的坐标,即在预定的游戏界面之内。然后调用绘制函数绘制事物即可。
关于地图的绘制,使用读取本地txt文件的方式读取本地的地图数据,小畔在这里在txt文件中用0和1来表示在游戏界面中某一个点是否有障碍。0表示无障碍,1表示有障碍,在程序中读取文件,然后将地图数据存储到数组中,最后绘制地图的函数只需要按照这个数组中的地图蓝板进行绘制即可。地图数据文件如下图。
同时障碍和游戏结束的判断息息相关,使用的是将蛇的身体的每一节身体坐标和地图进行比对,有过在有障碍的坐标处同时出现蛇的身体坐标及判断出蛇碰到了障碍物,游戏结束。
在蛇的移动中,如果没有按方向键的话,蛇会在原有的方向上继续保持移动的态势,所以程序中做的是一个每隔一段时间判断蛇目前的前进方向,然后根据目前的前进方向,按照上面所诉的蛇身体的控制方法进行移动。按键控制反向,只是改变蛇目前的移动方向。
以上是这个游戏中比较重要的几个点。话不多说,直接上代码:
body.h & body.cpp
#ifndef __BODY_H_
#define __BODY_H_
#include
#include
#include
class Body
{
int x;
int y;
int width;
COLORREF color;
int stat;
Body *next;
public:
enum Statu{ON, OFF};
public:
Body(int x = 0, int y = 0, int width = 10, COLORREF color = WHITE, Body *next = NULL, int stat = ON);
void setX(int x);
void setY(int y);
int getX();
int getY();
void setColor(COLORREF color);
COLORREF getColor();
void setNext(Body *next);
Body *getNext();
void setStat(int stat);
int getStat();
void update();
void moveLeft();
void moveRight();
void moveUp();
void moveDown();
};
#endif
#include "body.h"
Body::Body(int x, int y, int width, COLORREF color, Body *next, int stat)
{
this->x = x;
this->y = y;
this->width = width;
this->color = color;
this->next = next;
this->stat = stat;
}
void Body::setX(int x)
{
this->x = x;
}
void Body::setY(int y)
{
this->y = y;
}
int Body::getX()
{
return x;
}
int Body::getY()
{
return y;
}
void Body::setColor(COLORREF color)
{
this->color = color;
}
COLORREF Body::getColor()
{
return color;
}
void Body::setNext(Body *next)
{
this->next = next;
}
Body* Body::getNext()
{
return next;
}
void Body::setStat(int stat)
{
this->stat = stat;
}
int Body::getStat()
{
return stat;
}
void Body::update()
{
if (stat == ON)
{
setfillcolor(WHITE);
setlinecolor(RED);
}
else
{
setfillcolor(BLACK);
setlinecolor(BLACK);
}
fillrectangle(x, y, x+ width, y + width);
}
void Body::moveLeft()
{
x -= 10;
}
void Body::moveRight()
{
x += 10;
}
void Body::moveUp()
{
y -= 10;
}
void Body::moveDown()
{
y += 10;
}
Snake.h & Snake.cpp
#ifndef __SANKE_H_
#define __SNAKE_H_
#include "body.h"
#ifndef _SCREEN_SIZE_
#define _SCREEN_SIZE_
#define WIDTH 640
#define HEIGHT 480
#endif
class Snake
{
Body *head;
int dir;
int foodX;
int foodY;
enum Direction{UP, DOWN, LEFT, RIGHT};
char map[48][65]; //注意地图文件多一个换行符
public:
Snake(int x = 100, int y = 50, int dir = DOWN);
void update();
void setDir(int dir);
void move();
void start();
void getFoodLocation(int &x, int &y);
void createFood();
bool isTouchFood(); //判断是否碰到事物
void eatFoodAddBody(); // 吃食物长身体
bool isGameOver();
void initMap();
bool isTouchMap();
};
#endif
#include "Snake.h"
#include
#include
#include
#include
Snake::Snake(int x, int y, int dir)
{
head = new Body(x, y);
head->setNext(new Body(x,y-10));
this->dir = dir;
foodX = -1;
foodY = -1;
}
void Snake::start()
{
update();
int ch = 0;
createFood(); // 初始食物
while (1)
{
if (_kbhit())
{
ch = _getch(); // 一个方向键要用两个getch才能获取完
if (ch == 0xe0) //如果高位相等
{
ch = _getch(); //那么再获取一个
switch (ch)
{
case 0x004b: if (dir != RIGHT) dir = LEFT; break; // 设置蛇的移动方向
case 0x004d: if (dir != LEFT) dir = RIGHT; break;
case 0x0048: if (dir != DOWN) dir = UP; break;
case 0x0050: if (dir != UP) dir = DOWN; break;
}
}
//move();
}
move();
if (isTouchFood())
{
eatFoodAddBody(); //长身体
createFood(); // 产生新的食物
}
Sleep(100);
}
}
void Snake::setDir(int dir)
{
this->dir = dir;
}
void Snake::update()
{
Body *p = head;
do
{
p->update();
p = p->getNext();
} while (p != NULL);
}
void Snake::move()
{
Body *p = head;
Body *newTail = NULL;
//找到蛇靠近尾部的那个节点
while (p->getNext()->getNext()!= NULL)
{
p = p->getNext();
}
newTail = p;
p = p->getNext();
newTail->setNext(NULL);//将新节点的下一个节点置空
p->setStat(p->OFF); // 将原尾巴擦掉
p->update();
p->setX(head->getX());
p->setY(head->getY());
p->setNext(head->getNext());
head->setNext(p);
//更新蛇的头部
switch (dir)
{
case LEFT: head->moveLeft(); break;
case UP: head->moveUp(); break;
case DOWN: head->moveDown(); break;
case RIGHT: head->moveRight(); break;
}
if (isGameOver())
{
LOGFONT f;
gettextstyle(&f); // 获取当前字体设置
f.lfHeight = 48; // 设?米痔甯叨任? 48
_tcscpy_s(f.lfFaceName, _T("方正舒体")); // 设置字体为(高版本 VC 推荐使用 _tcscpy_s 函数)
f.lfQuality = ANTIALIASED_QUALITY; // 设置输出效果为抗锯齿
settextstyle(&f); // 设置字体样式
outtextxy(100, 200, _T("蛇撞死了 Game Over"));
Sleep(5000);
exit(0);
}
head->update(); // 重绘新的头
}
void Snake::getFoodLocation(int &x, int &y)
{
Body * p = NULL;
int mapy = 0;
int mapx = 0;
srand(time(NULL)); // 设置随机数种子
while (1)
{
x = (rand() % (WIDTH - 10)) / 10 *10; // 在指定的范围内产生随机数(屏幕范围内)并取10的倍数
y = (rand() % (HEIGHT - 10))/ 10 * 10;
p = head;
while (p != NULL) //是否是身体
{
if (p->getX() == x && p->getY() == y)
{
p = head;
break;
}
p = p->getNext();
}
for (int i = 0; i < 48; i++ ) // 是否是墙
{
mapy = i * 10;
for (int j = 0; j < 64; j++)
{
if (map[i][j] == '1')
{
mapx = j * 10;
if (x == mapx && y == mapy)
{
p = head;
break;
}
}
}
if (p == head)
{
break;
}
}
if (p == NULL) //遍历之后,满足食物坐标不是身体坐标的条件,则返回,否则生成新的事物坐标
{
return;
}
}
}
void Snake::createFood()
{
//得到事物坐标
getFoodLocation(foodX, foodY);
setfillcolor(YELLOW);// 设置填充颜色
setlinecolor(GREEN);// 设置划线颜色
fillrectangle(foodX, foodY, foodX + 10, foodY + 10);
}
bool Snake::isTouchFood() //判断是否碰到事物
{
Body *p = head;
if (p->getX() == foodX && p->getY() == foodY)
{
return true; // 如果碰到食物
}
return false;
}
void Snake::eatFoodAddBody()
{
Body *p = head;
//在身体的后面加一个身体,实际上坐标和原尾巴坐标一致,这样就会使蛇在下一次移动长长一节
while ( p->getNext() != NULL)//找到尾巴
{
p = p->getNext();
}
Body *newBody = new Body(p->getX(), p->getY());
p->setNext(newBody);
newBody->setNext(NULL);
}
bool Snake::isGameOver()
{
Body *p = head;
Body *q = p ->getNext();
int x;
int y;
while (q != NULL)
{
x = p->getX();
y = p->getY();
if (x == q->getX() && y == q->getY()) // 头碰到了自己的身体 die
{
return true;
}
if (x < 0 || y < 0 || x >= WIDTH || y >= HEIGHT) // 头撞向边界, 并且头都跑出去了
{
return true;
}
if (isTouchMap()) //碰到了地图
{
return true;
}
q = q->getNext();
}
return false;
}
void Snake::initMap()
{
FILE *fp = NULL;
int x = 0;
int y = 0;
fopen_s(&fp, "Map/map1.txt", "r "); // 只读方式打开文件
if (fp == NULL)
{
printf("地图文件打开失败\n");
return;
}
else
{
printf("地图文件打开成功, 文件编号:%d\n", fp);
}
//fread(map, 1, 64*48, fp);
for (int i = 0; i < 48; i++)
{
for (int j = 0; j < 65; j++)
{
map[i][j]=fgetc(fp);
}
}
//根据地图文件生成地图
setfillcolor(BROWN);
setlinecolor(BLACK);
for (int i = 0; i < 48; i++)
{
y = i*10;
for (int j = 0; j < 64; j++)
{
//printf("%c", map[i][j]);
if (map[i][j] == '1')
{
x = j*10;
fillrectangle(x, y, x + 10, y + 10);
}
}
}
fclose(fp);
}
bool Snake::isTouchMap()
{
Body *p = head;
int px = p->getX();
int py = p->getY();
int mapx = 0;
int mapy = 0;
for (int i = 0; i < 48; i++)
{
mapy = i * 10;
for (int j = 0; j < 64; j++)
{
if (map[i][j] == '1')
{
mapx = j * 10;
if (px == mapx && py == mapy)
{
return true;
}
}
}
}
return false;
}
main.cpp
#include
#include
#include
#include "body.h"
#include "Snake.h"
#ifndef _SCREEN_SIZE_
#define _SCREEN_SIZE_
#define WIDTH 640
#define HEIGHT 480
#endif
using namespace std;
int main()
{
initgraph(640, 480, SHOWCONSOLE);
Snake snake;
snake.initMap();
snake.start();
_getch();
return 0;
}
程序运行效果如下: