项目 2:贪吃蛇游戏开发
1、 项目概述
1.1 项目目标和主要内容
(1)本实验使用java语言开发,开发环境是idea,实现一个简易的贪吃蛇游戏。实现贪吃蛇游戏基本功能,屏幕上随机出现一个“食物”,称为豆子,上下左右控制“蛇”的移动,吃到“豆子”以后“蛇”的身体加长一点,得分增加,“蛇”碰到边界或,蛇头与蛇身相撞,蛇死亡,游戏结束。为游戏设计初始欢迎界面,游戏界面,游戏结束界面。
(2)进行交互界面的设计,有开始键、暂停键和停止退出的选项。对蛇吃到豆子进行分值计算,设置游戏速度,游戏音乐等拓展元素。
1.2 项目的主要功能
2、 项目设计
程序流程图:
2.1 项目总体框架项目框架图:
2.2 系统详细设计
【1】 模块划分描述
整体分3个类结构,包括框架类、面板类和蛇的设计类。
【2】 类关系描述
在JFrame窗口容器里添加设置组件大小、组件的可见性以及组件的位置。
在面板类中,设置游戏区域大小、键盘操作功能、食物坐标以及颜色填充等。
在蛇类设计中,画蛇及其位置和大小、画食物及其位置设置、蛇运动设计等。
2.3 其他描述
定义类节点Node
定义蛇类Snack,Graphics,绘图类,画蛇,蛇的初始方向是向右,Right=3。
随机设置食物的位置:
rx = (int)(Math.random() * (GameWidth - 10) + GameLocX);
ry = (int)(Math.random() * (GameHeight - 10) + GameLocY);
蛇的运动以及判断其是否咬到自己或撞到墙。
所有代码:
//框架类
import javax.swing.JFrame;public class greedySnackMain extends JFrame {
snackWin snackwin;
static final int Width = 800 , Height = 600 , LocX = 200 , LocY = 80;
public greedySnackMain() {
super("GreedySncak_SL");
snackwin = new snackWin();
//在JFrame窗口容器里添加其他组件
add(snackwin);
//设置组件的大小
this.setSize(Width, Height);
//设置组件的可见性
this.setVisible(true);
//设置组件的位置
this.setLocation(LocX, LocY);
//snackwin.requestFocus();
}
public static void main(String[] args) {
new greedySnackMain();
}
}
//面板类
import java.awt.Color;import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;import javax.swing.JButton;
import javax.swing.JPanel;public class snackWin extends JPanel implements ActionListener, KeyListener {
//用数字代表方向
static final int Up = 0 , Down = 1 , Left = 2 , Right = 3;
//GameWidth/GameHeight :游戏区域的宽、高
//GameLocX / GameLocY:游戏区域的左上角位置的坐标
static final int GameLocX = 50, GameLocY = 50 , GameWidth = 700 , GameHeight = 500 , Size = 10;//Size:每次移动的位置大小以及增加的长度
//rx,ry:食物的坐标
static int rx , ry , score = 0 , speed = 5;
boolean startFlag = false;
JButton startButton , stopButton , quitButton;
Snack snack;
public snackWin() {
snack = new Snack();
//随机设置食物的位置
rx = (int)(Math.random() * (GameWidth - 10) + GameLocX);
ry = (int)(Math.random() * (GameHeight - 10) + GameLocY);
startButton = new JButton("开始");
stopButton = new JButton("暂停");
quitButton = new JButton("退出");
setLayout(new FlowLayout(FlowLayout.LEFT));
// 添加三个按键:开始、暂停、退出
this.add(startButton);
this.add(stopButton);
this.add(quitButton);
// 添加三个按键的监听事件
startButton.addActionListener(this);
stopButton.addActionListener(this);
quitButton.addActionListener(this);
this.addKeyListener(this);
new Thread(new snackThread()).start();
}
public void paint(Graphics g)
{
super.paint(g); //没有会将button刷掉
//设置画笔颜色
g.setColor(Color.blue);
//用蓝色 填充一个矩形区域
g.fillRect(GameLocX, GameLocY, GameWidth, GameHeight);
g.setColor(Color.black);
//drawRect用于画矩形;X、Y为左上角的位置,width和height为矩形的宽和高
//用黑色化一个矩形
g.drawRect(GameLocX, GameLocY, GameWidth, GameHeight);
//绘制字符串
g.drawString("Score: " + score + " Speed: " + speed + " 速度最大为100" , 250, 25);
//用绿色填充一个长宽为10的矩形 表示 食物
g.setColor(Color.green);
g.fillRect(rx, ry, 10, 10);
//画蛇
snack.draw(g);
}
public void actionPerformed(ActionEvent e) {
//当开始按钮被点击时,开始按钮将不能再被点击
if(e.getSource() == startButton) {
startFlag = true;
startButton.setEnabled(false);
stopButton.setEnabled(true);
}
if(e.getSource() == stopButton) {
startFlag = false;
startButton.setEnabled(true);
stopButton.setEnabled(false);
}
//退出程序
if(e.getSource() == quitButton) {
System.exit(0);
}
this.requestFocus();
}
/**
* 当键盘被按下时调用
*/
public void keyPressed(KeyEvent e) {
//当还没按下开始按钮时,按键不可用
if(!startFlag)
return ;
switch(e.getKeyCode()) {
//如果蛇的长度为1,则表示刚开始,什么方向都可以;
//若蛇的长度不为1,则表示它在运动,此时不能操作它向反方向运动
case KeyEvent.VK_UP:
if(snack.length() != 1 && snack.getDir() == Down) break;
snack.changeDir(Up);
break;
case KeyEvent.VK_DOWN:
if(snack.length() != 1 && snack.getDir() == Up) break;
snack.changeDir(Down);
break;
case KeyEvent.VK_LEFT:
if(snack.length() != 1 && snack.getDir() == Right) break;
snack.changeDir(Left);
break;
case KeyEvent.VK_RIGHT:
if(snack.length() != 1 && snack.getDir() == Left) break;
snack.changeDir(Right);
break;
}
}
public void keyReleased(KeyEvent e) {}
public void keyTyped(KeyEvent e) {}
class snackThread implements Runnable
{
public void run() {
while(true) {
try {
//通过线程休眠时间控制蛇的速度
Thread.sleep(100 - speed >= 0 ? 100 - speed : 0);
repaint(); // 调用paint(),重绘组件(刷新界面)
if(startFlag) {
snack.move();
}
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//蛇类,对蛇的设计
import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.List;import javax.swing.JOptionPane;
/**
• 存放每个点的X、Y坐标
*/
class Node {
private int x , y;
public Node() {}
public Node(int a , int b){
x = a;
y = b;
}
public Node(Node tmp) {
x = tmp.getX();
y = tmp.getY();
}
int getX() {
return x;
}
int getY() {
return y;
}
void setX(int a) {
x = a;
}
void setY(int b) {
y = b;
}
}public class Snack {
static final int DIR [][] = {{0 , -1} , {0 , 1} , {-1 , 0} , {1 , 0}};
private List<Node> lt = new ArrayList<Node>();
private int curDir; //当前运动方向
/**
* 初始化
* 蛇的初始方向是向右,Right=3
*/
public Snack() {
curDir = 3;
lt.add(new Node(350 , 250));
}
/**
* List的长度表示蛇的长度
* @return
*/
int length() {
return lt.size();
}
int getDir() {
return curDir;
}
/**
* Graphics,绘图类
* 画蛇
* 找到每个下标指向的X、Y值,即组成蛇的每一点所在位置
*/
void draw(Graphics g)
{
g.setColor(Color.red);
for(int i = 0; i < lt.size(); i++) {
g.fillRect(lt.get(i).getX(), lt.get(i).getY(), 10, 10);
}
}
/**
* 判断蛇是否咬到自己
* 如果蛇的头部(0)与其他位置重合,则表示蛇咬到自己了
* @return
*/
boolean Dead() {
for(int i = 1; i < lt.size(); i++) {
if(lt.get(0).getX() == lt.get(i).getX() && lt.get(0).getY() == lt.get(i).getY())
return true;
}
return false;
}
/**
* 头部移动的位置
* @return tmp:头部的位置坐标
*/
Node headMove()
{
//当向上或向下时,curDir为0/1,此时X轴不变(即 snackWin.Size * DIR[curDir][0]的值为0),Y轴变化
//当向左或向右时,curDir为2/3,此时Y轴不变(即 snackWin.Size * DIR[curDir][1]的值为0),X轴变化
int tx = lt.get(0).getX() + snackWin.Size * DIR[curDir][0];
int ty = lt.get(0).getY() + snackWin.Size * DIR[curDir][1];
//用一个矩形代表蛇的一个节点,矩形的长宽为10,故设置Size也为10
//当头部的位置大于游戏区域的宽度证明它到达了最右边,此时提示“snake hit the wall”
if(tx > snackWin.GameLocX + snackWin.GameWidth - snackWin.Size) {
JOptionPane.showMessageDialog(null, "Snake hit the wall", "Message",
JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
//当头部的位置小于左边的X坐标,证明它向左走并到达最左边,此时提示“snake hit the wall”
if(tx < snackWin.GameLocX) {
JOptionPane.showMessageDialog(null, "Snake hit the wall", "Message",
JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
//与上同
if(ty > snackWin.GameLocY + snackWin.GameHeight - snackWin.Size) {
JOptionPane.showMessageDialog(null, "Snake hit the wall", "Message",
JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
if(ty < snackWin.GameLocY) {
JOptionPane.showMessageDialog(null, "Snake hit the wall", "Message",
JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
Node tmp = new Node(tx, ty);
return tmp;
}
void move()
{
//头部的位置
Node head = headMove();
//新添加的节点的位置
Node newNode = new Node();
boolean eat = false;
//abs()返回参数的绝对值
//通过判断头部的X/Y坐标位置和食物的X/Y坐标位置,由于头部大小是10,故距离小于10就代表吃到了
if(Math.abs(head.getX() - snackWin.rx) < 10 && Math.abs(head.getY() - snackWin.ry) < 10) {
eat = true;
newNode = new Node(lt.get(lt.size() - 1));
snackWin.rx = (int)(Math.random() * (snackWin.GameWidth - 10) + snackWin.GameLocX);
snackWin.ry = (int)(Math.random() * (snackWin.GameHeight - 10) + snackWin.GameLocY);
}
//把前一个点的位置给后一点(即移动)
for(int i = lt.size() - 1; i > 0; i--)
lt.set(i, lt.get(i - 1));
lt.set(0, head);
if(Dead()) {
//弹出对话框,告知游戏结束
JOptionPane.showMessageDialog(null, "Snake eating itself", "Message",
JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
//如果吃到了食物,则eat为true。此时添加节点、分数加一,速度加一
if(eat) {
lt.add(newNode);
snackWin.score++;
snackWin.speed++;
}
}
/**
* 改变蛇的方向
* @param dir
*/
void changeDir(int dir) {
curDir = dir;
}
}
运行结果: