源码:klskk23/Snake: 贪吃蛇小游戏基于Java swing. (github.com)
实验内容
1)实现贪吃蛇游戏基本功能,屏幕上随机出现一个“食物”,称为豆子。玩家能利用上下左右键控制“蛇”的移动,“蛇”吃到“豆子”后“蛇”身体加长一节,得分增加,“蛇”碰到边界或蛇头与蛇身相撞,“蛇”死亡,游戏结束。
2)进行交互界面的设计,要有开始键、暂停键和停止退出的选项,能够控制游戏进程。对蛇吃到豆子进行分值计算,可以设置游戏速度,游戏音乐等拓展元素。
实现效果
点击暂停即可暂停游戏,点击开始即可继续游戏,点击背景音乐按钮即可播放背景音乐,点击得分弹出得分面板。
实现思路
使用swing和绘图类实现
实现过程
1.首先需要创建一个名为GameFrame,用于放置GamePanel和按键。
为按键设置侦听器,再使用KeyListener设置按键侦听。
public class GameFrame extends JFrame {
GamePanel gamePanel;
Score score;
//public Timer timer0;
boolean stop0=true;
private boolean bgm_control_jud = true;//防止打开多个bgm;
public static Font font1 = new Font("宋体", Font.BOLD, 20);//定义字体1
public GameFrame() throws HeadlessException {
Container container = getContentPane();
//设置窗体属性
this.setTitle("贪吃蛇 BY 废旧螺栓机甲");
this.setSize(640, 690);
this.setLocation(100, 100);
this.setResizable(false);
this.setVisible(true);
this.setFocusable(true);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//设置键盘监听
this.keyEvent();
//开始按钮,重置游戏
JButton game_start = new JButton();
game_start.setText("开始");
game_start.setForeground(Color.red);
game_start.setFont(font1);
game_start.setBounds(0, 0, 150, 30);
game_start.setBackground(new Color(135, 206, 250));
game_start.setFocusable(false);
container.add(game_start);
//BGM操控
JButton bgm_control = new JButton();
bgm_control.setText("背景音乐");
bgm_control.setForeground(Color.yellow);
bgm_control.setFont(font1);
bgm_control.setBounds(300, 0, 150, 30);
bgm_control.setBackground(new Color(100, 149, 237));
bgm_control.setFocusable(false);
container.add(bgm_control);
bgm_control.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Sound.Btn_press();
if (bgm_control_jud) {
bgm_control_jud = false;
Sound.BGM();
}
}
});
//暂停按钮
JButton stop_game = new JButton();
stop_game.setText("暂停/继续");
stop_game.setBounds(150, 0, 150, 30);
stop_game.setForeground(Color.orange);
stop_game.setFont(font1);
stop_game.setBackground(new Color(147, 112, 219));
stop_game.setFocusable(false);
container.add(stop_game);
//计分板按钮
JButton score_get = new JButton();
score_get.setText("得分");//等待补充分数
score_get.setBounds(450, 0, 180, 30);
score_get.setForeground(Color.MAGENTA);
score_get.setFont(font1);
score_get.setBackground(new Color(238, 232, 170));
container.add(score_get);
score_get.setFocusable(false);
//JPanel对象
gamePanel = new GamePanel(null);
container.add(gamePanel);
game_start.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Sound.Btn_press();
if(!stop0){
stop0=true;gamePanel.stop=false;
}
}
});
stop_game.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Sound.Btn_press();
if(stop0){stop0=false;gamePanel.stop=true;}
}
});
score_get.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Sound.Btn_press();
GameFrame.this.end();
}
});
}
//键盘监听
private void keyEvent() {
addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
if(gamePanel.snake.getDirection()!=Direction.DOWN)gamePanel.snake.setDirection(Direction.UP);
break;
case KeyEvent.VK_DOWN:
if(gamePanel.snake.getDirection()!=Direction.UP)gamePanel.snake.setDirection(Direction.DOWN);
break;
case KeyEvent.VK_LEFT:
if(gamePanel.snake.getDirection()!=Direction.RIGHT)gamePanel.snake.setDirection(Direction.LEFT);
break;
case KeyEvent.VK_RIGHT:
if(gamePanel.snake.getDirection()!=Direction.LEFT)gamePanel.snake.setDirection(Direction.RIGHT);
break;
}
}
@Override
public void keyReleased(KeyEvent e) {
}
});
}
//结算
public int end(){
score=new Score(GameFrame.this);
score.setVisible(true);
return score.getScore();
}
}
2.蛇由很多节点和蛇头的方向组成,所以创建三个类Node,Direction,Snake来表示蛇。
定义变量isLive来判断蛇头是否和自身重合,判断输入方向(比如蛇头向上不得朝下移动)
public enum Direction {
UP,DOWN,LEFT,RIGHT
}
public class Node {
private int x;
private int y;
public Node() {
}
public Node(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
public class Snake {
//body
public LinkedList<Node>body;
private Direction direction= Direction.LEFT;
//die or live
public boolean isLive=true;
public Snake() {
initSnake();
}
public void initSnake(){
body=new LinkedList<>();
body.add(new Node(10,10));
body.add(new Node(11,10));
body.add(new Node(12,10));
body.add(new Node(13,10));
}
public void move(){
//蛇头
Node head=body.getFirst();
if(isLive){
switch (direction){
case UP:{
body.addFirst(new Node(head.getX(),head.getY()-1));
break;}
case DOWN:{
body.addFirst(new Node(head.getX(),head.getY()+1));
break;}
case RIGHT:{
body.addFirst(new Node(head.getX()+1,head.getY()));
break;}
case LEFT:{
body.addFirst(new Node(head.getX()-1,head.getY()));
break;}
}
body.removeLast();}
//live or die
head=body.getFirst();
if(head.getX()<0||head.getY()<0||head.getX()>30||head.getY()>30)isLive=false;
for(int i=1;i<body.size();i++){
Node node=body.get(i);
if(head.getX()== node.getX()&&head.getY()==node.getY())isLive=false;
}
}
public LinkedList<Node> getBody() {
return body;
}
public void setBody(LinkedList<Node> body) {
this.body = body;
}
public Direction getDirection() {
return direction;
}
public void setDirection(Direction direction) {
this.direction = direction;
}
//吃食物
public void eat(Food food) {
Node head=body.getFirst();
switch (direction){
case UP:{
body.addFirst(new Node(head.getX(),head.getY()-1));
break;}
case DOWN:{
body.addFirst(new Node(head.getX(),head.getY()+1));
break;}
case RIGHT:{
body.addFirst(new Node(head.getX()+1,head.getY()));
break;}
case LEFT:{
body.addFirst(new Node(head.getX()-1,head.getY()));
break;}
}
}
}
3.创建Food类,用于随机生成蛇的食物
public class Food {
private int x;
private int y;
public Food(int x, int y) {
this.x = x;
this.y = y;
}
public Food(){
Random r=new Random();
//生成横坐标
this.x=r.nextInt(30);
this.y=r.nextInt(30);
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
创建游戏窗体GamePanel,设置计时器,在屏幕上不断重绘,显示分数等。
public class GamePanel extends JPanel{
public static int u_score;
public boolean game_over=false;
public boolean stop=true;
public Timer timer;
public Snake snake;
private boolean repeat=false;//判断食物是否和蛇身体重合,默认不重合
Image img;
Food food;
public Score score;
public GamePanel(LayoutManager layout) {
super(layout);
score = new Score();
food = new Food(18, 18);
snake = new Snake();
this.setBounds(0, 30, 640, 640);
//添加背景图像
img = new ImageIcon("src/data/background.png").getImage();
stop=false;
timer = new Timer();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
if (!stop) {
snake.move();
if (!snake.isLive && !game_over) {
Sound.Game_over();
game_over = true;
}//结束音效
if (!repeat) {
//吃食物判断和食物是否和蛇身体重合判断
Node food0 = new Node(food.getX(), food.getY());
Node head = snake.getBody().getFirst();
if (head.getX() == food0.getX() && head.getY() == food0.getY()) {
snake.eat(food);
food = new Food();
Sound.Eat_food();
score.getScore();
u_score = score.getN();
}
} else {
food = new Food();
}
for (int i = 1; i < snake.body.size(); i++) {
Node node = snake.body.get(i);
if (food.getX() == node.getX() && food.getY() == node.getY()) repeat = true;
else repeat = false;
}
repaint();
}
}
};
timer.scheduleAtFixedRate(timerTask, 0, 200);
}
@Override
public void paint(Graphics g) {
//背景图绘制
Graphics2D g2=(Graphics2D) g;
g2.drawImage(img,0,0,this.getWidth(),this.getHeight(),this);
//网格线
g.setColor(Color.BLACK);
for (int i = 0; i <31; i++) {
g.drawLine(0, 20 * i, 600, 20 * i);
g.drawLine(20 * i, 0, 20 * i, 600);
}
//绘制snake
LinkedList<Node>body=snake.getBody();
g.setColor(Color.orange);
for(Node node:body){
g.fillRoundRect(node.getX()*20, node.getY()*20,20,20,50,50);
}
//绘制food
g.setColor(Color.GREEN);
g.fillRoundRect(food.getX()*20,food.getY()*20,20,20,50,50);
//绘制分数
g2.setColor(Color.pink);
Font font0=new Font("宋体",Font.BOLD,20);
g2.setFont(font0);
g2.drawString(String.valueOf(u_score),580,20);
Font font1=new Font("宋体",Font.BOLD,40);
g2.setFont(font1);
if(game_over){
g2.drawString("游戏结束,请退出",160,300);
g2.setFont(font0);
g2.drawString("最终得分"+String.valueOf(u_score),160,360);
}
}
}
4.创建音频播放器MusicPlayer类用于播放音效,创建Sound类用于各种音效的控制。
public class MusicPlayer implements Runnable{
File soundFile;//音乐文件
Thread thread;
boolean cir;//循环
public MusicPlayer(String filepath,boolean cir){
this.cir=cir;
soundFile=new File(filepath);//文件地址
}
public void run(){
byte[] auBuffer=new byte[1024*4096];//创建缓冲区
do{
AudioInputStream audioInputStream=null;
SourceDataLine auline=null;
try{
//从文件中获取音频输入流
audioInputStream= AudioSystem.getAudioInputStream(soundFile);
AudioFormat format=audioInputStream.getFormat();//获得音频格式
DataLine.Info info=new DataLine.Info(SourceDataLine.class,format);
auline=(SourceDataLine) AudioSystem.getLine(info);
auline.open(format);
auline.start();
int byteCount=0;
while(byteCount!=-1){
byteCount=audioInputStream.read(auBuffer,0,auBuffer.length);
if(byteCount>=0){
auline.write(auBuffer,0,byteCount);
}
}
}
catch (IOException e){e.printStackTrace();}
catch (UnsupportedAudioFileException e){e.printStackTrace();}
catch (LineUnavailableException e){e.printStackTrace();}
finally {
auline.drain();
auline.close();
}
}while (cir);
}
public void play(){
thread=new Thread(this);
thread.start();//开始线程
}
public void stop(){
thread.stop();//强制关闭
}
}
public class Sound {
//各种音效
static final String bgm = "音乐图片资源/bgm.wav";
static final String eat_beans = "音乐图片资源/eat_beans.wav";
static final String game_over = "音乐图片资源/game_over.wav";
static final String press_button="音乐图片资源/btn_press.wav";
private static void play(String filepath, boolean cir) {//描述如何播放音乐,包括位置和是否循环
MusicPlayer player = new MusicPlayer(filepath, cir);//创建播放器
player.play();//start to play music
}
static public void Btn_press(){play(press_button,false);}
static public void Game_over(){play(game_over,false);}
static public void Eat_food(){play(eat_beans,false);}
static public void BGM(){//播放bgm
play( bgm, false);
}
}
5.创建继承于JDialog的类Score用于返回分数信息
public class Score extends JDialog {
public int n=0;
public Score(GameFrame owner) {
super(owner,"分数",false);
Container container=getContentPane();
this.setLayout(null);
this.setFocusable(false);
this.setBounds(300,100,200,200);
JLabel score=new JLabel();
score.setText("你的得分是"+GamePanel.u_score);
score.setFont(GameFrame.font1);
score.setBounds(0,0,200,100);
container.add(score);
}
public Score() {
}
public int getScore(){
n=n+10;
return n;
}
public int getN() {
return n;
}
}
6.最后创建一个Start类,运行程序
public class Start {
public static void main(String[]args){
new GameFrame();
}
}
项目结构预览: