Java贪吃蛇小游戏,我家小AD从小水蛇成长为水中巨蟒!

Source

老程序员花二天时间做的Java贪吃蛇

引言:

每一个人童年时都有一个乃至数个伟大的梦想,我小时候的梦想:

1.孙悟空,因为我想成为一个降妖除魔、72变,最重要的是我想去哪就去哪,一个跟斗就起飞!
2.葫芦娃,因为分开时个个独挡一面、身怀绝技;合体时,完美无敌,想大就大、想小就小!
3.军事家,我从小比较喜欢看抗日电视剧,感觉我们国家从苦难中走出来很不容易,我那时希望自己成为军事家,能够保卫好我们的国家!
4.科学家,老师跟我们说科学家是国家栋梁!我也希望能够成为杰出的科学家,提升我们国家的科技。
5.工程师,成为一名优秀的工程师,能建设好我们的国家!

带着这些梦想,我通过十几年的努力学习,我从农村来到了城市,从学校走出来到了社会,成为了一名 IT工程师
这样也不错,好歹也算是完成了童年的梦想,虽然算不上很优秀,但是也很努力,IT工程师也是工程师!

你们呢?小伙伴们,可以留言说说你们曾经的梦想,让大家呱唧呱唧!

我现在想做的:

1.在自己岗位上做好自己的事情,就是为国家做贡献!
2.带小AD上分,让小AD变成200斤,那种很Carry、漫天飞羽可以坐死敌人的那种!


这不小AD叫我带着上分,因为体重才90斤不到,怎么打的过?于是我决定给AD增肥,让AD从小水蛇变成巨蟒!

