Java 贪吃虫小游戏

我参考了【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();
				}
			}
		}
	}
	 


}

这里用线程刷新画面,用双缓存避免了闪烁,用锁来控制线程的暂停可开始

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值