手把手教你用 Java 写出贪吃蛇小游戏(附源码)

一:窗口的绘制

1.1 定义窗口类------SnakeGame

java 复制代码
import javax.swing.*;

public class SnakeGame extends JFrame {

}

1.2 设置窗口的参数

java 复制代码
import javax.swing.*;

public class SnakeGame extends JFrame {
    public void launch(){
        //设置窗口是否可见
        this.setVisible(true);
        //设置窗口的大小
        this.setSize(600,600);
        //设置窗口的位置在屏幕上居中
        this.setLocationRelativeTo(null);
        //设置窗口的标题
        this.setTitle("贪吃蛇");
    }

    public static void main(String[] args) {
        GameWin gameWin = new GameWin();
        gameWin.launch();
    }
}

1.3 启动main方法

二:窗口网格的绘制

java 复制代码
	@Override
    public void paint(Graphics g) {
        //灰色背景
        g.setColor(Color.gray);
        //绘制矩形,和窗口一样,宽和高都是600
        g.fillRect(0,0,600,600);
        //网格线
        g.setColor(Color.black);
        //横线
        for (int i = 0; i <= 20 ; i++) {
            //横线
            g.drawLine(0,i * 30,600,i * 30);
            //竖线
            g.drawLine(i * 30,0,i * 30,600);
        }
    }

2.1 重写paint方法

2.1.1 为什么要重写paint方法

当我们使用JFrame创建出窗体时,遇到窗体的尺寸改变(ie.拉伸窗体),或者窗体的部分像素被移动到屏幕之外,都会导致窗体的刷新。窗体有一个系统自带的刷新方法。但这时如果窗体中有此前绘制出的图像,则图像会随窗体的刷新而消失,这时候我们就需要将绘制图像的方法重写入JFrame的paint方法中,让图像随窗体的刷新而同步再次被绘制出来。

2.1.2 实现方式

函数要在类继承JFrame或者JPanel两个属性下才能实现重写,并且这个方法是系统自动调用的。 重写绘制方法的本质是将图像数据化、对象化。将图像的属性(eg. 坐标、颜色、图形类型......)和其绘制方法写入paint()。

2.2 Graphics

  1. ​实际生活中如果需要画图,首先我们得准备一张纸,然后在拿一支画笔,配和一些颜色,就可以在纸上画出来各种各样的图形,例如圆圈、矩形等等。

  2. 程序中绘图也一样,也需要画布,画笔,颜料等等。AWT中提供了Canvas类充当画布,提供了Graphics类来充当画笔,通过调用Graphics对象的setColor()方法可以给画笔设置颜色。

2.3 设置网格线的参数

java 复制代码
		//灰色背景
        g.setColor(Color.gray);
        //绘制矩形,和窗口一样,宽和高都是600
        g.fillRect(0,0,600,600);
        //网格线颜色
        g.setColor(Color.black);
        //绘制网格线
        for (int i = 0; i <= 20 ; i++) {
            //横线
            g.drawLine(0,i * 30,600,i * 30);
            //竖线
            g.drawLine(i * 30,0,i * 30,600);
        }

三:游戏物体父类的创建------SnakeEntity

java 复制代码
import com.sysg.game.sanke.SnakeGame;
import java.awt.*;

public class SnakeEntity{

    //图片
    Image img;
    //坐标
    int x;
    int y;
    //宽高
    int width = 30;
    int height = 30;
    //窗口类的引用
    GameWin frame;

    public Image getImg() {
        return img;
    }

    public void setImg(Image img) {
        this.img = img;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public GameWin getFrame() {
        return frame;
    }

    public void setFrame(GameWin frame) {
        this.frame = frame;
    }

    public SnakeEntity() {
    }

    public SnakeEntity(Image img, int x, int y, int width, int height, GameWin frame) {
        this.img = img;
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        this.frame = frame;
    }

    //绘制自身
    public void paintSelf(Graphics g){
        g.drawImage(img,x,y,null);
    }
}

四:游戏工具类的创建------SnakeUtil

java 复制代码
import java.awt.*;
public class SnakeUtil{
    //蛇头
    public static Image upImg = Toolkit.getDefaultToolkit().getImage("img/up.png");
    public static Image downImg = Toolkit.getDefaultToolkit().getImage("img/down.png");
    public static Image leftImg = Toolkit.getDefaultToolkit().getImage("img/left.png");
    public static Image rightImg = Toolkit.getDefaultToolkit().getImage("img/right.png");
    //蛇身
    public static Image bodyImg = Toolkit.getDefaultToolkit().getImage("img/body.png");
    //食物
    public static Image foodImg = Toolkit.getDefaultToolkit().getImage("img/food.png");

    //绘制文字
    public static void drawWord(Graphics g,String str,Color color,int size,int x,int y){
        g.setColor(color);
        g.setFont(new Font("仿宋",Font.BOLD,size));
        g.drawString(str,x,y);
    }
}

4.1 获取蛇头的图片

java 复制代码
	public static Image upImg = Toolkit.getDefaultToolkit().getImage("img/up.png");
    public static Image downImg = Toolkit.getDefaultToolkit().getImage("img/down.png");
    public static Image leftImg = Toolkit.getDefaultToolkit().getImage("img/left.png");
    public static Image rightImg = Toolkit.getDefaultToolkit().getImage("img/right.png");

4.2 获取蛇身的图片

java 复制代码
public static Image bodyImg = Toolkit.getDefaultToolkit().getImage("img/body.png");

4.3 获取食物的图片

java 复制代码
public static Image foodImg = Toolkit.getDefaultToolkit().getImage("img/food.png");

4.4 绘制文字

java 复制代码
	//绘制文字
    public static void drawWord(Graphics g,String str,Color color,int size,int x,int y){
        g.setColor(color);
        g.setFont(new Font("仿宋",Font.BOLD,size));
        g.drawString(str,x,y);
    }

五:蛇头部的绘制------SnakeHeadEntity

java 复制代码
import java.awt.*;

public class SnakeHeadEntity extends SnakeEntity{
    //方向 up down left right
    private String direction = "right";

