在我们学习java的时候,为了提高我们的兴趣,我们经常会使用所学到的知识去做一些小游戏,这篇blog就介绍了一个经典而且好理解的小游戏-贪吃蛇。
一、使用知识Jframe
GUI
双向链表
线程
二、使用工具IntelliJ IDEA
jdk 1.8
三、开发过程
3.1素材准备
首先在开发之前应该准备一些素材,已备用,我主要找了一个图片以及一段优雅的音乐。
3.2 开发过程
3.2.1 创建项目首先进入idea首页 open一个你想放项目的文件夹进入之后右键文件名 new 一个新的Directory——Snake把准备好的素材复制到文件中继续创建目录 src/Sanke选中src Mark Directory as — Souces 把src添加为根目录
3.2.2 页面设计创建java Class 文件 Snake - new - java class SnakeName 接下来的时候会对这个SnakeName.java里面的代码不停完善
首先设置窗口格式
package Sanke;
import javax.swing.*;
/*** @author Swyee**/
public class SnakeGame extends JFrame {
SnakeGame(){
this.setBounds(100, 50, 700, 500);//设置窗口大小 this.setLayout(null);//更改layout 以便添加组件 this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置关闭窗口的状态 this.setResizable(false);//窗口不可以改变大小 this.setVisible(true);//设置焦点状态为true }
public static void main(String[] args) {
new SnakeGame();
}
}继续创建新的文件 SnakeGrid
package Sanke;
import java.awt.*;
/*** @author Swyee**/
public class SnakeGrid extends Panel {
SnakeGrid(){
this.setBounds(0, 0, 700, 400);
this.setBackground(Color.black);设置背景颜色
}
}将页面引用到SnakeGame.java中
package Sanke;
import javax.swing.*;
/*** @author Swyee**/
public class SnakeGame extends JFrame {
SnakeGrid snakeGrid= new SnakeGrid();
SnakeGame(){
this.setBounds(100, 50, 700, 500);//设置窗口大小 this.setLayout(null);//更改layout 以便添加组件 this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置关闭窗口的状态 this.setResizable(false);//窗口不可以改变大小 this.add(snakeGrid);
this.setVisible(true);//设置焦点状态为true }
public static void main(String[] args) {
new SnakeGame();
}
}
运行样式设置背景图片 背景音乐
在SnakeGrid.java中增加Music方法 设置画笔 绘图
package Sanke;
import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.*;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
/*** @author Swyee**/
public class SnakeGrid extends JPanel {
ImageIcon image = new ImageIcon("Snake/sky.jpg");//图片文件地址 File f= new File("Snake/music.wav");//音乐文件地址 SnakeGrid(){
this.setBounds(0, 0, 700, 400);
this.setBackground(Color.black);
}
/*** 设置画笔* @param g*/
@Override
public void paint(Graphics g) {
super.paint(g);
image.paintIcon(this, g, 0, 0); //设置背景图片 }
//读取音乐文件 void Music(){
try {
URI uri = f.toURI();
URL url = uri.toURL();
AudioClip aau= Applet.newAudioClip(url);
aau.loop();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block e.printStackTrace();
}
}
}
在SnakeName中调用
package Sanke;
import javax.swing.*;
/*** @author Swyee**/
public class SnakeGame extends JFrame {
SnakeGrid snakeGrid= new SnakeGrid();
SnakeGame(){
this.setBounds(100, 50, 700, 500);//设置窗口大小 this.setLayout(null);//更改layout 以便添加组件 this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置关闭窗口的状态 this.setResizable(false);//窗口不可以改变大小 this.add(snakeGrid);
//设置焦点 snakeGrid.setFocusable(true);
snakeGrid.requestFocus();
snakeGrid.Music();//调用打开音乐的方法 this.setVisible(true);//设置焦点状态为true }
public static void main(String[] args) {
new SnakeGame();
}
}
呈现
3.23 画蛇
蛇的身体将会有双向链表组成,双向链表能记录一个节点的上一个节点和下一个节点。蛇的移动其实就是节点的变化,从而达到一种移动的视觉。新建java Snake 创建节点
package Sanke;
import java.awt.Graphics;
public class Snake {
public static final int span=20;//间距public static final String up="u";
public static final String down="d";
public static final String left="l";
public static final String right="r";
class Node{
int row;
int col;
String dir;//方向Node next;
Node pre;
Node(int row,int col,String dir){
this.row = row;
this.col = col;
this.dir = dir;
}
public void draw(Graphics g) {
g.fillOval(col*span, row*span, span,span);
}
}
}画蛇
在snake里面增加draw()方法
/*把蛇画出来*/
public void draw(Graphics g) {
g.setColor(Color.yellow);
for(Node n=head;n!=null;n=n.next){
n.draw(g);
g.setColor(Color.green);
}
}
在SnakeGrid.java中创建蛇
Snake snake = new Snake();//创建蛇
并在paint中调用snake.draw(g);
/*** 设置画笔* @param g*/
@Override
public void paint(Graphics g) {
super.paint(g);
image.paintIcon(this, g, 0, 0); //设置背景图片 snake.draw(g);
}控制蛇的移动
在snake中增加键盘调用的方法:
/*调用键盘的上下左右键head.dir记录现在操作的是什么按钮,从而更改蛇的状态向上移送时,下键失效,其他四个方向也是如此判断*/
public void keyboard(KeyEvent e) {
switch(e.getKeyCode()){
case KeyEvent.VK_UP:
if(head.dir.equals(down)){
break;
}
head.dir=up;
break;
case KeyEvent.VK_DOWN:
if(head.dir.equals(up)){
break;
}
head.dir=down;
break;
case KeyEvent.VK_LEFT:
if(head.dir.equals(right)){
break;
}
head.dir=left;
break;
case KeyEvent.VK_RIGHT:
if(head.dir.equals(left)){
break;
}
head.dir=right;
break;
default:
break;
}
}
增加头部的方法
/*增加头部不管移动哪个方向都是在相应位置增加一个节点*/
public void addHead(){
Node node = null;
switch (head.dir){
case "l":
node = new Node(head.row,head.col-1,head.dir);
break;
case "r":
node = new Node(head.row,head.col+1,head.dir);
break;
case "u":
node = new Node(head.row-1,head.col,head.dir);
break;
case "d":
node = new Node(head.row+1,head.col,head.dir);
break;
default:
break;
}
node.next=head;
head.pre=node;
head=node;
}
删除尾部的方法
/*删除尾部 删除最后一个节点*/
public void deleteTail(){
tail.pre.next=null;
tail=tail.pre;
}
增加move的方法
/*增加move方法 一增一减,实现蛇的移动*/
public void move() {
addHead();
deleteTail();
}
在SnakeGrid中创建一个线程类,用来执行蛇的移动方法
class SnakeThread extends Thread{
@Override
public void run() {
while (true){
try {
Thread.sleep(300);//沉睡300ms 用来控制蛇的移动速度 } catch (InterruptedException e) {
e.printStackTrace();
}
repaint();//每次沉睡完之后都执行一下repaint()方法,重新绘画 }
}
print方法中调用remove 在SnakeGrid()创建键盘监听事件:
package Sanke;
import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
/*** @author Swyee**/
public class SnakeGrid extends JPanel {
Snake snake = new Snake();//创建蛇 ImageIcon image = new ImageIcon("Snake/sky.jpg");//图片文件地址 File f= new File("Snake/music.wav");//音乐文件地址 SnakeThread snakeThread = new SnakeThread();
SnakeGrid(){
this.setBounds(0, 0, 700, 400);
this.setBackground(Color.black);
snakeThread.start();
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
snake.keyboard(e);
}
});
}
/*** 设置画笔* @param g*/
@Override
public void paint(Graphics g) {
super.paint(g);
image.paintIcon(this, g, 0, 0); //设置背景图片 snake.move();//蛇移动 snake.draw(g);
}
//读取音乐文件 void Music(){
try {
URI uri = f.toURI();
URL url = uri.toURL();
AudioClip aau= Applet.newAudioClip(url);
aau.loop();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block e.printStackTrace();
}
}
class SnakeThread extends Thread{
@Override
public void run() {
while (true){
try {
Thread.sleep(300);//沉睡300ms 用来控制蛇的移动速度 } catch (InterruptedException e) {
e.printStackTrace();
}
repaint();//每次沉睡完之后都执行一下repaint()方法,重新绘画 }
}
}
}
执行main方法可以看到可以通过键盘进行控制移动了
3.24创建蛇的食物
增加食物的实例 以及画食物的方法 反映食物坐标的方法 新建Food.java
package Sanke;
import java.awt.*;
public class Food {
int row;
int col;
Food(){
row = 10;//创建食物的大小col =10;
}
public void repearShow(){
row = (int)(Math.random()*18);//生成随机数 乘以食物的大小可以得到坐标col = (int)(Math.random()*32);
}
public void draw(Graphics g) {//把食物画出来g.setColor(Color.red);
g.fillRect(col*20, row*20, 20, 20);//表示坐标
}
public Rectangle getCoordinates(){
return new Rectangle(col*20,row*20,20,20);//获得食物的坐标
}
}
修改Snake.java 增加判断蛇头位置的方法,修改午无参构造方法,改为有参构造,把food添加进来 修改move方法
package Sanke;
import java.awt.*;
import java.awt.event.KeyEvent;
/*** @author Swyee*/
public class Snake {
public static final int span=20;//间距public static final String up="u";
public static final String down="d";
public static final String left="l";
public static final String right="r";
Node body;//蛇的身体Node head;//蛇的头部Node tail;//蛇的头部Food food;
Snake(Food food){
body = new Node(5,20,left);
head = body;
tail = body;
this.food=food;
}
class Node{
int row;
int col;
String dir;//方向Node next;
Node pre;
Node(int row,int col,String dir){
this.row = row;
this.col = col;
this.dir = dir;
}
public void draw(Graphics g) {
g.fillOval(col*span, row*span, span,span);//坐标
}
}
/*把蛇画出来*/
public void draw(Graphics g) {
g.setColor(Color.yellow);
for(Node n=head;n!=null;n=n.next){
n.draw(g);
g.setColor(Color.green);
}
}
/*调用键盘的上下左右键head.dir记录现在操作的是什么按钮,从而更改蛇的状态向上移送时,下键失效,其他四个方向也是如此判断*/
public void keyboard(KeyEvent e) {
switch(e.getKeyCode()){
case KeyEvent.VK_UP:
if(head.dir.equals(down)){
break;
}
head.dir=up;
break;
case KeyEvent.VK_DOWN:
if(head.dir.equals(up)){
break;
}
head.dir=down;
break;
case KeyEvent.VK_LEFT:
if(head.dir.equals(right)){
break;
}
head.dir=left;
break;
case KeyEvent.VK_RIGHT:
if(head.dir.equals(left)){
break;
}
head.dir=right;
break;
default:
break;
}
}
/*增加头部*/
public void addHead(){
Node node = null;
switch (head.dir){
case "l":
node = new Node(head.row,head.col-1,head.dir);
break;
case "r":
node = new Node(head.row,head.col+1,head.dir);
break;
case "u":
node = new Node(head.row-1,head.col,head.dir);
break;
case "d":
node = new Node(head.row+1,head.col,head.dir);
break;
default:
break;
}
node.next=head;
head.pre=node;
head=node;
}
/*删除尾部*/
public void deleteTail(){
tail.pre.next=null;
tail=tail.pre;
}
/*增加move方法*/
public void move() {
addHead();
if(this.getSnakeRectangle().intersects(food.getCoordinates())){//当蛇头与食物重合的时候 蛇吃食物 食物刷新,不再删除尾巴,达到一种蛇增长的要求
food.repearShow();
}else{
deleteTail();
}
}
public Rectangle getSnakeRectangle(){//获取蛇头的坐标
return new Rectangle(head.col*span,head.row*span,span,span);
}
}
在修改snakegrid.java 贪吃蛇的功能就基本实现了
Food food = new Food();
Snake snake = new Snake(food);//创建蛇 ImageIcon image = new ImageIcon("Snake/sky.jpg");//图片文件地址 File f= new File("Snake/music.wav");//音乐文件地址 SnakeThread snakeThread = new SnakeThread();
@Override
public void paint(Graphics g) {
super.paint(g);
image.paintIcon(this, g, 0, 0); //设置背景图片 snake.move();//蛇移动 snake.draw(g);
food.draw(g);
}
3.2.5增加蛇的存活状态
在Snake中增加蛇的存活状态,每一次移动都判断下是否存活,修改SnakeGrid的线程,执行时进行判断是否存活
public void DeadOrLive(){//超出边框范围 蛇头撞到身体 游戏结束if(head.row<0 || head.row>rows-1 || head.col<0 ||head.col>cols){
islive=false;
}
for(Node n=head.next;n!=null;n=n.next){
if(n.col==head.col && n.row==head.row){
islive=false;
}
}
}
public void move() {
addHead();
if(this.getSnakeRectangle().intersects(food.getCoordinates())){//当蛇头与食物重合的时候 蛇吃食物 食物刷新,不再删除尾巴,达到一种蛇增长的要求
food.repearShow();
}else{
deleteTail();
}
DeadOrLive();//每移动一步都要判断一下是否存活}
class SnakeThread extends Thread{
boolean flag = true;
@Override
public void run() {
while (Snake.islive && flag) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block e.printStackTrace();
}
if (Snake.islive ) {
repaint();
}
}
if (!flag == false) {
JOptionPane.showMessageDialog(SnakeGrid.this, "游戏结束");
}
}
3.2.6 增加按钮最后的时候,给这个小游戏增加几个按钮,用来实现暂停开始
新建Button.java
package Sanke;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Button extends JPanel{
public static boolean isMove=true;//表示运行状态SnakeGrid snakeGrid;
Button(SnakeGrid snakeGrid){
this.snakeGrid=snakeGrid;
this.setBounds(0, 400, 700, 100);
JButton jb1 = new JButton("暂停游戏");
JButton jb2 = new JButton("继续游戏");
JButton jb3 = new JButton("重新游戏");
this.add(jb1);
this.add(jb2);
this.add(jb3);
jb1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
isMove=false;
}
});
jb2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
isMove=true;
snakeGrid.setFocusable(true);
snakeGrid.requestFocus();
}
});
jb3.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {//重新创建蛇等 重新开始游戏snakeGrid.snakeThread.stopThread();
Food food = new Food();
snakeGrid.food=food;
snakeGrid.snake=new Snake(food);
Snake.islive=true;
isMove=true;
SnakeGrid.SnakeThread st = snakeGrid.new SnakeThread();
snakeGrid.snakeThread=st;
st.start();
snakeGrid.setFocusable(true);
snakeGrid.requestFocus();
}
});
}
}
再修改SnakeGrid中的thread
package Sanke;
import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
/*** @author Swyee**/
public class SnakeGrid extends JPanel {
Food food = new Food();
Snake snake = new Snake(food);//创建蛇 ImageIcon image = new ImageIcon("Snake/sky.jpg");//图片文件地址 File f= new File("Snake/music.wav");//音乐文件地址 SnakeThread snakeThread = new SnakeThread();
SnakeGrid(){
this.setBounds(0, 0, 700, 400);
this.setBackground(Color.black);
snakeThread.start();
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
snake.keyboard(e);
}
});
}
/*** 设置画笔* @param g*/
@Override
public void paint(Graphics g) {
super.paint(g);
image.paintIcon(this, g, 0, 0); //设置背景图片 snake.move();//蛇移动 snake.draw(g);
food.draw(g);
}
//读取音乐文件 void Music(){
try {
URI uri = f.toURI();
URL url = uri.toURL();
AudioClip aau= Applet.newAudioClip(url);
aau.loop();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block e.printStackTrace();
}
}
class SnakeThread extends Thread{
boolean flag = true;
@Override
public void run() {
while(Snake.islive &&flag){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block e.printStackTrace();
}
if(Snake.islive&& Button.isMove){
repaint();
}
}
if(!flag==false){
JOptionPane.showMessageDialog(SnakeGrid.this, "游戏结束");
}
}
public void stopThread(){
flag=false;
}
}
}
在主页面中把按钮添加上去
package Sanke;
import javax.swing.*;
/*** @author Swyee**/
public class SnakeGame extends JFrame {
SnakeGrid snakeGrid= new SnakeGrid();
Button button = new Button(snakeGrid);
SnakeGame(){
this.setBounds(100, 50, 700, 500);//设置窗口大小 this.setLayout(null);//更改layout 以便添加组件 this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置关闭窗口的状态 this.setResizable(false);//窗口不可以改变大小 this.add(snakeGrid);
this.add(button);
//设置焦点 snakeGrid.setFocusable(true);
snakeGrid.requestFocus();
snakeGrid.Music();//调用打开音乐的方法 this.setVisible(true);//设置焦点状态为true }
public static void main(String[] args) {
new SnakeGame();
}
}
到这里这个小游戏就全部做完了,当然也可以在其基础上增加其他功能
也可以把这个小游戏打成jar包的形式进行运行,将打好的jar包和资源文件放在同一个目录下,即可正常运行访问
四、打jar包
源码
最后附上源码链接: