基于Java Swing的2048游戏实现详解
本文将深入解析一个完整的2048游戏实现,使用Java Swing作为GUI框架,适合Java初学者和游戏开发爱好者学习。
一、游戏架构设计
1.1 核心类结构
public class Game2048 extends JPanel {
// 游戏状态枚举
enum State { start, won, running, over }
// 方块类
class Tile {
private boolean merged;
private int value;
// 方块操作方法
int mergeWith(Tile other) { ... }
}
// 游戏主逻辑
private Tile[][] tiles; // 4x4游戏网格
private State gamestate = State.start; // 初始状态
}
1.2 游戏状态机
- start: 初始界面,等待玩家点击
- running: 游戏进行中
- won: 达成2048目标
- over: 无可用移动,游戏结束
二、图形界面实现
2.1 界面初始化
public Game2048() {
setPreferredSize(new Dimension(900, 700));
setBackground(new Color(0xFAF8EF)); // 米色背景
setFont(new Font("SansSerif", Font.BOLD, 48));
// 添加事件监听器
addMouseListener(...); // 鼠标点击开始游戏
addKeyListener(...); // 键盘方向键控制
}
2.2 网格绘制算法
void drawGrid(Graphics2D g) {
g.setColor(gridColor);
g.fillRoundRect(200, 100, 499, 499, 15, 15); // 主游戏区
for (int r = 0; r < side; r++) {
for (int c = 0; c < side; c++) {
if (tiles[r][c] == null) {
// 绘制空白格子
g.fillRoundRect(215 + c*121, 115 + r*121, 106, 106, 7, 7);
} else {
// 绘制数字方块
drawTile(g, r, c);
}
}
}
}
2.3 方块颜色方案
final Color[] colorTable = {
new Color(0x704A10), // 2
new Color(0xFFEAC3), // 4
new Color(0xFFF3D3), // 8
// ... 其他颜色对应更高数值
};
三、游戏核心逻辑
3.1 游戏初始化
void startGame() {
score = 0;
highest = 0;
gamestate = State.running;
tiles = new Tile[side][side]; // 4x4网格
addRandomTile(); // 生成两个初始方块
addRandomTile();
}
3.2 随机方块生成
private void addRandomTile() {
int pos = rand.nextInt(16); // 随机位置
int row = pos / 4, col = pos % 4;
// 90%概率生成2,10%概率生成4
int val = rand.nextInt(10) == 0 ? 4 : 2;
tiles[row][col] = new Tile(val);
}
3.3 移动与合并算法
private boolean move(int countDownFrom, int yIncr, int xIncr) {
boolean moved = false;
for (int i = 0; i < 16; i++) {
int j = Math.abs(countDownFrom - i);
int r = j / 4, c = j % 4;
if (tiles[r][c] == null) continue;
// 计算移动方向
int nextR = r + yIncr, nextC = c + xIncr;
while (isValidPosition(nextR, nextC)) {
if (tiles[nextR][nextC] == null) {
// 移动到空位置
tiles[nextR][nextC] = tiles[r][c];
tiles[r][c] = null;
moved = true;
}
else if (tiles[nextR][nextC].canMergeWith(tiles[r][c])) {
// 合并相同数字
int newValue = tiles[nextR][nextC].mergeWith(tiles[r][c]);
score += newValue;
moved = true;
break;
}
}
}
return moved;
}
3.4 移动方向实现
boolean moveUp() {
return move(0, -1, 0); // 从顶部开始向下移动
}
boolean moveDown() {
return move(15, 1, 0); // 从底部开始向上移动
}
// 左右移动同理
四、游戏状态检测
4.1 胜利条件检测
if (highest >= target) { // target=2048
gamestate = State.won;
}
4.2 失败条件检测
private boolean movesAvailable() {
// 临时启用检测模式
checkingAvailableMoves = true;
// 尝试四个方向是否还有移动可能
boolean hasMoves = moveUp() || moveDown() || moveLeft() || moveRight();
checkingAvailableMoves = false;
return hasMoves;
}
五、关键技术点解析
-
双缓冲技术:
- 通过
paintComponent()
实现平滑渲染 - 使用
repaint()
触发界面更新
- 通过
-
事件处理模型:
addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { case KeyEvent.VK_UP: moveUp(); break; // 其他方向... } repaint(); } });
-
颜色动态计算:
// 根据方块数值计算颜色索引 int colorIndex = (int)(Math.log(value) / Math.log(2)); g.setColor(colorTable[colorIndex]);
-
字体渲染优化:
FontMetrics fm = g.getFontMetrics(); int x = 215 + c*121 + (106 - fm.stringWidth(s)) / 2; // 水平居中 int y = 115 + r*121 + (asc + (106 - (asc + dec)) / 2; // 垂直居中
六、运行效果展示
游戏界面包含:
- 主游戏区域(4×4网格)
- 当前分数显示
- 历史最高分
- 状态提示信息
七、扩展建议
-
添加分数显示:
// 在drawGrid()中添加 g.drawString("分数: " + score, 300, 50); g.drawString("最高分: " + highest, 500, 50);
-
实现撤销功能:
private Tile[][] prevState; void saveState() { prevState = Arrays.copyOf(tiles, tiles.length); } void undo() { if (prevState != null) tiles = prevState; }
-
添加动画效果:
// 使用Swing Timer实现移动动画 Timer timer = new Timer(10, e -> { // 更新方块位置 repaint(); });
本文实现的2048游戏完整展示了:
- Java Swing GUI开发流程
- 游戏状态机管理
- 网格布局与渲染技术
- 核心游戏算法实现
- 事件处理机制
通过这个项目,可以学习到游戏开发的基本原理和Java GUI编程的核心技术。完整代码如下
package XIAXIA.game2048;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Random;
/**
* 基于Java Swing的2048游戏实现
* 核心功能:
* 1. 4×4游戏网格与方块移动逻辑
* 2. 方向键控制与鼠标交互
* 3. 方块合并与分数计算
* 4. 游戏状态管理(开始/进行中/胜利/结束)
*/
public class Game2048 extends JPanel {
// 游戏状态枚举
enum State {
start, won, running, over
}
// 方块颜色表,不同数值对应不同颜色
final Color[] colorTable = {
new Color(0x704A10), // 2的颜色
new Color(0xFFEAC3), // 4的颜色
new Color(0xFFF3D3), // 8的颜色
new Color(0xffdac3), // 16的颜色
new Color(0xE7AD8E), // 32的颜色
new Color(0xE7DB8E), // 64的颜色
new Color(0xffc4c3), // 128的颜色
new Color(0xE7948e), // 256的颜色
new Color(0xbe7e56), // 512的颜色
new Color(0xbe5e56), // 1024的颜色
new Color(0x9c3931), // 2048的颜色
new Color(0x701710) // >2048的颜色
};
// 游戏常量
final static int target = 2048; // 目标分数
static int highest; // 历史最高分
static int score; // 当前分数
// 界面颜色
private Color gridColor = new Color(0xBBADA0); // 网格背景色
private Color emptyColor = new Color(0xCDC1B4); // 空方块颜色
private Color startColor = new Color(0xFFEBCD); // 开始界面颜色
private Random rand = new Random(); // 随机数生成器
private Tile[][] tiles; // 4×4游戏方块数组
private int side = 4; // 网格尺寸
private State gamestate = State.start; // 初始游戏状态
private boolean checkingAvailableMoves; // 移动检查标志
public Game2048() {
// 界面初始化
setPreferredSize(new Dimension(900, 700));
setBackground(new Color(0xFAF8EF)); // 背景色
setFont(new Font("SansSerif", Font.BOLD, 48)); // 字体设置
setFocusable(true); // 允许接收键盘事件
// 鼠标事件监听 - 点击开始游戏
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
startGame();
repaint();
}
});
// 键盘事件监听 - 方向键控制
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
moveUp();
break;
case KeyEvent.VK_DOWN:
moveDown();
break;
case KeyEvent.VK_LEFT:
moveLeft();
break;
case KeyEvent.VK_RIGHT:
moveRight();
break;
}
repaint();
}
});
}
@Override
public void paintComponent(Graphics gg) {
super.paintComponent(gg);
Graphics2D g = (Graphics2D) gg;
// 开启抗锯齿,使图形更平滑
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// 绘制游戏网格
drawGrid(g);
}
/**
* 初始化游戏
*/
void startGame() {
if (gamestate != State.running) {
score = 0;
highest = 0;
gamestate = State.running;
tiles = new Tile[side][side]; // 创建4×4网格
addRandomTile(); // 添加两个初始方块
addRandomTile();
}
}
/**
* 绘制游戏网格和方块
*/
void drawGrid(Graphics2D g) {
// 绘制游戏区域背景
g.setColor(gridColor);
g.fillRoundRect(200, 100, 499, 499, 15, 15);
// 根据游戏状态绘制不同内容
if (gamestate == State.running) {
// 游戏进行中:绘制所有方块
for (int r = 0; r < side; r++) {
for (int c = 0; c < side; c++) {
if (tiles[r][c] == null) {
// 绘制空白方块
g.setColor(emptyColor);
g.fillRoundRect(215 + c * 121, 115 + r * 121, 106, 106, 7, 7);
} else {
// 绘制有数字的方块
drawTile(g, r, c);
}
}
}
} else {
// 非游戏状态:绘制开始/胜利/结束界面
g.setColor(startColor);
g.fillRoundRect(215, 115, 469, 469, 7, 7);
g.setColor(gridColor.darker());
g.setFont(new Font("SansSerif", Font.BOLD, 128));
g.drawString("2048", 310, 270);
g.setFont(new Font("SansSerif", Font.BOLD, 20));
if (gamestate == State.won) {
g.drawString("你是真的6", 390, 350);
} else if (gamestate == State.over) {
g.drawString("游戏结束", 400, 350);
}
g.setColor(gridColor);
g.drawString("任意点击开始游戏", 370, 420);
g.drawString("请使用上下左右控制方块", 340, 470);
}
}
/**
* 绘制单个方块
*/
void drawTile(Graphics2D g, int r, int c) {
int value = tiles[r][c].getValue();
// 根据方块值计算颜色索引
int colorIndex = (int) (Math.log(value) / Math.log(2)) - 1;
if (colorIndex < 0) colorIndex = 0;
if (colorIndex >= colorTable.length) colorIndex = colorTable.length - 1;
g.setColor(colorTable[colorIndex]);
g.fillRoundRect(215 + c * 121, 115 + r * 121, 106, 106, 7, 7);
// 绘制方块上的数字
String s = String.valueOf(value);
g.setColor(value < 128 ? colorTable[0] : Color.WHITE); // 根据数值选择文字颜色
// 文字居中处理
FontMetrics fm = g.getFontMetrics();
int asc = fm.getAscent();
int dec = fm.getDescent();
int x = 215 + c * 121 + (106 - fm.stringWidth(s)) / 2;
int y = 115 + r * 121 + (asc + (106 - (asc + dec)) / 2);
g.drawString(s, x, y);
}
/**
* 在随机位置添加新方块
*/
private void addRandomTile() {
// 随机选择一个位置
int pos = rand.nextInt(side * side);
int row, col;
// 找到空位置
do {
pos = (pos + 1) % (side * side);
row = pos / side;
col = pos % side;
} while (tiles[row][col] != null);
// 90%概率生成2,10%概率生成4
int val = rand.nextInt(10) == 0 ? 4 : 2;
tiles[row][col] = new Tile(val);
}
/**
* 核心移动逻辑
* @param countDownFrom 遍历起始位置
* @param yIncr Y方向移动增量
* @param xIncr X方向移动增量
* @return 是否发生了移动
*/
private boolean move(int countDownFrom, int yIncr, int xIncr) {
boolean moved = false;
// 遍历所有方块
for (int i = 0; i < side * side; i++) {
int j = Math.abs(countDownFrom - i);
int r = j / side;
int c = j % side;
if (tiles[r][c] == null) continue;
int nextR = r + yIncr;
int nextC = c + xIncr;
// 在有效范围内移动方块
while (nextR >= 0 && nextR < side && nextC >= 0 && nextC < side) {
Tile next = tiles[nextR][nextC];
Tile curr = tiles[r][c];
if (next == null) {
// 移动到空位置
if (checkingAvailableMoves) return true;
tiles[nextR][nextC] = curr;
tiles[r][c] = null;
r = nextR;
c = nextC;
nextR += yIncr;
nextC += xIncr;
moved = true;
} else if (next.canMergeWith(curr)) {
// 合并相同数字的方块
if (checkingAvailableMoves) return true;
int value = next.mergeWith(curr);
if (value > highest) highest = value;
score += value;
tiles[r][c] = null;
moved = true;
break;
} else {
break;
}
}
}
// 移动后处理
if (moved) {
if (highest < target) {
clearMerged();
addRandomTile();
// 检查游戏是否结束
if (!movesAvailable()) {
gamestate = State.over;
}
} else if (highest == target) {
gamestate = State.won;
}
}
return moved;
}
// 四个方向的移动方法
boolean moveUp() {
return move(0, -1, 0); // 从顶部开始向下移动
}
boolean moveDown() {
return move(side * side - 1, 1, 0); // 从底部开始向上移动
}
boolean moveLeft() {
return move(0, 0, -1); // 从左侧开始向右移动
}
boolean moveRight() {
return move(side * side - 1, 0, 1); // 从右侧开始向左移动
}
/**
* 清除方块的合并状态
*/
void clearMerged() {
for (Tile[] row : tiles) {
for (Tile tile : row) {
if (tile != null) {
tile.setMerged(false);
}
}
}
}
/**
* 检查是否还有可用的移动
* @return 是否还有移动可能
*/
boolean movesAvailable() {
checkingAvailableMoves = true;
boolean hasMoves = moveUp() || moveDown() || moveLeft() || moveRight();
checkingAvailableMoves = false;
return hasMoves;
}
/**
* 方块内部类
*/
class Tile {
private boolean merged; // 是否已合并
private int value; // 方块数值
Tile(int val) {
value = val;
}
int getValue() {
return value;
}
void setMerged(boolean m) {
merged = m;
}
/**
* 检查是否可以与另一个方块合并
*/
boolean canMergeWith(Tile other) {
return !merged && other != null && !other.merged && value == other.getValue();
}
/**
* 与另一个方块合并
* @return 合并后的新值
*/
int mergeWith(Tile other) {
if (canMergeWith(other)) {
value *= 2;
merged = true;
return value;
}
return -1;
}
}
/**
* 程序入口
*/
public static void main(String[] args) {
// 在事件调度线程中创建GUI
SwingUtilities.invokeLater(() -> {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setTitle("天上星的2048");
f.setResizable(false);
f.add(new Game2048(), BorderLayout.CENTER);
f.pack();
f.setLocationRelativeTo(null); // 居中显示
f.setVisible(true);
});
}
}
游戏扩展建议
- 添加分数显示:
// 在drawGrid方法中添加
g.drawString("分数: " + score, 300, 50);
g.drawString("最高分: " + highest, 500, 50);
- 增加动画效果:
// 使用Swing Timer实现移动动画
Timer animationTimer = new Timer(10, e -> {
// 更新方块位置
repaint();
});
- 添加音效反馈:
// 方块合并时播放音效
Clip mergeSound = AudioSystem.getClip();
mergeSound.open(AudioSystem.getAudioInputStream("merge.wav"));
mergeSound.start();
- 实现游戏存档:
// 使用Properties保存游戏状态
Properties props = new Properties();
props.setProperty("score", String.valueOf(score));
props.store(new FileWriter("save.dat"), "Game State");