Java小游戏之贪吃蛇

文章目录

一:窗口的绘制

1.1 定义窗口类——SnakeGame

import javax.swing.*;

public class SnakeGame extends JFrame {

}

1.2 设置窗口的参数

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方法

![在这里插入图片描述](https://img-blog.csdnimg.cn/84d5e157e3c143979e31c11c3c9efc53.png

二:窗口网格的绘制

	@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 设置网格线的参数

		//灰色背景
        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

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

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 获取蛇头的图片

	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 获取蛇身的图片

public static Image bodyImg = Toolkit.getDefaultToolkit().getImage("img/body.png");

4.3 获取食物的图片

public static Image foodImg = Toolkit.getDefaultToolkit().getImage("img/food.png");

4.4 绘制文字

	//绘制文字
    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

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 设置蛇头的方向

//方向 up down left right
private String direction = "right";

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

5.2 绘制自身

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

在这里插入图片描述

六:蛇头的简单移动

6.1 在HeadObj添加move方法

/**
     * 蛇的移动
     * 蛇身体的移动的代码一定要写在蛇头移动的前面
     */
    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方法

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

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

 /**
     * 窗口的绘制
     */
    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中添加键盘监听事件

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方法

   /**
     * 控制蛇头的移动
     * 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

/**
     * 贪吃蛇的越界处理
     */
    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()方法

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

九:蛇身的添加和移动

9.1 新建蛇身实体类——SnakeBodyEntity

/**
 * 蛇身体
 */
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()方法对蛇身进行初始化

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

9.4 在paint方法绘制蛇身体

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

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

9.5 在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;
        }

十:食物的随机生成

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

10.1 新建食物实体类——SnakeFoodEntity

/**
 * 食物实体类
 */
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 获取食物

/**
     * 获取食物
     * 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中添加食物实体类

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

10.4 在paint中绘制食物

    //食物绘制
    snakeFoodEntity.paintSelf(g);

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

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

     //食物
    SnakeFoodEntity snakeFoodEntity = this.frame.snakeFoodEntity;

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

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

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

十二:蛇的增长

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

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

//身体最后一节的坐标
        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方法后添加元素

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

12.3 修改move方法的遍历顺序

 /**
     * 蛇的移动
     * 蛇身体的移动的代码一定要写在蛇头移动的前面
     */
    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定义窗口的宽高

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

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

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

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

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

13.3 蛇吃食物,分数加一

在SnakeHeadEntity的paintSelf添加对应方法

@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定义游戏状态

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

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

/**
     * 窗口的绘制
     */
    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 绘制提示语

