IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 1小时写完一个Java贪吃蛇小游戏 -> 正文阅读

[Java知识库]1小时写完一个Java贪吃蛇小游戏

💓 前言

?之前我们用JavaScript写过一个简单的贪吃蛇小游戏,JavaScript写贪吃蛇小游戏!今天我们来用Java写下这个经典的贪吃蛇游戏,同时增加一些新的功能,比如,游戏计分、关卡设置、按空格键实现游戏暂停和继续、穿墙功能等等,先来简单的看下效果动画。

?

接下来我们就看下具体的实现过程吧!


第1??步??游戏背景绘制

🎈窗口绘制

首先创建一个简单的桌面窗口,普通类继承JFrame类便具有了创建窗口、监听鼠标键盘事件的功能。

public class GameWin extends JFrame {
    // 创建一个launch方法用来监听窗口信息
    public void launch(){ 
        //设置窗口是否可见,默认值为false,窗口不可见
        this.setVisible(true);
        //设置窗口的大小,宽、高
        this.setSize(600,600);
        //设置窗口的位置,在屏幕上居中
        this.setLocationRelativeTo(null);
        //设置窗口的标题
        this.setTitle("贪吃蛇");
    }
    
    // 创建main方法,获取当前窗口类对象,然后运行launch()方法,
    public static void main(String[] args) {
        GameWin gameWin = new GameWin();
        gameWin.launch();
    }
}

🎈 为窗口绘制网格

?窗口宽600,高600,每个小网格都设置为宽高30的正方形,最终窗口会被分割为20行20列,

// 首先重写paint方法,
@Override
public void paint(Graphics g) {
    // 在窗口中绘制一个灰色矩形作为背景,灰色矩形要填满窗口,所以宽高都为600
    g.setColor(Color.gray);
    // 四个参数,起始位置横纵坐标,终点横纵坐标
    g.fillRect(0,0,600,600);
    // 绘制网格线,和背景颜色灰色区分开,所以选择灰色的网格线,
    g.setColor(Color.black);
    // 网格是用20条横线,20条纵线相交形成,批量绘制,使用for循环,
    for (int i = 0; i <= 20 ; i++) {
        // 四个参数,线条的起始位置坐标,终点位置坐标,
        // 横线,x都为0,y不同
        g.drawLine(0,i * 30,600,i * 30);
        // 竖线,同理,改变坐标即可!
        g.drawLine(i * 30,0,i * 30,600);
    }
}

🎈游戏物体父类的编写


public class GameObj {

    // 定义游戏物体的图片
    Image img;
    // 定义物体的坐标
    int x;
    int y;
    // 定义物体的宽高,将宽高都设置为30,让它和每个格子宽高相同
    int width = 30;
    int height = 30;
    // 窗口类的引用
    GameWin frame;
    // 然后getter,setter方法....

    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 GameObj() {
    }

    public GameObj(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);
    }
}

🎈游戏工具类的创建

先将项目所需要的图片拷进来,然后在我们新建的GameUtils工具类里将这些图片获取进来!

public class GameUtils {

    // 蛇头,这里注意,蛇头的方向面向上下左右的时候,图片不同,所以都将其引入进来!
    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){
        // str:需要绘制的字符串,color:字符串的颜色,size:字符串字体的大小,x,y:字符串的坐标
        g.setColor(color); 
        g.setFont(new Font("仿宋",Font.BOLD,size));
        // 将文字绘制到窗口上
        g.drawString(str,x,y);
    }
}

2?? 蛇身绘制及移动

🎈?蛇头部绘制

先在GameObj中添加一个新的有参构造

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

?然后再创建HeadObj类,继承GameObj

public class HeadObj extends GameObj {
    //定义一个控制方向的变量,有四个值,up down left right,设置默认值为右
    private String direction = "right";
    
    // 然后是getset方法
    public String getDirection() {
        return direction;
    }

    public void setDirection(String direction) {
        this.direction = direction;
    }
     
    // 重写需要的方法
    public HeadObj(Image img, int x, int y, GameWin frame) {
        super(img, x, y, frame);
    }

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

然后返回到窗口类GameWin中,创建蛇头的对象。

// 蛇头对象, 所需要的参数依次是蛇头的图片,默认是朝右的图片,x,y坐标,窗口引用是this
HeadObj headObj = new HeadObj(GameUtils.rightImg,30,570,this);

再找到paint方法,在paint方法中添加绘制蛇头的方法。

// 绘制蛇头
headObj.paintSelf(g);

效果如图所示:?

🎈?蛇头的简单移动

在HeadObj类的paintSelf方法前,添加一个蛇头移动的move方法:

