简介
简易贪吃蛇,使用 javax.swing 组件构建游戏界面,通过监听键盘按键实现游戏操纵。
功能设计
- 按1 - 开始游戏
- 按2 - 重新开始
- 按3 - 暂停/继续
- 按Esc-退出游戏
- 统计吃到的苹果个数(得分)
- 难度控制,得分超过阈值时难度增加(蛇身移动速度加快)
实现
定义 SnakeGame 类:
继承 JPanel 类, 重写其由 JComponent 类中继承的 paintComponent 方法,在此方法中进行图像的绘制。
实现 ActionListener 类, 实现其 actionPerformed 方法, 通过监听在 SnakeGame 类中的Timer 计时器步长内按键的输入完成对图像的操作。
java
public class SnakeGame extends JPanel implements ActionListener {
//(timer = new Timer(delay, this)).start();
public void paintComponent(Graphics g) {
//在这里操作图像的绘制,蛇身和苹果等
}
public void actionPerformed(ActionEvent e) {
//在这里通过对定时器的监听完成对图像的操作
}
}
自定义按键适配器, 将键盘输入转换为程序识别的方向值,同时记录键盘输入。
java
/**
* 定义方向
*/
public interface Direction {
char LEFT = 'L';
char RIGHT = 'R';
char UP = 'U';
char DOWN = 'D';
}
/**
* 按键适配器,用于监听输入按键
*/
public class MyKeyAdapter extends KeyAdapter {
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
eventKeyCode = e.getKeyCode();
if ((keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_A) && direction != Direction.RIGHT) {
direction = Direction.LEFT;
} else if ((keyCode == KeyEvent.VK_RIGHT || keyCode == KeyEvent.VK_D) && direction != Direction.LEFT) {
direction = Direction.RIGHT;
} else if ((keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_W) && direction != Direction.DOWN) {
direction = Direction.UP;
} else if ((keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_S) && direction != Direction.UP) {
direction = Direction.DOWN;
}
}
}
在 paintComponent 和 actionPerformed 方法中实现游戏逻辑。
注意:
- 对可用像素点的处理:可用像素点个数 = 屏幕长 * 屏幕宽 / 单位大小(苹果大小)。
- 对蛇身初始坐标的处理: 存储蛇身的初始坐标须在可用像素点中。
- 对苹果坐标的处理:需判断新的苹果坐标是否在蛇身内。
- 对游戏是否存活的处理:蛇头撞到蛇身或者蛇头撞到边缘都应视为结束。
- 对接收到的按键值的处理:除方向按键外,其余按键值使用完之后需做清除。
完整实现代码如下
java
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.Arrays;
import java.util.Random;
public class SnakeGame extends JPanel implements ActionListener {
public interface Direction {
char LEFT = 'L';
char RIGHT = 'R';
char UP = 'U';
char DOWN = 'D';
}
public interface RunStatus {
char READY = '0'; // 就绪
char RUN = '1'; // 运行
char OVER = '2'; // 失败
char PAUSE = '3'; // 暂停/继续
}
final Random random = new Random();
volatile int eventKeyCode;
//记时步长
volatile int delay = 150;
Timer timer = null;
//屏幕大小
static final int SCREEN_WIDTH = 600;
static final int SCREEN_HEIGHT = 600;
//可用像素点
static final int UNIT_SIZE = 25;
static final int GAME_UNITS = (SCREEN_WIDTH * SCREEN_HEIGHT) / UNIT_SIZE;
//蛇身
volatile int bodyParts;
int snakeBodyX[];
int snakeBodyY[];
//目标
int appleX;
int appleY;
//得分
volatile int applesEaten;
volatile char direction;
volatile char runStatus;
SnakeGame() {
this.setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
this.setFocusable(true);
this.addKeyListener(new MyKeyAdapter());
(timer = new Timer(delay, this)).start();
init();
}
public void init() {
bodyParts = 1;
snakeBodyX = new int[GAME_UNITS];
Arrays.fill(snakeBodyX, -1);
snakeBodyX[0] = 0;
snakeBodyY = new int[GAME_UNITS];
Arrays.fill(snakeBodyY, -1);
snakeBodyY[0] = 0;
appleX = nextCoordinate(SCREEN_WIDTH, snakeBodyX);
appleY = nextCoordinate(SCREEN_HEIGHT, snakeBodyY);
applesEaten = 0;
direction = Direction.RIGHT;
runStatus = RunStatus.READY;
timer.setDelay(delay);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
String tip = "" + applesEaten;
int y = SCREEN_HEIGHT / 2 - 120;
switch (runStatus) {
case RunStatus.READY:
drawing(g);
g.setColor(Color.BLUE);
g.setFont(new Font(null, Font.ITALIC, 20));
g.drawString("按1-开始游戏", (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, (y = y + 40));
g.drawString("按2-重新开始", (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, (y = y + 40));
g.drawString("按3-暂停/继续", (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, (y = y + 40));
g.drawString("按Esc-退出游戏", (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, (y = y + 40));
break;
case RunStatus.PAUSE:
drawing(g);
g.setColor(Color.BLUE);
g.setFont(new Font(null, Font.ITALIC, 20));
y = y + 80;
g.drawString("按3-继续", (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, (y = y + 40));
g.drawString("按Esc-退出游戏", (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, (y = y + 40));
break;
case RunStatus.RUN:
drawing(g);
g.setColor(Color.blue);
g.setFont(new Font(null, Font.BOLD, 20));
g.drawString(tip, (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, g.getFont().getSize());
break;
case RunStatus.OVER:
g.setColor(Color.RED);
g.setFont(new Font("Ink Free", Font.BOLD, 40));
g.drawString("Game Over", (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, y - 120);
g.setFont(new Font(null, Font.ITALIC, 20));
g.drawString("得分:" + applesEaten, (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, y - 60);
g.setColor(Color.BLUE);
g.drawString("按2-重新开始", (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, (y = y + 40));
g.drawString("按Esc-退出游戏", (SCREEN_WIDTH - getFontMetrics(g.getFont()).stringWidth(tip)) / 2, (y = y + 40));
break;
default:
break;
}
}
/**
* 绘制蛇身和苹果
*
* @param g
*/
public void drawing(Graphics g) {
//苹果颜色
g.setColor(Color.PINK);
//画苹果
g.fillOval(appleX, appleY, UNIT_SIZE, UNIT_SIZE);
//蛇身颜色
g.setColor(Color.RED);
//填充蛇身
for (int i = 0; i < bodyParts; i++) {
g.fillRect(snakeBodyX[i], snakeBodyY[i], UNIT_SIZE, UNIT_SIZE);
}
}
/**
* 执行的动作
* 会定时执行此方法
*
* @param e
*/
public void actionPerformed(ActionEvent e) {
//前置事件
doSomeThing();
if (runStatus == RunStatus.RUN) {
move();
//撞到边缘
if (snakeBodyX[0] < 0 || snakeBodyX[0] > SCREEN_WIDTH || snakeBodyY[0] < 0 || snakeBodyY[0] > SCREEN_HEIGHT) {
runStatus = RunStatus.OVER;
}
//蛇身相撞
for (int i = bodyParts; i > 0; i--) {
if ((snakeBodyX[0] == snakeBodyX[i]) && (snakeBodyY[0] == snakeBodyY[i])) {
runStatus = RunStatus.OVER;
}
}
//得分
if ((snakeBodyX[0] == appleX) && (snakeBodyY[0] == appleY)) {
applesEaten++;
bodyParts++;
//重新生成苹果
appleX = nextCoordinate(SCREEN_WIDTH, snakeBodyX);
appleY = nextCoordinate(SCREEN_HEIGHT, snakeBodyY);
//预留难度设置的方法
difficultySettings();
}
}
//重新绘图, 执行 paintComponent 方法
repaint();
}
/**
* 蛇身移动
*/
private void move() {
for (int i = bodyParts; i > 0; i--) {
snakeBodyX[i] = snakeBodyX[(i - 1)];
snakeBodyY[i] = snakeBodyY[(i - 1)];
}
switch (direction) {
case Direction.UP:
snakeBodyY[0] -= UNIT_SIZE;
break;
case Direction.DOWN:
snakeBodyY[0] += UNIT_SIZE;
break;
case Direction.LEFT:
snakeBodyX[0] -= UNIT_SIZE;
break;
case Direction.RIGHT:
snakeBodyX[0] += UNIT_SIZE;
break;
default:
break;
}
}
private void doSomeThing() {
if (KeyEvent.VK_ESCAPE == eventKeyCode) {
System.out.println("退出程序");
System.exit(0);
return;
}
if (KeyEvent.VK_1 == eventKeyCode) {
System.out.println("开始游戏");
runStatus = RunStatus.RUN;
}
if (KeyEvent.VK_2 == eventKeyCode) {
if (runStatus != RunStatus.RUN) {
System.out.println("重新开始");
init();
}
runStatus = RunStatus.RUN;
}
if (KeyEvent.VK_3 == eventKeyCode) {
if (runStatus == RunStatus.RUN) {
System.out.println("暂停");
runStatus = RunStatus.PAUSE;
eventKeyCode = -1;
return;
}
if (runStatus == RunStatus.PAUSE) {
System.out.println("继续");
runStatus = RunStatus.RUN;
eventKeyCode = -1;
return;
}
}
eventKeyCode = -1;
}
/**
* 难度设置, 默认得分超过16的倍数时速度提升1/4
*/
private void difficultySettings() {
//难度增加, 速度加快
if (applesEaten % 16 == 0 && applesEaten != 0) {
timer.setDelay(timer.getDelay() - timer.getDelay() / 4);
}
}
/**
* 下一个苹果坐标
*
* @param randomFactorint
* @param arr
* @return
*/
synchronized private int nextCoordinate(int randomFactorint, int[] arr) {
int coordinate = random.nextInt(randomFactorint / UNIT_SIZE) * UNIT_SIZE;
for (int i = 0; i < arr.length; i++) {
if (arr[i] == -1) {
break;
}
if (coordinate == arr[i]) {
nextCoordinate(randomFactorint, arr);
}
}
return coordinate;
}
/**
* 按键适配器,用于监听输入按键
*/
public class MyKeyAdapter extends KeyAdapter {
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
eventKeyCode = e.getKeyCode();
if ((keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_A) && direction != Direction.RIGHT) {
direction = Direction.LEFT;
} else if ((keyCode == KeyEvent.VK_RIGHT || keyCode == KeyEvent.VK_D) && direction != Direction.LEFT) {
direction = Direction.RIGHT;
} else if ((keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_W) && direction != Direction.DOWN) {
direction = Direction.UP;
} else if ((keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_S) && direction != Direction.UP) {
direction = Direction.DOWN;
}
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setTitle("贪吃蛇");
SnakeGame snake = new SnakeGame();
frame.add(snake);
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
}
效果展示
游戏启动
游戏暂停