【Java小游戏】很简陋的 贪吃蛇 游戏

本文介绍了一位开发者如何使用Java实现一个简单的贪吃蛇游戏。游戏设计包括一个JFrame窗口,一个JPanel主面板,以及Snake和Food类。在GamePanel类中,创建线程使得游戏画面动起来,并处理键盘监听事件。Snake类负责小蛇的移动、方向判断和碰撞检测,Food类则管理食物的位置和更新。此外,还提到了可能的改进方案,如使用双缓存技术和定时器,以及加载图片资源。
摘要由CSDN通过智能技术生成

贪吃蛇

结果展示

首先展示一下结果,只有基础的功能,很简陋,主要还是为了巩固一下Java的基础:

开始游戏

游戏结束

设计思路

1、首先,需要一个窗口,直接使用JFrame就可以了,所以需要一个JFrame类作为游戏窗口。关于游戏窗口大小的设计,基本单元选择30X30,宽和高分别尾24个单元与16个单元。

2、因为习惯,不喜欢直接在JFrame中操作(忘记在哪里听过,一般都会在Jframe中定义一个JPanel),所以定义了一个JPanel类作为游戏的主面板。在里面进行游戏的所有操作

​ 在这个类里边,主要干两件事情:

​ a、定义一个线程,可以让游戏画面动起来

​ 线程里面的事件:重新绘制游戏界面、更新小蛇与食物的位置、判断游戏是否结束

​ b、定义一个键盘监听事件,可以监听键盘的操作

​ 监听空格键,改变游戏状态

​ 监听上下左右,改变小蛇的方向(交给小蛇🐍来判断,因为是小蛇的行为)

3、需要单独封装Snake以及Food在Jpanel中只需要实现与游戏相关的操作就可以了

全部的源码

Food

package game;

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

/*
* 因为全局只有一个,所以属性和方法都定义成静态的,方便在GamePanel和Snake中使用
* */
public class Food {
    public static Point location;
    public static boolean isEat;

    //初始化食物
    public static void initFood(){
        location = new Point(12,8);
        isEat = false;
    }

    //提供给游戏界面绘制食物
    public static void paintFood(Graphics g){
        Color color = g.getColor();
        g.setColor(Color.YELLOW);
        g.fillOval((location.x-1)*30,(location.y-1)*30,30,30);
        g.setColor(color);
    }

    //产生食物
    public static void createFood(){
        if(isEat){
            Random random = new Random();
            location.x = random.nextInt(24)+1;
            location.y = random.nextInt(14)+3;
            isEat = false;
        }
    }
}

Snake

package game;

import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.LinkedList;

/*
* 蛇类:
* 主要分为三部分:
* 1、绘制小蛇
*   提供给游戏面板绘制小蛇
* 2、键盘监听
*   通过游戏界面传递过来的KeyEvent改变小蛇的方向
* 3、小蛇位置的更新
*   位置更新包括更新后的边缘检测、碰撞检测、食物检测
* 4、其余的话,就是一些参数的Getter方法,提供给外界,判断游戏是否结束
* */
public class Snake {
    private int length; // 小蛇的长度
    private Point head; // 小蛇的头,不跟身体在一起,主要还是为了可以分开来处理
    private LinkedList<Point> body; // 链表存储小蛇的身体坐标
    private int direction; //蛇的方向,1-上、2-下、3-左、4-右
    private boolean isLive; //是否存活

    public Snake() {
        length = 3;
        body = new LinkedList<Point>();
        head = new Point(3,1);
        body.add(new Point(2,1));
        body.add(new Point(1,1));

        direction = 4;
        isLive = true;
    }

    //1、绘制小蛇,分开画头和身体,因为吃到头应该在食物顶层,而身体应该在食物底层
/*    public void snakePaint(Graphics g){
        Color color = g.getColor();
        //身体用蓝色
        g.setColor(Color.BLUE);
        for (int i = 0; i < length-1; i++) {
            Point bodyPoint = body.get(i);
            g.fillOval((bodyPoint.x-1)*30,(bodyPoint.y-1)*30,30,30);
        }
        //蛇头用红色
        g.setColor(Color.RED);
        g.fillOval((head.x-1)*30,(head.y-1)*30,30,30);
        //用完画笔回到最初的颜色
        g.setColor(color);
    }*/
    public void snakePaintHead(Graphics g){
        Color color = g.getColor();
        //蛇头用红色
        g.setColor(Color.RED);
        g.fillOval((head.x-1)*30,(head.y-1)*30,30,30);
        //用完画笔回到最初的颜色
        g.setColor(color);
    }
    public void snakePaintBody(Graphics g){
        Color color = g.getColor();
        //身体用蓝色
        g.setColor(Color.BLUE);
        for (int i = 0; i < length-1; i++) {
            Point bodyPoint = body.get(i);
            g.fillOval((bodyPoint.x-1)*30,(bodyPoint.y-1)*30,30,30);
        }
        //用完画笔回到最初的颜色
        g.setColor(color);
    }