    //蛇的移动
    public void move(){
        // 对方向变量direction进行判断
        switch (direction){
            // 如果direction为up,此时蛇头应该向上移动,y-height,
            // 我们已经在父类GameObj中规定了宽高都是30,和小网格的宽高是一样的,也就是一次移动一格
            case "up":
                y -= height;
                break;
            case "down":
                y += height;
                break;
            case "left":
                x -= width;
                break;
            case "right":
                x += width;
                default:
                    break;
        }
    }

接在在paintSelf中调用move方法

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

蛇的不断移动需要不停的调用repaint方法,所以找到窗口类GameWin在launch方法中添加一个while循环。

public void launch(){
    ...
    while(true){
        // 调用repaint方法
        repaint();
        // 每次调用repaint方法后,要有一个线程休眠,
        try {
            // 所以1s休眠5次
            Thread.sleep(200);
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
    }
}

🎈?键盘控制蛇的方向

在HeadObj的方法中,添加键盘监听事件

    public HeadObj(Image img, int x, int y, GameWin frame) {
        ...
        //键盘监听事件
        this.frame.addKeyListener(new KeyAdapter() {
            // keyPressed方法,表示键盘按下,这样就获取到了键盘事件e
            @Override
            public void keyPressed(KeyEvent e) {
                changeDirection(e);
            }
        });
    }
    
    // 如果把控制方向这个函数也写在监听事件里代码太长了,所以单独拿出来。
    // 控制移动方向  W -up  A - left   D -right  S-down
    public void changeDirection(KeyEvent e){
        // 通过switch语句对按下的键进行判断,
        switch (e.getKeyCode()){
            case KeyEvent.VK_A: 
                // 注意蛇不能朝当前的反方向进行移动,所以要判断当前方向,不是相反方向就能移动,
                if (!"right".equals(direction)){
                    // 移动了之后要改变当前方向的变量值,以及蛇头图片。
                    direction = "left";
                    img = GameUtils.leftImg;
                }
                break;
            case KeyEvent.VK_D:
                if (!"left".equals(direction)){
                    direction = "right";
                    img = GameUtils.rightImg;
                }
                break;
            case KeyEvent.VK_W:
                if (!"down".equals(direction)){
                    direction = "up";
                    img = GameUtils.upImg;
                }
                break;
            case KeyEvent.VK_S:
                if (!"up".equals(direction)){
                    direction = "down";
                    img = GameUtils.downImg;
                }
                break;
                default:
                    break;
        }
    }

看下效果:?

🎈?蛇头窗墙功能实现

如果蛇从一个方向消失,会再从另一个方向出来,找到HeadObj类,在它的paintSelf方法中实现

        // 窗墙功能
        // 很好理解,如果x<0,蛇头要撞左墙,那就让蛇头从右墙出现,x值为600-30=570,
        // 以下几种情况同理。
        if (x < 0){
            x = 570;
        } else if (x > 570){
            x = 0;
        } else if (y < 30){
            // 注意这里为什么是30不是0,因为我们的窗口有个标题,高度为30
            y = 570;
        }else if (y > 570){
            y = 30;
        }

看下效果:?

🎈?蛇身的添加及移动

首先创建蛇身的类BodyObj,依旧继承GameObj,然后重写需要的方法,paintSelf

public class BodyObj extends GameObj {
    public BodyObj(Image img, int x, int y, GameWin frame) {
        super(img, x, y, frame);
    }

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

?接着返回到窗口类GameWin中,定义一个蛇的身体的集合

// 蛇的身体集合
public List<BodyObj> bodyObjList = new ArrayList<>();
// 注意这里要引入java.util

?然后在launch方法中对蛇身进行初始化

public void launch(){
    ...
    // 蛇身初始化 ,第一节蛇的身体:第一个参数为蛇的图片,x为30,y为570,窗口引用this
    bodyObjList.add(new BodyObj(GameUtils.bodyImg, 30, 570, this));
    // 因为蛇身和蛇头都是紧挨着的,所以第二节身体改为0即可
    bodyObjList.add(new BodyObj(GameUtils.bodyImg, 0, 570, this));
    ...
}

?接着在窗口类GameWin中的paint方法中反向遍历这个集合,为什么要反向遍历呢,为了防止身体有重叠

@Override
public void paint(Graphics g){
    ...
    // 绘制蛇的身体
    for(int i = bodyObjList.size() - 1; i >= 0; i--){
        bodyObjList.get(i).paintSelf(g);
    }
    // 绘制蛇头
    ...
}
        

?接着在HeadObj类中,的move方法里,在蛇头移动的代码前,添加蛇身移动的代码,顺序不能变,否则要出错。

    //蛇的移动
    public void move(){
        // 蛇身体的移动
        // 先获取bodyObjList
        java.util.List<BodyObj> bodyObjList = this.frame.bodyObjList;
        // 然后在这里进行遍历,注意这里是从1开始遍历,因为第一个元素要先将它单独拎出来
        // 蛇身的移动其实就是,当前元素的坐标,等于它前面元素的坐标,
        for (int i = 1; i < bodyObjList.size(); i++) {
            bodyObjList.get(i).x = bodyObjList.get(i - 1).x;
            bodyObjList.get(i).y = bodyObjList.get(i - 1).y;
        }
        // 第一个元素,是和蛇头相连的,所以它的坐标要变为改变前的蛇头的坐标。
        bodyObjList.get(0).x = this.x;
        bodyObjList.get(0).y = this.y;
        ......
        // 一定要注意,蛇身移动的代码要写在蛇头移动之前!
    }

3?? 食物的随机位置生成

食物生成的位置是随机的,但是必须得在窗口类GameWin中,x在0-570之间,y在30-570间,并且得是30的倍数,新建一个FoodObj类,继承GameObj类,重写需要的方法,定义一个随机函数,然后再写个方法获取食物

public class FoodObj extends GameObj {

    //随机
    Random r = new Random();
    
    public FoodObj() {
        super();
    }

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

    //获取食物,返回值是类的对象
    public FoodObj getFood(){
        // 第一个参数是食物的图片,第二个参数是食物随机生成位置的x轴,
        // 但得是30的倍数,所以0-20随机生成,再乘以30,y同理,很好理解。
        return new FoodObj(GameUtils.foodImg,r.nextInt(20) * 30,(r.nextInt(19) + 1) * 30,this.frame);
    }

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

接着回到窗口类GameWin中获取食物的对象

// 食物
public FoodObj foodObj = new FoodObj().getFood();
// getFood这个方法的返回值就是foodObj对象

然后在窗口类GameWin的paint方法中绘制食物

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

4?? 蛇身的增长

🎈蛇吃食物

首先找到HeadObj在paintSelf方法中,获取窗口类中的食物对象

    public void paintSelf(Graphics g) {
        ......
        // 蛇吃食物,注意这段代码要放在蛇移动的move()方法之前,注意顺序
        FoodObj food = this.frame.foodObj;
        
        // 添加判断
        if (this.x == food.x && this.y == food.y){
            // 蛇头和食物重合了,食物应该被吃掉,
            // 接着就将一个新的随机生成的foodObj对象,赋值给窗口类中的foodObj
            this.frame.foodObj = food.getFood();
        }
        ......
    }

看下效果:?

🎈蛇身的增长

每当蛇吃掉一个食物,蛇的身体就应该增长一节,蛇增长的本质,是给蛇身体的集合添加一个元素,新元素的位置要根据蛇身体的最后一个元素的位置来确定。找到HeadObj的paintSelf方法

public void paintSelf(Graphics g) {
        ...
        //蛇吃食物
        ...
        // 定义两个变量表示蛇身体最后一节的坐标
        Integer newX = null;
        Integer newY = null;
        // 在蛇吃食物的代码中,获取蛇身的最后一节
        if (this.x == food.x && this.y == food.y){
            this.frame.foodObj = food.getFood();
            //获取蛇身的最后一个元素
            BodyObj lastBody = this.frame.bodyObjList.get(this.frame.bodyObjList.size() - 1);
            // 让增长的这节身体的坐标为蛇身最后一节的坐标!
            newX = lastBody.x;
            newY = lastBody.y;
        }
        move();
        //move结束后,新的bodyObj对象添加到bodyObjList,添加的前提得是newX和newY值不能为null,
        if (newX != null && newY != null){
            this.frame.bodyObjList.add(new BodyObj(GameUtils.bodyImg,newX,newY,this.frame));
        }
        
        ...
}

?最后还要改一个HeadObj中的move方法中,身体的遍历顺序,改为反向遍历?

// 蛇的移动
public void move(){
    ...
    for(int i = bodyObjList.size() - 1; i >= 1; i--){
        ...
    }
    ...
}    
    

看下效果:?

5??游戏功能设置

🎈?计分面板的实现

计分面板应该记录蛇吃了食物的数量

首先在窗口类GameWin中,定义窗口的宽高变量,接着将之前与窗口大小有关的数字,全部替换成我们现在定义的变量

public class GameWin extends JFrame {
    ...
    // 定义一个记录分数的变量
    public static int score = 0;
    
    // 窗口宽高
    int winWidth = 800;
    int winHeight = 600;

    ...
}

接着找到paint方法将分数绘制出来

// 分数绘制
GameUtils.drawWord(g,score + "分",Color.BLUE, 50, 650, 300);

最后找到HeadObj中,蛇吃食物的方法

public void paintSelf(Graphics g) {
        ...
        //蛇吃食物
        ...
        if (this.x == food.x && this.y == food.y){
            this.frame.foodObj = food.getFood();
            //获取蛇身的最后一个元素
            BodyObj lastBody = this.frame.bodyObjList.get(this.frame.bodyObjList.size() - 1);
            // 让增长的这节身体的坐标为蛇身最后一节的坐标!
            newX = lastBody.x;
            newY = lastBody.y;
            
            // 每吃一个食物,分数+1
            GameWin.score++;
        }

        ...
}

看下效果:?

🎈游戏开始的提示语

首先在窗口类中GameWin定义游戏状态的变量,之后绘制提示语,

public class GameWin extends JFrame {
    
    // 分数
    ...
    // 游戏状态 0 未开始,1 游戏中, 2 暂停, 3 失败, 4 通关
    public static int state = 0;

    ...

    paint方法{
        ...
        // 绘制提示语
        g.setColor(Color.gray);
        prompt(g);
    }

    // 绘制提示语
    void prompt(Graphics g){
        // 未开始时的提示语
        if(state == 0){
            // 先绘制一个矩形填充,x,y,width,height;
            g.fillRect(120, 240, 400, 70);
            // 然后调用工具类中的方法绘制提示语,字体大小,坐标x,y
            GameUtils.drawWord(g, "按下空格开始游戏", Color.yellow, 35,150,290)
    }
    之后在paint方法中调用prompt

}

看下效果:?

还有一点需要注意,只有在游戏中,才能重复执行repaint方法,所以要加个判断。

public void launch(){
    ...
    while(true){
        if(state == 1){
            // 调用repaint方法
            repaint();
        }
        // 每次调用repaint方法后,要有一个线程休眠,
        ...
    }
}

🎈?游戏的暂停功能

接下来我们为游戏开始添加键盘事件,按下空格游戏开始,也就是游戏状态state改为1,在窗口类的launch方法中添加键盘事件,要在while循环之上

//键盘事件
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;
                       default:
                           break;
              }
         }
   }
});

