【Java游戏开发项目-01贪吃蛇】强烈推荐Java小白练手!!!

本文详细介绍了使用Java编程实现贪吃蛇游戏的过程,包括游戏效果、架构设计和代码实现。游戏架构基于窗体,通过监听键盘事件控制蛇的移动,使用定时器实现游戏的动态刷新。在代码设计部分,讲解了窗体布局、游戏嵌板的创建、图像绘制、视窗刷新、食物刷新与死亡判断以及游戏暂停与重新开始的逻辑。此外,还展示了如何处理蛇的移动算法和碰撞检测,以及使用图片资源进行图像显示。
摘要由CSDN通过智能技术生成

一丶游戏效果

在这里插入图片描述

二、游戏架构

一个Java项目可以有多种设计模式,MVC,工厂,单例等等。
但在本次项目中,我们将介绍最基础的知识,在后续项目中逐步升级。
贪吃蛇项目的基本架构是窗体为主体,在面板中执行游戏操作,并在面板中绘制贪吃蛇图像和显示用户操作
在这里插入图片描述
在这里插入图片描述

三、代码设计

1.窗体架构

public class StartGame {
    public static void main(String[] args) {
        JFrame jFrame =new JFrame();
        jFrame .setTitle("贪吃豆");
        int width= Toolkit.getDefaultToolkit().getScreenSize().width;
        int height=Toolkit.getDefaultToolkit().getScreenSize().height;
        jFrame .setBounds((width-700)/2,(height-700)/2,700,700);
        jFrame .setResizable(false);
        jFrame .setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        GamePanel gamePanel= new GamePanel();
        jFrame.add(gamePanel);
        jFrame .setVisible(true);
    }
}

窗体方面我们利用Java中的JFrame创建一个窗体,并利用Toolkit获取屏幕大小,使的窗体能够正常显示在电脑中央
同时,这个main方法也是程序的入口,可以看出,我们在StartGame中加入了gamePanel这个对象,而我们的图像显示和游戏逻辑全部封装在GamePanel这个类中

2.游戏嵌板

Panel ,中文意思为“嵌板”。这个嵌板相当于窗体上的一个区域,我们可以根据自己的需求放置嵌板,并在对应的嵌板中设计我们的功能。这个项目只有一个嵌板。

package com.Karthus.game;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Random;