    //2、键盘监听
    public void listenKey(KeyEvent e){
        switch (e.getKeyCode()){
            case KeyEvent.VK_UP:
                //如果本来是向下的话,向上就无效,下面同理
                direction = (direction == 2) ? 2 : 1;
                break;
            case KeyEvent.VK_DOWN:
                direction = (direction == 1) ? 1 : 2;
                break;
            case KeyEvent.VK_LEFT:
                direction = (direction == 4) ? 4 : 3;
                break;
            case KeyEvent.VK_RIGHT:
                direction = (direction == 3) ? 3 : 4;
                break;
        }
    }

    //3、小蛇位置的更新
    public void move(){
        switch (direction){
            //小蛇上移动
            case 1:
                body.addFirst(new Point(head));
                head.y -= 1;
                break;
            //下
            case 2:
                body.addFirst(new Point(head));
                head.y += 1;
                break;
            //左
            case 3:
                body.addFirst(new Point(head));
                head.x -= 1;
                break;
            //右
            case 4:
                body.addFirst(new Point(head));
                head.x += 1;
                break;
        }

        //碰撞检测,是否撞到自己的身体
        if(isPeng()){
            return;
        }
        //食物检测,是否需要移除尾结点
        if(head.x == Food.location.x && head.y == Food.location.y){
            eatFood();
        }else {
            body.removeLast();
        }
        //边缘检测
        checkBounds();
    }

    //边缘检测
    public void checkBounds(){
        if(head.x < 1){
            head.x = 24;
        }else if(head.x > 24){
            head.x = 1;
        }else if(head.y < 1){
            head.y = 16;
        }else if(head.y > 16){
            head.y = 1;
        }
    }

    //碰撞检测,是否撞到自己的身体
    public boolean isPeng(){
        Point point;
        for (int i = 0; i < body.size(); i++) {
            point = body.get(i);
            if(head.x == point.x && head.y == point.y){
                isLive = false;
                return true;
            }
        }
        return false;
    }

    //吃到食物
    public void eatFood(){
        length++;
        Food.isEat = true;
    }

    //4、其余的方法
    //isLive
    public boolean isLive() {
        return isLive;
    }
    //length
    public int getLength() {
        return length;
    }
}

GamePanel

package game;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

/*
* 这个类是游戏的主类了,里面主要是做了两件事
* 1、定义了一个线程,使游戏画面动起来(其实也可以使用Timer类的)
*   :绘制游戏界面(包括小蛇、食物、游戏提示等)
*      更新小蛇的位置
*       检测小蛇的状态(游戏是否结束)
*
* 2、定义了游戏状态的键盘监听事件,返回给外层的Jframe,在jframe里面添加键盘事件
*   :游戏的状态变化(空格)
*     小蛇的移动变化(上下左右):将键盘事件交给Snake类去判断
* */
public class GamePanel extends JPanel {

    private Snake snake; //小蛇
    private int state; //游戏状态:0-是停止的,1-是开始的,2-结束
    private int score; //游戏得分

    public GamePanel() {
        setBounds(0,0,24*30,16*30);
        setBackground(Color.black);
        initGame();
        new Thread(new ViewGoThread()).start();
    }

    //初始化游戏
    public void initGame(){
        snake = new Snake();
        state = 0;
        score = 0;
        Food.initFood();
    }

    @Override
    public void paint(Graphics g) {
        //刷新面板
        super.paint(g);
        /*
        * 注意画的顺序,先画的会被后面画的覆盖
        * */
        //绘制小蛇身体
        snake.snakePaintBody(g);
        //绘制食物
        Food.paintFood(g);
        //绘制小蛇头
        snake.snakePaintHead(g);
        //绘制提示信息
        paintGameTips(g);
    }

    //绘制游戏里的提示信息
    public void paintGameTips(Graphics g){
        Color color = g.getColor();
        g.setColor(Color.RED);
        g.setFont(new Font("微软雅黑",Font.BOLD,20));
        switch (state){
            case 0:
                g.drawString("按下空格开始游戏",250,250);
                break;
            case 1:
                break;
            case 2:
                g.drawString("游戏结束,得分:"+score,250,250);
        }

        g.setColor(color);
    }