🎈?游戏的通关设置

在HeadObj中,蛇吃食物的代码中添加判断

//蛇吃食物
...
//通关判断
if (GameWin.score >= 15){
    //通关
    GameWin.state = 4;
}
...

接着回到窗口类GameWin中,在prompt方法里添加游戏通关的提示语

    // 把游戏暂停、通关、失败的提示语都加上
    void prompt(Graphics g){
        ...
        // 未开始
        ...
        // 暂停,
        if (state == 2){
            g.fillRect(120,240,400,70); 
            GameUtils.drawWord(g,"按下空格继续游戏",Color.yellow,35,150,290);
        }
        // 失败
        if (state == 3){
            g.fillRect(120,240,400,70); 
            GameUtils.drawWord(g,"游戏失败!" ,Color.yellow,35,150,290);
        }
        //通关
        if (state == 4){
            g.fillRect(120,240,400,70); // 颜色改为绿色
            GameUtils.drawWord(g,"达成条件,游戏通关",Color.green,35,150,290);
        }
    }

?🎈?蛇头与蛇身的碰撞判断

在HeadObj的move方法中

    //蛇的移动
    public void move(){
        //蛇身体的移动
        ...
        for (int i = bodyObjList.size() - 1; i >= 1; i--) {
            bodyObjList.get(i).x = bodyObjList.get(i - 1).x;
            bodyObjList.get(i).y = bodyObjList.get(i - 1).y;
            //蛇头与身体的碰撞判断,
            if (this.x == bodyObjList.get(i).x && this.y == bodyObjList.get(i).y){
                // 蛇头与蛇身撞上了,游戏失败,state状态改为3
                GameWin.state = 3;
            }
        }
        
        //蛇头的移动
        ...
        
    }

