java贪吃蛇小游戏(详解)[通俗易懂]

java贪吃蛇小游戏(详解)[通俗易懂]目录1.实现效果:​​2.游戏玩法3.需求分析4.代码实现1.实现效果:2.游戏玩法该游戏用上下左右控制蛇的方向,寻找吃的东西,每吃一口就能得到一定的积分,而且蛇的身子会越吃越长,身子越长玩的难度就越大,不能碰墙,不能咬到自己的身体,更不能咬自己的尾巴,等到了一定的分数,就能过关,然后继续玩下一关。这次我们以一关的实现为例,关卡控制交给读者自行添加。3.需求分析…

大家好,又见面了,我是你们的朋友全栈君。

首先给出代码下载地址(含素材):https://share.weiyun.com/8hkvy1Ja

目录

1.实现效果:

​​2.游戏玩法

3.需求分析

4.代码实现


1.实现效果:

java贪吃蛇小游戏(详解)[通俗易懂]java贪吃蛇小游戏(详解)[通俗易懂]2.游戏玩法

该游戏用上下左右控制蛇的方向,寻找吃的东西,每吃一口就能得到一定的积分,而且蛇的身子会越吃越长,身子越长玩的难度就越大,不能碰墙,不能咬到自己的身体,更不能咬自己的尾巴,等到了一定的分数,就能过关,然后继续玩下一关。这次我们以一关的实现为例,关卡控制交给读者自行添加。

3.需求分析

  • 方向控制

首先我们需要实现的是通过按键实现控制蛇的运动方向,需要注意的有两点:

1.蛇运动的时候不能向上一个状态的反方向运动,例如,原先向右,下一次改变的方向不能为左。

2.运动的时候如果按了一个方向键,再下一次按键之前将维持原先的方向运动。

2.如果蛇头和身体的图片不一样,那么蛇头要随着运动方向进行旋转。

  • 蛇的绘制

蛇我这里分为了蛇头和蛇身两部分,当然你也可以加蛇尾。这里以蛇头和蛇身两部分为例: 

蛇头游戏开始就已经存在,之后吃到一个食物都会使蛇身长度加一。蛇身的每一部分都会沿着它的前一部分的轨迹运动,而每一部分都会沿着蛇头的轨迹运动。

  • 食物绘制

食物绘制相对比较简单,当一个食物被吃掉以后,便在地图的其他随机的一个地方产生下一个食物。

  • 蛇和食物的生命周期

蛇:当蛇碰到地图边界,碰到自己的身体和尾巴的时候,即判定为死亡。

食物:当蛇头碰到食物,则食物死亡。

4.代码实现

  • 项目目录

java贪吃蛇小游戏(详解)[通俗易懂]

  • Constant类,存储一些常量。
public class Constant {
	public static final int GAME_WIDTH = 1024;//窗体宽度
	public static final int GAME_HEIGHT = 578;//窗体高度
	
	public static final String IMG_PRE="com/zzk/snake/img/";//图片路径前缀
}
  • MyFrame类,用于加载游戏窗体和不断刷新绘制窗体内容:
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import com.zzk.snake.constant.Constant;

