在B站看视频的时候,看到下面这个视频就比较感兴趣,跟着手动做了一遍,写下这篇博客,
【狂神说Java】一小时开发贪吃蛇游戏 https://www.bilibili.com/video/BV1HE41127CV?p=1
最终游戏的界面长这个(都是画图做的图片,能力有限)
具体的步骤,可以参照上面的视频听一下,简单易懂,(但是有一个问题,就是头部和身体重合的时候,头部的图片在下面被隐藏了,待改正,但是还没想好怎么实现,有知道怎么实现的,跪求告诉了...)
游戏规则:小蛇初始长度为3,可以上下左右四个方向移动,吃到蓝色的食物,身体长度加一,分数加10,可以穿过自己的身体,也可以穿过墙壁,(没有死亡,因为触碰身体就死掉的话很不爽,也有这个功能,只不过注释掉了)
玩了一会,之后的界面
之前使用IDEA写的代码,但是一直说的激活码失效了,一直自动退出,就很气,改用eclipse了
下面开始说一下,编写的步骤:
1. 首先创建一个简单的java项目 File -> 右键 - > New -> Project
2.创建以下目录
3,把相关的图片添加到此文件夹下
下面就是对应图片。。。有需要直接拿走,别吐槽,我自己都惊呆了,技术有限
这个是对应图片的名字
4. 创建一个 StartGames 类,用来绘制窗口
1 package com.wang.snake; 2 3 import javax.swing.*; 4 5 public class StartGames { 6 public static void main(String[] args) { 7 // 1.绘制一个静态窗口 8 JFrame frame = new JFrame("贪吃蛇小游戏"); 9 // 2.设置界面的大小 10 frame.setBounds(100,10,910,720); 11 // 3.设置窗口不可调整大小 12 frame.setResizable(false); 13 // 4.“X”的关闭事件,关闭窗口 14 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 15 // 5.把面板添加到窗口里 16 frame.add(new GamePanel()); 17 // 6.让窗口可以展示 18 frame.setVisible(true); 19 } 20 }
5.创建Data类,获取添加的图片信息
1 package com.wang.snake; 2 3 import javax.swing.*; 4 import java.net.URL; 5 6 /** 7 * 存放数据 8 */ 9 public class Data { 10 11 // 获取标题栏图片的相对路径 12 public static URL headerURL = Data.class.getResource("/statics/header.png"); 13 // 转换成图片 14 public static ImageIcon header = new ImageIcon(headerURL); 15 16 // 获取小蛇的头部图片 17 public static URL upURL = Data.class.getResource("/statics/up.png"); 18 public static URL rightURL = Data.class.getResource("/statics/right.png"); 19 public static URL downURL = Data.class.getResource("/statics/down.png"); 20 public static URL leftURL = Data.class.getResource("/statics/left.png"); 21 public static ImageIcon upHeader = new ImageIcon(upURL); 22 public static ImageIcon rightHeader = new ImageIcon(rightURL); 23 public static ImageIcon downHeader = new ImageIcon(downURL); 24 public static ImageIcon leftHeader = new ImageIcon(leftURL); 25 26 // 获取小蛇的身体图片 27 public static URL bodyURL = Data.class.getResource("/statics/body.png"); 28 public static ImageIcon body = new ImageIcon(bodyURL); 29 30 // 获取食物的图片 31 public static URL foodURL = Data.class.getResource("/statics/food.png"); 32 public static ImageIcon food = new ImageIcon(foodURL); 33 34 }
6.创建GamePanel类,绘制界面,包含了标题块,游戏块,以及小蛇的移动等功能的实现类
1 package com.wang.snake; 2 3 import javax.swing.*; 4 import java.awt.*; 5 import java.awt.event.ActionEvent; 6 import java.awt.event.ActionListener; 7 import java.awt.event.KeyEvent; 8 import java.awt.event.KeyListener; 9 import java.util.Iterator; 10 import java.util.Random; 11 12 public class GamePanel extends JPanel implements KeyListener, ActionListener { 13 14 // 游戏是否开始flag 15 boolean isStart = false; 16 // 游戏是否失败flag 17 boolean isFail = false; 18 19 //定义一个记分系统 20 int score; 21 22 // 定义一个小蛇 23 String fx; // 定义小蛇头部的朝向 24 int length; // 小蛇的长度 25 int[] snakeX = new int[600]; // 小蛇的X坐标 26 int[] snakeY = new int[500]; // 小蛇的Y坐标 27 28 // 定义一个食物 29 int foodX; // 食物的X坐标 30 int foodY; // 食物的Y坐标 31 // 随机数的对象 32 Random random = new Random(); 33 34 // 定义一个定时器 35 Timer timer = new Timer(100,this); 36 37 //创建构造器,调用init方法, 38 public GamePanel(){ 39 // 小蛇的初始化 40 init(); 41 42 // 获取键盘的监听事件 43 // 获取焦点,默认是不获取焦点的 44 this.setFocusable(true); 45 // 添加监听 46 this.addKeyListener(this); 47 // 启动定时器 48 timer.start(); 49 } 50 51 // 小蛇和食物的初始化的长度及位置 52 public void init(){ 53 // 记分的初期值设置为0 54 score = 0; 55 56 // 小蛇的长度是3个,头部位置在X:100,Y:100的坐标处,朝向右 57 fx = "R"; 58 length = 3; 59 snakeX[0] = 100; 60 snakeY[0] = 100; 61 // 身体部分的坐标,第一部分的位置在X:75,Y:100的坐标处, 62 snakeX[1] = 75; 63 snakeY[1] = 100; 64 // 第二部分的位置在X:50,Y:100的坐标处, 65 snakeX[2] = 50; 66 snakeY[2] = 100; 67 68 // 食物的初期的位置随机的坐标, 69 foodX = 25 + 25 * random.nextInt(34); 70 foodY = 75 + 25 * random.nextInt(24); 71 } 72 73 // Graphics 画笔 74 // 面板:画界面,画小蛇 75 @Override 76 protected void paintComponent(Graphics g) { 77 // 调用父类方法,清屏 78 super.paintComponent(g); 79 80 // 设置面板背景颜色(黑色) 81 this.setBackground(Color.white); 82 83 // 绘制头部的标题栏 84 Data.header.paintIcon(this,g,25,10); 85 86 // 绘制游戏区域 87 g.fillRect(25,75,850,600); 88 89 // 创建一个静态的小蛇 90 if (fx.equals("R")){ 91 Data.rightHeader.paintIcon(this,g,snakeX[0], snakeY[0]); 92 } else if (fx.equals("L")){ 93 Data.leftHeader.paintIcon(this,g,snakeX[0], snakeY[0]); 94 } else if (fx.equals("U")){ 95 Data.upHeader.paintIcon(this,g,snakeX[0], snakeY[0]); 96 } else if (fx.equals("D")){ 97 Data.downHeader.paintIcon(this,g,snakeX[0], snakeY[0]); 98 } 99 100 // 通过for循环,创建小蛇的身体部分 101 for (int i = 1; i < length; i++){ 102 Data.body.paintIcon(this,g,snakeX[i], snakeY[i]); 103 } 104 105 // 把食物添加到窗口里 106 Data.food.paintIcon(this,g,foodX, foodY); 107 108 // 游戏开始提示:是否开始游戏 109 if (isStart == false){ 110 // 提示文字的属性, 111 g.setColor(Color.white); 112 g.setFont(new Font("微软雅黑",Font.BOLD,40)); 113 g.drawString("按下空格开始游戏",300,300); 114 } 115 116 // 游戏失败提示:游戏失败 117 if (isFail){ 118 // 提示文字的属性, 119 g.setColor(Color.red); 120 g.setFont(new Font("微软雅黑",Font.BOLD,40)); 121 g.drawString("游戏失败,按下空格重新开始游戏",200,300); 122 } 123 124 // 记分提示文字的属性, 125 g.setColor(Color.white); 126 g.setFont(new Font("微软雅黑",Font.BOLD,18)); 127 g.drawString("长度:" + length,750,33); 128 g.drawString("分数:" + score,750,55); 129 } 130 131 /** 132 * 接受键盘的输入,进行监听 133 * @param e 134 */ 135 @Override 136 public void keyPressed(KeyEvent e) { 137 // 获取键盘按下的是哪一个键 138 int keyCode = e.getKeyCode(); 139 140 // 如果按下的是空格键,就启动或者暂停 141 if (keyCode == KeyEvent.VK_SPACE){ 142 // 如果游戏失败 143 if (isFail) { 144 // 游戏重新开始 145 isFail = false; 146 // 重新初始化游戏 147 init(); 148 } else { 149 // 暂停游戏 150 isStart = !isStart; 151 } 152 153 // 刷新界面 154 repaint(); 155 } 156 157 // 判断键盘按下的上下左右键,控制小蛇的走向 158 if (keyCode == KeyEvent.VK_LEFT) { 159 fx = "L"; 160 } else if (keyCode == KeyEvent.VK_RIGHT){ 161 fx = "R"; 162 }else if (keyCode == KeyEvent.VK_UP) { 163 fx = "U"; 164 }else if (keyCode == KeyEvent.VK_DOWN) { 165 fx = "D"; 166 } 167 168 } 169 170 /** 171 * 定时器,监听事件,执行定时操作 172 * @param e 173 */ 174 @Override 175 public void actionPerformed(ActionEvent e) { 176 177 // 如果游戏处于开始状态,并且游戏没有结束,小蛇就移动 178 if (isStart && isFail == false){ 179 180 // 除了头部,身体都跟着往右移动 181 for (int i = length - 1; i > 0; i--) { 182 snakeX[i] = snakeX[i-1]; 183 snakeY[i] = snakeY[i-1]; 184 } 185 186 // 通过控制方向,移动头部 187 if (fx.equals("R")){ 188 // 头部向右移动一步 189 snakeX[0] = snakeX[0] + 25; 190 // 如果已经到达边界,则从最右侧重新开始 191 if (snakeX[0] > 850){ 192 snakeX[0] = 25; 193 } 194 } else if (fx.equals("L")){ 195 // 头部向左移动一步 196 snakeX[0] = snakeX[0] - 25; 197 // 如果已经到达边界,则从最右侧重新开始 198 if (snakeX[0] < 25){ 199 snakeX[0] = 850; 200 } 201 } else if (fx.equals("U")){ 202 // 头部向左移动一步 203 snakeY[0] = snakeY[0] - 25; 204 // 如果已经到达边界,则从最右侧重新开始 205 if (snakeY[0] < 75){ 206 snakeY[0] = 650; 207 } 208 } else if (fx.equals("D")){ 209 // 头部向左移动一步 210 snakeY[0] = snakeY[0] + 25; 211 // 如果已经到达边界,则从最右侧重新开始 212 if (snakeY[0] > 650){ 213 snakeY[0] = 75; 214 } 215 } 216 217 //System.out.println("snakeX = "+snakeX[0]+" foodX="+foodX +"////snakeY = "+snakeY[0]+" foodY="+foodY); 218 // 判断小蛇的头的坐标和食物的坐标重合的情况下 219 if (snakeX[0] == foodX && snakeY[0] == foodY) 220 { 221 // 小蛇的身体长度加1 222 length ++ ;
snakeX[length-1] = snakeX[length-2] * 2 - snakeX[length-3]; snakeY[length-1] = snakeY[length-2] * 2 - snakeY[length-3]; 223 224 // 每当吃掉一个食物,积分+10 225 score = score + 10; 226 227 // 生成新的食物 228 foodX = 25 + 25 * random.nextInt(34); 229 foodY = 75 + 25 * random.nextInt(24); 230 } 231 232 // 结束判断:头和身体有接触,则游戏失败 233 //for (int i = 1; i < length; i++) { 234 // if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) { 235 // // isFail = true; 236 // 237 // } 238 //}
239 // 刷新界面 240 repaint(); 241 } 242 // 定时器启动 243 timer.start(); 244 } 245 246 /** 247 * 键盘按下, 248 * @param e 249 */ 250 @Override 251 public void keyTyped(KeyEvent e) { 252 253 } 254 255 /** 256 * 257 * @param e 258 */ 259 @Override 260 public void keyReleased(KeyEvent e) { 261 262 } 263 264 265 }
第二次改版,添加了计时器的功能,同时修改了之前Date类的类名=> ImageData
代码如下 GamePanel类的改修,底色为粉色的部分
1 package com.wang.snake; 2 3 import java.awt.Color; 4 import java.awt.Font; 5 import java.awt.Graphics; 6 import java.awt.event.ActionEvent; 7 import java.awt.event.ActionListener; 8 import java.awt.event.KeyEvent; 9 import java.awt.event.KeyListener; 10 import java.text.AttributedCharacterIterator; 11 import java.util.Random; 12 13 import javax.swing.JPanel; 14 import javax.swing.Timer; 15 16 public class GamePanel extends JPanel implements KeyListener, ActionListener { 17 18 // 游戏是否开始flag 19 boolean isStart = false; 20 // 游戏是否失败flag 21 boolean isFail = false; 22 23 // 计时器的显示格式: 时分秒 24 String initialTime = "00:00:00"; 25 // 程序第一次开始时间 26 long programStart = System.currentTimeMillis(); 27 // 程序一开始就是暂停的 28 long pauseStart = programStart; 29 // 程序暂停的总时间 30 long pauseCount = 0; 31 33 //定义一个记分系统 34 int score; 35 36 // 定义一个小蛇 37 String fx; // 定义小蛇头部的朝向 38 int length; // 小蛇的长度 39 int[] snakeX = new int[600]; // 小蛇的X坐标 40 int[] snakeY = new int[500]; // 小蛇的Y坐标 41 42 // 定义一个食物 43 int foodX; // 食物的X坐标 44 int foodY; // 食物的Y坐标 45 // 随机数的对象 46 Random random = new Random(); 47 48 // 定义一个定时器 49 Timer timer = new Timer(100,this); 50 51 //创建构造器,调用init方法, 52 public GamePanel(){ 53 // 小蛇的初始化 54 init(); 55 56 // 获取键盘的监听事件 57 // 获取焦点,默认是不获取焦点的 58 this.setFocusable(true); 59 // 添加监听 60 this.addKeyListener(this); 61 // 启动定时器 62 timer.start(); 63 } 64 65 // 小蛇和食物的初始化的长度及位置 66 public void init(){ 67 // 记分的初期值设置为0 68 score = 0; 69 70 // 小蛇的长度是3个,头部位置在X:100,Y:100的坐标处,朝向右 71 fx = "R"; 72 length = 3; 73 snakeX[0] = 100; 74 snakeY[0] = 100; 75 // 身体部分的坐标,第一部分的位置在X:75,Y:100的坐标处, 76 snakeX[1] = 75; 77 snakeY[1] = 100; 78 // 第二部分的位置在X:50,Y:100的坐标处, 79 snakeX[2] = 50; 80 snakeY[2] = 100; 81 82 // 食物的初期的位置随机的坐标, 83 foodX = 25 + 25 * random.nextInt(34); 84 foodY = 75 + 25 * random.nextInt(24); 85 } 86 87 // Graphics 画笔 88 // 面板:画界面,画小蛇 89 @Override 90 protected void paintComponent(Graphics g) { 91 // 调用父类方法,清屏 92 super.paintComponent(g); 93 94 // 设置面板背景颜色(黑色) 95 this.setBackground(Color.white); 96 97 // 绘制头部的标题栏 98 ImageData.header.paintIcon(this,g,25,10); 99 100 // 绘制游戏区域 101 g.fillRect(25,75,850,600); 102 103 // 创建一个静态的小蛇 104 if (fx.equals("R")){ 105 ImageData.rightHeader.paintIcon(this,g,snakeX[0], snakeY[0]); 106 } else if (fx.equals("L")){ 107 ImageData.leftHeader.paintIcon(this,g,snakeX[0], snakeY[0]); 108 } else if (fx.equals("U")){ 109 ImageData.upHeader.paintIcon(this,g,snakeX[0], snakeY[0]); 110 } else if (fx.equals("D")){ 111 ImageData.downHeader.paintIcon(this,g,snakeX[0], snakeY[0]); 112 } 113 114 // 通过for循环,创建小蛇的身体部分 115 for (int i = 1; i < length; i++){ 116 ImageData.body.paintIcon(this,g,snakeX[i], snakeY[i]); 117 } 118 119 // 把食物添加到窗口里 120 ImageData.food.paintIcon(this,g,foodX, foodY); 121 122 // 游戏开始提示:是否开始游戏 123 if (isStart == false){ 124 // 提示文字的属性, 125 g.setColor(Color.white); 126 g.setFont(new Font("微软雅黑",Font.BOLD,40)); 127 g.drawString("按下空格开始游戏",300,300); 128 } 129 130 // 游戏失败提示:游戏失败 131 if (isFail){ 132 // 提示文字的属性, 133 g.setColor(Color.red); 134 g.setFont(new Font("微软雅黑",Font.BOLD,40)); 135 g.drawString("游戏失败,按下空格重新开始游戏",200,300); 136 } 137 138 // 记分提示文字的属性, 139 g.setColor(Color.white); 140 g.setFont(new Font("微软雅黑",Font.BOLD,18)); 141 g.drawString("长度:" + length,750,33); 142 g.drawString("分数:" + score,750,55); 143 144 // 计时器提示文字的属性, 145 g.setColor(Color.white); 146 g.setFont(new Font("微软雅黑",Font.BOLD,18)); 147 g.drawString("计时器:" + initialTime,30,55); 148 149 } 150 151 /** 152 * 接受键盘的输入,进行监听 153 * @param e 154 */ 155 @Override 156 public void keyPressed(KeyEvent e) { 157 // 获取键盘按下的是哪一个键 158 int keyCode = e.getKeyCode(); 159 160 // 如果按下的是空格键,就启动或者暂停 161 if (keyCode == KeyEvent.VK_SPACE) { 162 // 如果游戏失败 163 if (isFail) { 164 // 游戏重新开始 165 isFail = false; 166 167 // 计时器时间初始化 168 programStart = System.currentTimeMillis(); 169 pauseStart = programStart; 170 pauseCount = 0; 171 172 // 重新初始化游戏 173 init(); 174 175 } else { 176 // 暂停游戏 177 isStart = !isStart; 178 // 如果是游戏开始,则获取暂停的时间,否则暂停的时间等于当前时间 179 if (isStart) { 180 pauseCount += (System.currentTimeMillis() - pauseStart); 181 } else { 182 pauseStart = System.currentTimeMillis(); 183 } 184 } 185 186 // 刷新界面 187 repaint(); 188 } 189 190 // 判断键盘按下的上下左右键,控制小蛇的走向 191 if (keyCode == KeyEvent.VK_LEFT) { 192 fx = "L"; 193 } else if (keyCode == KeyEvent.VK_RIGHT){ 194 fx = "R"; 195 }else if (keyCode == KeyEvent.VK_UP) { 196 fx = "U"; 197 }else if (keyCode == KeyEvent.VK_DOWN) { 198 fx = "D"; 199 } 200 } 201 202 /** 203 * 定时器,监听事件,执行定时操作 204 * @param e 205 */ 206 @Override 207 public void actionPerformed(ActionEvent e) { 208 209 // 如果游戏处于开始状态,并且游戏没有结束,小蛇就移动 210 if (isStart && isFail == false){ 211 // 计时器持续开始计时 212 long elapsed = System.currentTimeMillis() - programStart - pauseCount; 213 initialTime = format(elapsed); 214 215 // 除了头部,身体都跟着往右移动 216 for (int i = length - 1; i > 0; i--) { 217 snakeX[i] = snakeX[i-1]; 218 snakeY[i] = snakeY[i-1]; 219 } 220 221 // 通过控制方向,移动头部 222 if (fx.equals("R")){ 223 // 头部向右移动一步 224 snakeX[0] = snakeX[0] + 25; 225 // 如果已经到达边界,则从最右侧重新开始 226 if (snakeX[0] > 850){ 227 snakeX[0] = 25; 228 } 229 } else if (fx.equals("L")){ 230 // 头部向左移动一步 231 snakeX[0] = snakeX[0] - 25; 232 // 如果已经到达边界,则从最右侧重新开始 233 if (snakeX[0] < 25){ 234 snakeX[0] = 850; 235 } 236 } else if (fx.equals("U")){ 237 // 头部向左移动一步 238 snakeY[0] = snakeY[0] - 25; 239 // 如果已经到达边界,则从最右侧重新开始 240 if (snakeY[0] < 75){ 241 snakeY[0] = 650; 242 } 243 } else if (fx.equals("D")){ 244 // 头部向左移动一步 245 snakeY[0] = snakeY[0] + 25; 246 // 如果已经到达边界,则从最右侧重新开始 247 if (snakeY[0] > 650){ 248 snakeY[0] = 75; 249 } 250 } 251 252 //System.out.println("snakeX = "+snakeX[0]+" foodX="+foodX +"////snakeY = "+snakeY[0]+" foodY="+foodY); 253 // 判断小蛇的头的坐标和食物的坐标重合的情况下 254 if (snakeX[0] == foodX && snakeY[0] == foodY) 255 { 256 // 小蛇的身体长度加1 257 length ++ ; 258 snakeX[length-1] = snakeX[length-2] * 2 - snakeX[length-3]; 259 snakeY[length-1] = snakeY[length-2] * 2 - snakeY[length-3]; 260 261 // 每当吃掉一个食物,积分+10 262 score = score + 10; 263 264 // 生成新的食物 265 foodX = 25 + 25 * random.nextInt(34); 266 foodY = 75 + 25 * random.nextInt(24); 267 } 268 269 // 结束判断:头和身体有接触,则游戏失败 270 for (int i = 1; i < length; i++) { 271 if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) { 272 isFail = true; 273 } 274 } 275 // 刷新界面 276 repaint(); 277 } 278 // 定时器启动 279 timer.start(); 280 } 281 282 // 将毫秒数格式化 283 private String format(long elapsed) { 284 int hour, minute, second, milli; 285 286 milli = (int) (elapsed % 1000); 287 elapsed = elapsed / 1000; 288 289 second = (int) (elapsed % 60); 290 elapsed = elapsed / 60; 291 292 minute = (int) (elapsed % 60); 293 elapsed = elapsed / 60; 294 295 hour = (int) (elapsed % 60); 296 297 // 带有毫秒的显示 298 //return String.format("%02d:%02d:%02d %03d", hour, minute, second, milli); 299 return String.format("%02d:%02d:%02d", hour, minute, second); 300 301 } 302 303 /** 304 * 键盘按下, 305 * @param e 306 */ 307 @Override 308 public void keyTyped(KeyEvent e) { 309 310 } 311 312 /** 313 * 314 * @param e 315 */ 316 @Override 317 public void keyReleased(KeyEvent e) { 318 319 } 320 }
第三版 添加了限时模式以及菜单栏
初期的画面
限时模式
StartGames类的改修
1 package com.wang.snake; 2 3 import java.awt.Color; 4 import java.awt.Font; 5 6 import javax.swing.*; 7 8 public class StartGames { 9 10 private final static Font f = new Font("微软雅黑", Font.PLAIN, 15); 11 private final static Font f2 = new Font("微软雅黑", Font.PLAIN, 12); 12 13 public final static String ONE_MINUTE = "00:01:00"; 14 public final static String THREE_MINUTE = "00:03:00"; 15 public final static String FIVE_MINUTE = "00:05:00"; 16 public final static int ONE_MINUTE_END_TIME = 60000; 17 public final static int THREE_MINUTE_END_TIME = 180000; 18 public final static int FIVE_MINUTE_END_TIME = 300000; 19 public final static int COUNT_DOWN_TIME_STRAT = 1; 20 21 static JFrame frame; 22 static GamePanel gamePanel; 23 24 public static void main(String[] args) { 25 // 1.绘制一个静态窗口 26 frame = new JFrame("贪吃蛇小游戏"); 27 // 2.设置界面的大小 28 frame.setBounds(100, 0, 875, 635); 29 // 3.添加菜单栏 30 menuBar(); 31 // 4.设置窗口不可调整大小 32 frame.setResizable(false); 33 // 5.“X”的关闭事件,关闭窗口 34 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 35 // 6.把面板添加到窗口里 36 gamePanel = new GamePanel(); 37 frame.add(gamePanel); 38 // 7.让窗口可以展示 39 frame.setVisible(true); 40 41 } 42 43 // 添加菜单栏以及触发的事件 44 static void menuBar() { 45 // 添加菜单栏 46 JMenuBar bar = new JMenuBar(); 47 bar.setBackground(Color.white); 48 frame.setJMenuBar(bar); 49 // 设置画面属性 50 JMenu modes = new JMenu("画面显示设定"); 51 modes.setFont(f); 52 bar.add(modes); 53 54 // 不同的游戏模式 55 JMenu Settings = new JMenu("模式选择"); 56 Settings.setFont(f); 57 bar.add(Settings); 58 59 // 无限模式 60 JMenuItem infiniteMode = new JMenuItem("无限模式"); 61 infiniteMode.setFont(f2); 62 Settings.add(infiniteMode); 63 64 // 限时模式 65 JMenuItem countDownTimeMode = new JMenuItem("限时模式"); 66 countDownTimeMode.setFont(f2); 67 Settings.add(countDownTimeMode); 68 69 // 限时模式的时长,添加限时时长的菜单栏,以及限时时长 70 JMenu duration = new JMenu("限时模式的时长"); 71 duration.setFont(f); 72 bar.add(duration); 73 JMenuItem oneMinute = new JMenuItem("1分钟"); 74 oneMinute.setFont(f2); 75 duration.add(oneMinute); 76 JMenuItem threeMinute = new JMenuItem("3分钟"); 77 threeMinute.setFont(f2); 78 duration.add(threeMinute); 79 JMenuItem friveMinute = new JMenuItem("5分钟"); 80 friveMinute.setFont(f2); 81 duration.add(friveMinute); 82 83 // 选择无限模式时的事件 84 infiniteMode.addActionListener(e -> { 85 gamePanel.infiniteTime = "00:00:00"; 86 gamePanel.programStart = System.currentTimeMillis(); 87 gamePanel.pauseStart = gamePanel.programStart; 88 gamePanel.pauseCount = 0; 89 // 限时模式未开始 90 gamePanel.iscountDownTime = 0; 91 gamePanel.isStart = false; 92 gamePanel.init(); 93 // 刷新画面 94 gamePanel.repaint(); 95 }); 96 97 // 选择限时模式时的事件 98 countDownTimeMode.addActionListener(e -> { 99 // 设置限时3分钟 100 countDownModeMinuteaddActionListener(COUNT_DOWN_TIME_STRAT, THREE_MINUTE, THREE_MINUTE_END_TIME); 101 }); 102 103 // 选择限时1分钟 104 oneMinute.addActionListener(e -> { 105 countDownModeMinuteaddActionListener(COUNT_DOWN_TIME_STRAT, ONE_MINUTE, ONE_MINUTE_END_TIME); 106 }); 107 108 // 选择限时3分钟 109 threeMinute.addActionListener(e -> { 110 countDownModeMinuteaddActionListener(COUNT_DOWN_TIME_STRAT, THREE_MINUTE, THREE_MINUTE_END_TIME); 111 }); 112 113 // 选择限时5分钟 114 friveMinute.addActionListener(e -> { 115 countDownModeMinuteaddActionListener(COUNT_DOWN_TIME_STRAT, FIVE_MINUTE, FIVE_MINUTE_END_TIME); 116 }); 117 } 118 119 // 限时时长的共同方法 120 static void countDownModeMinuteaddActionListener(Integer iscountDownTime, String countDownTime, int endTime) { 121 gamePanel.isStart = false; 122 // 限时模式开始 123 gamePanel.iscountDownTime = iscountDownTime; 124 // 设置默认时长的限时格式 125 gamePanel.countDownTime = countDownTime; 126 // 默认时长的毫秒数 127 gamePanel.endTime = endTime; 128 gamePanel.init(); 129 gamePanel.repaint(); 130 } 131 }
GamePanel类的改修
1 package com.wang.snake; 2 3 import java.awt.Color; 4 import java.awt.Font; 5 import java.awt.Graphics; 6 import java.awt.event.ActionEvent; 7 import java.awt.event.ActionListener; 8 import java.awt.event.KeyEvent; 9 import java.awt.event.KeyListener; 10 import java.util.Random; 11 12 import javax.swing.JButton; 13 import javax.swing.JPanel; 14 import javax.swing.Timer; 15 16 public class GamePanel extends JPanel implements KeyListener, ActionListener { 17 18 private static final long serialVersionUID = 1L; 19 20 // 游戏是否开始flag 21 boolean isStart = false; 22 // 游戏是否失败flag 23 static boolean isFail = false; 24 25 // 计时器的显示格式: 时分秒 26 String infiniteTime = "00:00:00"; 27 // 程序第一次开始时间 28 long programStart = System.currentTimeMillis(); 29 // 程序一开始就是暂停的 30 long pauseStart = programStart; 31 // 程序暂停的总时间 32 long pauseCount = 0; 33 34 // 限时模式 35 String countDownTime; 36 int endTime; 37 // 限时模式是否开始:0未开始,1开始,2结束 38 int iscountDownTime = 0; 39 40 // 定义一个记分系统 41 int score; 42 43 // 定义一个小蛇 44 String fx; // 定义小蛇头部的朝向 45 int length; // 小蛇的长度 46 int[] snakeX = new int[600]; // 小蛇的X坐标 47 int[] snakeY = new int[500]; // 小蛇的Y坐标 48 49 // 定义一个食物 50 int foodX; // 食物的X坐标 51 int foodY; // 食物的Y坐标 52 // 随机数,用于食物的坐标 53 Random random = new Random(); 54 55 // 定时任务间隔时长 56 int timingDuration = 100; 57 // 定义一个定时器 58 Timer timer = new Timer(timingDuration, this); 59 60 // 创建构造器,调用init方法, 61 public GamePanel() { 62 // 小蛇的初始化 63 init(); 64 65 // 获取焦点,默认是不获取焦点的 66 this.setFocusable(true); 67 68 // 添加监听 69 this.addKeyListener(this); 70 71 // 启动定时器 72 timer.start(); 73 } 74 75 // 小蛇和食物的初始化的长度及位置 76 public void init() { 77 // 记分的初期值设置为0 78 score = 0; 79 80 // 小蛇的长度是3个,头部位置在X:100,Y:100的坐标处,朝向右 81 fx = "R"; 82 length = 3; 83 snakeX[0] = 105; 84 snakeY[0] = 90; 85 // 身体部分的坐标,第一部分的位置在X:75,Y:100的坐标处, 86 snakeX[1] = 80; 87 snakeY[1] = 90; 88 // 第二部分的位置在X:50,Y:100的坐标处, 89 snakeX[2] = 55; 90 snakeY[2] = 90; 91 92 // 食物的初期的位置随机的坐标, 93 foodX = 5 + 25 * random.nextInt(34); 94 foodY = 65 + 25 * random.nextInt(20); 95 } 96 97 // Graphics 画笔 98 // 面板:画界面,画小蛇 99 @Override 100 protected void paintComponent(Graphics g) { 101 // 调用父类方法,清屏 102 super.paintComponent(g); 103 104 // 设置面板背景颜色(黑色) 105 this.setBackground(Color.white); 106 107 // 绘制头部的标题栏 108 ImageData.header.paintIcon(this, g, 5, 5); 109 110 // 绘制游戏区域 111 g.fillRect(5, 65, 850, 500); 112 113 // 创建一个静态的小蛇 114 if (fx.equals("R")) { 115 ImageData.rightHeader.paintIcon(this, g, snakeX[0], snakeY[0]); 116 } else if (fx.equals("L")) { 117 ImageData.leftHeader.paintIcon(this, g, snakeX[0], snakeY[0]); 118 } else if (fx.equals("U")) { 119 ImageData.upHeader.paintIcon(this, g, snakeX[0], snakeY[0]); 120 } else if (fx.equals("D")) { 121 ImageData.downHeader.paintIcon(this, g, snakeX[0], snakeY[0]); 122 } 123 124 // 通过for循环,创建小蛇的身体部分 125 for (int i = 1; i < length; i++) { 126 ImageData.body.paintIcon(this, g, snakeX[i], snakeY[i]); 127 } 128 129 // 把食物添加到窗口里 130 ImageData.food.paintIcon(this, g, foodX, foodY); 131 132 // 游戏开始提示:是否开始游戏 133 if (isStart == false) { 134 // 提示文字的属性, 135 g.setFont(new Font("微软雅黑", Font.BOLD, 40)); 136 // 计时器提示文字的属性, 137 if (iscountDownTime == 1) { 138 g.setColor(Color.CYAN); 139 g.drawString("限时模式:按下空格开始游戏", 150, 300); 140 } else { 141 g.setColor(Color.white); 142 g.drawString("无限模式:按下空格开始游戏", 150, 300); 143 } 144 // 游戏失败提示:游戏失败 145 } else if (isFail) { 146 // 提示文字的属性, 147 g.setColor(Color.red); 148 g.setFont(new Font("微软雅黑", Font.BOLD, 40)); 149 g.drawString("游戏结束,按下空格重新开始游戏", 150, 300); 150 } 151 152 // 记分提示文字的属性, 153 g.setColor(Color.white); 154 g.setFont(new Font("微软雅黑", Font.BOLD, 18)); 155 g.drawString("长度:" + length, 740, 28); 156 g.drawString("分数:" + score, 740, 48); 157 158 // 计时器提示文字的属性, 159 if (iscountDownTime == 1) { 160 g.drawString("限时模式倒计时:" + countDownTime, 10, 48); 161 } else { 162 g.drawString("在线时长:" + infiniteTime, 10, 48); 163 } 164 } 165 166 /** 167 * 接受键盘的输入,进行监听 168 * 169 * @param e 170 */ 171 @Override 172 public void keyPressed(KeyEvent e) { 173 // 获取键盘按下的是哪一个键 174 int keyCode = e.getKeyCode(); 175 176 // 如果按下的是空格键,就启动或者暂停 177 if (keyCode == KeyEvent.VK_SPACE) { 178 // 如果游戏失败 179 if (isFail) { 180 // 游戏重新开始 181 isFail = false; 182 183 // 计时器时间初始化 184 programStart = System.currentTimeMillis(); 185 pauseStart = programStart; 186 pauseCount = 0; 187 188 // 重新初始化游戏 189 init(); 190 191 } else { 192 // 暂停游戏 193 isStart = !isStart; 194 // 如果是游戏开始,则获取暂停的时间,否则暂停的时间等于当前时间 195 if (isStart) { 196 pauseCount += (System.currentTimeMillis() - pauseStart); 197 } else { 198 pauseStart = System.currentTimeMillis(); 199 } 200 } 201 202 // 刷新界面 203 repaint(); 204 } 205 206 // 判断键盘按下的上下左右键,控制小蛇的走向 207 if (keyCode == KeyEvent.VK_LEFT) { 208 fx = "L"; 209 } else if (keyCode == KeyEvent.VK_RIGHT) { 210 fx = "R"; 211 } else if (keyCode == KeyEvent.VK_UP) { 212 fx = "U"; 213 } else if (keyCode == KeyEvent.VK_DOWN) { 214 fx = "D"; 215 } 216 } 217 218 /** 219 * 定时器,监听事件,执行定时操作 220 * 221 * @param e 222 */ 223 @Override 224 public void actionPerformed(ActionEvent e) { 225 226 // 如果游戏处于开始状态,并且游戏没有结束,小蛇就移动 227 if (isStart && isFail == false) { 228 // 计时器启动 229 format2(); 230 // 小蛇的长度增加以及移动, 231 snakeGrowUp(); 232 // 刷新界面 233 repaint(); 234 } 235 // 定时器启动 236 timer.start(); 237 } 238 239 // 计时器 240 private void format2() { 241 // 如果是限时模式的话,开始进行倒计时 242 if (iscountDownTime == 1) { 243 // 判断限时模式是否到时间了 244 if (endTime >= 1000) { 245 //System.out.println("endTime = " + endTime); 246 endTime = endTime - timingDuration; 247 countDownTime = format(endTime); 248 //System.out.println("countDownTime = " + countDownTime); 249 250 // 限时模式时间到,游戏结束 251 } else { 252 iscountDownTime = 2; 253 isFail = true; 254 } 255 // 无限模式 256 } else { 257 // 计时器开始计时 258 long elapsed = System.currentTimeMillis() - programStart - pauseCount; 259 infiniteTime = format(elapsed); 260 } 261 } 262 263 private void snakeGrowUp() { 264 // 除了头部,身体都跟着往右移动 265 for (int i = length - 1; i > 0; i--) { 266 snakeX[i] = snakeX[i - 1]; 267 snakeY[i] = snakeY[i - 1]; 268 } 269 270 // 通过控制方向,移动头部 271 if (fx.equals("R")) { 272 // 头部向右移动一步 273 snakeX[0] = snakeX[0] + 25; 274 // 如果已经到达边界,则从最右侧重新开始 275 if (snakeX[0] > 830) { 276 snakeX[0] = 5; 277 } 278 } else if (fx.equals("L")) { 279 // 头部向左移动一步 280 snakeX[0] = snakeX[0] - 25; 281 // 如果已经到达边界,则从最右侧重新开始 282 if (snakeX[0] < 5) { 283 snakeX[0] = 830; 284 } 285 } else if (fx.equals("U")) { 286 // 头部向左移动一步 287 snakeY[0] = snakeY[0] - 25; 288 // 如果已经到达边界,则从最右侧重新开始 289 if (snakeY[0] < 65) { 290 snakeY[0] = 540; 291 } 292 } else if (fx.equals("D")) { 293 // 头部向左移动一步 294 snakeY[0] = snakeY[0] + 25; 295 // 如果已经到达边界,则从最右侧重新开始 296 if (snakeY[0] > 540) { 297 snakeY[0] = 65; 298 } 299 } 300 301 // System.out.println("snakeX = "+snakeX[0]+" foodX="+foodX +"////snakeY = 302 // "+snakeY[0]+" foodY="+foodY); 303 // 判断小蛇的头的坐标和食物的坐标重合的情况下 304 if (snakeX[0] == foodX && snakeY[0] == foodY) { 305 // 小蛇的身体长度加1 306 length++; 307 snakeX[length - 1] = snakeX[length - 2] * 2 - snakeX[length - 3]; 308 snakeY[length - 1] = snakeY[length - 2] * 2 - snakeY[length - 3]; 309 310 // 每当吃掉一个食物,积分+10 311 score = score + 10; 312 313 // 生成新的食物 314 foodX = 5 + 25 * random.nextInt(34); 315 foodY = 65 + 25 * random.nextInt(20); 316 } 317 318 // 结束判断:头和身体有接触,则游戏失败 319 for (int i = 1; i < length; i++) { 320 if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) { 321 // isFail = true; 322 } 323 } 324 } 325 326 // 将毫秒数格式化 327 private static String format(long elapsed) { 328 int hour, minute, second, milli; 329 330 milli = (int) (elapsed % 1000); 331 elapsed = elapsed / 1000; 332 333 second = (int) (elapsed % 60); 334 elapsed = elapsed / 60; 335 336 minute = (int) (elapsed % 60); 337 elapsed = elapsed / 60; 338 339 hour = (int) (elapsed % 60); 340 341 // 带有毫秒的显示 342 // return String.format("%02d:%02d:%02d %03d", hour, minute, second, milli); 343 return String.format("%02d:%02d:%02d", hour, minute, second); 344 345 } 346 347 /** 348 * 键盘按下, 349 * 350 * @param e 351 */ 352 @Override 353 public void keyTyped(KeyEvent e) { 354 355 } 356 357 /** 358 * 359 * @param e 360 */ 361 @Override 362 public void keyReleased(KeyEvent e) { 363 364 } 365 }