小白贪吃蛇

小白贪吃蛇大体流程:

通过学习java中的相关类,比如类JPanel、类Random、类Color、类Graphics中的一些相关方法,以及参考一些资料,学习视频等,实现贪吃蛇吃到食物这一小游戏。

Step1

构造游戏窗体类GameFrame

Step2

创建主函数类AppMain

Step3

创建蛇类GameSnake

Step4

创建食物类GameFood

Step5

创建面板类GamePanel

小白贪吃蛇第一天

上午:构造类GameFrame

public class GameFrame extends JFrame {}

类中包含:

1、设置标题

2、窗体大小

3、窗体居中

4、窗体关闭退出程序

5、实例化面板类

6、面板类添加至窗体

package Second_App;

import javax.swing.JFrame;
import java.awt.*;

public class GameFrame extends JFrame {
    public GameFrame() {
        Container c = getContentPane();
        setTitle("小白贪吃蛇");
        //设置标题;
        setSize(520, 520);
        //设置窗体大小;
        setLocationRelativeTo(null);
        //设置窗体居中
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //窗体关闭时退出程序
        GamePanel gp = new GamePanel();
        //实例化面板类
        c.add(gp);
        //将面板类添加至窗体
    }
}
下午:构造主函数

public class AppMain {}

类中包含:

1、新建窗口类的实例对象

2、设置窗口可见

3、固定窗体大小

public class AppMain {
  public static void main(String[] args)
    {
       GameFrame gf = new GameFrame();
       // new出窗口类的实例对象
       gf.setVisible(true);
       // 使得窗口可见,默认不可见
       gf.setResizable(false);
       //固定窗体大小,用户不能自由改变
    }
}

小白贪吃蛇第二天

上午:构造蛇类

public class GameSnake {}

类包含

1、初始化蛇基本属性:蛇身、蛇体每节大小、蛇长、蛇初始方向

public int[] snakeX;//定义保存蛇身体X和Y坐标的两个数组;
public int[] snakeY;
private final int size;//定义蛇身每节大小
public int len;//定义蛇长
private int snakeFX;//定义蛇头方向(程序通过蛇头方向来移动)
public GameSnake() {
        snakeX=new int[300];//为两个数组初始化(300是任意设定,代表蛇最大长度)
        snakeY=new int[300];
        snakeX[0]=100;//为两个数组的前四个变量赋值(代表蛇初始身体所在位置,初始长为4)
        snakeY[0]=100;
        snakeX[1]=80;
        snakeY[1]=100;
        snakeX[2]=60;
        snakeY[2]=100;
        snakeX[3]=40;
        snakeY[3]=100;
        size=20;//为蛇身体节点大小赋值
        snakeFX=39;//为蛇头方向赋值(39是对应键盘右键Code值,即蛇初始化方向为右)
        len=4;//为蛇长赋值(代表初始长,可任意设置,若修改则需修改上面数组初始值)
    }

2、三个方法分别是:返回蛇头的方向、设置蛇头的方向、返回蛇蛇头节点大小

public int getSnakeFX()//返回蛇头方向
    {
        return snakeFX;
    }
    public void setSnakeFX(int snakeFX)//设置蛇头方向
    {
        this.snakeFX = snakeFX;
    }
    public int getSize()//返回蛇蛇头节点大小
    {
        return size;
    }

3、蛇死亡方法

 public boolean isDie()// 蛇的死亡方法,返回布尔值
    {
        // for循环遍历,注意从1开始,不是0--->SnakeX[0]被设为"蛇头"了,具体看下
        for (int i = 1; i < snakeX.length; i++)
        {
            if(snakeX[0]==snakeX[i]&&snakeY[0]==snakeY[i])// 判断蛇头是否碰到身体
            {
                // 弹出消息框
                JOptionPane.showMessageDialog(null, "撞到自己了!!!");
                return true;
            }
            if(snakeX[0]<0||snakeX[0]>520||snakeY[0]<0||snakeY[0]>520)// 判断蛇头是否出界
            {
                // 弹出消息框
                JOptionPane.showMessageDialog(null, "碰到墙壁了!!!");
                return true;
            }
        }
        return false;
    }
下午:构造食物类

public class GameFood {}

类包含:

1、食物基本属性

private final int size;//设置食物基本属性,食物的大小
private int foodX;//食物的两个坐标
private int foodY;
private final Random r;// 引入类Random,随机生成食物坐标
public GameFood()
{
        // 将蛇类作为自己的成员变量,这样做的目的是需要引用蛇类的方法和变量
    GameSnake snake = new GameSnake();//new出蛇类实例对象
    r=new Random();//new出Random类实例对象
    size=20;//为size赋值
    foodX=r.nextInt(24)*20;//为两个食物坐标赋值
    foodY=r.nextInt(24)*20;
    Collision(snake.snakeX, snake.snakeY);//调用Collision方法确保生成的食物坐标与蛇身不会碰撞
}

2、Collision方法

public void Collision(int[] arr1,int[] arr2)
{
    // 这个方法需要传入两个数组作为参数,分别是蛇身体的X和Y坐标
    for (int i = 0; i < arr1.length; i++)
    {
        // 如果发生碰撞则执行if块内的语句,会再次生成食物的坐标,并递归调用
        if(getFoodX()==arr1[i]&&getFoodY()==arr2[i])
        {
                foodX=r.nextInt(24)*20;
                foodY=r.nextInt(24)*20;
                Collision(arr1,arr2);
        }
    }
}

3、设置并返回食物两个坐标,以及返回食物大小

//设置食物两个坐标;
public void setFoodX(int foodX)
{
    this.foodX = foodX;
}

public void setFoodY(int foodY)
{
    this.foodY = foodY;
}
//返回食物两个坐标
public int getFoodX()
{
    return foodX;
}

public int getFoodY()
{
    return foodY;
}
//返回食物大小
public int getSize()
{
    return size;
}

小白贪吃蛇第三天

上午:构造面板类step1

public class GamePanel extends JPanel {}

类包含:

1、GamePanel方法

2、重写面板类的画图方法

3、内部类实现多线程

GamePanel方法
public GamePanel(){}

包含:

new出两个类的实例对象

开启线程

设置焦点