🎈?游戏失败后的重新开始

首先在窗口类中创建一个游戏重置的方法,需要实现两个功能,关闭当前窗口和开启新窗口。

    //游戏重置
    void resetGame(){
        //关闭当前窗口
        this.dispose();
        //开启新的窗口,可以直接调用main方法,需要一个字符串数组作为参数
        String[] args = {};
        main(args);
    }

然后给游戏再添加一个状态state,5,失败后重新开始

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

然后找到键盘事件的switch语句,添加一条判断语句

//键盘事件
...
switch (state){
    case 0:
    ...
    case 1:
    ...
    case 2:
    ...
    case 3:
        // 如果游戏失败,将游戏状态改为5,失败后重新开始
        state = 5;
        break;
    ...
}

然后找到while循环,添加一个if判断

while(true){
    if(state == 1){
        // 游戏中才调用
        repaint();
    }
    // 失败后重启
    if(state == 5){
        // 游戏状态改为0,然后调用游戏重启的方法
        state = 0;
        resetGame();
    }
    ...
}

然后将窗口类GameWin中的一个静态变量score,改为非静态

// 分数
public int score = 0;

那么与分数相关的代码都需要修改,改为this.frame.score++;

然后把游戏提示语句也进行下调整

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

🎈游戏的多个关卡设置