小AD:_明哥带我上分!快点,头输麻了
明世隐:你玩什么英雄?
小AD:我新练的英雄,很厉害,你猜猜看!
明世隐:元芳?贼厉害,我赌你的枪里没有子弹!
小AD:不,攻速很快。
明世隐:狄仁杰?现在是逮捕时间!
小AD:人家大炮。
明世隐:黄忠?大炮一架,彪悍的人生不需要解释!
小AD:no,很萌。
明世隐:兔耳朵大小姐,来一发吗,满足你。
小AD:是小鲁班。
明世隐:就这。有人需要技术支持吗; 智商二百五。我懂了,难怪你上不了分,让哥给你推荐一个新英雄。
小AD:什么英雄?
明世隐:狂蟒之灾—哥斯拉。
小AD:没听过,什么东西?好像很熟悉,名字听起来有点猛(du
明世隐:走,哥带你练级先!
小AD:好嘞


效果图

先上最终的效果图,这里界面做的感觉不是很好看,但我觉得问题不大,功能到位就好!

在这里插入图片描述

实现思路

两块画布:

画布1: 用来绘制静态东西,比如游戏区边框、网格、得分区域框、时间框、按钮等,无需刷新的部分。

画布2: 用来绘制游戏动态的部分,比如 蛇、蛇的移动、食物,蛇吃食物生长、积分显示、时间更新 等。

代码实现

创建窗口

首先创建一个游戏窗体类GameFrame,继承至JFrame,用来显示在屏幕上(window的对象),每个游戏都有一个窗口,设置好窗口标题、尺寸、布局等就可以。

/*
 * 游戏窗体类
 */
public class GameFrame extends JFrame {
    
      
	
	public GameFrame() {
    
      
		setTitle("贪吃蛇");//设置标题
		setSize(788, 476);//设定尺寸
		getContentPane().setBackground(new Color(169,169,169));//添加背景
		setLayout(new BorderLayout());
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//点击关闭按钮是关闭程序
        setLocationRelativeTo(null);   //设置居中
    	setResizable(false); //不允许修改界面大小
	}
}

小AD:明哥你在干嘛?不是带我练英雄吗?
明世隐:是啊,我正在热身,你等等!
小AD:哦。

画布1

创建面板容器BackPanel继承至JPanel

/*
 * 背景画布类
 */
public class BackPanel extends JPanel{
    
      
	BackPanel panel=this;
	private JFrame mainFrame=null;
	//构造里面初始化相关参数
	public BackPanel(JFrame frame){
    
      
		this.setLayout(null);
		this.setOpaque(false);
		this.mainFrame = frame;
		mainFrame.setVisible(true);
	}
}

再创建一个Main类,来启动这个窗口。

public class Main {
    
      
	//主类
	public static void main(String[] args) {
    
      
		GameFrame frame = new GameFrame();
		BackPanel panel = new BackPanel(frame);
		frame.add(panel);
		frame.setVisible(true);//设定显示
	}
}

右键执行这个Main类,窗口建出来了
在这里插入图片描述


小AD:哥你在做游戏?
明世隐:不,我在带你打野升级!
小AD:你真的在做游戏?
明世隐:哦不,我在带你打野升级!
小AD:哥,你真的可以这样吗?
明世隐:哥要把你培养成200斤的ADC.
小AD:我去你的吧,到时候嫁不出去就找你。
明世隐:好呀,我很多粉丝,你到时候挑一个带回家。我给你们发红包!你们给喜糖!
小AD:明哥真(sha)好(bi)!

创建菜单及菜单选项

创建菜单

private void  initMenu(){
    
      
	// 创建菜单及菜单选项
	jmb = new JMenuBar();
	JMenu jm1 = new JMenu("游戏");
	jm1.setFont(new Font("仿宋", Font.BOLD, 15));// 设置菜单显示的字体
	JMenu jm2 = new JMenu("帮助");
	jm2.setFont(new Font("仿宋", Font.BOLD, 15));// 设置菜单显示的字体
	
	JMenuItem jmi1 = new JMenuItem("开始新游戏");
	JMenuItem jmi2 = new JMenuItem("退出");
	jmi1.setFont(new Font("仿宋", Font.BOLD, 15));
	jmi2.setFont(new Font("仿宋", Font.BOLD, 15));
	
	JMenuItem jmi3 = new JMenuItem("操作说明");
	jmi3.setFont(new Font("仿宋", Font.BOLD, 15));
	JMenuItem jmi4 = new JMenuItem("失败判定");
	jmi4.setFont(new Font("仿宋", Font.BOLD, 15));
	
	jm1.add(jmi1);
	jm1.add(jmi2);
	
	jm2.add(jmi3);
	jm2.add(jmi4);
	
	jmb.add(jm1);
	jmb.add(jm2);
	mainFrame.setJMenuBar(jmb);// 菜单Bar放到JFrame上
	jmi1.addActionListener(this);
	jmi1.setActionCommand("Restart");
	jmi2.addActionListener(this);
	jmi2.setActionCommand("Exit");
	
	jmi3.addActionListener(this);
	jmi3.setActionCommand("help");
	jmi4.addActionListener(this);
	jmi4.setActionCommand("lost");
}

实现ActionListener并重写方法actionPerformed
在这里插入图片描述
actionPerformed方法的实现

@Override
public void actionPerformed(ActionEvent e) {
    
      
	String command = e.getActionCommand();
	System.out.println(command);
	UIManager.put("OptionPane.buttonFont", new FontUIResource(new Font("宋体", Font.ITALIC, 18)));
	UIManager.put("OptionPane.messageFont", new FontUIResource(new Font("宋体", Font.ITALIC, 18)));
	if ("Exit".equals(command)) {
    
      
		Object[] options = {
    
       "确定", "取消" };
		int response = JOptionPane.showOptionDialog(this, "您确认要退出吗", "",
				JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,
				options, options[0]);
		if (response == 0) {
    
      
			System.exit(0);
		} 
	}else if("Restart".equals(command)){
    
      
		if(!"end".equals(gamePanel.gameFlag)){
    
      
			JOptionPane.showMessageDialog(null, "正在游戏中无法重新开始!",
					"提示!", JOptionPane.INFORMATION_MESSAGE); 
		}else{
    
      
			if(gamePanel!=null) {
    
      
				gamePanel.restart();
				btnStart.setText("暂停");
			}
		}
	}else if("help".equals(command)){
    
      
		JOptionPane.showMessageDialog(null, "[下][S]、[左][A]、[右][D]、[上][W]控制方向",
				"提示!", JOptionPane.INFORMATION_MESSAGE);
	}else if("lost".equals(command)){
    
      
		JOptionPane.showMessageDialog(null, "蛇咬到自己则失败!",
				"提示!", JOptionPane.INFORMATION_MESSAGE);
	}else if("start".equals(command)){
    
      
		mainFrame.requestFocus();
		if("start".equals(gamePanel.gameFlag)){
    
      //进入暂停
			gamePanel.pause();
			btnStart.setText("继续");
		}else if("pause".equals(gamePanel.gameFlag)){
    
      //进入继续
			gamePanel.goOn();
			btnStart.setText("暂停");
		}else if("end".equals(gamePanel.gameFlag)){
    
      //进入重开
			gamePanel.restart();
			btnStart.setText("暂停");
		}
	}

}

在这里插入图片描述

绘制游戏区域

绘制游戏区域边框

//绘制边框
private void drawBorder(Graphics g) {
    
      
	BasicStroke bs_2=new BasicStroke(12L,BasicStroke.CAP_ROUND,BasicStroke.JOIN_MITER);
	Graphics2D g_2d=(Graphics2D)g;
	g_2d.setColor(new Color(128,128,128));
	g_2d.setStroke(bs_2);

	RoundRectangle2D.Double rect = new RoundRectangle2D.Double(6, 6, 613, 413, 2, 2);
	g_2d.draw(rect);
}	

绘制右边辅助区域

//绘制右边区域边框
private void drawBorderRight(Graphics g) {
    
      
	BasicStroke bs_2=new BasicStroke(12L,BasicStroke.CAP_ROUND,BasicStroke.JOIN_MITER);
	Graphics2D g_2d=(Graphics2D)g;
	g_2d.setColor(new Color(128,128,128));
	g_2d.setStroke(bs_2);
	
	RoundRectangle2D.Double rect = new RoundRectangle2D.Double(636, 6, 140, 413, 2, 2);
	g_2d.draw(rect);
}

在BackPanel 中重写paint 方法,并调用刚才两个区域绘制方法。
在这里插入图片描述
在这里插入图片描述
绘制积分区域

//绘制积分区域
private void drawCount(Graphics g) {
    
      
	BasicStroke bs_2=new BasicStroke(3L,BasicStroke.CAP_ROUND,BasicStroke.JOIN_MITER);
	Graphics2D g_2d=(Graphics2D)g;
	g_2d.setColor(new Color(70,70,70));
	g_2d.setStroke(bs_2);
	g_2d.drawRect(650, 17, 110, 80);
	
	//得分
	g.setFont(new Font("宋体", Font.BOLD, 20));
	g.drawString("得分:",680, 40);
}

绘制时间区域

//绘制时间区域
private void drawTime(Graphics g) {
    
      
	BasicStroke bs_2=new BasicStroke(3L,BasicStroke.CAP_ROUND,BasicStroke.JOIN_MITER);
	Graphics2D g_2d=(Graphics2D)g;
	g_2d.setColor(new Color(70,70,70));
	g_2d.setStroke(bs_2);
	g_2d.drawRect(650, 120, 110, 120);
	
	//时间
	g.setFont(new Font("宋体", Font.BOLD, 20));
	g.drawString("时间:",680, 140);
}

绘制网格(30列 20行)

//绘制网格
private void drawGrid(Graphics g) {
    
      
	Graphics2D g_2d=(Graphics2D)g;
	g_2d.setColor(new Color(255,255,255,150));
	int x1=12;
	int y1=20;
	int x2=612;
	int y2=20;
	for (int i = 0; i <= ROWS; i++) {
    
      
		y1 = 12 + 20*i;
		y2 = 12 + 20*i;
		g_2d.drawLine(x1, y1, x2, y2);		
	}
	
	y1=12;
	y2=412;
	for (int i = 0; i <= COLS; i++) {
    
      
		x1 = 12 + 20*i;
		x2 = 12 + 20*i;
		g_2d.drawLine(x1, y1, x2, y2);		
	}
}

在paint方法中调用
在这里插入图片描述

创建游戏右边区域的一个暂停按钮

//初始化
private void init() {
    
      
	// 开始/停止按钮
	btnStart = new JButton();
	btnStart.setFont(new Font("黑体", Font.PLAIN, 18));
	btnStart.setFocusPainted(false);
	btnStart.setText("暂停");
	btnStart.setBounds(660, 300, 80, 43);
	btnStart.setBorder(BorderFactory.createRaisedBevelBorder());
	this.add(btnStart);
	btnStart.addActionListener(this);
	btnStart.setActionCommand("start");
}

在这里插入图片描述
此时基本布局已经完成了。

小AD:明哥这是什么游戏!
明世隐:AD起飞小游戏。
小AD:这。。。,看起来我是不得不学咯。
明世隐:那不能这么说,我是这种强迫人家学习的人吗。
小AD:明哥真是(bu)好(yao)人(lian)!
明世隐:谢谢夸奖,我们继续。

画布2

GamePanel 继承至 JPanel 并重写 paint 方法
修改Main类,将画布2也放到窗口中

public class Main {
    
      
	//主类
	public static void main(String[] args) {
    
      
		GameFrame frame = new GameFrame();
		BackPanel panel = new BackPanel(frame);
		frame.add(panel);
		GamePanel gamePanel = new GamePanel(frame);
		panel.setGamePanel(gamePanel);
		frame.add(gamePanel);
		frame.setVisible(true);//设定显示
	}
}

画布2绘制一个小方块

因为游戏区域被分成了一个个的小格子,每个小格子就是一个单位,整个网格就是一个30,、20的二维数组。
于是第一行第一个元素,用数组下标来表示就是 0,0 、第一行第二个元素就是0、1
这样就好办了,我们创建一个Block类,设置坐标和宽高即可绘制方块(宽高为固定20,与网格对应)。

package main;

import java.awt.Graphics;

public class Block {
    
      

	private int x=0;
	private int y=0;
	private GamePanel panel=null;
	private String dir = "right"; //默认向右
	private int step = 1;
	
	public Block(int x,int y,GamePanel panel){
    
      
		this.x=x;
		this.y=y;
		this.panel=panel;
	}
	//绘制
	void draw(Graphics g){
    
      
		g.fillRect(12+x*20, 12+y*20, 20, 20);
	}
	
	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;
	}
}

