1,项目概述
1.1项目目标和主要内容
目标:整体程序无bug,实现贪吃蛇游戏基本功能;
1)实现贪吃蛇游戏基本功能,屏幕上随机出现一个“食物”,称为豆子。玩家能利用上下左右键控制“蛇”的移动,“蛇”吃到“豆子”后“蛇”身体加长一节,得分增加,“蛇”碰到边界或蛇头与蛇身相撞,“蛇”死亡,游戏结束。
2)进行交互界面的设计,要有开始键、暂停键和停止退出的选项,能够控制游戏进程。对蛇吃到豆子进行分值计算,可以设置游戏速度,游戏音乐等拓展元素。
使用Java编程语言,在IntelliJ IDEA Community Edition上进行开发。
主要内容:屏幕上随机出现一个“食物”,称为豆子,上下左右控制“蛇”的移动,吃到“豆子”以后“蛇”的身体加长一点,得分增加,“蛇”碰到边界或,蛇头与蛇身相撞,蛇死亡,游戏结束。为游戏设计初始欢迎界面,游戏界面,游戏结束界面。
2.项目设计
3.程序运行结果及分析:
分析;
可以实现贪吃蛇游戏的基本功能;
具有计分(每吃一个食物加1分)功能,开始游戏计分为零。开始游戏界面,空格控制游戏的暂停/开始游戏功能,WASD控制方向。游戏结束界面。
完整代码
import java.awt.*;
import java.awt.event.*;
import java.util.Objects;
import java.util.Random;
import javax.swing.*;
class Grid {
int x;
int y;
public Grid(int x0, int y0) {
x = x0;
y = y0;
}
}
public class Snakes extends JComponent {
private final int MAX_SIZE = 400;//蛇身体最长为400节
private final Grid temp = new Grid(0, 0);
private final Grid temp2 = new Grid(0, 0);
private Grid head = new Grid(227, 170);//头部的位置初始化为(227,170)
private final Grid[] body = new Grid[MAX_SIZE];
private boolean first_launch = false;
private int body_length = 5;//身体长度初始化为5
private boolean isRun = true;
private int RandomX, RandomY;
private Thread run;
private String direction = "R";//默认向右走
private String current_direction = "R";//当前方向
private final JLabel Score = new JLabel("0");
private final JLabel Time = new JLabel("");
private int hour = 0;
private int min = 0;
private int sec = 0;
private boolean pause = false;
public Snakes() {
//布局
JLabel label = new JLabel("POINT:");
add(label);
label.setBounds(5, 15, 80, 20);
label.setFont(new Font("微软雅黑", Font.PLAIN, 15));
add(Score);
Score.setBounds(90, 15, 80, 20);
Score.setFont(new Font("微软雅黑", Font.PLAIN, 15));
JLabel label2 = new JLabel("TIME:");
add(label2);
label2.setBounds(5, 45, 80, 20);
label2.setFont(new Font("微软雅黑", Font.PLAIN, 15));
add(Time);
Time.setBounds(90, 45, 80, 20);
Time.setFont(new Font("微软雅黑", Font.PLAIN, 15));
for (int i = 0; i < MAX_SIZE; i++) {
body[i] = new Grid(0, 0);
}
addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
super.keyPressed(e);
if (e.getKeyCode() == KeyEvent.VK_D) {
if (isRun && !current_direction.equals("L")) {
direction = "R";
}
}
if (e.getKeyCode() == KeyEvent.VK_A) {
if (isRun && !current_direction.equals("R")) {
direction = "L";
}
}
if (e.getKeyCode() == KeyEvent.VK_W) {
if (isRun && !current_direction.equals("D")) {
direction = "U";
}
}
if (e.getKeyCode() == KeyEvent.VK_S) {
if (isRun && !current_direction.equals("U")) {
direction = "D";
}
}
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
direction = "R";//默认向右走
current_direction = "R";//当前方向
first_launch = false;
isRun = true;
body_length = 5;
head = new Grid(227, 100);
Score.setText("0");
hour = 0;
min = 0;
sec = 0;
for (int i = 0; i < MAX_SIZE; i++) {
body[i].x = 0;
body[i].y = 0;
}
System.out.println("Start again");
}
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
if (!pause)//暂停
{
pause = true;
isRun = false;
} else//开始
{
pause = false;
isRun = true;
}
}
}
});
new Timer();
setFocusable(true);
}
public void paintComponent(Graphics g1) {
super.paintComponent(g1);
Graphics2D g = (Graphics2D) g1;
//画头
g.setColor(Color.BLACK);
g.fillRoundRect(head.x, head.y, 20, 20, 5, 5);
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, 5, 5);
}
//初始化食物位置
ProduceRandom();
} else {
//每次刷新身体
for (int i = 0; i < body_length; i++) {
g.fillRoundRect(body[i].x, body[i].y, 20, 20, 5, 5);
}
if (EatFood())//被吃了重新产生食物
{
ProduceRandom();
}
}
g.fillOval(RandomX, RandomY, 19, 19);
first_launch = true;
//墙
g.setStroke(new BasicStroke(4, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
g.setBackground(Color.WHITE);
g.drawRect(2, 77, 491, 469);
//网格线
for (int i = 1; i < 22; i++) {
g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
g.setColor(Color.WHITE);
g.drawLine(5 + i * 22, 79, 5 + i * 22, 542);//墙宽为4,预留1个像素,以免蛇蹭墙。
if (i <= 20) {
g.drawLine(4, 80 + i * 22, 491, 80 + 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 + 82;
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 + 82;
flag = true;
break;
} else {
if (i == body_length - 1) {
flag = false;
}
}
}
}
}
public void HitWall() {//判断是否撞墙
if (Objects.equals(current_direction, "L")) {
if (head.x < 7) {
new Snake().start();
isRun = false;
int result = JOptionPane.showConfirmDialog(null, "Game over! Try again?", "提醒", 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 Grid(227, 170);
Score.setText("6");
hour = 0;
min = 0;
sec = 0;
for (int i = 0; i < MAX_SIZE; i++) {
body[i].x = 0;
body[i].y = 0;
}
System.out.println("Start again");
}
}
}
if (Objects.equals(current_direction, "R")) {
if (head.x > 489) {
new Snake().start();
isRun = false;
int result = JOptionPane.showConfirmDialog(null, "Game over! Try again?", "提醒", 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 Grid(227, 170);
Score.setText("0");
hour = 0;
min = 0;
sec = 0;
for (int i = 0; i < MAX_SIZE; i++) {
body[i].x = 0;
body[i].y = 0;
}
System.out.println("Start again");
}
}
}
if (Objects.equals(current_direction, "U")) {
if (head.y < 94) {
new Snake().start();
isRun = false;
int result = JOptionPane.showConfirmDialog(null, "Game over! Try again?", "提醒", 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 Grid(227, 100);
Score.setText("0");
hour = 0;
min = 0;
sec = 0;
for (int i = 0; i < MAX_SIZE; i++) {
body[i].x = 0;
body[i].y = 0;
}
System.out.println("Start again");
}
}
}
if (Objects.equals(current_direction, "D")) {
if (head.y > 542) {
new Snake().start();
isRun = false;
int result = JOptionPane.showConfirmDialog(null, "Game over! Try again?", "提醒", 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 Grid(227, 170);
Score.setText("0");
hour = 0;
min = 0;
sec = 0;
for (int i = 0; i < MAX_SIZE; i++) {
body[i].x = 0;
body[i].y = 0;
}
System.out.println("Start again");
}
}
}
}
public void HitSelf() {//判断是否撞到自己身上
for (int i = 0; i < body_length; i++) {
if (body[i].x == head.x && body[i].y == head.y) {
new Snake().start();
isRun = false;
int result = JOptionPane.showConfirmDialog(null, "Game over! Try again?", "提醒", 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 Grid(227, 170);
Score.setText("0");
hour = 0;
min = 0;
sec = 0;
for (int j = 0; j < MAX_SIZE; j++) {
body[j].x = 0;
body[j].y = 0;
}
System.out.println("Start again");
}
break;
}
}
}
public boolean EatFood() {
return head.x == RandomX && head.y == RandomY;
}
public int s=0;
public void Thread() {
long millis = 300;//每隔300毫秒刷新一次
run = new Thread(() -> {
while (true) {
try {
Thread.sleep(millis);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
if (!pause) {
temp.x = head.x;
temp.y = head.y;
//头部移动
if (Objects.equals(direction, "L")) {
head.x -= 22;
}
if (Objects.equals(direction, "R")) {
head.x += 22;
}
if (Objects.equals(direction, "U")) {
head.y -= 22;
}
if (Objects.equals(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++;
s++;
body[body_length - 1].x = temp2.x;
body[body_length - 1].y = temp2.y;
Score.setText("" + (s));
new Snake().start();
}
repaint();
HitWall();
HitSelf();
}
}
});
run.start();
}
//计时器类
class Timer extends Thread {
public Timer() {
this.start();
}
@Override
public void run() {
while (true) {
if (isRun) {
sec += 1;
if (sec >= 60) {
sec = 0;
min += 1;
}
if (min >= 60) {
min = 0;
hour += 1;
}
showTime();
}
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private void showTime() {
String strTime;
if (hour < 10)
strTime = "0" + hour + ":";
else
strTime = "" + hour + ":";
if (min < 10)
strTime = strTime + "0" + min + ":";
else
strTime = strTime + "" + min + ":";
if (sec < 10)
strTime = strTime + "0" + sec;
else
strTime = strTime + "" + sec;
//在窗体上设置显示时间
Time.setText(strTime);
}
}
public static void main(String[] args) {
Snakes t = new Snakes();
t.Thread();
JFrame game = new JFrame();
game.setTitle("贪吃蛇.GluttonousSnake");
game.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
game.setSize(510, 590);
game.setResizable(false);
game.setLocationRelativeTo(null);
game.add(t);
game.setVisible(true);
}
}
class Snake extends Thread {
public Snake() {
}
}
4.总结
4.1 项目的难点和关键点
显示界面需要JFrame和Jpanel来实现。使用一个全局变量记录当前蛇的长度,方便绘图时画出正确的蛇。在做碰撞检测时,只需要将蛇头节点的坐标和蛇身的坐标、食物的坐标、墙的坐标进行比较即可。当蛇在移动时,需要将储存蛇的数组的每一个元素赋给后一个元素,最后计算首元素,此时最后一个元素的值被丢弃。若吃到食物,则将最后一个元素继续向后赋值,同时设置蛇的长度加一。
4.2 项目的评价:
贪吃蛇的主要功能可以很好的实现,在其基础上加入有趣的图片使得游戏更具有可玩性。
4.3 心得体会
通过该项目,练习了frame框架和GUI编程的用法。
5.参考文献
用Java开发贪食蛇小游戏@阿雪狼