    public String getDirection() {
        return direction;
    }

    public void setDirection(String direction) {
        this.direction = direction;
    }

    public SnakeHeadEntity (Image img, int x, int y, GameWin frame) {
        super(img, x, y, frame);
    }

    @Override
    public void paintSelf(Graphics g) {
        super.paintSelf(g);
    }
}

5.1 设置蛇头的方向

arduino 复制代码
//方向 up down left right
private String direction = "right";

默认蛇头的方向是向右的!

5.2 绘制自身

java 复制代码
    @Override
    public void paintSelf(Graphics g) {
        super.paintSelf(g);
    }

六:蛇头的简单移动

6.1 在HeadObj添加move方法

java 复制代码
/**
     * 蛇的移动
     * 蛇身体的移动的代码一定要写在蛇头移动的前面
     */
    public void  move(){
        //蛇头的移动
        switch (direction) {
            case "up":
                y -= height;
                break;
            case "down":
                y += height;
                break;
            case "left":
                x -= width;
                break;
            case "right":
                x += width;
                break;
            default:
                break;
        }

    }

6.2 在paintSelf方法中添加move方法

java 复制代码
@Override
    public void paintSelf(Graphics g) {
        //设置蛇头的位置
        super.paintSelf(g);
        //移动蛇头
        move();
    }

6.3 蛇的重复移动需要调用repaint()方法

java 复制代码
 /**
     * 窗口的绘制
     */
    public void launch(){
        //设置窗口是否可见、默认是false不可见
        this.setVisible(true);
        //设置窗口的大小
        this.setSize(winWidth,winHeight);
        //设置窗口的位置在屏幕居中
        this.setLocationRelativeTo(null);
        //设置窗口的标题
        this.setTitle("贪吃蛇");  
        //移动蛇头
        while(true){
           // repaint()方法是一个具有刷新页面效果的方法,若不调用repaint方法图形发生变化后不会立刻显示
           repaint();
            try {
                //一秒会调用五次
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

七:键盘控制蛇的方向

7.1 在SnakeHeadEntity中添加键盘监听事件

java 复制代码
public SnakeHeadEntity(Image img, int x, int y, SnakeGame frame) {
        super(img, x, y, frame);
        //监听键盘
        this.frame.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                //控制蛇头的移动
                changeDirection(e);
            }
        });
    }

7.2 编写changeDirection方法

java 复制代码
   /**
     * 控制蛇头的移动
     * w->a->s->d
     */
    public void changeDirection(KeyEvent e) {
        switch (e.getKeyCode()){
            //按下A键->朝左移动
            case KeyEvent.VK_A:
                if(!"right".equals(direction)){
                    //将方向改为左边
                    direction = "left";
                    //修改蛇头图片的方向
                    img = SnakeUtil.leftSnakeHeadImg;
                }
                break;

            //按下D键->朝右移动
            case KeyEvent.VK_D:
                if(!"left".equals(direction)){
                    //将方向改为右边
                    direction = "right";
                    //修改蛇头图片的方向
                    img = SnakeUtil.rightSnakeHeadImg;
                }
                break;

            //按下W键->朝上移动
            case KeyEvent.VK_W:
                if(!"down".equals(direction)){
                    //将方向改为上边
                    direction = "up";
                    //修改蛇头图片的方向
                    img = SnakeUtil.upSnakeHeadImg;
                }
                break;

            //按下S键->朝下移动
            case KeyEvent.VK_S:
                if(!"up".equals(direction)){
                    //将方向改为上边
                    direction = "down";
                    //修改蛇头图片的方向
                    img = SnakeUtil.downSnakeHeadImg;
                }
                break;
            default:
                break;
        }

    }

八:蛇越界后的处理

1.编写checkIndex()方法

注:标题栏高度为30,所以y<30,不是y<0

java 复制代码
/**
     * 贪吃蛇的越界处理
     */
    public void checkIndex(){
        if(x < 0){
            x = 570;
        } else if (x > 570){
            x = 0;
        } else if (y < 30){
            y = 570;
        } else if (y > 570){
            y = 30;
        }
    }

2.在paintSelf方法中添加checkIndex()方法

java 复制代码
@Override
    public void paintSelf(Graphics g) {
        //设置蛇头的位置
        super.paintSelf(g);
        //移动蛇头
        move();
        checkIndex();
    }

九:蛇身的添加和移动

9.1 新建蛇身实体类------SnakeBodyEntity

java 复制代码
/**
 * 蛇身体
 */
public class SnakeBodyEntity extends SnakeEntity {

    public SnakeBodyEntity(Image img, int x, int y, SnakeGame frame) {
        super(img, x, y, frame);
    }