实例化这个类,并在paint方法中调用draw绘制方法

//创建小方块
private void createClock() {
    
      
	int x=0;
	int y=0;
	Block block = new Block(x, y,this);
	curBlock=block;
}
@Override
public void paint(Graphics g) {
    
      
	super.paint(g);
	
	if(curBlock!=null){
    
      
		curBlock.draw(g);	
	}
}

此时效果图如下 :
在这里插入图片描述
在Block类中加入设定方向的方法
当然要限定不能反向哈,因为贪吃蛇游戏不运行这样操作的。

public void setDir(String dir){
    
      
	//保证不能反向操作
	if("right".equals(dir) && "left".equals(this.dir)){
    
      
		return ;
	}
	if("left".equals(dir) && "right".equals(this.dir)){
    
      
		return ;
	}
	if("up".equals(dir) && "down".equals(this.dir)){
    
      
		return ;
	}
	if("down".equals(dir) && "up".equals(this.dir)){
    
      
		return ;
	}
	this.dir=dir;
}

GamePanel添加键盘事件

//添加键盘监听
private void createKeyListener() {
    
      
	KeyAdapter l = new KeyAdapter() {
    
      
		//按下
		@Override
		public void keyPressed(KeyEvent e) {
    
      
			if(!"start".equals(gameFlag)) return ;
			int key = e.getKeyCode();
			switch (key) {
    
      
				//向上
				case KeyEvent.VK_UP:
				case KeyEvent.VK_W:
					if(curBlock!=null) curBlock.setDir("up");
					break;
					
				//向右	
				case KeyEvent.VK_RIGHT:
				case KeyEvent.VK_D:
					if(curBlock!=null) curBlock.setDir("right");
					break;
					
				//向下
				case KeyEvent.VK_DOWN:
				case KeyEvent.VK_S:
					if(curBlock!=null) curBlock.setDir("down");
					break;
					
				//向左
				case KeyEvent.VK_LEFT:
				case KeyEvent.VK_A:
					if(curBlock!=null) curBlock.setDir("left");
					break;
			}
		
		}
		//松开
		@Override
		public void keyReleased(KeyEvent e) {
    
      
		}
		
	};
	//给主frame添加键盘监听
	mainFrame.addKeyListener(l);
}

