Java小游戏之贪吃蛇

文章目录

  • 一:窗口的绘制
    • 1.1 定义窗口类——SnakeGame
    • 1.2 设置窗口的参数
    • 1.3 启动main方法
  • 二:窗口网格的绘制
    • 2.1 重写paint方法
      • 2.1.1 为什么要重写paint方法
      • 2.1.2 实现方式
    • 2.2 Graphics
    • 2.3 设置网格线的参数
  • 三:游戏物体父类的创建——SnakeEntity
  • 四:游戏工具类的创建——SnakeUtil
    • 4.1 获取蛇头的图片
    • 4.2 获取蛇身的图片
    • 4.3 获取食物的图片
    • 4.4 绘制文字
  • 五:蛇头部的绘制——SnakeHeadEntity
    • 5.1 设置蛇头的方向
    • 5.2 绘制自身
  • 六:蛇头的简单移动
    • 6.1 在HeadObj添加move方法
    • 6.2 在paintSelf方法中添加move方法
    • 6.3 蛇的重复移动需要调用repaint()方法
  • 七:键盘控制蛇的方向
    • 7.1 在SnakeHeadEntity中添加键盘监听事件
    • 7.2 编写changeDirection方法
  • 八:蛇越界后的处理
    • 1.编写checkIndex()方法
    • 2.在paintSelf方法中添加checkIndex()方法
  • 九:蛇身的添加和移动
    • 9.1 新建蛇身实体类——SnakeBodyEntity
    • 9.2 在SnakeGame定义蛇身集合
    • 9.3 在launch()方法对蛇身进行初始化
    • 9.4 在paint方法绘制蛇身体
    • 9.5 在move方法中添加身体移动逻辑
  • 十:食物的随机生成
    • 10.1 新建食物实体类——SnakeFoodEntity
    • 10.2 获取食物
    • 10.3 在SnakeGame中添加食物实体类
    • 10.4 在paint中绘制食物
  • 十一:蛇吃食物和食物的重新生成
    • 11.1 在SnakeHeadEntity中的paintSelf添加获取食物的对象
    • 11.2 判断蛇头和食物是否重合
  • 十二:蛇的增长
    • 12.1 定义身体最后一节的坐标
    • 12.2 在move方法后添加元素
    • 12.3 修改move方法的遍历顺序
  • 十三:计分面板的编写
    • 13.1 在SnakeGame定义窗口的宽高
    • 13.2 定义游戏分数,并且在paint绘制出来
    • 13.3 蛇吃食物,分数加一
  • 十四:游戏开始的提示语
    • 14.1 在SnakeGame定义游戏状态
    • 14.2 在while语句中添加判断,只有状态为1,才需要重复执行repaint()方法
    • 14.3 绘制提示语
    • 14.4 在paint方法添加提示语
  • 十五:游戏开始和暂停的键盘事件以及通关判断
    • 15.1 在SnakeGame的launch方法添加键盘函数
    • 15.2 通关判断
    • 15.3 添加通关提示语
  • 十六:蛇和身体碰撞判断
    • 16.1 在move方法中添加代码
    • 16.2 在prompt方法中添加游戏失败的提示语
  • 十七:双缓存解决画面闪烁
  • 十八:游戏失败后重新开始
    • 18.1 SnakeGame添加resetGame方法
    • 18.2 修改Switch语句
  • 十九:源码奉上
    • 19.1 SnakeGame
    • 19.2 SnakeUtil
    • 19.3 SnakeEntity
    • 19.4 SnakeHeadEntity
    • 19.5 SnakeBodyEntity
    • 19.6 SnakeFoodEntity

一:窗口的绘制

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

Java小游戏之贪吃蛇_第1张图片

二:窗口网格的绘制

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

Java小游戏之贪吃蛇_第2张图片

三:游戏物体父类的创建——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);
    }

Java小游戏之贪吃蛇_第3张图片

六:蛇头的简单移动

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

Java小游戏之贪吃蛇_第4张图片

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

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

你可能感兴趣的:(java,开发语言)