    @Override
    public void paintSelf(Graphics g) {
        super.paintSelf(g);
    }
}

9.2 在SnakeGame定义蛇身集合

/** * 蛇身集合 */ public List snakeBodyEntityList = new ArrayList<>();

9.3 在launch()方法对蛇身进行初始化

java 复制代码
//蛇身初始化
        snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,30,570,this));
        snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,0,570,this));

9.4 在paint方法绘制蛇身体

防止身体重叠,所以进行反向遍历

java 复制代码
//绘制蛇身体
        for (int i = snakeBodyEntityList.size() - 1; i >= 0; i--) {
            snakeBodyEntityList.get(i).paintSelf(graphicsImage);
        }

9.5 在move方法中添加身体移动逻辑

java 复制代码
//蛇身体的移动
        List<SnakeBodyEntity> snakeBodyEntityList = this.frame.snakeBodyEntityList;
        for (int i = snakeBodyEntityList.size() - 1; i >= 1; i--) {
            //蛇的移动就是上一个坐标和当前坐标相等
            snakeBodyEntityList.get(i).x = snakeBodyEntityList.get(i-1).x;
            snakeBodyEntityList.get(i).y = snakeBodyEntityList.get(i-1).y;
        }

十:食物的随机生成

食物的坐标需要满足x->(0-570),y->(30,570)并且是30的倍数

10.1 新建食物实体类------SnakeFoodEntity

java 复制代码
/**
 * 食物实体类
 */
public class SnakeFoodEntity extends SnakeEntity {

    /**
     * 随机
     */
    Random r = new Random();

    public SnakeFoodEntity() {
        super();
    }

    public SnakeFoodEntity(Image img, int x, int y, SnakeGame frame) {
        super(img, x, y, frame);
    }

    /**
     * 获取食物
     * x:0->570
     * y.3:0->570
     * @return
     */
    public SnakeFoodEntity getFood(){
        return new SnakeFoodEntity(SnakeUtil.snakeFoodImg,r.nextInt(20) * 30,(r.nextInt(19) + 1) * 30,this.frame);
    }

    @Override
    public void paintSelf(Graphics g) {
        super.paintSelf(g);
    }
}

10.2 获取食物

java 复制代码
/**
     * 获取食物
     * x:0->570
     * y.3:0->570
     * @return
     */
    public SnakeFoodEntity getFood(){
        return new SnakeFoodEntity(SnakeUtil.snakeFoodImg,r.nextInt(20) * 30,(r.nextInt(19) + 1) * 30,this.frame);
    }

10.3 在SnakeGame中添加食物实体类

java 复制代码
   /**
     * 食物
     */
    public SnakeFoodEntity snakeFoodEntity = new SnakeFoodEntity().getFood();

10.4 在paint中绘制食物

scss 复制代码
    //食物绘制
    snakeFoodEntity.paintSelf(g);

十一:蛇吃食物和食物的重新生成

11.1 在SnakeHeadEntity中的paintSelf添加获取食物的对象

ini 复制代码
     //食物
    SnakeFoodEntity snakeFoodEntity = this.frame.snakeFoodEntity;

注:此方法需要放在move()之前

11.2 判断蛇头和食物是否重合

java 复制代码
if(this.x == snakeFoodEntity.x && this.y == snakeFoodEntity.y ){
     //此时蛇头和食物重合了
     this.frame.snakeFoodEntity = snakeFoodEntity.getFood();
 }

十二:蛇的增长

蛇的增长本质就是给蛇身添加一个元素,根据集合的最后一个位置确定

12.1 定义身体最后一节的坐标

java 复制代码
//身体最后一节的坐标
        Integer newX = null;
        Integer newY = null;
        if(this.x == snakeFoodEntity.x && this.y == snakeFoodEntity.y ){
            //此时蛇头和食物重合了
            this.frame.snakeFoodEntity = snakeFoodEntity.getFood();
            //获取蛇身的最后一个元素
            SnakeBodyEntity lastSnakeBodyEntity = this.frame.snakeBodyEntityList.get(this.frame.snakeBodyEntityList.size() - 1);
            newX = lastSnakeBodyEntity.x;
            newY = lastSnakeBodyEntity.y;
           
        }

12.2 在move方法后添加元素

java 复制代码
if(newX != null && newY != null){
   this.frame.snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,newX,newY,this.frame));
}

12.3 修改move方法的遍历顺序

java 复制代码
 /**
     * 蛇的移动
     * 蛇身体的移动的代码一定要写在蛇头移动的前面
     */
    public void  move(){
        //蛇身体的移动
        List<SnakeBodyEntity> snakeBodyEntityList = this.frame.snakeBodyEntityList;
        for (int i = snakeBodyEntityList.size() - 1; i >= 1; i--) {
            //蛇的移动就是上一个坐标和当前坐标相等
            snakeBodyEntityList.get(i).x = snakeBodyEntityList.get(i-1).x;
            snakeBodyEntityList.get(i).y = snakeBodyEntityList.get(i-1).y;
        }
        snakeBodyEntityList.get(0).x = this.x;
        snakeBodyEntityList.get(0).y = this.y;
        //蛇头的移动
        switch (direction) {
            case "up":
                y -= height;
                break;
            case "down":
                y += height;
                break;
            case "left":
                x -= width;
                break;
            case "right":
                x += width;
                break;
            default:
                break;
        }

    }

十三:计分面板的编写

13.1 在SnakeGame定义窗口的宽高

ini 复制代码
//窗口宽高
int winWidth = 800;
int winHeight = 600;

