我参考了【Java】Java实现贪吃蛇小游戏(带详细注释)和
java贪吃蛇小游戏(详解)
先设置需要用到的常数
package Snake;
public class ConstantNumber {
public static int WIDTH=1024;
public static int HEIGHT=578;
static final int Up = 0 , Down = 1 , Left = 2 , Right = 3;
}
package Snake;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class MyFrame extends JFrame {
MyPanel jPanel_L=new MyPanel();
public void loadFrame(){
this.setTitle("贪吃蛇");//设置窗体标题
this.setSize(ConstantNumber.WIDTH, ConstantNumber.HEIGHT);//设置窗体大小
this.setBackground(Color.gray);//设置背景
this.setLocationRelativeTo(null);//居中
jPanel_L.setBackground(Color.black);
jPanel_L.setLayout(null);
jPanel_L.setBounds(0,0,800,700);
JPanel jPanel_R=new JPanel();
jPanel_R.setLayout(null);
jPanel_R.setBounds(800,0,800,800);
jPanel_R.setBackground(Color.gray);
jPanel_R.requestFocus(false);
add( jPanel_L);
JButton button1=new JButton("开始");
JButton button2 = new JButton("暂停");
JButton button3=new JButton("继续");
button1.setFocusable(false); //这行必不可少
button1.setBounds(900, 200, 100, 50); //设置按钮的大小位置
button2.setBounds(900, 400, 100, 50);
button2.setFocusable(false);
button3.setBounds(900, 50, 100, 50);
button3.setFocusable(false);
button1.addActionListener(new Button1Listen());
button2.addActionListener(new Button2Listen());
button3.addActionListener(new Button3Listen());
jPanel_R.add(button1);
jPanel_R.add(button2);
jPanel_R.add(button3);
add(jPanel_R);
// add(button1);
// add(button2);
//设置可关闭
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
//设置可见
this.setVisible(true);
}
//内部类监听按钮
class Button1Listen implements ActionListener
{
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
jPanel_L.Start();
}
}
class Button2Listen implements ActionListener
{
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
jPanel_L.pauseThread();
}
}
class Button3Listen implements ActionListener
{
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
jPanel_L.resumeThread();
}
}
public static void main(String[] args)
{
new MyFrame().loadFrame();
}
}
setFocusable(false)很重要,一开始暂停后就失去了对键盘的监听,因为button扰乱了聚焦,所以加上。
package Snake;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
public abstract class SnakeObject {
int x;//横坐标
int y;//纵坐标
public boolean live=true;//死亡/存活
public abstract void draw(Graphics g);
}
蛇和食物建一个共同的父类
package Snake;
import java.awt.Color;
import java.awt.Graphics;
public class Food extends SnakeObject{
Food()
{
x = (int)(Math.random() * 790);//800减去10 游戏屏幕大小减去事物size
y = (int)(Math.random() * 516);//这个范围是我打印出来范围坐标才确定的,我猜测是游戏上面加了边框,所以不能单纯减去size得到
}
public void draw(Graphics g)
{
g.setColor(Color.green);
g.fillRect(x, y, 10, 10);
}
}
package Snake;
import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JOptionPane;
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 Snakedraw extends SnakeObject{
static final int DIR [][] = {{0 , -1} , {0 , 1} , {-1 , 0} , {1 , 0}};
private List<Node> lt = new ArrayList<Node>();
private int curDir; //当前运动方向
private int speed=1;
/**
* 初始化
* 蛇的初始方向是向右,Right=3
*/
public Snakedraw() {
curDir = 3;
lt.add(new Node(350 , 250));
}
/**
* List的长度表示蛇的长度
* @return
*/
int length() {
return lt.size();
}
int getDir() {
return curDir;
}
public void draw(Graphics g)
{
g.setColor(Color.WHITE);
for(int i = 0; i < lt.size(); i++) {
g.fillRect(lt.get(i).getX(), lt.get(i).getY(), 10, 10);
}
}
/**
* 头部移动的位置
* @return tmp:头部的位置坐标
*/
Node headMove()
{
// 当向上或向下时,curDir为0/1,此时X轴不变(即 Snakedraw.Size * DIR[curDir][0]的值为0),Y轴变化
// 当向左或向右时,curDir为2/3,此时Y轴不变(即 Snakedraw.Size * DIR[curDir][1]的值为0),X轴变化
int tx = lt.get(0).getX() + speed * DIR[curDir][0];
int ty = lt.get(0).getY() + speed * DIR[curDir][1];
// System.out.println(ty);
if(tx<0||tx>790||ty<0||ty>516)
{
live=false;
return new Node(0,0);
}
else
return new Node(tx,ty);
}
void Dead1() {
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())
{
live= false;
break;
}
}
}
// void Dead2(Node newNode )
// {
// if( lt.contains(newNode))
// {
// live= false;
// }
// }
void move(Food food)
{
// 头部的位置
Node head = headMove();
// 新添加的节点的位置
Node newNode = new Node();
Dead1();
boolean eat = false;
// abs()返回参数的绝对值
// 通过判断头部的X/Y坐标位置和食物的X/Y坐标位置,由于头部大小是10,故距离小于10就代表吃到了
if(Math.abs(head.getX() - food.x) < 10 && Math.abs(head.getY() - food.y) < 10) {
food.live = false;
eat=true;
newNode = new Node(lt.get(lt.size() - 1));
}
// 把前一个点的位置给后一点(即移动)
for(int i = lt.size() - 1; i > 0; i--)
lt.set(i, lt.get(i - 1));
lt.set(0, head);
if(!live) {
// 弹出对话框,告知游戏结束
JOptionPane.showMessageDialog(null, "Game over", "Message", JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
if(eat) {
lt.add(newNode);
speed++;
}
}
void changeDir(int dir) {
curDir = dir;
}
}
Dead2的方法contains如果是同一对象即地址相同的情况下,才会返回true,而对于对象属性值相同但地址不同的不同对象,始终返回false。所以不可用
package Snake;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JPanel;
public class MyPanel extends JPanel implements KeyListener {
Snakedraw mySnake=new Snakedraw();
Food food=new Food();
private final Object lock = new Object();
private boolean start=false;
private boolean pause = false;
/**
* 调用该方法实现线程的暂停
*/
void pauseThread(){
pause = true;
}
/*
调用该方法实现恢复线程的运行
*/
void resumeThread(){
pause =false;
synchronized (lock){
lock.notify();
}
}
/**
* 这个方法只能在run 方法中实现,不然会阻塞主线程,导致页面无响应
*/
void onPause() {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public MyPanel()
{
super();
setFocusable(true);
addKeyListener(this);
}
public void Start()
{
start=true;
new MyThread().start();
}
public void keyPressed(KeyEvent e) {
if(!start) return;//先开始才有用
// System.out.print("work");
switch(e.getKeyCode()) {
// 如果蛇的长度为1,则表示刚开始,什么方向都可以;
// 若蛇的长度不为1,则表示它在运动,此时不能操作它向反方向运动
case KeyEvent.VK_UP:
if(mySnake.length() != 1 && mySnake.getDir() == ConstantNumber.Down) break;
mySnake.changeDir(ConstantNumber.Up);
break;
case KeyEvent.VK_DOWN:
if(mySnake.length() != 1 && mySnake.getDir() == ConstantNumber.Up) break;
mySnake.changeDir(ConstantNumber.Down);
break;
case KeyEvent.VK_LEFT:
if(mySnake.length() != 1 && mySnake.getDir() == ConstantNumber.Right) break;
mySnake.changeDir(ConstantNumber.Left);
break;
case KeyEvent.VK_RIGHT:
if(mySnake.length() != 1 && mySnake.getDir() == ConstantNumber.Left) break;
mySnake.changeDir(ConstantNumber.Right);
break;
}
}
@Override
public void keyReleased(KeyEvent arg0) {
// TODO Auto-generated method stub
}
@Override
public void keyTyped(KeyEvent arg0) {
// TODO Auto-generated method stub
}
//在repaint()调用paint()方法之前,还会先调用update()方法
//首先创建一张基于原面板的一场图像,先将绘制图像的工作在这张图片上面完成,最后再将这张图片直接贴到面板上面
public void paint(Graphics g)
{
super.paint(g); //没有会将button刷掉
if(mySnake.live){//如果蛇活着,就绘制
mySnake.draw(g);
if(food.live){//如果食物活着,就绘制
food.draw(g);
mySnake.move(food);
}else{//否则,产生新食物
food = new Food();
}
}else{//蛇死亡,弹出游戏结束字样
}
}
private Image offScreenImage;
public void update(Graphics g) {//g是前面屏幕的画笔
if(offScreenImage == null)
offScreenImage = this.createImage(ConstantNumber.WIDTH, ConstantNumber.HEIGHT);
Graphics gOffScreen = offScreenImage.getGraphics();//gOffScreen是背后屏幕的画笔
Color c = gOffScreen.getColor();
gOffScreen.setColor(Color.BLACK);
gOffScreen.fillRect(0, 0, ConstantNumber.WIDTH, ConstantNumber.HEIGHT);//画矩形
gOffScreen.setColor(c);
paint(gOffScreen);//在背后屏幕画
g.drawImage(offScreenImage, 0, 0, null);//将背后屏幕画的贴在前面屏幕上
}
class MyThread extends Thread{
@Override
public void run() {
super.run();
while(true){
while (pause){
onPause();
}
repaint();
try {
sleep(30);//每30毫秒重绘一次
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
这里用线程刷新画面,用双缓存避免了闪烁,用锁来控制线程的暂停可开始