public class GamePanel extends JPanel {
    int[] snakeX=new int[200];
    int[] snakeY=new int[200];
    int length;
    String direction;
    boolean isStart;
    boolean isDie;
    boolean isHungry;
    int foodX=200;
    int foodY=200;
    Timer timer;
    public GamePanel()
    {
        init();
        this.setFocusable(true);
        this.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                super.keyPressed(e);
                int keyCode=e.getKeyCode();
                if(keyCode==KeyEvent.VK_SPACE)
                {
                    if(isDie)
                    {
                        init();
                        repaint();
                    }
                    else
                    {
                       isStart=!isStart;
                       repaint();
                    }
                }
                if (keyCode==KeyEvent.VK_UP)
                {
                    direction="u";
                }
                if (keyCode==KeyEvent.VK_DOWN)
                {
                    direction="d";
                }
                if (keyCode==KeyEvent.VK_RIGHT)
                {
                    direction="r";
                }
                if (keyCode==KeyEvent.VK_LEFT)
                {
                    direction="l";
                }

            }
        });

        timer =new Timer(100, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if(isStart)
                {
                for(int i=length-1;i>0;i--)
                {
                    snakeX[i]=snakeX[i-1];
                    snakeY[i]=snakeY[i-1];
                }
                if("r".equals(direction))
                {
                    snakeX[0]+=25;
                }
                if("l".equals(direction))
                {
                    snakeX[0]-=25;
                }
                if("u".equals(direction))
                {
                    snakeY[0]-=25;
                }
                if("d".equals(direction))
                {
                    snakeY[0]+=25;
                }
                if(snakeX[0]>700)
                {
                    snakeX[0]=0;
                }
                if(snakeX[0]<0)
                {
                    snakeX[0]=675;
                }
                if(snakeY[0]>700)
                {
                    snakeY[0]=0;
                }
                if(snakeY[0]<0)
                {
                    snakeY[0]=675;
                }
                if(snakeX[0]==foodX&&snakeY[0]==foodY)
                {
                    length++;
                    foodX=new Random().nextInt(27)*25;
                    foodY=(new Random().nextInt(26)+1)*25;
                    isHungry=false;
                }
                for(int i=length-1;i>0;i--)
                {
                    if(snakeX[i]==snakeX[0]&&snakeY[i]==snakeY[0])
                    {
                        isDie=true;
                        isStart=false;
                    }
                }
              repaint();
            }}
        });
        timer.start();
    }
    public void init()
    {
        length=3;
        snakeX[0]=175;
        snakeY[0]=275;
        snakeX[1]=150;
        snakeY[1]=275;
        snakeX[2]=125;
        snakeY[2]=275;
        direction="r";
        isStart=false;
        isDie=false;
        isHungry=true;
    }
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        this.setBackground(new Color(231, 214, 214));
        g.setColor(new Color(252, 252, 252));
        g.fillRect(0,0,700,700);
        if(isDie)
        {
            g.setColor(new Color(65, 193, 193));
            g.setFont(new Font("微软雅黑",Font.BOLD,30));
            g.drawString("游戏死亡,按空格键重新开始",200,300);
        }

        switch (direction)
        {
            case "r":Images.right.paintIcon(this,g,snakeX[0],snakeY[0]);break;
            case "l":Images.left.paintIcon(this,g,snakeX[0],snakeY[0]);break;
            case "u":Images.up.paintIcon(this,g,snakeX[0],snakeY[0]);break;
            case "d":Images.down.paintIcon(this,g,snakeX[0],snakeY[0]);break;
        }
        if(isHungry) {
            for (int i = 1; i < length; i++) {
                Images.body.paintIcon(this, g, snakeX[i], snakeY[i]);
                isHungry=false;
            }
        }
        else {
            for (int i = 1; i < length-1; i++) {
                Images.body.paintIcon(this, g, snakeX[i], snakeY[i]);
            }
        }
        Images.food.paintIcon(this,g,foodX,foodY);
            if(!isStart&&!isDie)
            {
                g.setColor(new Color(65, 193, 193));
                g.setFont(new Font("微软雅黑",Font.BOLD,30));
                g.drawString("点击空格开始游戏",200,300);
            }
    }
}

以上是GamePanel嵌板的全部内容。由于程序较简单,我们将蛇的属性放置于该嵌板中。下面会详细介绍嵌板中的内容。

3.图像绘制与显示

想要绘制图像,首先得拿到图片资源
程序中获取图片的方法有很多种,这里我们将所有图片文件放入images文件夹中
在这里插入图片描述

在这里插入图片描述
点击Build,选择reBuild project
在这里插入图片描述
在这里插入图片描述
会生成一个out文件,这些文件时JVM运行的文件,也就是JAVA虚拟机,图片的路径将保存在这个文件中

新建一个images类,将需要的图片数据放入这个类中

public class Images {
    public static URL upUrl =Images.class.getResource("/images/head_up.png");
    public static URL rightUrl =Images.class.getResource("/images/head_right.png");
    public static URL leftUrl =Images.class.getResource("/images/head_left.png");
    public static URL downUrl =Images.class.getResource("/images/head_down.png");
    public static URL bodyUrl =Images.class.getResource("/images/body.png");
    public static URL barUrl =Images.class.getResource("/images/topBar.png");
    public static URL foodUrl=Images.class.getResource("/images/food.png");
    public static ImageIcon up =new ImageIcon(upUrl);
    public static ImageIcon right =new ImageIcon(rightUrl);
    public static ImageIcon left =new ImageIcon(leftUrl);
    public static ImageIcon down =new ImageIcon(downUrl);
    public static ImageIcon body =new ImageIcon(bodyUrl);
    public static ImageIcon bar=new ImageIcon(barUrl);
    public static ImageIcon food=new ImageIcon(foodUrl);
}

收集到图片资源后就可以开始绘制了
在GamePanel中有一个绘制方法

 @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        this.setBackground(new Color(231, 214, 214));
    }

g就相当于画笔,而每次生成一个嵌板对象时,都将自动调用该方法
对于整个嵌板就相当于一个坐标系,一张图片,我们想要放在嵌板中的某个地方,我们要考虑的问题,这个图片展多大空间,在嵌板的那个位置?

因此我们在GamePanel定义了蛇的二维坐标,并定义了蛇头位置朝向
下面给出蛇的画法

 switch (direction)
        {
            case "r":Images.right.paintIcon(this,g,snakeX[0],snakeY[0]);break;
            case "l":Images.left.paintIcon(this,g,snakeX[0],snakeY[0]);break;
            case "u":Images.up.paintIcon(this,g,snakeX[0],snakeY[0]);break;
            case "d":Images.down.paintIcon(this,g,snakeX[0],snakeY[0]);break;
        }
        for(int i=1;i<length;i++)
          Images.body.paintIcon(this, g, snakeX[i], snakeY[i]);

4.视窗刷新与按键监听

游戏必须得要动起来才是游戏,贪吃蛇也要能够自己动起来,并且能够监听键盘的方向键
首先在GamePanel类中加入监听方法

    this.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                super.keyPressed(e);
                int keyCode=e.getKeyCode();
                if(keyCode==KeyEvent.VK_SPACE)
                {
                    if(isDie)
                    {
                        init();
                        repaint();
                    }
                    else
                    {
                       isStart=!isStart;
                       repaint();
                    }
                }
                if (keyCode==KeyEvent.VK_UP)
                {
                    direction="u";
                }
                if (keyCode==KeyEvent.VK_DOWN)
                {
                    direction="d";
                }
                if (keyCode==KeyEvent.VK_RIGHT)
                {
                    direction="r";
                }
                if (keyCode==KeyEvent.VK_LEFT)
                {
                    direction="l";
                }

            }
        }

监听按键利用适配器模式

我们知道游戏刷新的本质是视图的刷新,也就是说我们要不断重复执行嵌板中的画图方法,我们定义一个Timer计时器让他计时刷新

即让是刷新我们还要弄清楚小蛇每次移动的算法,那么小蛇每次移动都是由后一节向前一节传递,而头部的刷新是由小蛇运动的方向决定的,刷新代码如下

  timer =new Timer(100, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if(isStart)
                {
                for(int i=length-1;i>0;i--)
                {
                    snakeX[i]=snakeX[i-1];
                    snakeY[i]=snakeY[i-1];
                }
                if("r".equals(direction))
                {
                    snakeX[0]+=25;
                }
                if("l".equals(direction))
                {
                    snakeX[0]-=25;
                }
                if("u".equals(direction))
                {
                    snakeY[0]-=25;
                }
                if("d".equals(direction))
                {
                    snakeY[0]+=25;
                }
                if(snakeX[0]>700)
                {
                    snakeX[0]=0;
                }
                if(snakeX[0]<0)
                {
                    snakeX[0]=675;
                }
                if(snakeY[0]>700)
                {
                    snakeY[0]=0;
                }
                if(snakeY[0]<0)
                {
                    snakeY[0]=675;
                }
                if(snakeX[0]==foodX&&snakeY[0]==foodY)
                {
                    length++;
                    foodX=new Random().nextInt(27)*25;
                    foodY=(new Random().nextInt(26)+1)*25;
                    isHungry=false;
                }
                for(int i=length-1;i>0;i--)
                {
                    if(snakeX[i]==snakeX[0]&&snakeY[i]==snakeY[0])
                    {
                        isDie=true;
                        isStart=false;
                    }
                }
              repaint();
            }}
        });
        timer.start();
    }

repaint将会重新执行paintComponent方法

5.食物刷新与小蛇死亡判断

每次吃完一个食物后将有新事物刷新,同时蛇身变长

 if(snakeX[0]==foodX&&snakeY[0]==foodY)
                {
                    length++;
                    foodX=new Random().nextInt(27)*25;
                    foodY=(new Random().nextInt(26)+1)*25;
                    isHungry=false;
                }

小蛇死亡的判断则是判断蛇头与蛇身的坐标是否重合

   for(int i=length-1;i>0;i--)
                {
                    if(snakeX[i]==snakeX[0]&&snakeY[i]==snakeY[0])
                    {
                        isDie=true;
                        isStart=false;
                    }
                }

6.游戏暂停与重新开始

在游戏过程中如果要暂停或者游戏死亡后要重新开始,这就需要控制嵌板的画图计时,因此加入了isStart判定
空格监听

  if(keyCode==KeyEvent.VK_SPACE)
                {
                    if(isDie)
                    {
                        init();
                        repaint();
                    }
                    else
                    {
                       isStart=!isStart;
                       repaint();
                    }
                }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值