文章目录
- 大三Java课设,我之前写过python贪吃蛇,那篇文章比较火所以写了个JAVA贪吃蛇
-
- 一、先搭个能跑的窗口
- 二、初级版:Swing数组实现基础贪吃蛇
-
- [2.1 游戏面板 GamePanel.java](#2.1 游戏面板 GamePanel.java)
- 三、中级版:JavaFX实现带UI交互
- 四、高级版:带AI、皮肤、排行榜
- 五、常见问题
- 六、总结
大三Java课设,我之前写过python贪吃蛇,那篇文章比较火所以写了个JAVA贪吃蛇
班上40个人,30个交了贪吃蛇。老师在讲台上翻作业,翻到第10个脸就绿了------"你们这是交作业还是交Ctrl+C?"
我交的也是贪吃蛇,但是自己一行一行敲的。老师后来把我叫过去,问能不能把代码分享给班上参考。
码龄三年了,今天把三个版本的贪吃蛇全部开源:从Swing数组版,到带计分暂停的JavaFX版,再到带AI自动寻路的炫酷版。你拿去交课设也好,面试当项目也好,都够用了。
源码我放三个地方,哪个方便用哪个:
| 版本 | GitHub | Gitee | 网盘 |
|---|---|---|---|
| 初级版 | GitHub | Gitee | 百度网盘 提取码: ngg7 |
| 中级版 | GitHub | Gitee | - |
| 高级版 | GitHub | Gitee | - |
📸 如何获得效果图: 把代码跑起来,用系统截图工具(Win+Shift+S或Mac Command+Shift+4)截几张运行界面,替换下面标注的位置即可。
图片素材:链接:https://pan.baidu.com/s/1MKhUOA4WeYFFOsBoblh0kQ?pwd=xmrr#list/path=百度网盘网页链接 提取码:xmrr
一、先搭个能跑的窗口
不管哪个版本,第一步都是搭一个能跑起来的窗口。Swing的JFrame最简单:
java
import javax.swing.*;
public class SnakeGame extends JFrame {
public SnakeGame() {
setTitle("贪吃蛇");
setBounds(10, 10, 900, 720);
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(new GamePanel());
setVisible(true);
}
public static void main(String[] args) {
new SnakeGame();
}
}
900×720的窗口,25×25的格子,刚好画28×24的网格。
📸 
二、初级版:Swing数组实现基础贪吃蛇
这个版本约200行代码,实现功能:绘制窗口、控制移动、吃食物变长、撞墙/撞自身游戏结束。
2.1 游戏面板 GamePanel.java
java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
public class GamePanel extends JPanel implements KeyListener, ActionListener {
int length = 3;
int[] snakeX = new int[600];
int[] snakeY = new int[500];
String direction = "R";
int foodX, foodY;
Random random = new Random();
int score = 0;
boolean isStarted = false;
boolean isFailed = false;
Timer timer = new Timer(100, this);
public GamePanel() {
init();
this.setFocusable(true);
this.addKeyListener(this);
timer.start();
}
private void init() {
length = 3;
snakeX[0] = 100; snakeY[0] = 100;
snakeX[1] = 75; snakeY[1] = 100;
snakeX[2] = 50; snakeY[2] = 100;
direction = "R";
foodX = 25 + 25 * random.nextInt(34);
foodY = 75 + 25 * random.nextInt(24);
score = 0;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
this.setBackground(Color.WHITE);
// 头部区域
g.setColor(Color.LIGHT_GRAY);
g.fillRect(25, 11, 850, 50);
// 游戏区域
g.setColor(Color.BLACK);
g.fillRect(25, 75, 850, 600);
// 蛇头
g.setColor(Color.RED);
g.fillRect(snakeX[0], snakeY[0], 25, 25);
// 蛇身
g.setColor(Color.GREEN);
for (int i = 1; i < length; i++) {
g.fillRect(snakeX[i], snakeY[i], 25, 25);
}
// 食物
g.setColor(Color.ORANGE);
g.fillOval(foodX, foodY, 25, 25);
// 分数
g.setColor(Color.WHITE);
g.setFont(new Font("微软雅黑", Font.BOLD, 18));
g.drawString("Score: " + score, 700, 45);
if (!isStarted) {
g.setColor(Color.WHITE);
g.setFont(new Font("微软雅黑", Font.BOLD, 30));
g.drawString("Press Space to Start", 300, 350);
}
if (isFailed) {
g.setColor(Color.RED);
g.setFont(new Font("微软雅黑", Font.BOLD, 30));
g.drawString("GAME OVER! Press Space", 250, 350);
}
}
@Override
public void actionPerformed(ActionEvent e) {
if (isStarted && !isFailed) {
// 身体前移
for (int i = length - 1; i > 0; i--) {
snakeX[i] = snakeX[i - 1];
snakeY[i] = snakeY[i - 1];
}
// 移动蛇头
switch (direction) {
case "R": snakeX[0] += 25; break;
case "L": snakeX[0] -= 25; break;
case "U": snakeY[0] -= 25; break;
case "D": snakeY[0] += 25; break;
}
// 边界检测
if (snakeX[0] > 850 || snakeX[0] < 25 || snakeY[0] > 650 || snakeY[0] < 75) {
isFailed = true;
}
// 自身碰撞
for (int i = 1; i < length; i++) {
if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
isFailed = true;
break;
}
}
// 吃到食物
if (snakeX[0] == foodX && snakeY[0] == foodY) {
length++;
score += 10;
foodX = 25 + 25 * random.nextInt(34);
foodY = 75 + 25 * random.nextInt(24);
}
repaint();
}
timer.start();
}
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_SPACE) {
if (isFailed) {
isFailed = false;
init();
} else {
isStarted = !isStarted;
}
repaint();
}
// 禁止掉头
if (keyCode == KeyEvent.VK_UP && !direction.equals("D")) {
direction = "U";
} else if (keyCode == KeyEvent.VK_DOWN && !direction.equals("U")) {
direction = "D";
} else if (keyCode == KeyEvent.VK_LEFT && !direction.equals("R")) {
direction = "L";
} else if (keyCode == KeyEvent.VK_RIGHT && !direction.equals("L")) {
direction = "R";
}
}
@Override public void keyTyped(KeyEvent e) {}
@Override public void keyReleased(KeyEvent e) {}
}
在这里
三、中级版:JavaFX实现带UI交互
这个版本约500行代码,用JavaFX替代Swing,界面更现代,增加了开始/暂停/继续、生命值系统和最高分记录。
3.1 主入口 Main.java
java
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/game.fxml"));
primaryStage.setTitle("贪吃蛇 - JavaFX中级版");
primaryStage.setScene(new Scene(root, 800, 600));
primaryStage.setResizable(false);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
3.2 蛇实体类 Snake.java
java
import java.util.LinkedList;
import javafx.scene.paint.Color;
public class Snake {
private LinkedList<Point> body;
private Direction direction;
private Color headColor = Color.RED;
private Color bodyColor = Color.GREEN;
public Snake(int startX, int startY) {
body = new LinkedList<>();
body.add(new Point(startX, startY));
body.add(new Point(startX - 1, startY));
body.add(new Point(startX - 2, startY));
direction = Direction.RIGHT;
}
public void move(boolean grow) {
Point head = body.getFirst();
Point newHead = null;
switch (direction) {
case UP: newHead = new Point(head.x, head.y - 1); break;
case DOWN: newHead = new Point(head.x, head.y + 1); break;
case LEFT: newHead = new Point(head.x - 1, head.y); break;
case RIGHT: newHead = new Point(head.x + 1, head.y); break;
}
body.addFirst(newHead);
if (!grow) {
body.removeLast();
}
}
public boolean checkSelfCollision() {
Point head = body.getFirst();
for (int i = 1; i < body.size(); i++) {
if (head.equals(body.get(i))) {
return true;
}
}
return false;
}
public Point getHead() { return body.getFirst(); }
public LinkedList<Point> getBody() { return body; }
public void setDirection(Direction d) { this.direction = d; }
public Direction getDirection() { return direction; }
}
3.3 游戏控制器核心片段
java
import javafx.animation.AnimationTimer;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
public class GameController {
private Canvas canvas;
private GraphicsContext gc;
private Snake snake;
private Food food;
private int score = 0;
private int lives = 3;
private boolean running = false;
private AnimationTimer gameLoop = new AnimationTimer() {
private long lastUpdate = 0;
@Override
public void handle(long now) {
if (!running) return;
if (now - lastUpdate >= 150_000_000) {
update();
render();
lastUpdate = now;
}
}
};
private void update() {
boolean ateFood = snake.getHead().equals(food.getPosition());
snake.move(ateFood);
if (ateFood) {
score += 10;
food.regenerate(snake.getBody());
}
if (snake.checkWallCollision(GRID_WIDTH, GRID_HEIGHT) || snake.checkSelfCollision()) {
lives--;
if (lives <= 0) {
gameOver();
} else {
resetSnake();
}
}
}
}
📸 截游戏画面放在这里。
四、高级版:带AI、皮肤、排行榜
这个版本约1000+行代码,展示Java游戏开发的完整技术栈。
4.1 AI寻路算法
java
import java.awt.Point;
import java.util.*;
public class AIPathFinder {
private static final int[][] DIRECTIONS = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};
public static Direction findNextDirection(Point head, Point food,
Set<Point> obstacles,
int gridWidth, int gridHeight) {
Queue<Point> queue = new LinkedList<>();
Map<Point, Point> cameFrom = new HashMap<>();
Set<Point> visited = new HashSet<>();
queue.offer(head);
visited.add(head);
while (!queue.isEmpty()) {
Point current = queue.poll();
if (current.equals(food)) {
return getFirstDirection(cameFrom, head, current);
}
for (int[] dir : DIRECTIONS) {
Point next = new Point(current.x + dir[0], current.y + dir[1]);
if (next.x >= 0 && next.x < gridWidth &&
next.y >= 0 && next.y < gridHeight &&
!obstacles.contains(next) && !visited.contains(next)) {
visited.add(next);
cameFrom.put(next, current);
queue.offer(next);
}
}
}
return null;
}
private static Direction getFirstDirection(Map<Point, Point> cameFrom,
Point start, Point end) {
Point current = end;
while (cameFrom.get(current) != null && !cameFrom.get(current).equals(start)) {
current = cameFrom.get(current);
}
if (current.x < start.x) return Direction.LEFT;
if (current.x > start.x) return Direction.RIGHT;
if (current.y < start.y) return Direction.UP;
return Direction.DOWN;
}
}
4.2 皮肤管理器
java
import javax.swing.*;
import java.awt.*;
import java.util.*;
public class SkinManager {
private static SkinManager instance;
private List<Skin> skins = new ArrayList<>();
private Skin currentSkin;
private Random random = new Random();
private SkinManager() {
loadSkins();
randomSkin();
}
public static SkinManager getInstance() {
if (instance == null) {
instance = new SkinManager();
}
return instance;
}
private void loadSkins() {
skins.add(new Skin("经典", Color.RED, Color.GREEN, Color.ORANGE));
skins.add(new Skin("暗黑", Color.CYAN, Color.DARK_GRAY, Color.YELLOW));
skins.add(new Skin("森林", new Color(34, 139, 34), new Color(0, 100, 0), Color.ORANGE));
skins.add(new Skin("海洋", Color.BLUE, new Color(0, 191, 255), Color.RED));
}
public void randomSkin() {
currentSkin = skins.get(random.nextInt(skins.size()));
}
public Skin getCurrentSkin() { return currentSkin; }
public static class Skin {
public final Color headColor;
public final Color bodyColor;
public final Color foodColor;
public final String name;
public Skin(String name, Color head, Color body, Color food) {
this.name = name;
this.headColor = head;
this.bodyColor = body;
this.foodColor = food;
}
}
}
📸 
。
五、常见问题
画面闪烁? 继承JPanel重写paintComponent,Swing自动启用双缓冲。
食物生成在蛇身上? 生成后遍历蛇身校验,重合则重新生成。
键盘响应不灵敏? 确保JPanel调用了setFocusable(true)。
六、总结
三个版本从简单到复杂:
· 初级版:200行,Swing数组实现,适合刚学完面向对象的同学
· 中级版:500行,JavaFX LinkedList实现,带计分暂停生命值
· 高级版:1000+行,BFS寻路AI、多套皮肤、本地排行榜
源码在文首三个地方都有,哪个方便用哪个。
最后问一句:你们Java课设交的啥?有没有被老师吐槽过?评论区唠唠。
我是郭wes,码龄三年,愿大家都发高质文章,一起加油。