注:将之前定义的600,600替换为winWidth ,winHeight

13.2 定义游戏分数,并且在paint绘制出来

arduino 复制代码
//当前分数
public int score = 0;

//分数绘制
SnakeUtil.drawWord(graphicsImage,score  +"分" , Color.BLUE , 50,650,330);

13.3 蛇吃食物,分数加一

在SnakeHeadEntity的paintSelf添加对应方法

java 复制代码
@Override
    public void paintSelf(Graphics g) {
        //设置蛇头的位置
        super.paintSelf(g);
        //食物
        SnakeFoodEntity snakeFoodEntity = this.frame.snakeFoodEntity;
        //身体最后一节的坐标
        Integer newX = null;
        Integer newY = null;
        if(this.x == snakeFoodEntity.x && this.y == snakeFoodEntity.y ){
            //此时蛇头和食物重合了
            this.frame.snakeFoodEntity = snakeFoodEntity.getFood();
            //获取蛇身的最后一个元素
            SnakeBodyEntity lastSnakeBodyEntity = this.frame.snakeBodyEntityList.get(this.frame.snakeBodyEntityList.size() - 1);
            newX = lastSnakeBodyEntity.x;
            newY = lastSnakeBodyEntity.y;
            //分数+1
            this.frame.score ++;
        }
        //移动蛇头
        move();
        if(newX != null && newY != null){
            this.frame.snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,newX,newY,this.frame));
        }
        //贪吃蛇的越界处理,移动后新的身体对象添加到snakeBodyEntityList
        checkIndex();
    }

十四:游戏开始的提示语

14.1 在SnakeGame定义游戏状态

java 复制代码
//游戏状态 0-未开始 1-游戏中 2-暂停 3-失败 4-通关 5-失败后重启 6-下一关
    public static int state = 0;

14.2 在while语句中添加判断,只有状态为1,才需要重复执行repaint()方法

java 复制代码
/**
     * 窗口的绘制
     */
    public void launch(){
        //设置窗口是否可见、默认是false不可见
        this.setVisible(true);
        //设置窗口的大小
        this.setSize(winWidth,winHeight);
        //设置窗口的位置在屏幕居中
        this.setLocationRelativeTo(null);
        //设置窗口的标题
        this.setTitle("贪吃蛇");
        //蛇身初始化
        snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,30,570,this));
        snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,0,570,this));
        //对开始游戏添加键盘事件
        this.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                //点击空格
                if(e.getKeyCode() == KeyEvent.VK_SPACE){
                    switch (state){
                        case 0:
                            //未开始
                            state = 1;
                            break;
                        case 1:
                            //游戏中
                            state = 2;
                            repaint();
                            break;
                        case 2:
                            //游戏暂停
                            state = 1;
                            break;
                        case 3:
                            //失败后重启
                            state = 5;
                            break;

                        case 4:
                            //游戏通关,进入下一关
                            state = 6;
                            break;
                        default:
                            break;

                    }
                }
            }
        });
        //移动蛇头
        while(true){
            //只有状态为游戏中,才需要反复执行repaint()方法
            if(state == 1){
                // repaint()方法是一个具有刷新页面效果的方法,若不调用repaint方法图形发生变化后不会立刻显示
                repaint();
            }
            try {
                //一秒会调用五次
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

14.3 绘制提示语

java 复制代码
/**
     * 绘制提示语
     * @param g
     */
    void prompt(Graphics g){
        //未开始
        if(state == 0){
            g.fillRect(120,240,400,70);
            SnakeUtil.drawWord(g,"按下空格开始游戏",Color.yellow,35,150,290);
        }
      
    }

14.4 在paint方法添加提示语

java 复制代码
/**
     * 绘制
     * @param g
     */
    @Override
    public void paint(Graphics g){
       

        //灰色背景
        g.setColor(Color.gray);
        //绘制矩形,和窗口一样,宽和高都是600
        g.fillRect(0,0,winWidth,winHeight);
        //网格线颜色
        g.setColor(Color.black);
        //绘制网格线
        for (int i = 0; i <= 20 ; i++) {
            //横线
            g.drawLine(0,i * 30,600,i * 30);
            //竖线
            g.drawLine(i * 30,0,i * 30,600);
        }
        //绘制蛇身体
        for (int i = snakeBodyEntityList.size() - 1; i >= 0; i--) {
            snakeBodyEntityList.get(i).paintSelf(g);
        }
        //绘制蛇头
        snakeHeadEntity.paintSelf(g);
        //食物绘制
        snakeFoodEntity.paintSelf(g);
        //关卡绘制
        SnakeUtil.drawWord(g,"第"+SnakeUtil.level+"关",Color.orange,40,650,260);
        //分数绘制
        SnakeUtil.drawWord(g,score  +"分" , Color.BLUE , 50,650,330);
        //绘制提示语
        g.setColor(Color.gray);
        prompt(g);
    }

十五:游戏开始和暂停的键盘事件以及通关判断

15.1 在SnakeGame的launch方法添加键盘函数

java 复制代码
//对开始游戏添加键盘事件
        this.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                //点击空格
                if(e.getKeyCode() == KeyEvent.VK_SPACE){
                    switch (state){
                        case 0:
                            //未开始
                            state = 1;
                            break;
                        case 1:
                            //游戏中
                            state = 2;
                            repaint();
                            break;
                        case 2:
                            //游戏暂停
                            state = 1;
                            break;
                        case 3:
                            //失败后重启
                            state = 5;
                            break;

                        case 4:
                            //游戏通关,进入下一关
                            state = 6;
                            break;
                        default:
                            break;

                    }
                }
            }
        });