首先在工具类GameUtils中定义关卡的变量,level默认1,从第一关开始

// 蛇身
// 食物
...
// 关卡
public static int level = 1;
...

接着找到主窗口GameWin,在主窗口中绘制关卡的文字,

// 绘制蛇头
// 食物绘制
...
// 关卡  参数,字体大小,坐标x,y
GameUtils.drawWord(gImage,"第" + GameUtils.level + "关", Color.orange, 40,650,260)

接着我们再为游戏添加一个新的状态state,6,下一关

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

   

然后接着找到键盘的switch判断,添加一条新语句,如果游戏通关,状态改为6

...
case 4:
    // 下一关,
    state = 6;
    break;
...

接着在while循环中,再写一条if判断

...
// 通关下一关
// 由于现在这个贪吃蛇游戏,设置的是只有3关,所以多添加个判断,
// 如果当前游戏是第3关,就不能再执行这个方法了,因为第三关已经是最后一关了。
if(state == 6 && GameUtils.level != 3){
    state = 1;
    GameUtils.level++;
    // 接着调用游戏重置的方法
    resetGame();
}

然后将通关后的提示语进行相应的修改

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

6?? 优化:双缓存解决画面闪动问题

我们的游戏画面一直在闪,文字很明显,闪动的原因是什么呢?

是因为窗口中所有的元素都是绘制出来的,每次重新绘制的时候,需要将所有的元素重新一个一个绘制到窗口中,所以解决的思路是,重新创建一个空的图片,将所有要绘制的小元素绘制到空图片之上,最后把绘制好的空图片,一次性绘制到主窗口中。

首先在窗口类GameWin中定义双缓存的图片

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

然后在paint方法中进行初始化设置

为防止重复定义双缓存图片,我们加上判断条件

@Override
public void paint(Graphics g){
    // 初始化双缓存图片
    if(offScreenImage == null){
        // 当双缓存图片为null的时候,我们再生成实例对象
        // 创建一个宽高和我们窗口大小一样的图片
        offScreenImage = this.createImage(winWidth,winHeight);
    }
    // 获取图片对应的graphics对象
    Graphics gImage = offScreenImage.getGraphics();
    // 然后将之前的g全部替换为gImage
    ...
}

最后将双缓存的图片绘制到主窗口中。

...
// 食物绘制
// 分数绘制
// 绘制提示语
...
// 将双缓存图片绘制到窗口中
g.drawImage(offScreenImage, 0, 0,null);

这样窗口就不会有闪动的迹象了!


至此贪吃蛇游戏就基本实现啦!注意这里不同关卡蛇移动的速度是一样的,未进行更改!


啊啊啊我又入榜了,啊啊啊这是第二篇入榜文章啦!

?

?

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-03 15:57:54  更:2022-03-03 16:02:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 12:00:00-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码