/**
     * 绘制提示语
     * @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方法添加提示语

/**
     * 绘制
     * @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方法添加键盘函数

//对开始游戏添加键盘事件
        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中添加

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

15.3 添加通关提示语

/**
     * 绘制提示语
     * @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方法中添加代码

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

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

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

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

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

//定义双缓存图片
    Image offScreenImage = null;
/**
     * 绘制
     * @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方法

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

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

    }

18.2 修改Switch语句

/**
     * 窗口的绘制
     */
    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

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

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

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

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

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

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);
    }
}

  • 14
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论
以下是一个简单的 Java 贪吃蛇小游戏的示例代码: ```java import java.awt.*; import java.awt.event.*; import javax.swing.*; public class SnakeGame extends JFrame implements KeyListener { private static final long serialVersionUID = 1L; private final int WIDTH = 300; private final int HEIGHT = 300; private final int DOT_SIZE = 10; private final int ALL_DOTS = 900; private final int RAND_POS = 29; private int DELAY = 140; private int x[] = new int[ALL_DOTS]; private int y[] = new int[ALL_DOTS]; private int dots; private int apple_x; private int apple_y; private boolean leftDirection = false; private boolean rightDirection = true; private boolean upDirection = false; private boolean downDirection = false; private boolean inGame = true; private Timer timer; private Image ball; private Image apple; private Image head; public SnakeGame() { addKeyListener(this); setTitle("Snake Game"); setBackground(Color.black); setResizable(false); setSize(WIDTH, HEIGHT); ImageIcon iid = new ImageIcon(getClass().getResource("dot.png")); ball = iid.getImage(); ImageIcon iia = new ImageIcon(getClass().getResource("apple.png")); apple = iia.getImage(); ImageIcon iih = new ImageIcon(getClass().getResource("head.png")); head = iih.getImage(); initGame(); } public void initGame() { dots = 3; for (int z = 0; z < dots; z++) { x[z] = 50 - z * 10; y[z] = 50; } locateApple(); timer = new Timer(DELAY, new GameCycle()); timer.start(); } public void paint(Graphics g) { super.paint(g); if (inGame) { g.drawImage(apple, apple_x, apple_y, this); for (int z = 0; z < dots; z++) { if (z == 0) g.drawImage(head, x[z], y[z], this); else g.drawImage(ball, x[z], y[z], this); } Toolkit.getDefaultToolkit().sync(); } else { gameOver(g); } } public void gameOver(Graphics g) { String msg = "Game Over"; Font small = new Font("Helvetica", Font.BOLD, 14); FontMetrics metr = getFontMetrics(small); g.setColor(Color.white); g.setFont(small); g.drawString(msg, (WIDTH - metr.stringWidth(msg)) / 2, HEIGHT / 2); } public void checkApple() { if ((x[0] == apple_x) && (y[0] == apple_y)) { dots++; locateApple(); } } public void move() { for (int z = dots; z > 0; z--) { x[z] = x[(z - 1)]; y[z] = y[(z - 1)]; } if (leftDirection) { x[0] -= DOT_SIZE; } if (rightDirection) { x[0] += DOT_SIZE; } if (upDirection) { y[0] -= DOT_SIZE; } if (downDirection) { y[0] += DOT_SIZE; } } public void checkCollision() { for (int z = dots; z > 0; z--) { if ((z > 4) && (x[0] == x[z]) && (y[0] == y[z])) { inGame = false; } } if (y[0] >= HEIGHT) { inGame = false; } if (y[0] < 0) { inGame = false; } if (x[0] >= WIDTH) { inGame = false; } if (x[0] < 0) { inGame = false; } if (!inGame) { timer.stop(); } } public void locateApple() { int r = (int) (Math.random() * RAND_POS); apple_x = ((r * DOT_SIZE)); r = (int) (Math.random() * RAND_POS); apple_y = ((r * DOT_SIZE)); } public void keyReleased(KeyEvent e) { } public void keyPressed(KeyEvent e) { int key = e.getKeyCode(); if ((key == KeyEvent.VK_LEFT) && (!rightDirection)) { leftDirection = true; upDirection = false; downDirection = false; } if ((key == KeyEvent.VK_RIGHT) && (!leftDirection)) { rightDirection = true; upDirection = false; downDirection = false; } if ((key == KeyEvent.VK_UP) && (!downDirection)) { upDirection = true; rightDirection = false; leftDirection = false; } if ((key == KeyEvent.VK_DOWN) && (!upDirection)) { downDirection = true; rightDirection = false; leftDirection = false; } } public void keyTyped(KeyEvent e) { } class GameCycle implements ActionListener { public void actionPerformed(ActionEvent e) { if (inGame) { checkApple(); checkCollision(); move(); } repaint(); } } public static void main(String[] args) { new SnakeGame().setVisible(true); } } ``` 在代码中,`initGame()` 方法初始化游戏,`paint(Graphics g)` 方法绘制游戏界面,`checkApple()` 方法检查是否吃到了苹果,`move()` 方法移动贪吃蛇,`checkCollision()` 方法检查是否发生碰撞,`locateApple()` 方法随机生成苹果的位置,`keyPressed(KeyEvent e)` 方法处理键盘事件。运行代码后,即可开始玩贪吃蛇小游戏

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

随意石光

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值