15.2 通关判断

在SnakeHeadEntity的paintSelf中添加

java 复制代码
//通关判断
        if(this.frame.score >= 3){
            //通关
            SnakeGame.state = 4;
        }

15.3 添加通关提示语

java 复制代码
/**
     * 绘制提示语
     * @param g
     */
    void prompt(Graphics g){
        //未开始
        if(state == 0){
            g.fillRect(120,240,400,70);
            SnakeUtil.drawWord(g,"按下空格开始游戏",Color.yellow,35,150,290);
        }

        //游戏暂停
        if(state == 2){
            g.fillRect(120,240,400,70);
            SnakeUtil.drawWord(g,"按下空格开始游戏",Color.yellow,35,150,290);
        }

        //通关
        if(state == 4){
            g.fillRect(120,240,400,70);
            if(SnakeUtil.level == 3){
                SnakeUtil.drawWord(g,"达成条件,游戏通关",Color.green,35,150,290);
            } else {
                SnakeUtil.drawWord(g,"达成条件,点击空格开始下一关",Color.green,35,150,290);
            }

        }
    }

十六:蛇和身体碰撞判断

16.1 在move方法中添加代码

java 复制代码
//蛇头和身体碰撞判断
            if(this.x == snakeBodyEntityList.get(i).x && this.y == snakeBodyEntityList.get(i).y){
                //蛇咬到自己,失败
                SnakeGame.state = 3;
            }

16.2 在prompt方法中添加游戏失败的提示语

java 复制代码
//失败
        if(state == 3){
            g.fillRect(120,240,400,70);
            SnakeUtil.drawWord(g,"失败,按下空格重新开始",Color.red,35,150,290);
        }

十七:双缓存解决画面闪烁

原因:每次刷新都是重新绘制图片

java 复制代码
//定义双缓存图片
    Image offScreenImage = null;
java 复制代码
/**
     * 绘制
     * @param g
     */
    @Override
    public void paint(Graphics g){
        //初始化双缓存图片
        if(offScreenImage == null){
            offScreenImage = this.createImage(winWidth,winHeight);
        }
        //获取图片对应的Graphics对象
        Graphics graphicsImage = offScreenImage.getGraphics();

        //灰色背景
        graphicsImage.setColor(Color.gray);
        //绘制矩形,和窗口一样,宽和高都是600
        graphicsImage.fillRect(0,0,winWidth,winHeight);
        //网格线颜色
        graphicsImage.setColor(Color.black);
        //绘制网格线
        for (int i = 0; i <= 20 ; i++) {
            //横线
            graphicsImage.drawLine(0,i * 30,600,i * 30);
            //竖线
            graphicsImage.drawLine(i * 30,0,i * 30,600);
        }
        //绘制蛇身体
        for (int i = snakeBodyEntityList.size() - 1; i >= 0; i--) {
            snakeBodyEntityList.get(i).paintSelf(graphicsImage);
        }
        //绘制蛇头
        snakeHeadEntity.paintSelf(graphicsImage);
        //食物绘制
        snakeFoodEntity.paintSelf(graphicsImage);
        //关卡绘制
        SnakeUtil.drawWord(graphicsImage,"第"+SnakeUtil.level+"关",Color.orange,40,650,260);
        //分数绘制
        SnakeUtil.drawWord(graphicsImage,score  +"分" , Color.BLUE , 50,650,330);
        //绘制提示语
        graphicsImage.setColor(Color.gray);
        prompt(graphicsImage);
        //将双缓存图片绘制到主窗口中
        g.drawImage(offScreenImage,0,0,null);
    }

十八:游戏失败后重新开始

18.1 SnakeGame添加resetGame方法

java 复制代码
/**
     * 失败后,重置游戏
     */
    void resetGame(){
        //关闭当前窗口

        //开启新的窗口
        String[] args = {};
        main(args);

    }

18.2 修改Switch语句