    //判断游戏是否结束
    public void isGameOver(){
        if(!snake.isLive()){
            score = snake.getLength()-3;
            state = 2;
        }
    }



    //因为会有忙式等待,或许使用定时器会更好一点,之后再修改
    private class ViewGoThread implements Runnable{
        @Override
        public void run() {
            //每隔xx做一次
            while(true){
                //重新绘制图像
                repaint();
                //更新小蛇与食物
                if(state == 1){
                    snake.move();
                    Food.createFood();
                }
                //判断游戏是否结束
                isGameOver();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //监听键盘的事件,本来是打算在Jpanel里面添加就好了的,但是好像不可以,只能返回给jFrame来监听
    private class MyKeyListener extends KeyAdapter{
        @Override
        public void keyPressed(KeyEvent e) {
            if(e.getKeyCode() == KeyEvent.VK_SPACE){
                if(state == 0){ //如果是停止的,就动起来
                    state = 1;
                }else if(state == 1){ //如果是动的,就停下来
                    state = 0;
                }else if(state == 2){ //游戏结束状态,再按一次就初始化游戏
                    initGame();
                }
            }
            if(state == 1){
                snake.listenKey(e);
            }
        }
    }
    public KeyAdapter myKeyListener(){
        return new MyKeyListener();
    }
}

SnakeGame

package game;

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

/*
* 主启动类
* */
public class SnakeGame extends JFrame {

    public SnakeGame(String title) throws HeadlessException {
        super(title);
        //设置窗口位置大小
        setBounds(400,300,24*30,16*30);

        //加载游戏主面板
        GamePanel gamePanel = new GamePanel();
        getContentPane().add(gamePanel);
        addKeyListener(gamePanel.myKeyListener());

        //设置窗口默认关闭事件以及可视化
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setVisible(true);
    }

    public static void main(String[] args) {
        new SnakeGame("贪吃蛇小游戏");
    }
}

改进(下面的我还没有使用)

1、双缓存技术

​ 可以使用双缓存来保证游戏画面不会闪烁,但是本身运行起来似乎闪烁不大,所以没有使用

//定义一个图像
private Image offScreenImage = null;

@Override
public void update(Graphics g) {
    if (offScreenImage == null) {
        offScreenImage = this.createImage(GAME_WIDTH, GAME_HEIGHT);
    }
    //先把所有元素画在一个图片上
    Graphics goffScreen = offScreenImage.getGraphics();// 重新定义一个画虚拟桌布的画笔//
    paint(goffScreen);
    
    //再把这个图片画到JPanel中
    g.drawImage(offScreenImage, 0, 0, null);
}

2、定时器

​ 可以使用Timer来代替这里的线程

//定时器
Timer timer = new Timer(100,new MyActionListener()); //100ms执行一次

private class MyActionListener implements ActionListener{
    //事件监听
    @Override
    public void actionPerformed(ActionEvent e) {
		//这里就是要做的事情了
    }
}

timer.start(); //定时器开始工作
timer.stop(); //定时器停止工作

3、加载图片资源代替圆圆的蛇

可以定义一个Data类来专门存储图片资源

import javax.swing.*;
import java.net.URL;

public class Data {
    public static URL headUrl = Data.class.getResource("statics/head.png");
    public static URL bodyUrl = Data.class.getResource("statics/body.png");
    public static URL foodUrl = Data.class.getResource("statics/food.png");

    public static ImageIcon head = new ImageIcon(headUrl);
    public static ImageIcon food = new ImageIcon(foodUrl);
    public static ImageIcon body = new ImageIcon(bodyUrl);
}

//方便再其他的地方调用:
@Override
public void paint(Graphics g) {
    //this:就是自己定义的Jpanel了,表示在哪个组件上面画图标
	Data.food.paintIcon(this,g,foodX,foodY);
    //也可以用下面这个
    g.drawImage(Data.food.getImage(),foodX,foodY,null);
}

总结

  • 在刚开始想要自己从头开始写的时候,以为会很难,但是从真正设计到实现却并没有想象中的那么困难(大概可能是以前看过了)
  • 设计一个类似这样的Java小游戏,总结起来,好像只有两样东西,一个是游戏面板,另一个就是游戏元素
    • 在游戏面板里面,定义游戏的整体操作(游戏界面的绘制,判断游戏是否结束,游戏初始化等等)整个游戏的流程控制(线程或者定时器),以及 键盘的监听(当然也可以是其他的监听)
    • 而游戏元素则具体定义不同的游戏元素的内部规则,就比如贪吃蛇中,小蛇位置的更新,更新后会发生什么等具体的实现。
  • 无了~
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值