一.项目要求
(1)实现贪吃蛇游戏基本功能,屏幕上随机出现一个“食物”,称为豆子, 上下左右控制“蛇”的移动,吃到“豆子”以后“蛇”的身体加长一点。
(2)“蛇”碰到边界或蛇头与蛇身相撞,蛇死亡,游戏结束。
(3)为游戏设计友好的交互界面;例如欢迎界面,游戏界面,游戏结束界面。要有开始键、暂停键和停止退出的选项。
(4)对蛇吃到豆子进行分值计算,可以设置游戏速度,游戏音乐 等拓展元素。
二.项目平台
IDEA.Java Swing
三.项目实现过程
1.实现键盘控制:
addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
super.keyPressed(e);
if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
if (isrun && current_direction != "L") {
direction = "R";
}
}
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
if (isrun && current_direction != "R") {
direction = "L";
}
}
if (e.getKeyCode() == KeyEvent.VK_UP) {
if (isrun && current_direction != "D") {
direction = "U";
}
}
if (e.getKeyCode() == KeyEvent.VK_DOWN) {
if (isrun && current_direction != "U") {
direction = "D";
}
}
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
direction = "R";//默认向右走
current_direction = "R";//当前方向
first_launch = false;
isrun = true;
body_length = 3;
head = new Tile(227, 100);
Score.setText("0");
for (int i = 0; i < MAX_SIZE; i++) {
body[i].x = 0;
body[i].y = 0;
}
run = new Thread();
run.start();
System.out.println("Start again");
}
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
if (!pause)//暂停
{
pause = true;
isrun = false;
} else//开始
{
pause = false;
isrun = true;
}
}
}
});
setFocusable(true);
}
2.游戏画面及内容设置:
public void paintComponent(Graphics g1) {
super.paintComponent(g1);
Graphics2D g = (Graphics2D) g1;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);//消除画图锯齿
g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);//画线平滑
//画头部
g.setColor(Color.RED);
g.fillRoundRect(head.x, head.y, 20, 20, 10, 10);
g.setPaint(new GradientPaint(115, 135, Color.ORANGE, 230, 135, Color.PINK, true));//颜色设置
if (!first_launch) {
//初始化身体
int x = head.x;
for (int i = 0; i < body_length; i++) {
x -= 22;//相邻两个方块的间距为2个像素,方块宽度都为20像素
body[i].x = x;
body[i].y = head.y;
g.fillRoundRect(body[i].x, body[i].y, 20, 20, 10, 10);
}
//初始化食物位置
ProduceRandom();
g.fillOval(randomx, randomy, 19, 19);
} else {
//每次刷新身体
for (int i = 0; i < body_length; i++) {
g.fillRoundRect(body[i].x, body[i].y, 20, 20, 10, 10);
}
if (EatFood())//被吃了重新产生食物
{
ProduceRandom();
g.fillOval(randomx, randomy, 19, 19);
} else {
g.fillOval(randomx, randomy, 19, 19);
}
}
first_launch = true;
//墙
g.setStroke(new BasicStroke(4, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
g.drawRect(2, 7, 491, 469);
//网格线
for (int i = 1; i < 22; i++) {
g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
g.setColor(Color.blue);
g.drawLine(5 + i * 22, 9, 5 + i * 22, 472);
if (i <= 20) {
g.drawLine(4, 10 + i * 22, 491, 10 + i * 22);
}
}
}
3.食物随机产生:
public void ProduceRandom() {
boolean flag = true;
Random rand = new Random();
randomx = (rand.nextInt(21) + 1) * 22 + 7;
randomy = (rand.nextInt(20) + 1) * 22 + 12;
while (flag) {
for (int i = 0; i < body_length; i++) {
if (body[i].x == randomx && body[i].y == randomy) {
randomx = (rand.nextInt(21) + 1) * 22 + 7;
randomy = (rand.nextInt(20) + 1) * 22 + 12;
flag = true;
break;
} else {
if (i == body_length - 1) {
flag = false;
}
}
}
}
}
4.触碰墙与身体的判定:
public void HitWall() {//判断是否撞墙
if (current_direction == "L") {
if (head.x < 7) {
isrun = false;
int result = JOptionPane.showConfirmDialog(null, "Game over! Try again?", "Information", JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_NO_OPTION) {
direction = "R";//默认向右走
current_direction = "R";//当前方向
first_launch = false;
isrun = true;
body_length = 5;
head = new Tile(227, 100);
Score.setText("0");
for (int i = 0; i < MAX_SIZE; i++) {
body[i].x = 0;
body[i].y = 0;
}
run = new Thread();
run.start();
System.out.println("Start again");
} else {
run.stop();
}
}
}
if (current_direction == "R") {
if (head.x > 489) {
isrun = false;
int result = JOptionPane.showConfirmDialog(null, "Game over! Try again?", "Information", JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_NO_OPTION) {
direction = "R";//默认向右走
current_direction = "R";//当前方向
first_launch = false;
isrun = true;
body_length = 3;
head = new Tile(227, 100);
Score.setText("0");
for (int i = 0; i < MAX_SIZE; i++) {
body[i].x = 0;
body[i].y = 0;
}
run = new Thread();
run.start();
System.out.println("Start again");
} else {
run.stop();
}
}
}
if (current_direction == "U") {
if (head.y < 12) {
isrun = false;
int result = JOptionPane.showConfirmDialog(null, "Game over! Try again?", "Information", JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_NO_OPTION) {
direction = "R";//默认向右走
current_direction = "R";//当前方向
first_launch = false;
isrun = true;
body_length = 5;
head = new Tile(227, 100);
Score.setText("6");
for (int i = 0; i < MAX_SIZE; i++) {
body[i].x = 0;
body[i].y = 0;
}
run = new Thread();
run.start();
System.out.println("Start again");
} else {
run.stop();
}
}
}
if (current_direction == "D") {
if (head.y > 472) {
isrun = false;
int result = JOptionPane.showConfirmDialog(null, "Game over! Try again?", "Information", JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_NO_OPTION) {
direction = "R";//默认向右走
current_direction = "R";//当前方向
first_launch = false;
isrun = true;
body_length = 5;
head = new Tile(227, 100);
Score.setText("0");
for (int i = 0; i < MAX_SIZE; i++) {
body[i].x = 0;
body[i].y = 0;
}
run = new Thread();
run.start();
System.out.println("Start again");
} else {
run.stop();
}
}
}
}
public void HitSelf() {//判断是否撞到自己身上
for (int i = 0; i < body_length; i++) {
if (body[i].x == head.x && body[i].y == head.y) {
isrun = false;
int result = JOptionPane.showConfirmDialog(null, "Game over! Try again?", "Information", JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_NO_OPTION) {
direction = "R";//默认向右走
current_direction = "R";//当前方向
first_launch = false;
isrun = true;
body_length = 3;
head = new Tile(227, 100);
Score.setText("0");
for (int j = 0; j < MAX_SIZE; j++) {
body[j].x = 0;
body[j].y = 0;
}
run = new Thread();
run.start();
System.out.println("Start again");
} else {
run.stop();
}
break;
}
}
}
5.移动与身体加长:
public boolean EatFood() {
if (head.x == randomx && head.y == randomy) {
return true;
} else {
return false;
}
}
public void Thread() {
long millis = 300;//每隔300毫秒刷新一次
run = new Thread() {
public void run() {
while (true) {
try {
Thread.sleep(millis);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
if (!pause) {
temp.x = head.x;
temp.y = head.y;
//头部移动
if (direction == "L") {
head.x -= 22;
}
if (direction == "R") {
head.x += 22;
}
if (direction == "U") {
head.y -= 22;
}
if (direction == "D") {
head.y += 22;
}
current_direction = direction;//刷新当前前进方向
//身体移动
for (int i = 0; i < body_length; i++) {
temp2.x = body[i].x;
temp2.y = body[i].y;
body[i].x = temp.x;
body[i].y = temp.y;
temp.x = temp2.x;
temp.y = temp2.y;
}
if (EatFood()) {
body_length++;
body[body_length - 1].x = temp2.x;
body[body_length - 1].y = temp2.y;
Score.setText("" + (body_length - 3));
}
repaint();
HitWall();
HitSelf();
}
}
}
};
run.start();
}
四.最终项目实现
项目完整代码:
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.*;
class Tile {
int x;
int y;
public Tile(int x0, int y0) {
x = x0;
y = y0;
}
}
public class Snake extends JComponent {
private final int MAX_SIZE = 400;//蛇身体最长为400节
private Tile temp = new Tile(0, 0);
private Tile temp2 = new Tile(0, 0);
private Tile head = new Tile(227, 100);//头部的位置初始化为(227,100)
private Tile[] body = new Tile[MAX_SIZE];
private String direction = "R";//默认向右走
private String current_direction = "R";//当前方向
private boolean first_launch = false;
private boolean isrun = true;
private int randomx, randomy;
private int body_length = 3;//身体长度初始化为3
private Thread run;
private JLabel label = new JLabel("当前得分:");
private JLabel label1 = new JLabel("说明:");
private JTextArea explain = new JTextArea("游戏界面按上下左右键实现移动,按ESC重新开始,按空格键可以实现暂停和开始");
private JLabel Score = new JLabel("0");
private Font f = new Font("宋体", Font.PLAIN, 15);
private Font f1 = new Font("宋体", Font.PLAIN, 13);
private JPanel p = new JPanel();
private boolean pause = false;
public Snake() {
String lookAndFeel = UIManager.getSystemLookAndFeelClassName();
try {
UIManager.setLookAndFeel(lookAndFeel);
} catch (ClassNotFoundException e1) {
// TODO 自动生成的 catch 块
e1.printStackTrace();
} catch (InstantiationException e1) {
// TODO 自动生成的 catch 块
e1.printStackTrace();
} catch (IllegalAccessException e1) {
// TODO 自动生成的 catch 块
e1.printStackTrace();
} catch (UnsupportedLookAndFeelException e1) {
// TODO 自动生成的 catch 块
e1.printStackTrace();
}
//布局
add(label);
label.setBounds(500, 30, 80, 20);
label.setFont(f);//得分标签
add(Score);
Score.setBounds(500, 60, 80, 20);
Score.setFont(f);//得分
add(p);
p.setBounds(498, 110, 93, 1);
p.setBorder(BorderFactory.createLineBorder(Color.black));//分隔线
add(label1);
label1.setBounds(500, 140, 80, 20);
label1.setFont(f);//说明标签
add(explain);
explain.setBounds(498, 180, 100, 350);
explain.setFont(f1);//说明
explain.setLineWrap(true);
explain.setOpaque(false);
for (int i = 0; i < MAX_SIZE; i++) {
body[i] = new Tile(0, 0);
}
addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
super.keyPressed(e);
if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
if (isrun && current_direction != "L") {
direction = "R";
}
}
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
if (isrun && current_direction != "R") {
direction = "L";
}
}
if (e.getKeyCode() == KeyEvent.VK_UP) {
if (isrun && current_direction != "D") {
direction = "U";
}
}
if (e.getKeyCode() == KeyEvent.VK_DOWN) {
if (isrun && current_direction != "U") {
direction = "D";
}
}
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
direction = "R";//默认向右走
current_direction = "R";//当前方向
first_launch = false;
isrun = true;
body_length = 3;
head = new Tile(227, 100);
Score.setText("0");
for (int i = 0; i < MAX_SIZE; i++) {
body[i].x = 0;
body[i].y = 0;
}
run = new Thread();
run.start();
System.out.println("Start again");
}
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
if (!pause)//暂停
{
pause = true;
isrun = false;
} else//开始
{
pause = false;
isrun = true;
}
}
}
});
setFocusable(true);
}
public void paintComponent(Graphics g1) {
super.paintComponent(g1);
Graphics2D g = (Graphics2D) g1;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);//消除画图锯齿
g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);//画线平滑
//画头部
g.setColor(Color.RED);
g.fillRoundRect(head.x, head.y, 20, 20, 10, 10);
g.setPaint(new GradientPaint(115, 135, Color.ORANGE, 230, 135, Color.PINK, true));//颜色设置
if (!first_launch) {
//初始化身体
int x = head.x;
for (int i = 0; i < body_length; i++) {
x -= 22;//相邻两个方块的间距为2个像素,方块宽度都为20像素
body[i].x = x;
body[i].y = head.y;
g.fillRoundRect(body[i].x, body[i].y, 20, 20, 10, 10);
}
//初始化食物位置
ProduceRandom();
g.fillOval(randomx, randomy, 19, 19);
} else {
//每次刷新身体
for (int i = 0; i < body_length; i++) {
g.fillRoundRect(body[i].x, body[i].y, 20, 20, 10, 10);
}
if (EatFood())//被吃了重新产生食物
{
ProduceRandom();
g.fillOval(randomx, randomy, 19, 19);
} else {
g.fillOval(randomx, randomy, 19, 19);
}
}
first_launch = true;
//墙
g.setStroke(new BasicStroke(4, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
g.drawRect(2, 7, 491, 469);
//网格线
for (int i = 1; i < 22; i++) {
g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
g.setColor(Color.blue);
g.drawLine(5 + i * 22, 9, 5 + i * 22, 472);
if (i <= 20) {
g.drawLine(4, 10 + i * 22, 491, 10 + i * 22);
}
}
}
public void ProduceRandom() {
boolean flag = true;
Random rand = new Random();
randomx = (rand.nextInt(21) + 1) * 22 + 7;
randomy = (rand.nextInt(20) + 1) * 22 + 12;
while (flag) {
for (int i = 0; i < body_length; i++) {
if (body[i].x == randomx && body[i].y == randomy) {
randomx = (rand.nextInt(21) + 1) * 22 + 7;
randomy = (rand.nextInt(20) + 1) * 22 + 12;
flag = true;
break;
} else {
if (i == body_length - 1) {
flag = false;
}
}
}
}
}
public void HitWall() {//判断是否撞墙
if (current_direction == "L") {
if (head.x < 7) {
isrun = false;
int result = JOptionPane.showConfirmDialog(null, "Game over! Try again?", "Information", JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_NO_OPTION) {
direction = "R";//默认向右走
current_direction = "R";//当前方向
first_launch = false;
isrun = true;
body_length = 5;
head = new Tile(227, 100);
Score.setText("0");
for (int i = 0; i < MAX_SIZE; i++) {
body[i].x = 0;
body[i].y = 0;
}
run = new Thread();
run.start();
System.out.println("Start again");
} else {
run.stop();
}
}
}
if (current_direction == "R") {
if (head.x > 489) {
isrun = false;
int result = JOptionPane.showConfirmDialog(null, "Game over! Try again?", "Information", JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_NO_OPTION) {
direction = "R";//默认向右走
current_direction = "R";//当前方向
first_launch = false;
isrun = true;
body_length = 3;
head = new Tile(227, 100);
Score.setText("0");
for (int i = 0; i < MAX_SIZE; i++) {
body[i].x = 0;
body[i].y = 0;
}
run = new Thread();
run.start();
System.out.println("Start again");
} else {
run.stop();
}
}
}
if (current_direction == "U") {
if (head.y < 12) {
isrun = false;
int result = JOptionPane.showConfirmDialog(null, "Game over! Try again?", "Information", JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_NO_OPTION) {
direction = "R";//默认向右走
current_direction = "R";//当前方向
first_launch = false;
isrun = true;
body_length = 5;
head = new Tile(227, 100);
Score.setText("6");
for (int i = 0; i < MAX_SIZE; i++) {
body[i].x = 0;
body[i].y = 0;
}
run = new Thread();
run.start();
System.out.println("Start again");
} else {
run.stop();
}
}
}
if (current_direction == "D") {
if (head.y > 472) {
isrun = false;
int result = JOptionPane.showConfirmDialog(null, "Game over! Try again?", "Information", JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_NO_OPTION) {
direction = "R";//默认向右走
current_direction = "R";//当前方向
first_launch = false;
isrun = true;
body_length = 5;
head = new Tile(227, 100);
Score.setText("0");
for (int i = 0; i < MAX_SIZE; i++) {
body[i].x = 0;
body[i].y = 0;
}
run = new Thread();
run.start();
System.out.println("Start again");
} else {
run.stop();
}
}
}
}
public void HitSelf() {//判断是否撞到自己身上
for (int i = 0; i < body_length; i++) {
if (body[i].x == head.x && body[i].y == head.y) {
isrun = false;
int result = JOptionPane.showConfirmDialog(null, "Game over! Try again?", "Information", JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_NO_OPTION) {
direction = "R";//默认向右走
current_direction = "R";//当前方向
first_launch = false;
isrun = true;
body_length = 3;
head = new Tile(227, 100);
Score.setText("0");
for (int j = 0; j < MAX_SIZE; j++) {
body[j].x = 0;
body[j].y = 0;
}
run = new Thread();
run.start();
System.out.println("Start again");
} else {
run.stop();
}
break;
}
}
}
public boolean EatFood() {
if (head.x == randomx && head.y == randomy) {
return true;
} else {
return false;
}
}
public void Thread() {
long millis = 300;//每隔300毫秒刷新一次
run = new Thread() {
public void run() {
while (true) {
try {
Thread.sleep(millis);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
if (!pause) {
temp.x = head.x;
temp.y = head.y;
//头部移动
if (direction == "L") {
head.x -= 22;
}
if (direction == "R") {
head.x += 22;
}
if (direction == "U") {
head.y -= 22;
}
if (direction == "D") {
head.y += 22;
}
current_direction = direction;//刷新当前前进方向
//身体移动
for (int i = 0; i < body_length; i++) {
temp2.x = body[i].x;
temp2.y = body[i].y;
body[i].x = temp.x;
body[i].y = temp.y;
temp.x = temp2.x;
temp.y = temp2.y;
}
if (EatFood()) {
body_length++;
body[body_length - 1].x = temp2.x;
body[body_length - 1].y = temp2.y;
Score.setText("" + (body_length - 3));
}
repaint();
HitWall();
HitSelf();
}
}
}
};
run.start();
}
public static void main(String[] args) {
Snake t = new Snake();
t.Thread();
JFrame game = new JFrame();
game.getContentPane().setBackground(Color.lightGray);
game.setTitle("Snake");
game.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
game.setSize(602, 517);//总窗体大小
game.setResizable(false);
game.setLocationRelativeTo(null);
game.add(t);
game.setVisible(true);
}
}
运行界面:
ps:对墙的结束判定进行了修改,能够实现碰墙自动转向(默认顺时针,并考虑特殊情况)。
public void HitWall() {//判断是否撞墙
if (current_direction == "L") {
if (head.x < 8) {
if (head.y < 13) {
direction = "D";//特殊情况向下走
} else {
direction = "U";//向上走
}
}
}
if (current_direction == "R") {
if (head.x > 450) {
if (head.y > 450) {
direction = "U";//特殊情况向上走
} else {
direction = "D";//向下走
}
}
}
if (current_direction == "U") {
if (head.y < 13) {
if (head.x > 450) {
direction = "L";//特殊情况向左走
} else {
direction = "R";//向右走
}
}
}
if (current_direction == "D") {
if (head.y > 450) {
if (head.x < 8) {
direction = "R";//特殊情况向右走
} else {
direction = "L";//向左走
}
}
}
}