java 复制代码
/**
     * 窗口的绘制
     */
    public void launch(){
        //设置窗口是否可见、默认是false不可见
        this.setVisible(true);
        //设置窗口的大小
        this.setSize(winWidth,winHeight);
        //设置窗口的位置在屏幕居中
        this.setLocationRelativeTo(null);
        //设置窗口的标题
        this.setTitle("贪吃蛇");
        //蛇身初始化
        snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,30,570,this));
        snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,0,570,this));
        //对开始游戏添加键盘事件
        this.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                //点击空格
                if(e.getKeyCode() == KeyEvent.VK_SPACE){
                    switch (state){
                        case 0:
                            //未开始
                            state = 1;
                            break;
                        case 1:
                            //游戏中
                            state = 2;
                            repaint();
                            break;
                        case 2:
                            //游戏暂停
                            state = 1;
                            break;
                        case 3:
                            //失败后重启
                            state = 5;
                            break;

                        case 4:
                            //游戏通关,进入下一关
                            state = 6;
                            break;
                        default:
                            break;

                    }
                }
            }
        });
        //移动蛇头
        while(true){
            //只有状态为游戏中,才需要反复执行repaint()方法
            if(state == 1){
                // repaint()方法是一个具有刷新页面效果的方法,若不调用repaint方法图形发生变化后不会立刻显示
                repaint();
            }
            //失败重启
            if(state == 5){
                state = 0;
                resetGame();
            }
            //进入下一关
            if(state == 6 && SnakeUtil.level != 3){
                state = 1;
                SnakeUtil.level++;
                resetGame();
            }
            try {
                //一秒会调用五次
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

十九:源码奉上

19.1 SnakeGame

java 复制代码
package com.sysg.game.sanke;

import com.sysg.game.sanke.entity.SnakeBodyEntity;
import com.sysg.game.sanke.entity.SnakeFoodEntity;
import com.sysg.game.sanke.entity.SnakeHeadEntity;
import com.sysg.game.sanke.utils.SnakeUtil;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;

/**
 * @author ikun
 */
public class SnakeGame extends JFrame {

    //定义双缓存图片
    Image offScreenImage = null;

    //游戏状态 0-未开始 1-游戏中 2-暂停 3-失败 4-通关 5-失败后重启 6-下一关
    public static int state = 0;


    //当前分数
    public int score = 0;

    //窗口宽高
    int winWidth = 800;
    int winHeight = 600;


    /**
     * 创建蛇头对象
     * 默认为右边、坐标为30,570、窗口为当前this
     */
    SnakeHeadEntity snakeHeadEntity = new SnakeHeadEntity(SnakeUtil.rightSnakeHeadImg,60,570,this);

    /**
     * 蛇身集合
     */
    public List<SnakeBodyEntity> snakeBodyEntityList = new ArrayList<>();

    /**
     * 食物
     */
    public SnakeFoodEntity snakeFoodEntity = new SnakeFoodEntity().getFood();

    /**
     * 窗口的绘制
     */
    public void launch(){
        //设置窗口是否可见、默认是false不可见
        this.setVisible(true);
        //设置窗口的大小
        this.setSize(winWidth,winHeight);
        //设置窗口的位置在屏幕居中
        this.setLocationRelativeTo(null);
        //设置窗口的标题
        this.setTitle("贪吃蛇");
        //蛇身初始化
        snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,30,570,this));
        snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,0,570,this));
        //对开始游戏添加键盘事件
        this.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                //点击空格
                if(e.getKeyCode() == KeyEvent.VK_SPACE){
                    switch (state){
                        case 0:
                            //未开始
                            state = 1;
                            break;
                        case 1:
                            //游戏中
                            state = 2;
                            repaint();
                            break;
                        case 2:
                            //游戏暂停
                            state = 1;
                            break;
                        case 3:
                            //失败后重启
                            state = 5;
                            break;

                        case 4:
                            //游戏通关,进入下一关
                            state = 6;
                            break;
                        default:
                            break;

                    }
                }
            }
        });
        //移动蛇头
        while(true){
            //只有状态为游戏中,才需要反复执行repaint()方法
            if(state == 1){
                // repaint()方法是一个具有刷新页面效果的方法,若不调用repaint方法图形发生变化后不会立刻显示
                repaint();
            }
            //失败重启
            if(state == 5){
                state = 0;
                resetGame();
            }
            //进入下一关
            if(state == 6 && SnakeUtil.level != 3){
                state = 1;
                SnakeUtil.level++;
                resetGame();
            }
            try {
                //一秒会调用五次
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 绘制
     * @param g
     */
    @Override
    public void paint(Graphics g){
        //初始化双缓存图片
        if(offScreenImage == null){
            offScreenImage = this.createImage(winWidth,winHeight);
        }
        //获取图片对应的Graphics对象
        Graphics graphicsImage = offScreenImage.getGraphics();

        //灰色背景
        graphicsImage.setColor(Color.gray);
        //绘制矩形,和窗口一样,宽和高都是600
        graphicsImage.fillRect(0,0,winWidth,winHeight);
        //网格线颜色
        graphicsImage.setColor(Color.black);
        //绘制网格线
        for (int i = 0; i <= 20 ; i++) {
            //横线
            graphicsImage.drawLine(0,i * 30,600,i * 30);
            //竖线
            graphicsImage.drawLine(i * 30,0,i * 30,600);
        }
        //绘制蛇身体
        for (int i = snakeBodyEntityList.size() - 1; i >= 0; i--) {
            snakeBodyEntityList.get(i).paintSelf(graphicsImage);
        }
        //绘制蛇头
        snakeHeadEntity.paintSelf(graphicsImage);
        //食物绘制
        snakeFoodEntity.paintSelf(graphicsImage);
        //关卡绘制
        SnakeUtil.drawWord(graphicsImage,"第"+SnakeUtil.level+"关",Color.orange,40,650,260);
        //分数绘制
        SnakeUtil.drawWord(graphicsImage,score  +"分" , Color.BLUE , 50,650,330);
        //绘制提示语
        graphicsImage.setColor(Color.gray);
        prompt(graphicsImage);
        //将双缓存图片绘制到主窗口中
        g.drawImage(offScreenImage,0,0,null);
    }

    /**
     * 绘制提示语
     * @param g
     */
    void prompt(Graphics g){
        //未开始
        if(state == 0){
            g.fillRect(120,240,400,70);
            SnakeUtil.drawWord(g,"按下空格开始游戏",Color.yellow,35,150,290);
        }

        //游戏暂停
        if(state == 2){
            g.fillRect(120,240,400,70);
            SnakeUtil.drawWord(g,"按下空格开始游戏",Color.yellow,35,150,290);
        }


        //失败
        if(state == 3){
            g.fillRect(120,240,400,70);
            SnakeUtil.drawWord(g,"失败,按下空格重新开始",Color.red,35,150,290);
        }

        //通关
        if(state == 4){
            g.fillRect(120,240,400,70);
            if(SnakeUtil.level == 3){
                SnakeUtil.drawWord(g,"达成条件,游戏通关",Color.green,35,150,290);
            } else {
                SnakeUtil.drawWord(g,"达成条件,点击空格开始下一关",Color.green,35,150,290);
            }

        }
    }

    /**
     * 失败后,重置游戏
     */
    void resetGame(){
        //关闭当前窗口

        //开启新的窗口
        String[] args = {};
        main(args);

    }

    public static void main(String[] args) {
        SnakeGame snakeGame = new SnakeGame();
        snakeGame.launch();
    }



}

19.2 SnakeUtil

java 复制代码
package com.sysg.game.sanke.utils;

import java.awt.*;

/**
 * 贪吃蛇工具类
 * @author ikun
 */
public class SnakeUtil {

    //蛇头
    public static Image upSnakeHeadImg = Toolkit.getDefaultToolkit().getImage("src/main/resources/static/img/snake/up.png");
    public static Image downSnakeHeadImg = Toolkit.getDefaultToolkit().getImage("src/main/resources/static/img/snake/down.png");
    public static Image rightSnakeHeadImg = Toolkit.getDefaultToolkit().getImage("src/main/resources/static/img/snake/right.png");
    public static Image leftSnakeHeadImg = Toolkit.getDefaultToolkit().getImage("src/main/resources/static/img/snake/left.png");
    //蛇身
    public static Image snakeBodyImg = Toolkit.getDefaultToolkit().getImage("src/main/resources/static/img/snake/body.png");
    //食物
    public static Image snakeFoodImg = Toolkit.getDefaultToolkit().getImage("src/main/resources/static/img/snake/food.png");
    //关卡,默认是第一关
    public static int level = 1;




    /**
     * 绘制文字
     * @param g 绘图的实体类
     * @param str 字符串的内容
     * @param color 字符串的颜色
     * @param strFontSize 字符串的字体大小
     * @param x 字符串的横坐标
     * @param y 字符串的纵坐标
     */
    public static void drawWord(Graphics g,String str,Color color,int strFontSize,int x,int y){
        g.setColor(color);
        //设置字体样式,Font.BOLD->粗体
        g.setFont(new Font("仿宋",Font.BOLD,strFontSize));
        //将文字绘制到屏幕上
        g.drawString(str,x,y);
    }


}

19.3 SnakeEntity

java 复制代码
package com.sysg.game.sanke.entity;

import com.sysg.game.sanke.SnakeGame;

import java.awt.*;

/**
 * 贪吃蛇实体类
 */
public class SnakeEntity {

    //物体的图片
    Image img;

    //物体的坐标
    int x,y;

    //物体的宽高,都为30,与方格保持一致
    int width = 30,height = 30;

    //窗口类的引用
    SnakeGame frame;

    public Image getImg() {
        return img;
    }

    public void setImg(Image img) {
        this.img = img;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public SnakeGame getFrame() {
        return frame;
    }

    public void setFrame(SnakeGame frame) {
        this.frame = frame;
    }

    /**
     * 有参构造
     * @param img
     * @param x
     * @param y
     * @param frame
     */
    public SnakeEntity(Image img, int x, int y, SnakeGame frame) {
        this.img = img;
        this.x = x;
        this.y = y;
        this.frame = frame;
    }

    /**
     * 有参构造
     * @param img
     * @param x
     * @param y
     * @param width
     * @param height
     * @param frame
     */
    public SnakeEntity(Image img, int x, int y, int width, int height, SnakeGame frame) {
        this.img = img;
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        this.frame = frame;
    }

    /**
     * 无参构造
     */
    public SnakeEntity() {
    }

    //绘制自身的方法
    public void paintSelf(Graphics g){
        g.drawImage(img,x,y,null);
    }
}

19.4 SnakeHeadEntity

java 复制代码
package com.sysg.game.sanke.entity;

import com.sysg.game.sanke.SnakeGame;
import com.sysg.game.sanke.utils.SnakeUtil;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.List;

/**
 * 蛇头实体类
 * @author ikun
 */
public class SnakeHeadEntity extends SnakeEntity{
    /**
     * 蛇头方向 up down left right
     * 默认为right
     */
    private String direction = "right";

    public String getDirection() {
        return direction;
    }

    public void setDirection(String direction) {
        this.direction = direction;
    }

    public SnakeHeadEntity(Image img, int x, int y, SnakeGame frame) {
        super(img, x, y, frame);
        //监听键盘
        this.frame.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                //控制蛇头的移动
                changeDirection(e);
            }
        });
    }

    /**
     * 控制蛇头的移动
     * w->a->s->d
     */
    public void changeDirection(KeyEvent e) {
        switch (e.getKeyCode()){
            //按下A键->朝左移动
            case KeyEvent.VK_A:
                if(!"right".equals(direction)){
                    //将方向改为左边
                    direction = "left";
                    //修改蛇头图片的方向
                    img = SnakeUtil.leftSnakeHeadImg;
                }
                break;

            //按下D键->朝右移动
            case KeyEvent.VK_D:
                if(!"left".equals(direction)){
                    //将方向改为右边
                    direction = "right";
                    //修改蛇头图片的方向
                    img = SnakeUtil.rightSnakeHeadImg;
                }
                break;

            //按下W键->朝上移动
            case KeyEvent.VK_W:
                if(!"down".equals(direction)){
                    //将方向改为上边
                    direction = "up";
                    //修改蛇头图片的方向
                    img = SnakeUtil.upSnakeHeadImg;
                }
                break;

            //按下S键->朝下移动
            case KeyEvent.VK_S:
                if(!"up".equals(direction)){
                    //将方向改为上边
                    direction = "down";
                    //修改蛇头图片的方向
                    img = SnakeUtil.downSnakeHeadImg;
                }
                break;
            default:
                break;
        }

    }

    /**
     * 蛇的移动
     * 蛇身体的移动的代码一定要写在蛇头移动的前面
     */
    public void  move(){
        //蛇身体的移动
        List<SnakeBodyEntity> snakeBodyEntityList = this.frame.snakeBodyEntityList;
        for (int i = snakeBodyEntityList.size() - 1; i >= 1; i--) {
            //蛇的移动就是上一个坐标和当前坐标相等
            snakeBodyEntityList.get(i).x = snakeBodyEntityList.get(i-1).x;
            snakeBodyEntityList.get(i).y = snakeBodyEntityList.get(i-1).y;
            //蛇头和身体碰撞判断
            if(this.x == snakeBodyEntityList.get(i).x && this.y == snakeBodyEntityList.get(i).y){
                //蛇咬到自己,失败
                SnakeGame.state = 3;
            }
        }
        snakeBodyEntityList.get(0).x = this.x;
        snakeBodyEntityList.get(0).y = this.y;
        //蛇头的移动
        switch (direction) {
            case "up":
                y -= height;
                break;
            case "down":
                y += height;
                break;
            case "left":
                x -= width;
                break;
            case "right":
                x += width;
                break;
            default:
                break;
        }

    }


    @Override
    public void paintSelf(Graphics g) {
        //设置蛇头的位置
        super.paintSelf(g);
        //食物
        SnakeFoodEntity snakeFoodEntity = this.frame.snakeFoodEntity;
        //身体最后一节的坐标
        Integer newX = null;
        Integer newY = null;
        if(this.x == snakeFoodEntity.x && this.y == snakeFoodEntity.y ){
            //此时蛇头和食物重合了
            this.frame.snakeFoodEntity = snakeFoodEntity.getFood();
            //获取蛇身的最后一个元素
            SnakeBodyEntity lastSnakeBodyEntity = this.frame.snakeBodyEntityList.get(this.frame.snakeBodyEntityList.size() - 1);
            newX = lastSnakeBodyEntity.x;
            newY = lastSnakeBodyEntity.y;
            //分数+1
            this.frame.score ++;
        }
        //通关判断
        if(this.frame.score >= 3){
            //通关
            SnakeGame.state = 4;
        }

        //移动蛇头
        move();
        if(newX != null && newY != null){
            this.frame.snakeBodyEntityList.add(new SnakeBodyEntity(SnakeUtil.snakeBodyImg,newX,newY,this.frame));
        }
        //贪吃蛇的越界处理,移动后新的身体对象添加到snakeBodyEntityList
        checkIndex();
    }

    /**
     * 贪吃蛇的越界处理
     */
    public void checkIndex(){
        if(x < 0){
            x = 570;
        } else if (x > 570){
            x = 0;
        } else if (y < 30){
            y = 570;
        } else if (y > 570){
            y = 30;
        }
    }


}

