一、各个类之间的关系

之前学软件工程不努力,现在徒伤悲啊,不会用visio画类图…大致的类、方法之间的关系就是这样。

Java实现贪吃蛇心得 java贪吃蛇思路_sed

二、游戏界面

Java实现贪吃蛇心得 java贪吃蛇思路_游戏_02

三、具体实现:

1.游戏窗口

要考虑的问题有:

a.窗口的名字,大小,位置;

b.添加的组件有哪些;

c.设置点击关闭窗口之后不仅要关掉窗口还要消除后台进程;

如何看后台进程是否还在运行?

打开任务管理器:

Java实现贪吃蛇心得 java贪吃蛇思路_游戏_03

//首先对于第一个问题:
//便于以后方便修改设置为static final
public static final int WINDOWWIDTH = 640;
public static final int WINDOWHEIGHT = 480;
public static final int GAMETABLEWIDTH = 400;//游戏区域,也就是蓝色部分;

this.setLayout(null);//窗口默认的是边布局管理器,要自定义大小等就需要将参数设置为null,然后自己设置setBounds;

this.setTitle("贪吃蛇");//用这个函数可以设置窗口名字;
this.setBounds(20,20,width,height);//这个函数可以设置窗口大小及其位置;
//x,y为左上角在容器(窗口)中的坐标,我们设置成20,20;

//但是要窗口在屏幕居中显示的话,就要用到另一个函数获取屏幕大小;
Demension d = ToolKit.getDefaultToolKit().getScreenSize();
//于是,上面的setBounds的参数设置:width = (d.width-WINDOWWIDTH )/2;height = (d.height - WINDOWHEIGHT)/2;
//要添加的组件有:
private JButton btStart;//开始
private JButton btPause;//暂停
private JButton btStop;//结束
private label lbScore;//得分
private GreedySnakeGameTable gsgt;//游戏区域,画布(canvas)类型

//添加组件三部曲:
//1.new出来
btStart = new JButton("开始");
//2.设置边界
btStart.setBounds(500,50,60,20);
//3.添加组件到窗口
this.add(btStart);
//4.添加组件触发事件(可有可无)
btStart.addActionListener(new ActionAdapter(){
    gsgt.start();//这个start方法在GreedySnakeGameTable类中定义;
    //gsgt.pause();
    //gsgt.stop();
})
//其他两个按钮类似就不赘述了;

//分数标签的设置
lbScore = new JLabel("0");//初始为0
lbScore.setBounds(500, 200, 60, 20);
this.add(lbScore);
//要是键盘可控显示的数字的话就要用textField,例如在控制计算器的操作数;

//游戏区域
gsgt = new GreedySnakeGameTable();
gsgt.setBounds(20, 20, GAMETABLEWIDTH + 1, GAMETABLEWIDTH + 1);//这里加一我也不记得为什么了哈哈哈
this.add(gsgt);
//对于后台线程的关闭处理:
//第一种方法:添加一个监听器,然后覆盖窗口监听器里的closed方法,这里使用的是匿名类匿名对象的方法,适用于只出现一次的类,还适用于覆盖接口里的方法,因为接口的对象不能new;
this.addWindowListener(new WindowAdapter(){
    public void windowClosed(WindowEvent arg0) {
        System.exit(0);             
    }
})
第二种:
 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

2.游戏的各个组成

要考虑的问题有:
1.蛇如何产生,大小;
2.食物的产生,而且如何保证不长在蛇身上;
3.蛇定义好之后如何显示;
4.食物的显示;
5.蛇如何移动;
6.如何使键盘可以控制蛇的移动;
7.如何实现多次连续按下键盘,蛇的加速;
8.关于蛇的优化,例如撞墙会GAME OVER,自己撞自己会GAME OVER;
9.对了还有游戏画布的基本设置,颜色等;

//1.蛇的产生:考虑到蛇是变长的,设置成数组不好定义数组大小,因此设置为由点Point组成的集合ArrayList;然后用一个for循环,一段一段显示蛇的身体;