匿名内部类添加键盘监听事件this.addKeyListener(new KeyAdapter(){
@Override
public void keyPressed(KeyEvent e){}
}

public GamePanel()
    {
        // new出两个类的实例对象
        gf = new GameFood();
        snake = new GameSnake();
        // 开启线程
        new Move().start();
        // 设置焦点,这行代码是很必要的,因为默认的焦点在窗体,需要设置到这个面板中来
        this.setFocusable(true);
        // 通过匿名内部类添加键盘监听事件
        this.addKeyListener(new KeyAdapter()
        {
            @Override
            public void keyPressed(KeyEvent e)
            {
                // 根据玩家按压键盘所对应的Code值做出相应的处理
                switch (e.getKeyCode())
                {
                    // 37-40分别对应右、上、左、下,前面if语句的作用是不支持蛇头的180度转向
                    case 37:
                        if (snake.getSnakeFX() != 39)
                            snake.setSnakeFX(37);
                        break;
                    case 38:
                        if (snake.getSnakeFX() != 40)
                            snake.setSnakeFX(38);
                        break;
                    case 39:
                        if (snake.getSnakeFX() != 37)
                            snake.setSnakeFX(39);
                        break;
                    case 40:
                        if (snake.getSnakeFX() != 38)
                            snake.setSnakeFX(40);
                        break;
                    default:
                        break;
                }
            }
        });
    }
下午:构造面板类step2

重写面板类的画图方法
@Override
public void paint(Graphics g){}

包含:

背景色

画背景

食物色

画食物

蛇头色

画蛇头

蛇身色

for循环画蛇身

@Override
    public void paint(Graphics g)
    {
        // 设置背景色为绿色
        g.setColor(Color.green);
        // 画背景
        g.fillRect(0, 0, 500, 500);
        // 设置食物为黑色
        g.setColor(Color.black);
        // 画食物
        g.fillRect(gf.getFoodX(), gf.getFoodY(), gf.getSize(), gf.getSize());
        // 设置蛇头为红色
        g.setColor(Color.red);
        // 画蛇头
        g.fillRect(snake.snakeX[0], snake.snakeY[0], snake.getSize(), snake.getSize());
        // 设置蛇身为蓝色,可自己选择
        g.setColor(Color.blue);
        // 通过for循环画蛇身
        for (int i = 1; i < snake.len; i++)
        {
            g.fillRect(snake.snakeX[i], snake.snakeY[i], snake.getSize(), snake.getSize());
        }
    }

小白贪吃蛇第四天

上午:构造面板类step3

内部类实现多线程

class Move extends Thread{}

类包含:

线程休眠时间

移动蛇身

吃到食物

判断是否死亡

重绘方法

class Move extends Thread
    {
        @Override
        public void run()
        {
            while (true)
            {
                try
                {
                    // 设置线程休眠时间,400是任意的,越小就会执行的越快,相当于增加难度
                    Thread.sleep(400);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                // 移动蛇身,一定要放在移动蛇头的前面,不然会导致蛇头坐标的丢失,读者可以自行尝试
                for (int i = snake.len; i > 0; i--)
                {
                    // for循环的遍历需要从大到小,否则导致坐标值的覆盖变为一点
                    snake.snakeX[i] = snake.snakeX[i - 1];
                    snake.snakeY[i] = snake.snakeY[i - 1];
                }
                // 根据方向移动蛇头
                switch (snake.getSnakeFX())
                {
                    case 37:
                        snake.snakeX[0] -= 20;
                        break;
                    case 38:
                        snake.snakeY[0] -= 20;
                        break;
                    case 39:
                        snake.snakeX[0] += 20;
                        break;
                    case 40:
                        snake.snakeY[0] += 20;
                        break;
                    default:
                        break;
                }
                // 吃到食物
                if (snake.snakeX[0] == gf.getFoodX() && snake.snakeY[0] == gf.getFoodY())
                {
                    // 长度增加
                    snake.len++;
                    // 重新生成食物
                    Random r = new Random();
                    gf.setFoodX(r.nextInt(24) * 20);
                    gf.setFoodY(r.nextInt(24) * 20);
                    // 确保新生成的食物不会碰撞到蛇
                    gf.Collision(snake.snakeX, snake.snakeY);
                }
                // 判断是否死亡,如果死亡则结束while循环,相当于结束了这个线程
                if (snake.isDie())
                    break;
                // 重绘方法,也是很必要的方法,如果没有的话看不到动的效果
                repaint();
            }
        }
    }
下午:运行检测

总代码:
GameFrame.java

package Second_App;

import javax.swing.JFrame;
import java.awt.*;

public class GameFrame extends JFrame {
    public GameFrame() {
        Container c = getContentPane();
        setTitle("小白贪吃蛇");//设置标题;
        setSize(520, 520);//设置窗体大小;
        setLocationRelativeTo(null);//设置窗体居中
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//窗体关闭时退出程序
        GamePanel gp = new GamePanel();//实例化面板类
        c.add(gp);//将面板类添加至窗体
    }
}

AppMain.java

package Second_App;

public class AppMain {
    public static void main(String[] args)
    {
        GameFrame gf = new GameFrame();// new出窗口类的实例对象
        gf.setVisible(true);// 使得窗口可见,默认是不可见的
        gf.setResizable(false);//固定窗体大小,用户不能自由改变
    }
}

GameSnake.java

package Second_App;

import javax.swing.JOptionPane;

public class GameSnake {

    public int[] snakeX;//定义保存蛇身体X和Y坐标的两个数组;
    public int[] snakeY;
    private final int size;//定义蛇身每节大小
    public int len;//定义蛇长
    private int snakeFX;//定义蛇头方向(程序通过蛇头方向来移动)
    public GameSnake()
    {
        snakeX=new int[300];//为两个数组初始化(300是任意设定,代表蛇最大长度)
        snakeY=new int[300];
        snakeX[0]=100;//为两个数组的前四个变量赋值(代表蛇初始身体所在位置,初始长为4)
        snakeY[0]=100;
        snakeX[1]=80;
        snakeY[1]=100;
        snakeX[2]=60;
        snakeY[2]=100;
        snakeX[3]=40;
        snakeY[3]=100;
        size=20;//为蛇身体节点大小赋值
        snakeFX=39;//为蛇头方向赋值(39是对应键盘右键Code值,即蛇初始化方向为右)
        len=4;//为蛇长赋值(代表初始长,可任意设置,若修改则需修改上面数组初始值)
    }
    public int getSnakeFX()//返回蛇头方向
    {
        return snakeFX;
    }
    public void setSnakeFX(int snakeFX)//设置蛇头方向
    {
        this.snakeFX = snakeFX;
    }
    public int getSize()//返回蛇蛇头节点大小
    {
        return size;
    }
    public boolean isDie()// 蛇的死亡方法,返回布尔值
    {
        // for循环遍历,注意从1开始,不是0--->SnakeX[0]被设为"蛇头"了,具体看下
        for (int i = 1; i < snakeX.length; i++)
        {
            if(snakeX[0]==snakeX[i]&&snakeY[0]==snakeY[i])// 判断蛇头是否碰到身体
            {
                // 弹出消息框
                JOptionPane.showMessageDialog(null, "撞到自己了!!!");
                return true;
            }
            if(snakeX[0]<0||snakeX[0]>520||snakeY[0]<0||snakeY[0]>520)// 判断蛇头是否出界
            {
                // 弹出消息框
                JOptionPane.showMessageDialog(null, "碰到墙壁了!!!");
                return true;
            }
        }
        return false;
    }
}

GameFood.java

package Second_App;

import java.util.Random;

public class GameFood {

    private final int size;//设置食物基本属性,食物的大小
    private int foodX;//食物的两个坐标
    private int foodY;
    private final Random r;// 引入类Random,随机生成食物坐标
    public GameFood()
    {
        // 将蛇类作为自己的成员变量,这样做的目的是需要引用蛇类的方法和变量
        GameSnake snake = new GameSnake();//new出蛇类实例对象
        r=new Random();//new出Random类实例对象
        size=20;//为size赋值
        foodX=r.nextInt(24)*20;//为两个食物坐标赋值
        foodY=r.nextInt(24)*20;
        Collision(snake.snakeX, snake.snakeY);//调用Collision方法确保生成的食物坐标与蛇身不会碰撞
    }
    // 如果食物生成时碰撞到蛇,则递归调用重新生成,确保不会在食物的生成是与蛇发生碰撞
    public void Collision(int[] arr1,int[] arr2)
    {
        // 这个方法需要传入两个数组作为参数,分别是蛇身体的X和Y坐标
        for (int i = 0; i < arr1.length; i++)
        {
            // 如果发生碰撞则执行if块内的语句,会再次生成食物的坐标,并递归调用
            if(getFoodX()==arr1[i]&&getFoodY()==arr2[i])
            {
                foodX=r.nextInt(24)*20;
                foodY=r.nextInt(24)*20;
                Collision(arr1,arr2);
            }
        }
    }
    //设置食物两个坐标;
    public void setFoodX(int foodX)
    {
        this.foodX = foodX;
    }

    public void setFoodY(int foodY)
    {
        this.foodY = foodY;
    }
    //返回食物两个坐标
    public int getFoodX()
    {
        return foodX;
    }

    public int getFoodY()
    {
        return foodY;
    }
    //返回食物大小
    public int getSize()
    {
        return size;
    }
}

GamePanel.java

package Second_App;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Random;
import javax.swing.JPanel;

public class GamePanel extends JPanel {
    // 为了使用食物类和蛇类的变量和方法,将它们定义为成员变量
    private final GameFood gf;
    private final GameSnake snake;
    public GamePanel()
    {
        // new出两个类的实例对象
        gf = new GameFood();
        snake = new GameSnake();
        // 开启线程
        new Move().start();
        // 设置焦点,这行代码是很必要的,因为默认的焦点在窗体,需要设置到这个面板中来
        this.setFocusable(true);
        // 通过匿名内部类添加键盘监听事件
        this.addKeyListener(new KeyAdapter()
        {
            @Override
            public void keyPressed(KeyEvent e)
            {
                // 根据玩家按压键盘所对应的Code值做出相应的处理
                switch (e.getKeyCode())
                {
                    // 37-40分别对应右、上、左、下,前面if语句的作用是不支持蛇头的180度转向
                    case 37:
                        if (snake.getSnakeFX() != 39)
                            snake.setSnakeFX(37);
                        break;
                    case 38:
                        if (snake.getSnakeFX() != 40)
                            snake.setSnakeFX(38);
                        break;
                    case 39:
                        if (snake.getSnakeFX() != 37)
                            snake.setSnakeFX(39);
                        break;
                    case 40:
                        if (snake.getSnakeFX() != 38)
                            snake.setSnakeFX(40);
                        break;
                    default:
                        break;
                }
            }
        });
    }
    // 重写面板类的画图方法
    @Override
    public void paint(Graphics g)
    {
        // 设置背景色为绿色
        g.setColor(Color.green);
        // 画背景
        g.fillRect(0, 0, 500, 500);
        // 设置食物为黑色
        g.setColor(Color.black);
        // 画食物
        g.fillRect(gf.getFoodX(), gf.getFoodY(), gf.getSize(), gf.getSize());
        // 设置蛇头为红色
        g.setColor(Color.red);
        // 画蛇头
        g.fillRect(snake.snakeX[0], snake.snakeY[0], snake.getSize(), snake.getSize());
        // 设置蛇身为蓝色,可自己选择
        g.setColor(Color.blue);
        // 通过for循环画蛇身
        for (int i = 1; i < snake.len; i++)
        {
            g.fillRect(snake.snakeX[i], snake.snakeY[i], snake.getSize(), snake.getSize());
        }
    }
    // 定义内部类实现多线程
    class Move extends Thread
    {
        @Override
        public void run()
        {
            while (true)
            {
                try
                {
                    // 设置线程休眠时间,400是任意的,越小就会执行的越快,相当于增加难度
                    Thread.sleep(400);
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                // 移动蛇身,一定要放在移动蛇头的前面,不然会导致蛇头坐标的丢失,读者可以自行尝试
                for (int i = snake.len; i > 0; i--)
                {
                    // for循环的遍历需要从大到小,否则导致坐标值的覆盖变为一点
                    snake.snakeX[i] = snake.snakeX[i - 1];
                    snake.snakeY[i] = snake.snakeY[i - 1];
                }
                // 根据方向移动蛇头
                switch (snake.getSnakeFX())
                {
                    case 37:
                        snake.snakeX[0] -= 20;
                        break;
                    case 38:
                        snake.snakeY[0] -= 20;
                        break;
                    case 39:
                        snake.snakeX[0] += 20;
                        break;
                    case 40:
                        snake.snakeY[0] += 20;
                        break;
                    default:
                        break;
                }
                // 吃到食物
                if (snake.snakeX[0] == gf.getFoodX() && snake.snakeY[0] == gf.getFoodY())
                {
                    // 长度增加
                    snake.len++;
                    // 重新生成食物
                    Random r = new Random();
                    gf.setFoodX(r.nextInt(24) * 20);
                    gf.setFoodY(r.nextInt(24) * 20);
                    // 确保新生成的食物不会碰撞到蛇
                    gf.Collision(snake.snakeX, snake.snakeY);
                }
                // 判断是否死亡,如果死亡则结束while循环,相当于结束了这个线程
                if (snake.isDie())
                    break;
                // 重绘方法,也是很必要的方法,如果没有的话看不到动的效果
                repaint();
            }
        }
    }
}

小蛇运行起来啦。嘻嘻
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

后续

代码修改及功能添加
Update1:边界修改->下、右边界

在这里插入图片描述
运行此代码,结果如下:
在这里插入图片描述

在这里插入图片描述
此部分代码下、右边界处理不合适,导致蛇头及蛇身进去才判断撞到墙壁,作以下修改:
在这里插入图片描述
运行效果:
在这里插入图片描述
在这里插入图片描述

Update2:吃一食物,蛇身减一

在这里插入图片描述
运行效果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Add1:计分->吃一食物,分数加一

添部分代码:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
运行代码,效果
在这里插入图片描述在这里插入图片描述

Add2:穿墙

在这里插入图片描述
运行效果:
在这里插入图片描述
在这里插入图片描述
总结:

虽然基本功能已经实现,但如背景音乐等其他高级功能,就得去查阅相应资料及学习相关视频教程。临近寒假,备考期末。相关高级功能,学习后会相应补充。

小白愿与大家共同进步,学习交流。欢迎大家留言!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值