19.5 SnakeBodyEntity

java 复制代码
package com.sysg.game.sanke.entity;

import com.sysg.game.sanke.SnakeGame;

import java.awt.*;

/**
 * 蛇身体
 */
public class SnakeBodyEntity extends SnakeEntity {

    public SnakeBodyEntity(Image img, int x, int y, SnakeGame frame) {
        super(img, x, y, frame);
    }

    @Override
    public void paintSelf(Graphics g) {
        super.paintSelf(g);
    }
}

19.6 SnakeFoodEntity

java 复制代码
package com.sysg.game.sanke.entity;

import com.sysg.game.sanke.SnakeGame;
import com.sysg.game.sanke.utils.SnakeUtil;

import java.awt.*;
import java.util.Random;

/**
 * 食物实体类
 */
public class SnakeFoodEntity extends SnakeEntity {

    /**
     * 随机
     */
    Random r = new Random();

    public SnakeFoodEntity() {
        super();
    }

    public SnakeFoodEntity(Image img, int x, int y, SnakeGame frame) {
        super(img, x, y, frame);
    }

    /**
     * 获取食物
     * x:0->570
     * y.3:0->570
     * @return
     */
    public SnakeFoodEntity getFood(){
        return new SnakeFoodEntity(SnakeUtil.snakeFoodImg,r.nextInt(20) * 30,(r.nextInt(19) + 1) * 30,this.frame);
    }

    @Override
    public void paintSelf(Graphics g) {
        super.paintSelf(g);
    }
}

二十:静态资源

百度网盘链接:pan.baidu.com/s/1xe18K1dW...

提取码:6ynh

相关推荐
苏三说技术41 分钟前
Claude Code从失控到起飞,只用了这些技巧
后端
长栎2 小时前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode2 小时前
Redis 在生产项目的使用
前端·后端
用户559822481222 小时前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode2 小时前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战2 小时前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha2 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn2 小时前
Docker 容器管理入门 — 从镜像到容器编排
后端
用户762352425912 小时前
ShardingJDBC
后端
行者全栈架构师2 小时前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端