|本项目源于【狂神说Java】GUI编程入门到游戏实战
B站收UP主:遇见狂神说。即可看见教程
或者点击链接GUI编程入门到游戏实战
贪吃蛇
目录
一、初始静态界面
1、主启动类:
游戏主窗口的创建,大小不可变
package com.gui.lesson01.game; import javax.swing.*; public class StartGame { public static void main(String[] args) { JFrame frame = new JFrame(); frame.setBounds(10,10,815,630); frame.setResizable(false);//窗口大小不可变 frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.setVisible(true); //正常游戏界面都应该在面板上 frame.add(new GamePanel()); } }
2、数据中心类
package com.gui.lesson01.game; import javax.swing.*; import java.net.URL; public class Data { //使用相对路径存放素材 static URL headURL = Data.class.getResource("statics/head.png"); static URL upURL = Data.class.getResource("statics/up.png"); static URL downURL = Data.class.getResource("statics/down.png"); static URL leftURL = Data.class.getResource("statics/left.png"); static URL rightURL = Data.class.getResource("statics/right.png"); static URL bodyURL = Data.class.getResource("statics/body.png"); static URL foodURL = Data.class.getResource("statics/food.png"); //将上面获取的图片绘制成图标 static ImageIcon head = new ImageIcon(headURL); static ImageIcon up = new ImageIcon(upURL); static ImageIcon down = new ImageIcon(downURL); static ImageIcon left = new ImageIcon(leftURL); static ImageIcon right = new ImageIcon(rightURL); static ImageIcon body = new ImageIcon(bodyURL); static ImageIcon food = new ImageIcon(foodURL); }
3、游戏面板类
package com.gui.lesson01.game; import javax.swing.*; import java.awt.*; public class GamePanel extends JPanel { //绘制组件函数,我们在游戏中所有的东西使用画笔来画 @Override protected void paintComponent(Graphics g) { super.paintComponent(g);//清屏作用,不用出现闪屏 //绘制静态面板 this.setBackground(Color.black); Data.head.paintIcon(this,g,25,0);//游戏头部,广告栏 g.fillRect(25,65,750,500);//默认游戏界面 } }
二、小蛇绘制
-
修改GamePanel类,并且赋予小蛇初始位置
修改GamePanel类
package com.gui.lesson01.game; import javax.swing.*; import java.awt.*; public class GamePanel extends JPanel { //定义蛇的数据结构 int length;//蛇的长度 int[] snakeX = new int[600]; int[] snakeY = new int[500]; //构造器 public GamePanel(){ init(); } //初始化小蛇 public void init(){ length = 1; snakeX[0] = 100;snakeY[0] = 100;//脑袋坐标 snakeX[1] = 75;snakeY[1] = 100;//第一块身体坐标 snakeX[2] = 25;snakeY[2] = 100;//第二块身体坐标 } //绘制组件函数,我们在游戏中所有的东西使用画笔来画 @Override protected void paintComponent(Graphics g) { super.paintComponent(g);//清屏作用,不用出现闪屏 //绘制静态面板 this.setBackground(Color.BLUE); Data.header.paintIcon(this,g,25,0);//游戏头部,广告栏 g.fillRect(25,65,750,500);//默认游戏界面 //绘制静态的小蛇 Data.right.paintIcon(this,g,snakeX[0],snakeY[0]); for (int i = 1; i < length; i++) { Data.body.paintIcon(this,g,snakeX[i],snakeY[i] );//身体的坐标 } } }
新增游戏状态
package com.gui.lesson01.game; import javax.swing.*; import java.awt.*; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; public class GamePanel extends JPanel implements KeyListener { //定义蛇的数据结构 int length;//蛇的长度 int[] snakeX = new int[600]; int[] snakeY = new int[500]; String fx;//蛇的初始方向 //游戏状态,默认不开始 boolean isStart = false; //构造器 public GamePanel(){ init(); //获得焦点和键盘事件 this.setFocusable(true); this.addKeyListener(this); } //初始化小蛇 public void init(){ length = 3; snakeX[0] = 100;snakeY[0] = 100;//脑袋坐标 snakeX[1] = 75;snakeY[1] = 100;//第一块身体坐标 snakeX[2] = 25;snakeY[2] = 100;//第二块身体坐标 fx = "R";//初始方向向右 } //绘制组件函数,我们在游戏中所有的东西使用画笔来画 @Override protected void paintComponent(Graphics g) { super.paintComponent(g);//清屏作用,不用出现闪屏 //1.绘制静态面板 this.setBackground(Color.BLUE); Data.header.paintIcon(this,g,25,0);//游戏头部,广告栏 g.fillRect(25,65,750,500);//默认游戏界面 //2.绘制静态的小蛇 Data.right.paintIcon(this,g,snakeX[0],snakeY[0]); for (int i = 1; i < length; i++) { Data.body.paintIcon(this,g,snakeX[i],snakeY[i] );//身体的坐标 } //3.画游戏状态 if (!isStart){ g.setColor(Color.white); g.setFont(new Font("微软雅黑",Font.BOLD,40));//设置字体,第二个参数加粗 g.drawString("按下空格开始游戏!",300,450); } } //游戏状态的键盘监听事件 @Override public void keyPressed(KeyEvent e) { int keyCode = e.getKeyCode();//获得键盘按键是哪一个 if (keyCode == KeyEvent.VK_SPACE){//如果按下键盘,则取反游戏状态 isStart = !isStart; repaint(); } } @Override public void keyTyped(KeyEvent e) { } @Override public void keyReleased(KeyEvent e) { } }
三、小蛇移动
定时器实现小蛇移动
-
想要小蛇动起来,即要用到定时器及时更新面板状态
-
面板实现ActionListener,重写actionPerformed方法
修改GamePanel类
package com.gui.lesson01.game; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; public class GamePanel extends JPanel implements KeyListener, ActionListener { //定义蛇的数据结构 int length;//蛇的长度 int[] snakeX = new int[600]; int[] snakeY = new int[500]; String fx;//蛇的初始方向 //游戏状态,默认不开始 boolean isStart = false; //定时器 // Timer timer = new Timer(100,this);//监听当前类,100毫秒刷新一次 Timer timer = new Timer(100,this); //构造器 public GamePanel(){ init(); //获得焦点和键盘事件 this.setFocusable(true);//获得焦点事件 this.addKeyListener(this);//获得事件监听事件 timer.start();//游戏一开始就开始计时 } //初始化小蛇 public void init(){ length = 3; snakeX[0] = 100;snakeY[0] = 100;//脑袋坐标 snakeX[1] = 75;snakeY[1] = 100;//第一块身体坐标 snakeX[2] = 25;snakeY[2] = 100;//第二块身体坐标 fx = "R";//初始方向向右 } //绘制组件函数,我们在游戏中所有的东西使用画笔来画 @Override protected void paintComponent(Graphics g) { super.paintComponent(g);//清屏作用,不用出现闪屏 //1.绘制静态面板 this.setBackground(Color.BLUE); Data.header.paintIcon(this,g,25,11);//游戏头部,广告栏 g.fillRect(25,65,750,500);//默认游戏界面 //2.绘制静态的小蛇 Data.right.paintIcon(this,g,snakeX[0],snakeY[0]); for (int i = 1; i < length; i++) { Data.body.paintIcon(this,g,snakeX[i],snakeY[i] );//身体的坐标 } //3.画游戏状态 if (!isStart){ g.setColor(Color.white); g.setFont(new Font("微软雅黑",Font.BOLD,40));//设置字体,第二个参数加粗 g.drawString("按下空格开始游戏!",300,450); } } //游戏状态的键盘监听事件 @Override public void keyPressed(KeyEvent e) { int keyCode = e.getKeyCode();//获得键盘按键是哪一个 if (keyCode == KeyEvent.VK_SPACE){//如果按下键盘,则取反游戏状态 isStart = !isStart; repaint(); } } //事件监听-需要用到固定时间来刷新,1S10次 @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(fx.equals("R")){ snakeX[0] = snakeX[0] + 25; } //边界判断 if(snakeX[0]>750){ snakeX[0] = 25; } repaint();//重画页面 } timer.start();//定时开启~ } @Override public void keyTyped(KeyEvent e) { } @Override public void keyReleased(KeyEvent e) { } }
小蛇方向控制
-
控制方向则要严格计算每次坐标的大小
-
比如向上走超出边框则马上从最底再次进入游戏界面
-
例如蛇走到顶端,下一步出现在底端的坐标则刚好放在最下方的时刻,即为(游戏界面最底端到最顶端的距离-蛇头宽度)
修改GamePanel类
package com.gui.lesson01.game; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; public class GamePanel extends JPanel implements KeyListener, ActionListener { //定义蛇的数据结构 int length;//蛇的长度 int[] snakeX = new int[600]; int[] snakeY = new int[500]; String fx;//蛇的初始方向 //游戏状态,默认不开始 boolean isStart = false; //定时器 Timer timer = new Timer(100,this);//监听当前类,100毫秒刷新一次 //构造器 public GamePanel(){ init(); //获得焦点和键盘事件 this.setFocusable(true);//获得焦点事件 this.addKeyListener(this);//获得事件监听事件 timer.start();//游戏一开始就开始计时 } //初始化小蛇 public void init(){ length = 3; snakeX[0] = 100;snakeY[0] = 100;//脑袋坐标 snakeX[1] = 75;snakeY[1] = 100;//第一块身体坐标 snakeX[2] = 25;snakeY[2] = 100;//第二块身体坐标 fx = "R";//初始方向向右 } //绘制组件函数,我们在游戏中所有的东西使用画笔来画 @Override protected void paintComponent(Graphics g) { super.paintComponent(g);//清屏作用,不用出现闪屏 //1.绘制静态面板 this.setBackground(Color.BLUE); Data.header.paintIcon(this,g,25,11);//游戏头部,广告栏 g.fillRect(25,65,750,500);//默认游戏界面 //把小蛇画上去 if(fx.equals("R")){ Data.right.paintIcon(this,g,snakeX[0],snakeY[0]); }else if(fx.equals("up")){ Data.up.paintIcon(this,g,snakeX[0],snakeY[0]); }else if(fx.equals("down")){ Data.down.paintIcon(this,g,snakeX[0],snakeY[0]); }else if(fx.equals("L")){ Data.left.paintIcon(this,g,snakeX[0],snakeY[0]); } //2.绘制静态的小蛇 //Data.right.paintIcon(this,g,snakeX[0],snakeY[0]); for (int i = 1; i < length; i++) { Data.body.paintIcon(this,g,snakeX[i],snakeY[i] );//身体的坐标 } //3.画游戏状态 if (!isStart){ g.setColor(Color.white); g.setFont(new Font("微软雅黑",Font.BOLD,40));//设置字体,第二个参数加粗 g.drawString("按下空格开始游戏!",300,450); } } //游戏状态的键盘监听事件 @Override public void keyPressed(KeyEvent e) { int keyCode = e.getKeyCode();//获得键盘按键是哪一个 if (keyCode == KeyEvent.VK_SPACE){//如果按下键盘,则取反游戏状态 isStart = !isStart; repaint(); } //小蛇移动监听 if (keyCode==KeyEvent.VK_UP){ fx="up"; }else if (keyCode==KeyEvent.VK_DOWN){ fx="down"; }else if (keyCode==KeyEvent.VK_LEFT){ fx="left"; }else if (keyCode==KeyEvent.VK_RIGHT){ fx="right"; } } //事件监听-需要用到固定时间来刷新,1S10次 @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(fx.equals("R")){ snakeX[0] = snakeX[0] + 25; if(snakeX[0]>850){ snakeX[0] = 25; } }else if(fx.equals("L")){ snakeX[0] = snakeX[0] - 25; if(snakeX[0]<25){ snakeX[0] = 25; } }else if(fx.equals("up")){ snakeY[0] = snakeY[0] - 25; if(snakeY[0]<75){ snakeX[0] = 650; } }else if(fx.equals("down")){ snakeY[0] = snakeY[0] + 25; if(snakeX[0]>650){ snakeX[0] = 75; } } repaint();//重画页面 } timer.start();//定时开启~ } @Override public void keyTyped(KeyEvent e) { } @Override public void keyReleased(KeyEvent e) { } }
小蛇吃食物
-
定义食物坐标,并且需要一个random来使食物随机生成在界面上
//初始化食物的坐标 int foodX; int foodY; Random random=new Random();
-
在画板中,将食物画到界面上
//把食物画上去 Data.food.paintIcon(this,g,foodX,foodY);
-
在监听事件中,判定吃食物
//吃食物判断 if (snakeX[0]==foodX && snakeY[0]==foodY){ length++;//长度+1 //再次随机生成食物 //初始化食物,把食物随机分布在界面上 foodX=25+25*random.nextInt(34); foodY=75+25*random.nextInt(24); }
小蛇失败判定
-
新建游戏状态
boolean isFail= false;//默认游戏状态失败
-
绘制失败在画板上
if (isFail){ g.setColor(Color.red); g.setFont(new Font("微软雅黑", Font.BOLD, 40));//设置字体 g.drawString("失败,按下空格重新开始!", 300, 450); }
-
监听键盘事件判断游戏状态
if (keyCode==KeyEvent.VK_SPACE){//如果按下空格,则取反游戏状态 if (isFail){ //重新开始 isFail=false; init();//重新开始重置游戏初始状态 }else { isStart=!isStart; } repaint(); }
-
游戏事件监听,当小蛇撞到自己判断游戏失败
//失败判断,撞到自己算失败 for (int i=1;i<length;i++){ if (snakeX[0]==snakeX[i] && snakeY[0]==snakeY[i]){ isFail=true; } }
添加游戏数据面板
-
新增游戏初始分数
//初始化分数 int score; //初始化小蛇 public void init() { length = 3; snakeX[0] = 100;snakeY[0] = 100;//脑袋坐标 snakeX[1] = 75;snakeY[1] = 100;//第一块身体坐标 snakeX[2] = 50;snakeY[2] = 100;//第二块身体坐标 fx = "R";//初始化方向向右 //初始化食物,把食物随机分布在界面上 foodX=25+25*random.nextInt(34); foodY=75+25*random.nextInt(24); //初始分数 score=0; }
-
画分数和长度到面板
//画积分和长度 g.setColor(Color.WHITE); g.setFont(new Font("宋体", Font.BOLD, 15)); g.drawString("长度:"+length,750,35); g.drawString("分数:"+score,750,50);
-
事件判断吃到食物分数增加10
//吃食物判断 if (snakeX[0]==foodX && snakeY[0]==foodY){ length++;//长度+1 score=score+10; //再次随机生成食物 //初始化食物,把食物随机分布在界面上 foodX=25+25*random.nextInt(34); foodY=75+25*random.nextInt(24); }
四、Data数据类
package cn.lxz.day07.snake; import javax.swing.*; import java.net.URL; public class Data { //头部图片 public static URL headerUrl = Data.class.getResource("/statics/header.png"); public static ImageIcon header = new ImageIcon(headerUrl); //头部:上下左右 public static URL upUrl = Data.class.getResource("/statics/up.png"); public static URL downUrl = Data.class.getResource("/statics/down.png"); public static URL leftUrl = Data.class.getResource("/statics/left.png"); public static URL rightUrl = Data.class.getResource("/statics/right.png"); public static ImageIcon up = new ImageIcon(upUrl); public static ImageIcon down = new ImageIcon(downUrl); public static ImageIcon left = new ImageIcon(leftUrl); public static ImageIcon right = new ImageIcon(rightUrl); //身体 public static URL bodyUrl = Data.class.getResource("/statics/body.png"); public static ImageIcon body = new ImageIcon(bodyUrl); //食物 public static URL foodUrl = Data.class.getResource("/statics/food.png"); public static ImageIcon food = new ImageIcon(foodUrl); }
五、StartGame 主函数开启
package cn.lxz.day07.snake; import javax.swing.*; public class StartGame { public static void main(String[] args) { //1.新建一个窗口 JFrame frame = new JFrame("狂神说Java-贪吃蛇小游戏"); frame.setBounds(600,10,900,720); // 设置窗口的位置和大小 frame.setResizable(false); //窗口大小不可调整,即固定窗口大小 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置关闭事件,游戏可以关闭 //2.添加我们自己编写的画布背景 frame.add(new GamePanel()); frame.setVisible(true); //将窗口展示出来 } }
六、GamePanel 游戏面板
package cn.lxz.day07.snake; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.Random; //游戏的面板 public class GamePanel extends JPanel implements KeyListener, ActionListener { //定义蛇的数据结构 int lenth; //蛇的长度 int[] snakeX = new int[600]; //蛇的坐标x int[] snakeY = new int[500]; //蛇的坐标y String fx = "R"; //蛇的方向 : R:右 L:左 U:上 D:下 boolean isStart = false; //游戏是否开始 Timer timer = new Timer(100, this); //定时器:第一个参数,就是定时执行时间 //食物 int foodx; int foody; Random random = new Random(); boolean isFail = false; //游戏是否结束 int score; //游戏分数! //构造方法 public GamePanel() { init();//初始化 this.setFocusable(true); //获取焦点事件 this.addKeyListener(this); //键盘监听事件 timer.start(); } //初始化方法 public void init() { lenth = 3;//初始小蛇有三节,包括小脑袋 //初始化开始的蛇,给蛇定位, snakeX[0] = 100; snakeY[0] = 100; snakeX[1] = 75; snakeY[1] = 100; snakeX[2] = 50; snakeY[2] = 100; //初始化食物数据 foodx = 25 + 25 * random.nextInt(34); foody = 75 + 25 * random.nextInt(24); score = 0; //初始化游戏分数 } /** * 一开始我们进来我们需要绘制面板 * Alt+ins Overrid 里面有个paintComponent方法用来绘制组件 * 画组件 * * @param g 游戏中的所有东西,都用这只g画笔来画就够了 */ public void paintComponent(Graphics g) { super.paintComponent(g);//清屏 //绘制静态面板 this.setBackground(Color.pink); //设置面板的背景色 Data.header.paintIcon(this, g, 25, 11); //绘制头部广告栏 /** * Paints the icon. * The top-left corner of the icon is drawn at * the point (<code>x</code>, <code>y</code>) * in the coordinate space of the graphics context <code>g</code>. * If this icon has no image observer, * this method uses the <code>c</code> component * as the observer. * * @param c the component to be used as the observer * if this icon has no image observer * @param g the graphics context * @param x the X coordinate of the icon's top-left corner * @param y the Y coordinate of the icon's top-left corner */ g.fillRect(25, 75, 850, 600); //绘制游戏区域 //把小蛇画上去 if (fx.equals("R")) { //蛇的头通过方向变量来判断 Data.right.paintIcon(this, g, snakeX[0], snakeY[0]); } else if (fx.equals("L")) { Data.left.paintIcon(this, g, snakeX[0], snakeY[0]); } else if (fx.equals("U")) { Data.up.paintIcon(this, g, snakeX[0], snakeY[0]); } else if (fx.equals("D")) { Data.down.paintIcon(this, g, snakeX[0], snakeY[0]); } for (int i = 1; i < lenth; i++) { Data.body.paintIcon(this, g, snakeX[i], snakeY[i]); //蛇的身体长度根据lenth来控制 } //画食物 Data.food.paintIcon(this, g, foodx, foody); g.setColor(Color.white); g.setFont(new Font("微软雅黑", Font.BOLD, 18)); g.drawString("长度 " + lenth, 750, 35); g.drawString("分数 " + score, 750, 50); //游戏提示 if (isStart == false) { g.setColor(Color.white); g.setFont(new Font("微软雅黑", Font.BOLD, 40)); g.drawString("按下空格开始游戏!", 300, 300); } //失败判断 if (isFail) { g.setColor(Color.RED); g.setFont(new Font("微软雅黑", Font.BOLD, 40)); g.drawString("失败, 按下空格重新开始", 200, 300); } } @Override public void keyTyped(KeyEvent e) { } //键盘监听事件 @Override public void keyPressed(KeyEvent e) { int keyCode = e.getKeyCode(); //获取按下的键盘 if (keyCode == KeyEvent.VK_SPACE) { //如果是空格 if (isFail) { //如果游戏失败,从头再来! isFail = false; init(); //重新初始化 } else { //否则,暂停游戏 isStart = !isStart; } repaint(); } //键盘控制走向 if (keyCode == KeyEvent.VK_LEFT) { fx = "L"; } else if (keyCode == KeyEvent.VK_RIGHT) { fx = "R"; } else if (keyCode == KeyEvent.VK_UP) { fx = "U"; } else if (keyCode == KeyEvent.VK_DOWN) { fx = "D"; } } @Override public void keyReleased(KeyEvent e) { } //定时执行的操作 @Override public void actionPerformed(ActionEvent e) { //如果游戏处于开始状态,并且没有结束,则小蛇可以移动 if (isStart && isFail == false) { //右移:即让后一个移到前一个的位置即可 ! for (int i = lenth - 1; i > 0; i--) { //除了脑袋都往前移:身体移动 snakeX[i] = snakeX[i - 1]; //即第i节(后一节)的位置变为(i-1:前一节)节的位置! snakeY[i] = snakeY[i - 1]; } //通过方向控制,头部移动 if (fx.equals("R")) { snakeX[0] = snakeX[0] + 25; if (snakeX[0] > 850) snakeX[0] = 25; } else if (fx.equals("L")) { snakeX[0] = snakeX[0] - 25; if (snakeX[0] < 25) snakeX[0] = 850; } else if (fx.equals("U")) { snakeY[0] = snakeY[0] - 25; if (snakeY[0] < 75) snakeY[0] = 650; } else if (fx.equals("D")) { snakeY[0] = snakeY[0] + 25; if (snakeY[0] > 650) snakeY[0] = 75; } //吃食物:当蛇的头和食物一样时,算吃到食物! if (snakeX[0] == foodx && snakeY[0] == foody) { //1.长度加一 lenth++; //每吃一个食物,增加积分 score = score + 10; //2.重新生成食物 foodx = 25 + 25 * random.nextInt(34); foody = 75 + 25 * random.nextInt(24); } //结束判断,头和身体撞到了 for (int i = 1; i < lenth; i++) { //如果头和身体碰撞,那就说明游戏失败 if (snakeX[i] == snakeX[0] && snakeY[i] == snakeY[0]) { isFail = true; } } repaint(); //需要不断的更新页面实现动画 } timer.start();//让时间动起来! } }
七、游戏素材
八、总结:
GUI中常用的几种容器和标签方法就到这里结束了,在贪吃蛇小游戏中,几乎涵盖了所有的GUI基础,还有javaSE的部分知识
还可优化的点:
-
游戏拓展性:
-
比如可以有多种样子的食物,分别代表不同的分数
-
添加等级,到达一定分数就将定时器的时间缩短,以实现小蛇速度的提升
-
连接数据库,将游戏数据写入数据库,再次打开游戏可以继续玩上次记录
-