java-贪吃蛇小游戏
java GUI编程实现贪吃蛇小游戏
一 准备
首先准备三张图片,分别是:蛇头,蛇身体,食物。(尺寸 25 * 25 像素)
我创建的是一个springboot项目,所以我把这些静态资源放在了resources目录下的static文件夹下,创建个文件夹叫snake,再把这三张图片放进去。
二 新建一个Data类,作为图片资源
import cn.hutool.core.io.resource.ClassPathResource; import javax.swing.*; import java.net.URL; /** * create by fzg * 2022/11/15 13:07 * 获取数据,图片 */ // 数据:图片资源 public class Data { // 图片资源放哪里都可以,只要能找到,并且将类型转化为URL // 我用的是胡图工具的 ClassPathResource // 蛇头 public static URL headerURL = new ClassPathResource("/static/snake/header.png").getUrl(); // 蛇身体 public static URL bodyURL = new ClassPathResource("/static/snake/body.png").getUrl(); // 食物 public static URL foodURL = new ClassPathResource("/static/snake/food.png").getUrl(); public static ImageIcon header = new ImageIcon(headerURL); public static ImageIcon body = new ImageIcon(bodyURL); public static ImageIcon food = new ImageIcon(foodURL); }
三 Snake类,定义蛇的数据结构和初始化方法
/** * create by fzg * 2022/11/15 13:33 */ // 贪吃蛇基本类 public class Snake { // 定义蛇的数据结构 int length; // 蛇的长度 int[] snakex = new int[600]; // 蛇的x坐标 int[] snakey = new int[500]; // 蛇的y坐标 // 初始化方向 String fx; // 初始化方法 public void init(){ // 初始化长度3 length = 3; // 脑袋的坐标 snakex[0] = 100; snakey[0] = 100; // 第一个身体的坐标 snakex[1] = 75; snakey[1] = 100; // 第二个身体的坐标 snakex[2] = 50; snakey[2] = 100; // 初始化方向往右 fx = "D"; } public Snake(){ init(); } }
四 游戏面板类 GamePanel(核心代码)
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; /** * create by fzg * 2022/11/15 13:39 */ /** * 游戏面板 * 键盘监听器 * 定时器 */ public class GamePanel extends JPanel implements KeyListener, ActionListener { // 游戏当前的状态 boolean isStart = false; // 默认不开始 // 游戏失败,默认FALSE boolean isFail = false; // 初始化成绩 int score = 0; Snake snake = new Snake(); // 定时器(小蛇快慢) Timer timer = new Timer(500,this); // 食物坐标 int foodx; int foody; // 前一个按键 String prePressKey = ""; Random random = new Random(); public GamePanel(){ // 获取焦点 this.setFocusable(true); // 获取键盘的监听事件 this.addKeyListener(this); timer.start(); createFood(); } /** * 绘制面板, * 绘制游戏的画面 */ @Override protected void paintComponent(Graphics g){ // 清屏,没有这段代码就会不停闪屏 super.paintComponent(g); // 绘制静态面板,背景颜色:白色 this.setBackground(Color.WHITE); // 默认游戏界面 g.fillRect(25,75,850,600); // 食物 Data.food.paintIcon(this,g,foodx,foody); // 画积分 g.setColor(Color.blue); g.setFont(new Font("微软雅黑",Font.BOLD,18)); g.drawString("长度: ",750,35); g.drawString("分数: ",750,53); g.setColor(Color.RED); g.drawString(snake.length + "",800,35); g.drawString(score + "",800,53); // 画小蛇 for (int i = 0; i < snake.length; i++) { if (i == 0){ // 画脑袋 Data.header.paintIcon(this,g,snake.snakex[i],snake.snakey[i]); }else { Data.body.paintIcon(this,g,snake.snakex[i],snake.snakey[i]); } } // 游戏未开始时 if (!isStart){ g.setColor(Color.WHITE); g.setFont(new Font("微软雅黑",Font.BOLD,40)); g.drawString("按下空格开始游戏", 200,300); // 说明一下:请确保键盘的WSAD键处于英文状态下 g.setColor(Color.GREEN); g.setFont(new Font("微软雅黑",Font.PLAIN,25)); g.drawString("请确保键盘上的WSAD键处于英文状态",200,350); g.drawString("除了键盘上的WSAD键,还可以按up,down,left,right键",200,400); } // 游戏失败 if (isFail){ g.setColor(Color.RED); g.setFont(new Font("微软雅黑",Font.BOLD,40)); g.drawString("游戏结束",300,200); g.setFont(new Font("微软雅黑",Font.PLAIN,30)); g.drawString("按下空格重新开始游戏", 300,300); timer.setDelay(500); } } /** * Invoked when an action occurs. * * @param e * * 事件监听 * 通过固定事件监听 */ @Override public void actionPerformed(ActionEvent e) { if (isStart && !isFail){ // 如果游戏状态是开始,就让小蛇动起来 for (int i = snake.length - 1; i > 0; i--) { snake.snakex[i] = snake.snakex[i - 1]; snake.snakey[i] = snake.snakey[i - 1]; } // 走向 switch (snake.fx){ case "D": snake.snakex[0] = snake.snakex[0] + 25; // 边界判断(撞墙就死) if (snake.snakex[0] > 850){ // 这是小蛇撞墙不会死,从另一边出来 // snake.snakex[0] = 25; // 让小蛇撞墙就game over isFail = true; } break; case "A": snake.snakex[0] = snake.snakex[0] - 25; // 边界判断(撞墙就死) if (snake.snakex[0] < 25){ // snake.snakex[0] = 850; isFail = true; } break; case "W": snake.snakey[0] = snake.snakey[0] - 25; // 边界判断(撞墙就死) if (snake.snakey[0] < 75){ // snake.snakey[0] = 650; isFail = true; } break; case "S": snake.snakey[0] = snake.snakey[0] + 25; // 边界判断(撞墙就死) if (snake.snakey[0] > 650){ // snake.snakey[0] = 75; isFail = true; } break; } // 把下面的代码注释,让小蛇可以掉头,并且吃到自己的身体也不会死 // 判断失败,撞到自己就算失败 // for (int i = 1; i < snake.length; i++) { // if (snake.fx.equals("W") && prePressKey.equals("S")){ // isFail = false; // }else if (snake.fx.equals("S") && prePressKey.equals("W")){ // isFail = false; // }else if (snake.fx.equals("A") && prePressKey.equals("D")){ // isFail = false; // }else if(snake.fx.equals("D") && prePressKey.equals("A")){ // isFail = false; // }else { // if (snake.snakex[0] == snake.snakex[i] && snake.snakey[0] == snake.snakey[i]) { // isFail = true; // } // } // } // 吃到食物 if (snake.snakex[0] == foodx && snake.snakey[0] == foody){ // 长度加1 snake.length++; // 加分 score = addCarry(score,10); /** * 分数等级: * 0 - 50:原速度 0.5s * 50 - 100:-100毫秒 0.4s * 100 - 150:-100毫秒 0.3s * 150 - 200:-100毫秒 0.2s * 200 - 250: -100毫秒 0.1s * 250 - 以上: -50毫秒 0.05s 最快速度了 */ if (score >= 50 && score < 100){ timer.setDelay(400); }else if (score >= 100 && score < 150){ timer.setDelay(300); }else if (score >= 150 && score < 200){ timer.setDelay(200); }else if (score >= 200 && score < 250){ timer.setDelay(100); }else if (score >= 250){ timer.setDelay(50); } // 再次生成食物 createFood(); } // 重画页面 repaint(); } // 开启定时 timer.start(); } /** * 生成食物 * g.fillRect(25,75,850,600); 界面的位置和宽高, * 游戏界面坐标:25,75 ;距离左边25,距离上边75 * 宽度:850 ; 高度: 600 * 食物必须随机生成在游戏界面内 * 所以:食物的x坐标 必须在25 到 850 + 25之间, * 如果刚好等于850 + 25那么食物就生成在游戏界面的右边所以不能等于850 + 25 * y坐标也是 * 所以食物的生成坐标区间 * x: [25,850] * y: [75,675-25] */ public void createFood(){ /** * 把食物随机分配到界面上 * random.nextInt生成随机整数 * 850 / 25 = 34 最大值 * 650 / 25 = 26 最大值 */ foodx = 25 + 25 * random.nextInt(34); foody = 75 + 25 * random.nextInt(26); } /** * 位运算实现相加 */ int addCarry(int a,int b){ int result; int sum,carry; sum = a ^ b; carry = (a & b) << 1; if (carry == 0){ result = sum; }else { result = addCarry(sum,carry); } return result; } /** * Invoked when a key has been typed. * See the class description for {@link KeyEvent} for a definition of * a key typed event. * * @param e */ @Override public void keyTyped(KeyEvent e) { } /** * Invoked when a key has been pressed. * See the class description for {@link KeyEvent} for a definition of * a key pressed event. * 键盘按下 * @param e */ @Override public void keyPressed(KeyEvent e) { // 获取键盘按键是哪个 int keyCode = e.getKeyCode(); /** * 上:38 * 下:40 * 左:37 * 右:39 */ if (keyCode == KeyEvent.VK_SPACE){ // 按下的是空格 if (isFail){ // 重新开始 isFail = false; snake.init(); createFood(); score = 0; }else { isStart = !isStart; } repaint(); } // 小蛇移动 switch (keyCode){ case KeyEvent.VK_W: snake.fx = "W"; break; case KeyEvent.VK_S: snake.fx = "S"; break; case KeyEvent.VK_A: snake.fx = "A"; break; case KeyEvent.VK_D: snake.fx = "D"; break; case 37: snake.fx = "A"; break; case 38: snake.fx = "W"; break; case 39: snake.fx = "D"; break; case 40: snake.fx = "S"; break; } prePressKey = snake.fx; } /** * Invoked when a key has been released. * See the class description for {@link KeyEvent} for a definition of * a key released event. * * @param e */ @Override public void keyReleased(KeyEvent e) { } }
1. 游戏规则:蛇头撞墙结束游戏
2. 根据积分等级,蛇的移动速度加快,积分越高,移动速度越快
3. 键盘:WSAD 和 上下左右键 都可以控制蛇的移动方向
4. 蛇可以撞到自己的身体,可以直接反方向移动,都不会结束游戏
五 最后一个类,启动类 StartGame
import javax.swing.*; /** * create by fzg * 2022/11/15 14:23 */ // 游戏启动类 public class StartGame { public static void main(String[] args) { JFrame jFrame = new JFrame(); jFrame.setBounds(500, 280,950,800); jFrame.setResizable(false); // 窗口大小不准拉伸 // 正常的游戏都在面板上 GamePanel gamePanel = new GamePanel(); jFrame.add(gamePanel); jFrame.setVisible(true); jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); } }
六. 这样一个贪吃蛇小游戏就做成了
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具