在Block类中创建移动方法
变量说明:
xDir 布尔值:true表示横向移动,false表示上下移动;
step是步数:当xDir为true,我们设定为 1 和 -1 横向移动1表示向右,-1表示向左移动;当xDir为false,向下移动为1,向上移动为-1。
方法说明:
1.xDir为true则让 x+step;
2.反之xDir为false则让 y+step;
3.执行重绘,这样才能移动。

//移动
void move(){
    
      
	boolean xDir = false;//是否横向移动
	if("right".equals(dir) || "left".equals(dir) ){
    
      //左右
		xDir = true;
		if("right".equals(dir)){
    
      //右
			step=1;
		}else {
    
      
			step=-1;
		}
	}else if("up".equals(dir) || "down".equals(dir) ){
    
      //上下
		xDir = false;
		if("down".equals(dir)){
    
      //下
			step=1;
		}else {
    
      
			step=-1;
		}
	}
	if(xDir){
    
      //X方向的移动,step 正数向右 负数向左
		x += step;
	}else{
    
      //Y方向的移动,step 正数向下 负数向上
		y += step;
	}
	panel.repaint();
}

调用move方法(线程)

//游戏线程,用来自动移动
private class GameThread implements Runnable {
    
      
	@Override
	public void run() {
    
      
		while (true) {
    
      
			if("start".equals(gameFlag)){
    
      
				curBlock.move();
			}
			try {
    
      
				Thread.sleep(300);
			} catch (InterruptedException e) {
    
      
				e.printStackTrace();
			}
		}
	}
}

