1 运行展示
2 设计思路
设计一个贪吃蛇游戏,我们应当遵循了一系列步骤来确保游戏的基本功能:蛇的移动、吃苹果、游戏结束判断、以及重新开始游戏。以下是设计思路的分步总结.
1. 初始化游戏界面
- 使用Swing组件:利用
JFrame
和JPanel
创建游戏窗口和绘图面板。 - 设置游戏参数:定义蛇的大小、游戏区域的大小、苹果的位置等参数。
- 初始化游戏状态:设置蛇的初始长度、位置、移动方向等。
2. 游戏逻辑实现
- 蛇的移动:通过数组记录蛇身体每一部分的位置,并在每次移动时更新这些位置。
- 吃苹果:检测蛇头和苹果的位置,若重合则增加蛇的长度,并重新放置苹果。
- 碰撞检测:检查蛇头是否触碰到边界或自身,若是,则游戏结束。
3. 绘制游戏界面
- 重写
paintComponent
方法:使用Graphics
对象绘制蛇、苹果和游戏结束时的提示信息。 - 使用计时器:
javax.swing.Timer
用于控制游戏的更新频率,每个时间间隔都会重绘界面并更新游戏状态。
4. 控制游戏
- 键盘监听:实现
KeyListener
接口,根据用户的按键输入改变蛇的移动方向。 - 暂停功能:通过监听特定的按键(如P键)来实现游戏的暂停和继续。
5. 重新开始游戏
- 添加按钮:在游戏面板中添加一个重新开始的按钮。
- 事件监听:为按钮添加
ActionListener
,在游戏结束时显示按钮,并在点击时重置游戏状态。
3 技术列举
使用到的Java概念
- 面向对象编程:通过类和对象来模拟游戏中的实体(如蛇、苹果)和行为(如移动、吃苹果)。
- 事件驱动编程:使用事件监听器来响应用户的输入(键盘事件)和交互(按钮点击事件)。
- 图形用户界面(GUI):使用Java Swing库来创建和管理窗口、按钮、绘图等GUI组件。
- 多线程和并发:使用计时器(
Timer
)来控制游戏逻辑的周期性执行,这涉及到Java的并发编程。
4 核心功能实现思路
在贪吃蛇游戏中,最主要的两个功能是蛇的移动和游戏状态的管理(包括吃苹果和碰撞检测)。这两个功能是游戏运行的核心,决定了游戏的基本玩法和用户体验。
1. 蛇的移动
蛇的移动是贪吃蛇游戏的基础。玩家通过控制蛇的移动来吃苹果,游戏的乐趣和挑战性主要来自于如何控制蛇的移动路径和速度。
实现思路:
- 数据结构:使用两个数组分别存储蛇身体每一部分的x和y坐标。蛇头是数组的第一个元素,蛇尾是最后一个元素。
- 移动逻辑:在每个时间间隔,根据蛇的当前移动方向更新蛇头的位置,然后让蛇身体的每一部分移动到前一部分的位置,实现蛇的整体移动。
- 方向控制:通过监听键盘事件,允许玩家改变蛇的移动方向,但不能让蛇直接反向移动。
2. 游戏状态的管理
游戏状态的管理决定了游戏的进程和结束条件。它包括检测蛇是否吃到苹果以增长身体,以及蛇是否撞到自己或游戏边界导致游戏结束。
实现思路:
- 吃苹果:
- 检测碰撞:在蛇移动后,检查蛇头的位置是否与苹果的位置重合。如果是,表示蛇吃到了苹果。
- 增长蛇身:增加蛇的长度,并在游戏区域内随机生成一个新的苹果位置。
- 碰撞检测:
- 边界检测:检查蛇头是否移动出游戏区域的边界。如果是,游戏结束。
- 自身碰撞检测:检查蛇头是否与蛇身的其他部分位置重合。如果是,同样游戏结束。
- 游戏结束处理:当游戏结束时,停止游戏循环,可能显示游戏结束的信息,并提供重新开始游戏的选项。
这两个功能的实现是构建一个可玩的贪吃蛇游戏的关键。它们不仅涉及到游戏的基本逻辑和用户交互,还需要考虑如何在界面上有效地反映游戏状态的变化,提供流畅和有趣的游戏体验。
5 核心功能代码实现
1. 蛇的移动
private void move() {
for (int z = dots; z > 0; z--) {
x[z] = x[(z - 1)];
y[z] = y[(z - 1)];
}
if (leftDirection) {
x[0] -= DOT_SIZE;
}
if (rightDirection) {
x[0] += DOT_SIZE;
}
if (upDirection) {
y[0] -= DOT_SIZE;
}
if (downDirection) {
y[0] += DOT_SIZE;
}
}
2. 游戏状态的管理
1 吃苹果方法
private void checkApple() {
if ((x[0] == apple_x) && (y[0] == apple_y)) {
dots++;
locateApple();
}
}
2 碰撞检测方法
private void checkCollision() {
for (int z = dots; z > 0; z--) {
if ((z > 4) && (x[0] == x[z]) && (y[0] == y[z])) {
inGame = false;
}
}
if (y[0] >= HEIGHT) {
inGame = false;
}
if (y[0] < 0) {
inGame = false;
}
if (x[0] >= WIDTH) {
inGame = false;
}
if (x[0] < 0) {
inGame = false;
}
if (!inGame) {
timer.stop();
}
}
6 全部代码实现
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.ArrayList;
import java.util.Collections;
import java.util.Random;
public class SnakeGame extends JPanel implements KeyListener, ActionListener {
private final int WIDTH = 300;
private final int HEIGHT = 300;
private final int DOT_SIZE = 10;
private final int ALL_DOTS = 900;
private final int RANDOM_POSITION = 29;
private final int DELAY = 140;
private final int x[] = new int[ALL_DOTS];
private final int y[] = new int[ALL_DOTS];
// 在SnakeGame类中添加
private JButton restartButton;
private int dots;
private int apple_x;
private int apple_y;
private boolean leftDirection = false;
private boolean rightDirection = true;
private boolean upDirection = false;
private boolean downDirection = false;
private boolean inGame = true;
private boolean paused = false;
private Timer timer;
private Random random;
private ArrayList<Integer> highScores = new ArrayList<>();
public SnakeGame() {
initBoard();
}
private void initBoard() {
restartButton = new JButton("Restart");
restartButton.setBounds(WIDTH / 2 - 50, HEIGHT / 2, 100, 30);
restartButton.addActionListener(this);
restartButton.setFocusable(false);
restartButton.setVisible(false); // 初始时不显示按钮
this.add(restartButton);
addKeyListener(this);
setBackground(Color.black);
setFocusable(true);
setPreferredSize(new Dimension(WIDTH, HEIGHT));
initGame();
}
private void initGame() {
dots = 3;
for (int z = 0; z < dots; z++) {
x[z] = 50 - z * 10;
y[z] = 50;
}
locateApple();
timer = new Timer(DELAY, this);
timer.start();
}
private void restartGame() {
dots = 3;
for (int z = 0; z < dots; z++) {
x[z] = 50 - z * 10;
y[z] = 50;
}
rightDirection = true;
leftDirection = false;
upDirection = false;
downDirection = false;
inGame = true;
restartButton.setVisible(false); // 重置游戏时隐藏按钮
timer.start(); // 重新开始计时
locateApple(); // 重新放置苹果
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
private void doDrawing(Graphics g) {
if (inGame) {
g.setColor(Color.red);
g.fillOval(apple_x, apple_y, DOT_SIZE, DOT_SIZE);
for (int z = 0; z < dots; z++) {
if (z == 0) {
g.setColor(Color.green);
g.fillRect(x[z], y[z], DOT_SIZE, DOT_SIZE);
} else {
g.setColor(new Color(45, 180, 0));
g.fillRect(x[z], y[z], DOT_SIZE, DOT_SIZE);
}
}
Toolkit.getDefaultToolkit().sync();
} else {
gameOver(g);
}
}
private void gameOver(Graphics g) {
String msg = "Game Over";
Font small = new Font("Helvetica", Font.BOLD, 14);
FontMetrics metr = getFontMetrics(small);
g.setColor(Color.white);
g.setFont(small);
g.drawString(msg, (WIDTH - metr.stringWidth(msg)) / 2, HEIGHT / 2);
checkHighScore();
displayHighScores(g);
restartButton.setVisible(true); // 显示重新开始按钮
}
private void checkHighScore() {
highScores.add(dots);
Collections.sort(highScores, Collections.reverseOrder());
while (highScores.size() > 3) {
highScores.remove(highScores.size() - 1);
}
}
private void displayHighScores(Graphics g) {
g.drawString("High Scores:", 100, HEIGHT / 2 + 50);
for (int i = 0; i < highScores.size(); i++) {
g.drawString((i + 1) + ". " + highScores.get(i), 100, HEIGHT / 2 + 65 + i * 15);
}
}
private void checkApple() {
if ((x[0] == apple_x) && (y[0] == apple_y)) {
dots++;
locateApple();
}
}
private void move() {
for (int z = dots; z > 0; z--) {
x[z] = x[(z - 1)];
y[z] = y[(z - 1)];
}
if (leftDirection) {
x[0] -= DOT_SIZE;
}
if (rightDirection) {
x[0] += DOT_SIZE;
}
if (upDirection) {
y[0] -= DOT_SIZE;
}
if (downDirection) {
y[0] += DOT_SIZE;
}
}
private void checkCollision() {
for (int z = dots; z > 0; z--) {
if ((z > 4) && (x[0] == x[z]) && (y[0] == y[z])) {
inGame = false;
}
}
if (y[0] >= HEIGHT) {
inGame = false;
}
if (y[0] < 0) {
inGame = false;
}
if (x[0] >= WIDTH) {
inGame = false;
}
if (x[0] < 0) {
inGame = false;
}
if (!inGame) {
timer.stop();
}
}
private void locateApple() {
int r = (int) (Math.random() * RANDOM_POSITION);
apple_x = ((r * DOT_SIZE));
r = (int) (Math.random() * RANDOM_POSITION);
apple_y = ((r * DOT_SIZE));
}
@Override
public void actionPerformed(ActionEvent e) {
if (inGame) {
checkApple();
checkCollision();
move();
} else if (e.getSource() == restartButton) {
restartGame();
}
repaint();
}
@Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if ((key == KeyEvent.VK_LEFT) && (!rightDirection)) {
leftDirection = true;
upDirection = false;
downDirection = false;
}
if ((key == KeyEvent.VK_RIGHT) && (!leftDirection)) {
rightDirection = true;
upDirection = false;
downDirection = false;
}
if ((key == KeyEvent.VK_UP) && (!downDirection)) {
upDirection = true;
rightDirection = false;
leftDirection = false;
}
if ((key == KeyEvent.VK_DOWN) && (!upDirection)) {
downDirection = true;
rightDirection = false;
leftDirection = false;
}
if (key == KeyEvent.VK_P) {
paused = !paused;
if (paused) {
timer.stop();
} else {
timer.start();
}
}
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Snake Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new SnakeGame());
frame.pack();
frame.setResizable(false);
frame.setVisible(true);
frame.setLocationRelativeTo(null);
});
}
}
7 总结
本文提供了一个贪吃蛇游戏的完整Java实现,包括游戏设计思路、关键功能的实现方法、以及完整的代码。游戏使用Java Swing库构建图形用户界面,通过面向对象编程、事件驱动编程、图形用户界面(GUI)设计、以及多线程和并发控制,实现了一个基本但功能完整的贪吃蛇游戏。游戏的核心功能包括蛇的移动、吃苹果、碰撞检测、游戏状态管理、以及游戏的重新开始功能。