public class MyFrame extends Frame{
	/**
	 * 加载窗体
	 */
	public void loadFrame(){
		this.setTitle("贪吃蛇");//设置窗体标题
		this.setSize(Constant.GAME_WIDTH, Constant.GAME_HEIGHT);//设置窗体大小
		this.setBackground(Color.BLACK);//设置背景
		this.setLocationRelativeTo(null);//居中
		//设置可关闭
		this.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		});
		//设置可见
		this.setVisible(true);
		//运行重绘线程
		new MyThread().start();
	}
	/**
	 * 防止图片闪烁,使用双重缓存
	 * 
	 * @param g
	 */
	Image backImg = null;

	@Override
	public void update(Graphics g) {
		if (backImg == null) {
			backImg = createImage(Constant.GAME_WIDTH, Constant.GAME_HEIGHT);
		}
		Graphics backg = backImg.getGraphics();
		Color c = backg.getColor();
		backg.setColor(Color.BLACK);
		backg.fillRect(0, 0, Constant.GAME_WIDTH, Constant.GAME_HEIGHT);
		backg.setColor(c);
		paint(backg);		
		g.drawImage(backImg, 0, 0, null);
	}
	/**
	 * 这里创建一个不断重绘的线程内部类
	 * 
	 * @param args
	 */
	class MyThread extends Thread{
		@Override
		public void run() {
			while(true){
				repaint();
				try {
					sleep(30);//每30毫秒重绘一次
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

这里为了防止图片闪烁所以说添加了一个新方法,具体细节原因请读者自行学习。

  • GameUtil类,用于获取图片和处理图片旋转
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;

import javax.imageio.ImageIO;

public class GameUtil {

	/**
	 * 根据图片的相对路径获取图片
	 * 
	 * @param imagePath
	 * @return 图片
	 */
	public static Image getImage(String imagePath) {
		URL url = GameUtil.class.getClassLoader().getResource(imagePath);
		BufferedImage img = null;
		try {
			img = ImageIO.read(url);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return img;
	}
	/**
	 * 按指定角度旋转图片
	 * @param bufferedimage
	 * @param degree
	 * @return 图片
	 */
	public static Image rotateImage(final BufferedImage bufferedimage, final int degree) {
		int w = bufferedimage.getWidth();// 得到图片宽度。
		int h = bufferedimage.getHeight();// 得到图片高度。
		int type = bufferedimage.getColorModel().getTransparency();// 得到图片透明度。
		BufferedImage img;// 空的图片。
		Graphics2D graphics2d;// 空的画笔。
		(graphics2d = (img = new BufferedImage(w, h, type)).createGraphics())
				.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		graphics2d.rotate(Math.toRadians(degree), w / 2, h / 2);// 旋转,degree是整型,度数,比如垂直90度。
		graphics2d.drawImage(bufferedimage, 0, 0, null);// 从bufferedimagecopy图片至img,0,0是img的坐标。
		graphics2d.dispose();
		return img;// 返回复制好的图片,原图片依然没有变,没有旋转,下次还可以使用。
	}
}
  • ImageUtil类,用于存储图片,方便使用
import java.awt.Image;
import java.util.HashMap;
import java.util.Map;

import com.zzk.snake.constant.Constant;

public class ImageUtil {
	public static Map<String,Image> images = new HashMap<>();
	
	static{
		images.put("snake_body", GameUtil.getImage(Constant.IMG_PRE+"snake_body.png"));
		images.put("food", GameUtil.getImage(Constant.IMG_PRE+"food.png"));
		images.put("snake_head", GameUtil.getImage(Constant.IMG_PRE+"snake_head.png"));
		images.put("background", GameUtil.getImage(Constant.IMG_PRE+"background.jpg"));
		images.put("fail", GameUtil.getImage(Constant.IMG_PRE+"fail.png"));
	}
}
  • Drawable和Moveable接口,蛇有移动和绘制的能力
import java.awt.Graphics;

public interface Drawable {
	void draw(Graphics g);
}
public interface Moveable {
	void move();
}
  • SnakeObject类,蛇和食物的父类,由于食物和蛇都需要进行绘制,都有生命周期,所以抽取出一个父类
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;

public abstract class SnakeObject implements Drawable {

	int x;//横坐标
	int y;//纵坐标
	Image img;//图片
	int width;//图片宽度
	int height;//图片高度
	public boolean live;//死亡/存活
	
	@Override
	public abstract void draw(Graphics g);
	/**
	 * 获取图片对应的矩形
	 * 
	 * @return
	 */
	public Rectangle getRectangle() {
		return new Rectangle(x, y, width, height);
	}
}
  • Food类,食物类,绘制食物
import java.awt.Graphics;

import com.zzk.snake.constant.Constant;
import com.zzk.snake.util.ImageUtil;

public class Food extends SnakeObject{

	public Food(){
		this.live=true;
		this.img=ImageUtil.images.get("food");
		this.width=img.getWidth(null);
		this.height=img.getHeight(null);
		this.x=(int) (Math.random()*(Constant.GAME_WIDTH-width+10));
		this.y=(int) (Math.random()*(Constant.GAME_HEIGHT-40-height)+40);

	}
	/**
	 * 食物被吃的方法
	 * @param mySnake
	 */
	public void eaten(MySnake mySnake){
		if(mySnake.getRectangle().intersects(this.getRectangle())&&live&&mySnake.live){
			this.live=false;//食物死亡
			mySnake.setLength(mySnake.getLength()+1);//长度加一
			mySnake.score+=10*mySnake.getLength();//加分
		}
	}
	/**
	 * 绘制食物
	 */
	@Override
	public void draw(Graphics g) {
		g.drawImage(img, x, y, null);
	}
	
}
  • MySnake ,蛇类,用于绘制蛇,用了一个LinkedList<Point>存储蛇的每一次移动的轨迹点,当蛇吃到东西时,从尾部的轨迹点绘制一块蛇身。每次移动后添加新的轨迹点,同时移除不必要的轨迹点。
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import java.util.List;

import com.zzk.snake.constant.Constant;
import com.zzk.snake.util.GameUtil;
import com.zzk.snake.util.ImageUtil;

public class MySnake extends SnakeObject implements Moveable {
	//蛇头图片(未旋转)
	private static final BufferedImage IMG_SNAKE_HEAD = (BufferedImage) ImageUtil.images.get("snake_head");

	private int speed;//移动速度
	private int length;//长度
	private int num;//
	public static List<Point> bodyPoints = new LinkedList<>();
	public int score = 0;//分数
	private static BufferedImage newImgSnakeHead;//旋转后的蛇头图片
	boolean up, down, left, right = true;//初始态向右
	public MySnake(int x, int y) {
		this.live = true;
		this.x = x;
		this.y = y;
		this.img = ImageUtil.images.get("snake_body");
		this.width = img.getWidth(null);
		this.height = img.getHeight(null);
		this.speed = 5;
		this.length = 1;
		this.num = width / speed;
		newImgSnakeHead = IMG_SNAKE_HEAD;
	}

	public int getLength() {
		return length;
	}
	public void setLength(int length) {
		this.length=length;
	}
	/**
	 * 接收键盘按下事件
	 * @param e
	 */
	public void keyPressed(KeyEvent e) {
		switch (e.getKeyCode()) {
		case KeyEvent.VK_UP:
			if (!down) {// 不能向初始方向的反方向移动
				up = true;
				down = false;
				left = false;
				right = false;
				newImgSnakeHead = (BufferedImage) GameUtil.rotateImage(IMG_SNAKE_HEAD, -90);//旋转图片
			}
			break;
		case KeyEvent.VK_DOWN:
			if (!up) {
				up = false;
				down = true;
				left = false;
				right = false;
				newImgSnakeHead = (BufferedImage) GameUtil.rotateImage(IMG_SNAKE_HEAD, 90);
			}
			break;
		case KeyEvent.VK_LEFT:
			if (!right) {
				up = false;
				down = false;
				left = true;
				right = false;
				newImgSnakeHead = (BufferedImage) GameUtil.rotateImage(IMG_SNAKE_HEAD, -180);
			}
			break;
		case KeyEvent.VK_RIGHT:
			if (!left) {
				up = false;
				down = false;
				left = false;
				right = true;
				newImgSnakeHead = IMG_SNAKE_HEAD;
			}
			break;
		}
	}
	/**
	 * 移动
	 */
	@Override
	public void move() {
		if (up)
			y -= speed;
		else if (down)
			y += speed;
		else if (left)
			x -= speed;
		else if (right)
			x += speed;
	}
	/**
	 * 绘制
	 */
	@Override
	public void draw(Graphics g) {
		outOfBounds();//处理出界问题
		eatBody();//处理是否吃到身体问题
		bodyPoints.add(new Point(x, y));//保存轨迹
		if (bodyPoints.size() == (this.length+1) * num) {//当保存的轨迹点的个数为蛇的长度+1的num倍时
			bodyPoints.remove(0);//移除第一个
		}
		g.drawImage(newImgSnakeHead, x, y, null);//绘制蛇头
		drawBody(g);//绘制蛇身
		move();//移动
	}
	/**
	 * 处理是否吃到到身体问题
	 */
	public void eatBody(){
		for (Point point : bodyPoints) {
			for (Point point2 : bodyPoints) {
				if(point.equals(point2)&&point!=point2){
					this.live=false;//食物死亡
				}
			}
		}
	}
	/**
	 * 绘制蛇身
	 * @param g
	 */
	public void drawBody(Graphics g) {
		int length = bodyPoints.size() - 1-num;//前num个存储的是蛇头的当前轨迹坐标
		for (int i = length; i >= num; i -= num) {//从尾部添加
			Point p = bodyPoints.get(i);
			g.drawImage(img, p.x, p.y, null);
		}
	}

	/**
	 * 处理出界问题
	 */
	private void outOfBounds() {
		boolean xOut = (x <= 0 || x >= (Constant.GAME_WIDTH - width));
		boolean yOut = (y <= 40 || y >= (Constant.GAME_HEIGHT - height));
		if (xOut || yOut) {
			live = false;
		}
	}
}
  • SnakeClient类,加载窗体,控制游戏流程,我这里没有进行关卡控制和开始界面等,读者可以自行修改。
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import com.zzk.snake.core.Food;
import com.zzk.snake.core.MyFrame;
import com.zzk.snake.core.MySnake;
import com.zzk.snake.util.ImageUtil;

public class SnakeClient extends MyFrame{
	
	public MySnake mySnake = new MySnake(100, 100);//蛇
	public Food food = new Food();//食物
	Image background = ImageUtil.images.get("background");//背景图片
	Image fail = ImageUtil.images.get("fail");//游戏结束的文字
	@Override
	public void loadFrame() {
		super.loadFrame();
		//添加键盘监听器,处理键盘按下事件
		addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				mySnake.keyPressed(e);//委托给mysnake
			}
		});
	}
	/**
	 * 绘制界面
	 */
	@Override
	public void paint(Graphics g) {
		g.drawImage(background, 0, 0, null);//绘制背景
		if(mySnake.live){//如果蛇活着,就绘制
			mySnake.draw(g);
			if(food.live){//如果食物活着,就绘制
				food.draw(g);
				food.eaten(mySnake);
			}else{//否则,产生新食物
				food = new Food();
			}
		}else{//蛇死亡,弹出游戏结束字样
			g.drawImage(fail, (background.getWidth(null)-fail.getWidth(null))/2, (background.getHeight(null)-fail.getHeight(null))/2, null);
		}
		drawScore(g);//绘制分数
	}
	/**
	 * 绘制分数
	 * @param g
	 */
	public void drawScore(Graphics g){
		g.setFont(new Font("Courier New", Font.BOLD, 40));
		g.setColor(Color.WHITE);
		g.drawString("SCORE:"+mySnake.score,700,100);
	}
	public static void main(String[] args) {
		new SnakeClient().loadFrame();//加载窗体
	}
}

 

GitHub地址:https://github.com/a13835614623/JavaGame(其他java游戏也在其中)

Gitee地址:https://gitee.com/zzk4513/JavaGame

代码下载地址(含素材):https://share.weiyun.com/8hkvy1Ja

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/147644.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 关系数据库理论之最小函数依赖集「建议收藏」

    关系数据库理论之最小函数依赖集「建议收藏」前言在本文中,会介绍为什么要引入最小函数依赖集,最小函数依赖集是什么,以及如何求最小函数依赖集。为什么需要最小函数依赖集在关系数据模型中,一个关系通常由R(U,F)构成,U为属性的全集,F为函数依赖集。在实际生活中,我们可以根据语义来定义关系中属性的依赖关系,例如学号可以唯一确定一位学生的姓名、性别等等。但是,有时候给出的函数依赖集并不是最简的,这有时会拖累我们对关系的后续处理,例如关系的分…

    2022年6月17日
    25
  • PHP TCPDF导出支持中文的pdf

    PHP TCPDF导出支持中文的pdfPHP使用TCPDF导出支持中文的pdf一、下载https://github.com/tecnickcom/tcpdf下载TCPDF压缩包二、使用方法 require_once(‘./TCPDF/tcpdf.php’); $pdf=new\TCPDF(); $pdf->AddPage(); $html=”<p>helloworld你好世界</p>”; $pdf->WriteHtml(20,$html); //四种模式I输出

    2025年10月3日
    3
  • Latex公式换行且不加编号[通俗易懂]

    Latex公式换行且不加编号[通俗易懂]实现两个效果:1.公式可以换行,等号对齐2.公式后面没有系统的编号\begin{equation} \begin{aligned} E&=\frac{1}{2}\sum_{j=1}^{2}(z_j-f_j(x_k))^2\\ &=\frac{1}{2}(z_1-f_1(x_k))^2+\frac{1}{2}(z_2-f_2(x_k))^2\nonumber \end{aligned} \end{equation}实现的效果:注意点:首先要引入包。\usepack

    2022年6月12日
    129
  • 构建增强现实移动应用程序的六款顶级工具

    构建增强现实移动应用程序的六款顶级工具

    2021年6月6日
    125
  • java中的向上取整和向下取整

    java中的向上取整和向下取整向上取整:比自己大的最小整数。向下取整:比自己小的最大整数。publicclassRoundingUp{publicstaticvoidmain(String[]args){System.out.println(Math.ceil(1.5));//2.0System.out.println(Math.ceil(-1.5));//…

    2022年6月21日
    64
  • lookdiv激活码2021【在线注册码/序列号/破解码】

    lookdiv激活码2021【在线注册码/序列号/破解码】,https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月19日
    41

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号