在GamePanel中启动线程

mainThread = new Thread(new GameThread());
mainThread.start();

启动后默认是向右移动的,我按上下左右这些键就可以控制方向。
在这里插入图片描述

小AD:明哥你那么厉害,怎么学的呀,对于我这种很瘦的ADC,有什么可以推荐的吗?
明世隐:有啊,我从CSDN官方那边搞来了一套 《Java 工程师学习成长知识图谱》,尺寸 870mm x 560mm,展开后有一张办公桌大小,也可以折叠成一本书的尺寸,原件129元现价 29 元
小AD:我这种也可以吗?
明世隐:可以啊,此图在手,Java我有,帮助小白从零进阶 Java 工程师,有兴趣的小伙伴可以了解一下

在这里插入图片描述


绘制蛇

仅仅一个小方块肯定是不行的,贪吃蛇的特点:

1.蛇头—方向键控制移动
2.蛇身—跟随蛇头移动
3.吃到实物增加蛇身体长度

Snake类

Snake就是一个个的小方块组成的,所以用一个List集合来存储多个Block即可。
针对上述三点,我想到:

1.蛇头完全用Block实例,因为Block的实例可以满足蛇头的运动,我们给蛇头加入一个颜色即可。


2.蛇身体跟随蛇头移动,从蛇身的最后(集合的最后元素)开始,将每一个的方块位置设置成前一个方块的位置。以达到移动的效果。蛇头则自行移动。