private void initSnakeBody()
{
    sb = new ArrayList<Point>();
    for(int i = 0;i < 4;i++)
    {
        sb.add(new Point(GAMEWIDTH/2+1-i,GAMEWIDTH/2));
        //游戏画布400,逻辑上分成20个小格子,每个格子的边长是GAMEWIDTH = 20,为了让其居中显示;
    }
}
//2.食物的产生:食物是随机产生的,也是一个点,有x和y坐标;还要确保食物不长在蛇身上;
private void initFood()
{
    int x = rand.nextInt(GAMEWIDTH);
    int y = rand.nextInt(GAMEWIDTH);//这里nextInt(n)产生一个0-n的随机数;
    while(true)
    {
        for(int i = 0;i < sb.size();i++)
        {
            Point p = sb.get(i);//这里get是Point里的一个方法,返回点的横纵坐标;
            if(p.x == x&&p.y == y)
            {
                break;
            }
        }
        if(i == sb.size())
        {//如果是正常退出for循环
            break;
        }
        //如果是因为if语句退出的循环,则重新产生食物点;
        int x = rand.nextInt(GAMEWIDTH);
        int y = rand.nextInt(GAMEWIDTH);
    }
    food = new Point(x, y);
}
//3.蛇的显示:设置蛇的颜色,主要由两个函数实现:
public void PaintSnake(Graphics g)
{
    for(int i = 0;i < sb.size();i++)
    {
        g.setColor(new Color(255,0,0));
        //这里三个参数为RGB值,可以自己指定;
        p = sb.get(i);
        g.fillOval(p.x*SNAKEBODYWIDTH,p.y*SNAKEBODYWIDTH,SNAKEBODYWIDTH,SNAKEBODYWIDTH)
    }
}
//4.画食物也是一样的:
private void paintFood(Graphics g) {
    g.setColor(new Color(200, 0, 0));
    g.fillOval(food.x * SNAKEBODYWIDTH, food.y * SNAKEBODYWIDTH, SNAKEBODYWIDTH, SNAKEBODYWIDTH);

    }
//显示在画布上
public void paint(Graphics g) {
    g.setColor(new Color(55, 196, 185));
    g.fillRect(0, 0, Game.GAMETABLEWIDTH, Game.GAMETABLEWIDTH);//覆盖Paint方法,自定义颜色大小;
    paintFood(g);
    paintSnake(g);
    }
//5.关于蛇的移动:思路是这样的,砍蛇尾,长蛇头,如果吃到食物了,就不砍尾;还有,关于撞墙Over的问题;
//还需要考虑的问题是,蛇移动的方向的确定,这里用一个数组来指定;
Private void SnakeMove()
{
    int[] xlnc = {1,0,-1,0};//右下左上坐标的变化;
    int[] ylnc = {0,-1,0,1};
    Ponit h = sb.get(0);//得到蛇头的点的坐标;
    if(x >= GAMEWIDTH || y>=GAMEWIDTH || x <0 || y <0)
    {
        timer.stop();
        this.removeKeyListener(mka);
        //游戏结束后取消键盘控制;
    }
    if(x == food.x&&y == food.y)
    {
        initFood();
    }
    else
    {
        sb.remove(sb.size() - 1);
    }
    sb.add(0,new Point(x,y));
}
6.键盘控制:设置一个keyListener来转换方向,还需要考虑的问题是,加速的问题;
private class MyKeyAdapter extends KeyAdapter {
        public void keyPressed(KeyEvent e) {
            switch (e.getKeyCode()) {
            case KeyEvent.VK_RIGHT:
                changeDirection(0);
                break;
            case KeyEvent.VK_UP:
                changeDirection(1);
                break;
            case KeyEvent.VK_LEFT:
                changeDirection(2);
                break;
            case KeyEvent.VK_DOWN:
                changeDirection(3);
                break;
            }
}

private void changeDirection(int dir) {
        if (direction != (dir + 2) % 4) {
            if (direction == dir) {
                snakeMove();//如果重复按下同一个方向键,就移动两倍
                repaint();
            }
        direction = dir;
        }
    }
}
//7.对定时器的设置(控制蛇移动的速度,每移动一次,重绘刷新一次)
private void initTimer() {
    timer = new Timer(300, new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            snakeMove();
            repaint();
        }
    });
}
//这里对各个按钮的事件实现:
public void start()
 {
    this.requestFocus();
    timer.start();
}

public void pause() 
{
    this.requestFocus();
    timer.stop();
}

public void stop() 
{
    this.requestFocus();
    timer.stop();
}