一,具体要求
①实现贪吃蛇游戏基本功能,屏幕上随机出现一个"食物”,称为豆子。上下左右控制"蛇"的移动,吃到“豆子"以后"蛇"的身体加长一点。
②“蛇"碰到边界或蛇头与蛇身相撞,蛇死亡,游戏结束。
③为游戏设计友好的交互界面; 例如欢迎界面,游戏界面,游戏结束界面。
要有开始键、暂停键和停止退出的选项。
④对蛇吃到豆子进行分值计算,可以设置游戏速度,游戏音乐等拓展元素。
具体实现的功能
1.绘制游戏界面。
2.绘制移动的贪吃蛇。
3.随机绘制食物并且位置不与当前蛇身体重合且不超出游戏界面。
4.键盘按键控制蛇的前进方向。
5.文件读写,存入和读取最高分。
食物的生成规则为:
①不能超出游戏区域
②不能在蛇身上
游戏结束时的检测规则为:
①蛇是否超出游戏区域
②蛇头是否撞到自己身上
设计思路
1.地图。地图决定了贪吃蛇的活动区域,电脑屏幕的大小是固定的,如果你不决定地图的大小的话,你的蛇很可能会出去屏幕外面,这样都无法观测到蛇的运行了。
2.蛇头以及蛇身。想想贪吃蛇游戏,除了地图之外,如果没画出来蛇的图形的话,那岂不是黑乎乎的一片,什么也看不到吗。所以用什么来表示和画出蛇的样子,就需要我们去思考了。
3.食物。贪吃蛇,贪吃蛇,不吃食物怎么可以叫做贪吃蛇呢?所以要想想食物是怎么生成的,而且食物也不能生成在蛇的身体上,或者界外。
4.蛇的移动判断。贪吃蛇只有可以移动才能吃到食物,才能真正实现增加长度的功能,并且移动的方向还有移动是否可行的判断,我们都需要自己去考虑一下具体是如何是实现的。
5.显示得分和长度。其实说比较主要的因素,就是蛇和食物以及地图了,其他都是增加游戏的可玩性。
二.基本设计流程
1.地图的生成
应该考虑的是,我们是要固定生成的地图,还是要让玩家决定地图的大小呢?
例如,固定地图的话,我们可以定义一个值为常数的全局变量,无论是地图的生成,还是一些食物生成的判断或者是蛇体撞墙的判断都可以使用那个全局变量。可以用一个宏定义来定义一个最大值N,然后生成地图的话可以用这个N来确定;
-下面是相关的代码,具体用处我在函数中已经注释出来了。 有一点在DEV C++中地图可以定义为map数组,可是到了VS中map数组已经有自己的初始化定义了,所以可能会报错,我们直接定义一个map1就可以解决所有问题了。
还有关于输出数组,如果用二维数组来输出的话,一定一定要在行和列的for循环中间加一个输出换行的符号,要不然会直接输入在同一行上的,所生成的地图出来会变的很不是样子。有兴趣的话可以把printf输出的那个换行注释掉,看一下生成的效果。
关于墙体形状的设置,一定要注意无论定义什么样的墙体,必须登场,字符串字节为1,那么在数组输出空白处的地方一定要是单空格,如果墙体输出的是两个字符的话,在空白处输出的一定要是双空格,要不输出的地图也会非常奇怪。
三.实验过程
初始地图,蛇身定义
public class GamePanel extends JPanel implements ActionListener {
//窗口大小 默认600*600 单位大小为贪吃蛇地图大小 数字越大地图越大 判定点越大。
static final int SCREEN_WIDTH = 600;
static final int SCREEN_HEIGHT = 600;
static final int UNIT_SIZE = 20;
static final int GAME_UNITS = (SCREEN_WIDTH*SCREEN_HEIGHT)/UNIT_SIZE;
static final int DELAY = 100; //DELAY为贪吃蛇移动速度 数字越小 贪吃蛇移动速度越快
final int[] x=new int[GAME_UNITS];//
final int[] y=new int[GAME_UNITS];
int bodyParts = 1;//蛇身初始化长度为1
int applesEaten;//纪录蛇吃了多少苹果
int appleX;//苹果的坐标
int appleY;
char direction = 'R';//默认初始化窗口贪吃蛇的运动方向为向右
boolean running =false;
Timer timer;//引入计时器参数timer
Random random;//随机数random
GamePanel(){//面板构造器
random = new Random();
this.setPreferredSize(new Dimension(SCREEN_WIDTH,SCREEN_HEIGHT));//组件高度长度确定为刚才设定的SCREEN_WIDTH和SCREEN_HEIGHT
this.setBackground(Color.BLACK);//背景面板颜色设置为黑色
this.setFocusable(true);//让窗口可以被操作 改为false 那么无法用键盘操纵蛇
this.addKeyListener(new MyKeyAdapter());//确保窗口内的东西可以被键盘指定键操控
startGame();//开始游戏
}
(2).苹果生成与绘制组建
public void startGame(){
newApple();//生成新苹果
running =true;//开始运行
timer = new Timer(DELAY,this);//计时器启动
timer.start();
}
public void paintComponent(Graphics g){
super.paintComponent(g);//绘制组件
draw(g);//绘制容器
}
3.初始蛇头的创建
public void draw(Graphics g){
if(running){
for(int i = 0;i<SCREEN_HEIGHT/UNIT_SIZE;i++){
g.drawLine(i*UNIT_SIZE,0,i*UNIT_SIZE,SCREEN_HEIGHT);
g.drawLine(0,i*UNIT_SIZE,SCREEN_HEIGHT,i*UNIT_SIZE);//绘制地图 根据窗口大小来画出每次蛇移动的格子
}
g.setColor(Color.red);
g.fillOval(appleX,appleY,UNIT_SIZE,UNIT_SIZE);//苹果占一个格子
for(int i = 0 ; i< bodyParts;i++){
if(i == 0){
g.setColor(Color.green);
g.fillRect(x[i],y[i],UNIT_SIZE,UNIT_SIZE);
}//画出初始化绿色的蛇头位置,前面设定的x[],y[]用来确认蛇头
else{
g.setColor(new Color(45,180,0));//绿色
g.fillRect(x[i],y[i],UNIT_SIZE,UNIT_SIZE);//确认蛇身
}
}
g.setColor(Color.red);
g.setFont(new Font("Ink Free",Font.BOLD,40));
FontMetrics metrics = getFontMetrics(g.getFont());
g.drawString("Score:"+applesEaten,(SCREEN_WIDTH - metrics.stringWidth("Score:"+applesEaten))/2,g.getFont().getSize());//画出分数 分数的位置在窗口最上方中间
}
else{
gameOver(g);
}
}
4.苹果的生成
public void newApple(){
appleX = random.nextInt((int) SCREEN_WIDTH/UNIT_SIZE)*UNIT_SIZE;
appleY = random.nextInt((int) SCREEN_HEIGHT/UNIT_SIZE)*UNIT_SIZE;//随机生成新苹果
}
5.蛇头移动的操作
public void move(){
for(int i = bodyParts;i>0;i--){
x[i] = x[i-1];
y[i] = y[i-1];
}
switch (direction){
case 'U':
y[0] = y [0] - UNIT_SIZE;
break;//当向上移动时,蛇头的纵坐标变为原纵坐标-单位长度
case 'D':
y[0] = y [0] + UNIT_SIZE;
break;//当向下移动时,蛇头的纵坐标变为原纵坐标+单位长度
case 'L':
x[0] = x [0] - UNIT_SIZE;
break;//当向左移动时,蛇头的横坐标变为原横坐标-单位长度
case 'R':
x[0] = x [0] + UNIT_SIZE;
break;//当向右移动时,蛇头的横坐标变为原横坐标+单位长度
}
}
6.蛇身的增长与蛇死亡的条件
public void checkApple(){
if((x[0] == appleX) &&(y[0] == appleY)){
bodyParts++;
applesEaten++;
newApple();//当蛇头坐标与苹果坐标相同时,蛇身变长,分数加一,然后再生成新苹果
}
}
public void checkCollisions(){
//如果蛇头和蛇身相撞 停止运行
for(int i = bodyParts;i>0;i--){
if((x[0] == x[i]) && (y[0] ==y[i])){
running = false;
}
}
//如果蛇头撞到窗口左边界 停止运行
if(x[0] < 0){
running = false;
}
//如果蛇头撞到窗口右边界 停止运行
if (x[0] > SCREEN_WIDTH){
running = false;
}
//如果蛇头撞到窗口上边界 停止运行
if(y[0]<0){
running = false;
}
//如果蛇头撞到窗口下边界 停止运行
if(y[0]>SCREEN_HEIGHT){
running = false;
}
//如果停止运行,则计时器也停止
if (!running){
timer.stop();
}
}
7.游戏结束时的界面显示与得分情况
public void gameOver(Graphics g){
//当游戏失败时,也显示分数
g.setColor(Color.red);
g.setFont(new Font("Ink Free",Font.BOLD,40));
FontMetrics metrics1 = getFontMetrics(g.getFont());
g.drawString("Score:"+applesEaten,(SCREEN_WIDTH - metrics1.stringWidth("Score:"+applesEaten))/2,g.getFont().getSize());
//游戏失败时,显示gameover
g.setColor(Color.red);
g.setFont(new Font("Ink Free",Font.BOLD,75));
FontMetrics metrics2 = getFontMetrics(g.getFont());
g.drawString("Game Over",(SCREEN_WIDTH - metrics2.stringWidth("Game Over"))/2,SCREEN_HEIGHT/2);
}
8.游戏过程检测及设置键盘监听
public void actionPerformed(ActionEvent e) {
if(running){
move();//窗口运行时,让蛇开始运动
checkApple();//检测蛇是否吃到苹果
checkCollisions();//检测蛇头是否撞到了边界或者蛇身
}
repaint();//停止运行就改变画面
}
public class MyKeyAdapter extends KeyAdapter{
//首先我们要弄清楚一件事便是,当蛇前进时,是不能后退的,只能向左或向右,同理,蛇向左运动时,是没有办法向右的。
public void keyPressed(KeyEvent e){
switch (e.getKeyCode()){
case KeyEvent.VK_LEFT:
if(direction!= 'R'){//键盘侦听,接受到的键为←时,如果这时蛇没有向右运动,则方向可变为←
direction='L';
}
break;
case KeyEvent.VK_RIGHT:
if(direction != 'L'){//接受到的键为→时,如果这时蛇没有向左运动,则方向可变为→
direction='R';
}
break;
case KeyEvent.VK_UP:
if(direction != 'D'){//接受到的键为↑时,如果这时蛇没有向下运动,则方向可变为↑
direction='U';
}
break;
case KeyEvent.VK_DOWN:
if(direction != 'U'){//接受到的键为↓时,如果这时蛇没有向右运动,则方向可变为↓
direction='D';
}
break;
case KeyEvent.VK_SPACE:
}
}
}