3.吃到实物的时候,创建一个block实例,添加到集合的最后,达到增加蛇身体的效果。

绘制蛇

public class Snake {
    
      

	private List blocks = new ArrayList();
	private GamePanel panel = null;
	private Block head=null;
	
	public Snake(GamePanel panel){
    
      
		this.panel=panel;
		//初始化蛇
		initSnake();
	}
	//初始化蛇
	private void initSnake() {
    
      
		//蛇头
		head = createClock(1,0,Color.red);
		//蛇身
		createClock(0,0,null);
	}
	//创建小方块
	private Block createClock(int x,int y,Color color) {
    
      
		Block block = new Block(x, y,color,panel);
		blocks.add(block);
		return block;
	}
	//绘制
	void draw(Graphics g){
    
      
		Block block = null;
		for (int i = 0; i < blocks.size(); i++) {
    
      
			block = (Block)blocks.get(i);
			block.draw(g);
		}
	}
}

上述代码很好理解,默认在1,0 这个各种创建蛇头,0,0 这个格子创建一个蛇身体。
在GamePanel创建Snake实例,并在paint方法中调用(原来的方块代码要注销)。

	private void createSnake() {
    
      
		snake = new Snake(this);
	}
public void paint(Graphics g) {
    
      
	super.paint(g);
	
	//curBlock.draw(g);
	if(snake!=null){
    
      
		snake.draw(g);
	}
}

此时红色的蛇头和黑色的蛇身就绘制了。
在这里插入图片描述
蛇移动

1.身体移动,把前一个方块的位置赋值给后一个(逆序)
2.头单独移动

//移动
void move(){
    
      
	//蛇身体移动
	//后一个的位置全部往前取一个(逆序来)
	Block body = null;
	Block lastBody = null;
	for (int i = blocks.size()-1; i >=1; i--) {
    
      
		body = (Block)blocks.get(i);
		lastBody = (Block)blocks.get(i-1);
		body.setX(lastBody.getX());
		body.setY(lastBody.getY());
	}
	//蛇头单独移动
	if(head!=null) head.move();
	//重绘不需要在block中调用了
	panel.repaint();
}

在线程中修改为蛇的移动方法

//游戏线程,用来自动移动
private class GameThread implements Runnable {
    
      
	@Override
	public void run() {
    
      
		while (true) {
    
      
			if("start".equals(gameFlag)){
    
      
				if(snake!=null) snake.move();
				//curBlock.move();
			}
			try {
    
      
				Thread.sleep(300);
			} catch (InterruptedException e) {
    
      
				e.printStackTrace();
			}
		}
	}
}

在这里插入图片描述


小AD:这就是说的狂蟒之灾?
明世隐:这不是它小时候吗?急什么!
小AD:我信你个鬼,你确定不是lady gaga?
明世隐:就算是,也是boy snake!
小AD:你这个怎么只会往右边走。
明世隐:看我的。

根据键盘设定方向,只需要设定蛇头的方向即可,因为蛇身自动跟随的。

//设定方向
public void setDir(String dir){
    
      
	if(head!=null) head.setDir(dir);
}

在GamePanel中键盘监听稍微修改一下(原来是监听方块,现在是监听蛇)

//添加键盘监听
private void createKeyListener() {
    
      
	KeyAdapter l = new KeyAdapter() {
    
      
		//按下
		@Override
		public void keyPressed(KeyEvent e) {
    
      
			if(!"start".equals(gameFlag)) return ;
			int key = e.getKeyCode();
			switch (key) {
    
      
				//向上
				case KeyEvent.VK_UP:
				case KeyEvent.VK_W:
					//if(curBlock!=null) curBlock.setDir("up");
					if(snake!=null) snake.setDir("up");
					break;
					
				//向右	
				case KeyEvent.VK_RIGHT:
				case KeyEvent.VK_D:
//					if(curBlock!=null) curBlock.setDir("right");
					if(snake!=null) snake.setDir("right");
					break;
					
				//向下
				case KeyEvent.VK_DOWN:
				case KeyEvent.VK_S:
//					if(curBlock!=null) curBlock.setDir("down");
					if(snake!=null) snake.setDir("down");
					break;
					
				//向左
				case KeyEvent.VK_LEFT:
				case KeyEvent.VK_A:
//					if(curBlock!=null) curBlock.setDir("left");
					if(snake!=null) snake.setDir("left");
					break;
			}
		
		}
		//松开
		@Override
		public void keyReleased(KeyEvent e) {
    
      
		}
		
	};
	//给主frame添加键盘监听
	mainFrame.addKeyListener(l);
}

