概述:
使用Java语言和Swing库来开发一个贪吃蛇游戏。 创建一个完整的贪吃蛇游戏涉及到图形用户界面(GUI)设计、游戏逻辑、事件处理等多个方面。以下是一个使用Java Swing库实现的基本贪吃蛇游戏的示例代码。这个示例提供了游戏的基本框架,包括蛇的移动、食物的生成和蛇的增长。
源码:
直接上源码,大家拿回去就能自己跑起来玩和后续优化。
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Random;
/**
* @Author derek_smart
* @Date 202/6/26 15:11
* @Description 贪吃蛇
* <p>
*/
public class SnakeGame extends JPanel implements ActionListener {
private static final int GRID_SIZE = 20;
private static final int TILE_SIZE = 20;
private static final int GAME_SIZE = GRID_SIZE * TILE_SIZE;
private static final int DELAY = 100;
private int score = 0;
private int level = 1;
private final ArrayList<Point> snake = new ArrayList<>();
private Point food;
private char direction = 'R';
private boolean running = true;
private Timer timer;
/**
* - 设置面板的首选大小和背景颜色。
* - 设置面板为可聚焦,以便接收键盘事件。
* - 添加键盘监听器以便捕获玩家的方向输入。
* - 调用 `startGame()` 方法来初始化游戏。
*/
public SnakeGame() {
setPreferredSize(new Dimension(GAME_SIZE, GAME_SIZE));
setBackground(Color.BLACK);
setFocusable(true);
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
if (direction != 'R') direction = 'L';
break;
case KeyEvent.VK_RIGHT:
if (direction != 'L') direction = 'R';
break;
case KeyEvent.VK_UP:
if (direction != 'D') direction = 'U';
break;
case KeyEvent.VK_DOWN:
if (direction != 'U') direction = 'D';
break;
}
}
});
startGame();
}
/***
* - 清空蛇的身体部分的列表。
* - 将蛇的初始位置设置在游戏区域的中心。
* - 调用 `generateFood()` 方法来放置第一个食物。
* - 创建并启动一个计时器,以固定的延迟触发 `actionPerformed()` 方法。
*/
private void startGame() {
snake.clear();
snake.add(new Point(GRID_SIZE / 2, GRID_SIZE / 2));
generateFood();
timer = new Timer(DELAY, this);
timer.start();
}
/**
* 使用随机数生成器在游戏区域内随机放置食物。
*/
private void generateFood() {
Random random = new Random();
int x = random.nextInt(GRID_SIZE);
int y = random.nextInt(GRID_SIZE);
food = new Point(x, y);
increaseLevel(); // 增加游戏级别
}
private void increaseLevel() {
if (score % 5 == 0 && score != 0) { // 每得到5分,游戏级别增加
level++;
if (timer.getDelay() > 40) { // 保证游戏速度不会太快
timer.setDelay(timer.getDelay() - 10);
}
}
}
/**
* - 负责绘制游戏的图形界面。
* - 如果游戏正在运行,绘制食物和蛇的身体。
* - 如果游戏结束,调用 `gameOver()` 方法来显示游戏结束信息。
* @param g
*/
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
drawScoreAndLevel(g); // 绘制得分和级别
if (running) {
g.setColor(Color.RED);
g.fillRect(food.x * TILE_SIZE, food.y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
for (Point p : snake) {
g.setColor(Color.GREEN);
g.fillRect(p.x * TILE_SIZE, p.y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
}
} else {
gameOver(g);
}
}
private void drawScoreAndLevel(Graphics g) {
g.setColor(Color.WHITE);
g.drawString("Score: " + score, 5, 15);
g.drawString("Level: " + level, 5, 30);
}
/**
* - 负责处理蛇的移动逻辑。
* - 根据当前方向更新蛇头的位置。
* - 调用 `checkCollision()` 来检查是否发生碰撞。
* - 调用 `checkFood()` 来检查蛇头是否与食物重合。
*/
private void move() {
for (int i = snake.size() - 1; i > 0; i--) {
snake.set(i, new Point(snake.get(i - 1)));
}
Point head = snake.get(0);
switch (direction) {
case 'L':
head.x--;
break;
case 'R':
head.x++;
break;
case 'U':
head.y--;
break;
case 'D':
head.y++;
break;
}
checkCollision();
checkFood();
}
/**
* - 检查蛇头是否触碰到游戏边界或自身的身体。
* - 如果发生碰撞,设置游戏状态为结束并停止计时器。
*/
private void checkCollision() {
Point head = snake.get(0);
if (head.x < 0 || head.y < 0 || head.x >= GRID_SIZE || head.y >= GRID_SIZE) {
running = false;
}
for (int i = 1; i < snake.size(); i++) {
if (head.equals(snake.get(i))) {
running = false;
}
}
if (!running) {
timer.stop();
}
}
/**
* - 检查蛇头是否与食物位置重合。
* - 如果重合,将食物位置添加到蛇的身体中,并生成新食物。
*/
private void checkFood() {
Point head = snake.get(0);
if (head.equals(food)) {
score++; // 增加得分
snake.add(new Point(food));
generateFood();
}
}
/**
* - 当游戏结束时调用,显示游戏结束的文本信息。
* @param g
*/
private void gameOver(Graphics g) {
g.setColor(Color.WHITE);
g.drawString("Game Over", GAME_SIZE / 2, GAME_SIZE / 2);
g.drawString("Score: " + score, GAME_SIZE / 2, GAME_SIZE / 2 + 20);
g.drawString("Level: " + level, GAME_SIZE / 2, GAME_SIZE / 2 + 40);
}
/**
* - 实现了 `ActionListener` 接口的方法,定时触发。
* - 如果游戏正在运行,调用 `move()` 方法并重绘面板。
* @param e
*/
@Override
public void actionPerformed(ActionEvent e) {
if (running) {
move();
repaint();
}
}
/**
* - 创建游戏窗口,并添加 `SnakeGame` 面板到窗口中。
* - 设置窗口的关闭操作、大小、位置,并使其可见。
* @param args
*/
public static void main(String[] args) {
JFrame frame = new JFrame("贪吃蛇");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.add(new SnakeGame(), BorderLayout.CENTER);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
时序图:
sequenceDiagram
participant User as 玩家
participant Main as 主函数
participant SnakeGame as 贪吃蛇游戏
participant Timer as 计时器
participant Food as 食物
User->>Main: 运行游戏
Main->>SnakeGame: 创建游戏实例
SnakeGame->>SnakeGame: 初始化游戏(startGame)
SnakeGame->>Food: 生成食物(generateFood)
SnakeGame->>Timer: 启动计时器(timer.start)
loop 每个时间周期
Timer->>SnakeGame: 触发事件(actionPerformed)
SnakeGame->>SnakeGame: 移动蛇(move)
SnakeGame->>SnakeGame: 检查碰撞(checkCollision)
SnakeGame->>SnakeGame: 检查食物(checkFood)
SnakeGame->>SnakeGame: 绘制游戏界面(repaint)
User->>SnakeGame: 输入控制指令
SnakeGame->>SnakeGame: 改变方向
end
SnakeGame->>User: 显示游戏结束(gameOver)
User->>Main: 关闭游戏
时序图中,描述了玩家与 SnakeGame
类的交互流程:
- 玩家运行游戏,主函数创建
SnakeGame
实例。 - 游戏初始化,生成食物,并启动计时器。
- 在游戏的主循环中,计时器每个时间周期触发
actionPerformed
事件。 - 游戏移动蛇,检查碰撞和食物,然后绘制游戏界面。
- 玩家输入控制指令,改变蛇的移动方向。
- 当游戏结束时,显示游戏结束信息。
- 玩家关闭游戏。
上班无聊打开玩玩还是可以的。
后续优化和建议
可以从以下几个方面进行:
1. 代码重构和模块化
- 分离关注点:将游戏逻辑、渲染逻辑和输入处理分离到不同的类或方法中,提高代码的可读性和可维护性。
- 使用MVC模式:采用Model-View-Controller (MVC) 设计模式,将游戏状态(模型)、游戏界面(视图)和用户交互(控制器)分离。
2. 游戏逻辑增强
- 增加难度级别:随着得分的增加,逐渐提高游戏速度或引入更复杂的地图。
- 实现蛇的旋转:允许蛇头旋转,使蛇的移动更加自然。
- 增加障碍物:在游戏区域内添加障碍物,增加游戏难度。
3. 用户界面改进
- 美化界面:使用图片或更复杂的图形来代替简单的颜色块,提升视觉效果。
- 增加动画效果:添加如蛇移动、吃食物等动画,增强游戏的感官体验。
- 响应式设计:确保游戏界面能够适应不同大小的屏幕。
4. 功能增加
- 暂停/恢复功能:允许玩家暂停游戏,并在暂停后恢复。
- 得分和生命系统:引入生命值的概念,蛇撞到自己或墙壁时失去生命值。
- 多人模式:支持本地或网络多人游戏。