Java GUI 实例:贪吃蛇游戏
2022-01-15 本文已影响0人
yjtuuige
一、实现效果
- 空格暂停;
- 上、下、左、右控制方向;
- 长度随着食物的吃下,而增加;
- 定时刷新;
- 累计长度和分数等。
二、实现思路:
- 定义所有功能的数据
- 绘制功能需要的图形
- 添加监听功能需要的事件(帧率事件)键盘、鼠标
三、实现代码
- Data(数据类):初始化需要的图片;
- StartGame(游戏主启动类):主要实现窗口的加载,和添加 GamePanel 面板到窗口;
- GamePanel(游戏的面板):最重要的实现部分,包括:
- 定义需要的数据
- 绘制图像
- 实现事件监听
- 静态界面绘制
- StartGame.java
package com.xxx.gui.snake;
import javax.swing.*;
/**
* 游戏主启动类
*/
public class StartGame {
public StartGame() {
JFrame frame = new JFrame("贪吃蛇");
// 长宽尺寸,根据内容计算得出
frame.setBounds(10, 10, 915, 730);
// 窗口大小不可变,避免游戏变形
frame.setResizable(false);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
// 将游戏面板添加到窗口中
frame.add(new GamePanel());
frame.setVisible(true);
}
public static void main(String[] args) {
new StartGame();
}
}
- Date.java
package com.xxx.gui.snake;
import javax.swing.*;
import java.net.URL;
/**
* 数据类
*/
public class Data {
// 相对路径 header.png
// 绝对路径 / 相当于当前的项目
// 顶部图片
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);
}
- GamePanel.java
package com.xxx.gui.snake;
import javax.swing.*;
import java.awt.*;
/**
* 游戏的面板
*/
public class GamePanel extends JPanel {
// 绘制面板,游戏中所有的东西,都是用这个笔来画
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // 清屏
// 绘制静态面板
this.setBackground(Color.WHITE);
// 顶部图片绘制到当前组件中
Data.header.paintIcon(this, g, 25, 11);
// 默认的游戏界面,坐标及尺寸,经过计算得出
g.fillRect(25, 75, 850, 600);
}
}
- 绘制静态小蛇
- GamePanel.java
package com.xxx.gui.snake;
import javax.swing.*;
import java.awt.*;
/**
* 游戏的面板
*/
public class GamePanel extends JPanel {
// 定义蛇的数据结构
int length; // 蛇的长度
int[] snakeX = new int[600]; // 蛇的 X 坐标,25*25
int[] snakeY = new int[500]; // 蛇的 Y 坐标,25*25
String fx; // 初始方向:向右
// 游戏当前状态:开始,停止
boolean isStart = false; // 默认停止
// 构造器
public GamePanel() {
// 调用初始化
init();
}
// 初始化方法
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";
}
// 绘制面板,游戏中所有的东西,都是用这个笔来画
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // 清屏
// 绘制静态面板
this.setBackground(Color.WHITE);
// 顶部图片绘制到当前组件中
Data.header.paintIcon(this, g, 25, 11);
// 默认的游戏界面,坐标及尺寸,经过计算得出
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 < length; i++) {
Data.body.paintIcon(this, g, snakeX[i], snakeY[i]);
}
//游戏状态
if (isStart == false) {
g.setColor(Color.white);
g.setFont(new Font("黑体", Font.BOLD, 40)); // 设置字体
g.drawString("按下空格开始游戏", 300, 300); // 初始文字
}
}
}
- 小蛇开始移动
- GamePanel.java
package com.xxx.gui.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;
/**
* 游戏的面板
* ActionListener(接口):实现定时器
*/
public class GamePanel extends JPanel implements KeyListener, ActionListener {
// 定义蛇的数据结构
int length; // 蛇的长度
int[] snakeX = new int[600]; // 蛇的 X 坐标,25*25
int[] snakeY = new int[500]; // 蛇的 Y 坐标,25*25
String fx; // 初始方向:向右
// 游戏当前状态:开始,停止
boolean isStart = false; // 默认停止
// 定时器:100(ms) 毫秒刷新一次(1000ms=1秒)
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] = 50;
snakeY[2] = 100;
fx = "R";
}
// 绘制面板,游戏中所有的东西,都是用这个笔来画
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // 清屏
// 绘制静态面板
this.setBackground(Color.WHITE);
// 顶部图片绘制到当前组件中
Data.header.paintIcon(this, g, 25, 11);
// 默认的游戏界面,坐标及尺寸,经过计算得出
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 < length; i++) {
Data.body.paintIcon(this, g, snakeX[i], snakeY[i]);
}
//游戏状态
if (isStart == false) {
g.setColor(Color.white);
g.setFont(new Font("黑体", Font.BOLD, 40)); // 设置字体
g.drawString("按下空格开始游戏", 300, 300); // 初始文字
}
}
// 键盘监听事件
@Override
public void keyPressed(KeyEvent e) {
// 获得键盘按键
int keyCode = e.getKeyCode();
// 如果按下空格键
if (keyCode == KeyEvent.VK_SPACE) {
isStart = !isStart; // 取反
repaint(); // 重新绘制
}
// 小蛇移动(通过方向键判断)
if (keyCode == KeyEvent.VK_UP) {
fx = "U";
} else if (keyCode == KeyEvent.VK_DOWN) {
fx = "D";
} else if (keyCode == KeyEvent.VK_LEFT) {
fx = "L";
} else if (keyCode == KeyEvent.VK_RIGHT) {
fx = "R";
}
}
// 事件监听:定时执行时的操作(刷新)
@Override
public void actionPerformed(ActionEvent e) {
// 如果是开始状态,让小蛇移动
if (isStart) {
// 移动:
for (int i = length - 1; i > 0; i--) {
// 身体后一节移到前一节的位置(snakeX[1]=snakeX[0])
snakeX[i] = snakeX[i - 1];
snakeY[i] = snakeY[i - 1];
}
// 通过方向键控制,头部移动
if (fx.equals("R")) {
// 头部 X 坐标,右移一格(25)
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;
}
repaint(); // 重画页面
}
timer.start(); // 定时器开启
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
}
- 小蛇开始吃食物
- GamePanel.java
package com.xxx.gui.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;
/**
* 游戏的面板
* ActionListener(接口):实现定时器
*/
public class GamePanel extends JPanel implements KeyListener, ActionListener {
// 定义蛇的数据结构
int length; // 蛇的长度
int[] snakeX = new int[600]; // 蛇的 X 坐标,25*25
int[] snakeY = new int[500]; // 蛇的 Y 坐标,25*25
String fx; // 初始方向:向右
// 食物坐标
int foodX;
int foodY;
Random random = new Random(); // 获取随机数
// 游戏当前状态:开始,停止
boolean isStart = false; // 默认停止
// 定时器:100(ms) 毫秒刷新一次(1000ms=1秒)
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] = 50;
snakeY[2] = 100;
fx = "R";
// 把食物随机分布在界面上
foodX = 25 + 25 * random.nextInt(34); // 34:850/25
foodY = 75 + 25 * random.nextInt(34); // 24:600/25
}
// 绘制面板,游戏中所有的东西,都是用这个笔来画
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // 清屏
// 绘制静态面板
this.setBackground(Color.WHITE);
// 顶部图片绘制到当前组件中
Data.header.paintIcon(this, g, 25, 11);
// 默认的游戏界面,坐标及尺寸,经过计算得出
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 < length; i++) {
Data.body.paintIcon(this, g, snakeX[i], snakeY[i]);
}
// 画食物
Data.food.paintIcon(this, g, foodX, foodY);
//游戏状态
if (isStart == false) {
g.setColor(Color.white);
g.setFont(new Font("黑体", Font.BOLD, 40)); // 设置字体
g.drawString("按下空格开始游戏", 300, 300); // 初始文字
}
}
// 键盘监听事件
@Override
public void keyPressed(KeyEvent e) {
// 获得键盘按键
int keyCode = e.getKeyCode();
// 如果按下空格键
if (keyCode == KeyEvent.VK_SPACE) {
isStart = !isStart; // 取反
repaint(); // 重新绘制
}
// 小蛇移动(通过方向键判断)
if (keyCode == KeyEvent.VK_UP) {
fx = "U";
} else if (keyCode == KeyEvent.VK_DOWN) {
fx = "D";
} else if (keyCode == KeyEvent.VK_LEFT) {
fx = "L";
} else if (keyCode == KeyEvent.VK_RIGHT) {
fx = "R";
}
}
// 事件监听:定时执行时的操作(刷新)
@Override
public void actionPerformed(ActionEvent e) {
// 如果是开始状态,让小蛇移动
if (isStart) {
// 吃食物
if (snakeX[0] == foodX && snakeY[0] == foodY) {
// 身体长度+1
length++;
// 再次随机食物
// 把食物随机分布在界面上
foodX = 25 + 25 * random.nextInt(34);
foodY = 75 + 25 * random.nextInt(34);
}
// 移动:
for (int i = length - 1; i > 0; i--) {
// 身体后一节移到前一节的位置(snakeX[1]=snakeX[0])
snakeX[i] = snakeX[i - 1];
snakeY[i] = snakeY[i - 1];
}
// 通过方向键控制,头部移动
if (fx.equals("R")) {
// 头部 X 坐标,右移一格(25)
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;
}
repaint(); // 重画页面
}
timer.start(); // 定时器开启
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
}
- 失败判定,积分系统
- GamePanel.java
package com.xxx.gui.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;
/**
* 游戏的面板
* ActionListener(接口):实现定时器
*/
public class GamePanel extends JPanel implements KeyListener, ActionListener {
// 定义蛇的数据结构
int length; // 蛇的长度
int[] snakeX = new int[600]; // 蛇的 X 坐标,25*25
int[] snakeY = new int[500]; // 蛇的 Y 坐标,25*25
String fx; // 初始方向:向右
// 食物坐标
int foodX;
int foodY;
Random random = new Random(); // 获取随机数
// 成绩
int score;
// 游戏当前状态:开始,停止
boolean isStart = false; // 默认停止
// 游戏失败状态
boolean isFail = false;
// 定时器:100(ms) 毫秒刷新一次(1000ms=1秒)
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] = 50;
snakeY[2] = 100;
fx = "R";
// 把食物随机分布在界面上
foodX = 25 + 25 * random.nextInt(34); // 34:850/25
foodY = 75 + 25 * random.nextInt(24); // 24:600/25
score = 0;
}
// 绘制面板,游戏中所有的东西,都是用这个笔来画
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // 清屏
// 绘制静态面板
this.setBackground(Color.WHITE);
// 顶部图片绘制到当前组件中
Data.header.paintIcon(this, g, 25, 11);
// 默认的游戏界面,坐标及尺寸,经过计算得出
g.fillRect(25, 75, 850, 600);
// 画积分
g.setColor(Color.WHITE);
g.setFont(new Font("黑体", Font.BOLD, 16));
g.drawString("长度 " + length, 750, 32);
g.drawString("分数 " + score, 750, 53);
// 画食物
Data.food.paintIcon(this, g, foodX, foodY);
// 把小蛇画上去
// 蛇头初始化,向右,需要通过方向来判断
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 < length; i++) {
Data.body.paintIcon(this, g, snakeX[i], snakeY[i]);
}
// 游戏状态
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("失败,按下空格重新开始", 300, 300); // 初始文字
}
}
// 键盘监听事件
@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_UP) {
fx = "U";
} else if (keyCode == KeyEvent.VK_DOWN) {
fx = "D";
} else if (keyCode == KeyEvent.VK_LEFT) {
fx = "L";
} else if (keyCode == KeyEvent.VK_RIGHT) {
fx = "R";
}
}
// 事件监听:定时执行时的操作(刷新)
@Override
public void actionPerformed(ActionEvent e) {
// 如果是开始状态,且没有结束,让小蛇移动
if (isStart && isFail == false) {
// 吃食物
if (snakeX[0] == foodX && snakeY[0] == foodY) {
// 身体长度+1
length++;
// 分数+10
score += 10;
// 再次随机食物
// 把食物随机分布在界面上
foodX = 25 + 25 * random.nextInt(34);
foodY = 75 + 25 * random.nextInt(24);
}
// 移动:
for (int i = length - 1; i > 0; i--) {
// 身体后一节移到前一节的位置(snakeX[1]=snakeX[0])
snakeX[i] = snakeX[i - 1];
snakeY[i] = snakeY[i - 1];
}
// 通过方向键控制,头部移动
if (fx.equals("R")) {
// 头部 X 坐标,右移一格(25)
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;
}
// 失败判断:撞到自己
// 遍历身体的每一个坐标
for (int i = 1; i < length; i++) {
// 头部坐标与身体坐标重合
if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
// 游戏失败
isFail = true;
}
}
repaint(); // 重画页面
}
timer.start(); // 定时器开启
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
}
Java 版贪吃蛇,图片素材:
- 食物 – food.png
- 上 – up.png
- 下 – down.png
- 左 – left.png
- 右 – right.png
- 身体 – body.png
- 顶部图片 – header.png