在这里插入图片描述


小AD:怎么有点像王者的左右方向操作。
明世隐:那可不
小AD:可是它就这样游来游去的,我想到了天线宝宝。
明世隐:嗯对,跟你被压到塔下出不了塔一样,萌。
小AD:你。。。
明世隐:我做两个野怪给它升级不就完了。
小AD:嗯,你真有(dou)才(bi

绘制食物

绘制一个椭圆,来代替蛋的效果

public class Food {
    
      
	private int x=0;
	private int y=0;
	private GamePanel panel=null;
	
	public Food(int x,int y,GamePanel panel){
    
      
		this.x=x;
		this.y=y;
		this.panel=panel;
	}
	
	//绘制
	void draw(Graphics g){
    
      
		Color oColor = g.getColor();
		g.setColor(new Color(252,246,219));
		//椭圆
		//g.drawOval(12+x*20+1, 12+y*20+2, 18, 14);
		g.fillOval(12+x*20+1, 12+y*20+4, 18, 12);
		g.setColor(oColor);
	}
}

GamePanel中创建并绘制这个蛋。

	private void createFood() {
    
      
		food = new Food(0, 1, this);
	}
@Override
public void paint(Graphics g) {
    
      
	super.paint(g);
	
	//curBlock.draw(g);
	if(snake!=null){
    
      
		snake.draw(g);
	}
	
	if(food!=null){
    
      
		food.draw(g);
	}
}

在这里插入图片描述

吃食物

先把刚才的创建食物的地方修改一下,改为随机x、y位置(但是每次创建的时候要判断不能在蛇和它身体的位置,如果是,要重新随机—递归)。

//创建实物
private void createFood() {
    
      
	int[][] res = getPos();
	int x = res[0][0];
	int y = res[0][1];
	
	food = new Food(x, y, this);
}
//获取随机坐标
public int[][] getPos(){
    
      
	Random random = new Random();
	int x = random.nextInt(30);
	int y = random.nextInt(20);
	int[][] res = new int[1][2];
	if(checkPos(x, y)){
    
      //递归
		return getPos();
	}else {
    
      
		res[0][0]=x;
		res[0][1]=y;
		return res;
	}
}
//判断实物的坐标,是否与蛇重叠
private boolean checkPos(int x,int y){
    
      
	if(snake!=null){
    
      
		List blocks = snake.getBlocks();
		Block block = null;
		for (int i = 0; i < blocks.size(); i++) {
    
      
			block = (Block)blocks.get(i);
			if(block.getX()==x && block.getY()==y){
    
      //与蛇位置重叠
				return true;
			}
		}
	}
	return false;
}

当蛇头移动的时候,如果蛇头的x、y与食物的x、y相同,则判断为吃到了.

//判断吃
public void eat() {
    
      
	if(head==null) return ;
	Food food = panel.food;
	
	if(head.getX()==food.getX() && head.getY()==food.getY()){
    
      //吃到了
		doEat();
	}
}

吃到以后,要做的几个事情:

1.食物重新随机位置
2.积分加10
3.蛇增加一个格子,放到集合的最后(所以要先记录下蛇蛇尾部最后一格的x、y坐标)。

修改Snake中的move方法,记录最后一格的x、y坐标

//移动
void move(){
    
      
	//蛇身体移动
	//后一个的位置全部往前取一个(逆序来)
	Block body = null;
	Block lastBody = null;
	for (int i = blocks.size()-1; i >=1; i--) {
    
      
		body = (Block)blocks.get(i);
		lastBody = (Block)blocks.get(i-1);
		
		//记录最后一格的移动X\Y坐标
		if(i==blocks.size()-1){
    
      
			lastX = body.getX();
			lastY = body.getY();
		}
		body.setX(lastBody.getX());
		body.setY(lastBody.getY());
	}
	//蛇头单独移动
	if(head!=null) head.move();
	
	//吃
	eat();
	
	//重绘不需要在block中调用了
	panel.repaint();
}

执行吃的动作

//执行吃的动作
	private void doEat() {
    
      
		/*
		 * 1.食物重新随机位置
		 * 2.积分加10
		 * 3.蛇增加一个格子,做完身体放到集合的最后。
		 */
		Food food = panel.food;
		//1.食物重新随机位置
		int[][] res = panel.getPos();
		int x = res[0][0];
		int y = res[0][1];
		food.setX(x);
		food.setY(y);
		//2.积分加10
		panel.curCount+=10;
		//3.蛇增加一个格子,做完身体放到集合的最后。
		Block block = createClock(lastX,lastY,null);
		blocks.add(block);
	}

运行一下
在这里插入图片描述


小AD:那就是脸撞上去就行了呗。
明世隐:嗯,对啊,你不就喜欢骑刺客脸吗?
小AD:过分了哈。
明世隐:你就当做是打野怪,吃一个,升1级。然后就会越来越长。
小AD:果然是越来越长了呢。
明世隐:嗯。。。(嘴角上仰)

边界处理

1.如果蛇头越界,则从另外一边重新入场,这个代码非常简单。
2.在move方法里面调用这个方法即可。

//出界处理
private void outside() {
    
      
	if(head==null) return ;
	//出界的话从另一边再次进入
	if(head.getX()<0){
    
      
		head.setX(29);
	}else if(head.getX()>29){
    
      
		head.setX(0);
	}else if(head.getY()<0){
    
      
		head.setY(19);
	}else if(head.getY()>19){
    
      
		head.setY(0);
	}
}

在这里插入图片描述


小AD:你这不是开外挂吗?出地图外了还可以回来。
明世隐:嗯,对啊,我程序员就是这么牛b?服不服
小AD:我就不服。
明世隐:不服,我还可以让它自己咬自己。
小AD:来啊

蛇咬到自己

1.用蛇头与蛇身进行比较,一旦有x、y都相同的情况,则游戏结束。
2.在move方法里面调用这个方法。

//咬到自己 了
private void eatSelf() {
    
      
	if(head==null) return ;
	Block body = null;
	for (int i = blocks.size()-1; i >=1; i--) {
    
      
		body = (Block)blocks.get(i);
		if(body.getX() == head.getX() && body.getY() == head.getY()){
    
      //头与身体重叠 了
			panel.gameOver();
			break;
		}
	}
}

最后

加入时间、游戏结束、重新开始等就完成了
在这里插入图片描述

看到这里的大佬,动动发财的小手 点赞 + 回复 + 收藏,能【 关注 】一波就更好了。


想要代码的 加微信私聊 我!


小AD耍了起来

小AD:哇,明哥,果然是越来越长、越来越好玩!
明世隐:比你塔下罚站好玩多了吧!
小AD:现在有狂蟒之灾的感觉了。
明世隐:怎么样,跟着明哥既可轻松学代码,又开心好玩!
小AD:可是很多知识要学呀,有点乱乱的,不知道怎么学起。
明世隐:所以我从CSDN搞来下面的图谱呀。

为了帮助更多小白从零进阶 Java 工程师,从CSDN官方那边搞来了一套 《Java 工程师学习成长知识图谱》,尺寸 870mm x 560mm,展开后有一张办公桌大小,也可以折叠成一本书的尺寸,原件129元现价 29 元先到先得,有兴趣的小伙伴可以了解一下

在这里插入图片描述