大家好,又见面了,我是你们的朋友全栈君。
首先给出代码下载地址(含素材):https://share.weiyun.com/8hkvy1Ja
目录
1.实现效果:
2.游戏玩法
该游戏用上下左右控制蛇的方向,寻找吃的东西,每吃一口就能得到一定的积分,而且蛇的身子会越吃越长,身子越长玩的难度就越大,不能碰墙,不能咬到自己的身体,更不能咬自己的尾巴,等到了一定的分数,就能过关,然后继续玩下一关。这次我们以一关的实现为例,关卡控制交给读者自行添加。
3.需求分析
- 方向控制
首先我们需要实现的是通过按键实现控制蛇的运动方向,需要注意的有两点:
1.蛇运动的时候不能向上一个状态的反方向运动,例如,原先向右,下一次改变的方向不能为左。
2.运动的时候如果按了一个方向键,再下一次按键之前将维持原先的方向运动。
2.如果蛇头和身体的图片不一样,那么蛇头要随着运动方向进行旋转。
- 蛇的绘制
蛇我这里分为了蛇头和蛇身两部分,当然你也可以加蛇尾。这里以蛇头和蛇身两部分为例:
蛇头游戏开始就已经存在,之后吃到一个食物都会使蛇身长度加一。蛇身的每一部分都会沿着它的前一部分的轨迹运动,而每一部分都会沿着蛇头的轨迹运动。
- 食物绘制
食物绘制相对比较简单,当一个食物被吃掉以后,便在地图的其他随机的一个地方产生下一个食物。
- 蛇和食物的生命周期
蛇:当蛇碰到地图边界,碰到自己的身体和尾巴的时候,即判定为死亡。
食物:当蛇头碰到食物,则食物死亡。
4.代码实现
- 项目目录
- 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
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/147644.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...