贪吃蛇小游戏

java制作贪吃蛇窗口小游戏

游戏功能简介

(1)游戏界面生成:设计算法设计游戏界面,让玩家可以操纵角色进行游戏

(2)角色控制:玩家可以使用键盘wsad控制角色的上下左右四个方向

(3)果子生成:在游戏中果子位置随机生成,被吃掉后也是重新在游戏里随机生成

(4)计分系统:根据玩家吃到的果子数来算,一个果子为一分

(5)游戏界面:设计包括计分板、游戏名字、功能提示

(6)游戏胜利:获得到一定的果子分数后,游戏胜利,并提示,按下空格键space可以再次游戏

(7)游戏失败:游戏过程中如果咬到自己的尾巴则为游戏失败,并提示,按下space键可以重新开始游戏

(8)暂停/继续:游戏过程中如果想要上厕所,可以按下space键暂停游戏,方便后再次按下space键继续游戏

核心代码详解

代码共创建了六个类,分别为主要核心的窗口类、设计细节和传输图片的功能类、游戏主角的头、身体和果子三个类以及工具类

主要核心窗口类

​ 创建GameFrame类并继承JFframe方便进行窗口绘制public class GameFrame extends JFrame,定义状态state方便后续进行游戏的功能正常,并定义初始分数为0,接下来通过画笔创建窗口以及游戏角色的模型,通过定义集合 public List<SnackBody> snackBodies = new ArrayList<>();来存储蛇身并用for循环绘制蛇身,然后设置键盘录入this.addKeyListener(new KeyAdapter() wsad操纵角色的移动,由于设计过程中游戏界面闪的比较厉害影响游戏体验,重写设计双缓存来缓解游戏的闪烁问题,最后通过键盘录入以及条件语句设计游戏的开始、暂停/继续、失败

package one.SnackGame;

import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

public class GameFrame extends JFrame{
//游戏状态 0.未开始 1.游戏ing 2.暂停 3.失败 4.胜利 5.失败后重启
public static int state = 0;
//分数
public int score = 0;
//定义双缓存图片
Image ScreenImage = null;
//窗口宽高
int width = 800;
int height = 600;

//蛇头对象
SnackHead snackhead = new SnackHead(GameUtility.upImage, 60, 300, this);

//蛇身集合
public List<SnackBody> snackBodies = new ArrayList<>();
//获取食物
public SnackFood snackFood = new SnackFood().getFood();

//打印窗口
public void printFrame(){
    //窗口可见
    setVisible(true);
    //窗口大小
    setSize(width, height);
    //窗口居中
    setLocationRelativeTo(null);
    //窗口标题
    setTitle("Snack Game");

    //蛇身初始化
    snackBodies.add(new SnackBody(GameUtility.bodyImage, 30, 300, this));
    snackBodies.add(new SnackBody(GameUtility.bodyImage, 0, 300, this));

    //键盘操纵
    this.addKeyListener(new KeyAdapter() {
        @Override
        public void keyPressed(KeyEvent e) {
            if(e.getKeyCode() == KeyEvent.VK_SPACE){
                switch (state) {
                        //开始
                    case 0:
                        state = 1;
                        break;
                        //暂停
                    case 1:
                        state = 2;
                        break;
                        //继续
                    case 2:
                        state = 1;
                        break;
                        //失败后重新开始
                    case 3:
                        state = 5;
                    default:
                        break;
                }
            }
        }
    });

    while(true){
        if(state == 1) {
            repaint();
        }
        if(state == 5){
            state = 0;
            reset();
        }
        try {
            //1秒等于1000毫秒
            Thread.sleep(200);
        }
        catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

//画出网格
@Override
public void paint(Graphics g) {
    super.paint(g);
    //初始化双缓存图片
    if(ScreenImage == null){
        ScreenImage = this.createImage(width, height);
    }
    //获取双缓存画笔
    Graphics gImage = ScreenImage.getGraphics();
    //背景颜色
    gImage.setColor(Color.gray);
    gImage.fillRect(0, 0, width, height);
    //勾勒横线和竖线
    gImage.setColor(Color.black);
    for(int i = 0; i < 20; i++){
        gImage.drawLine(0, i * 30, 600, i * 30);
    }
    gImage.setColor(Color.black);
    for(int i = 0; i < 20; i++){
        gImage.drawLine(i * 30, 0, i * 30, 600);
    }
    //绘制蛇身
    for(int i = snackBodies.size()-1; i>=0; i--){
        snackBodies.get(i).paintSelf(gImage);
    }
    //绘制蛇头
    snackhead.paintSelf(gImage);
    //绘制食物
    snackFood.paintSelf(gImage);
    //绘制分数
    GameUtility.drawWorld(gImage,  score + "分", Color.BLUE, 50, 650, 300);
    //绘制提示语
    gImage.setColor(Color.gray);
    prompt(gImage);
    //将双缓存图片绘制到窗口
    g.drawImage(ScreenImage, 0, 0, null);
}

void prompt(Graphics g){
    //未开始
    if(state == 0){
        g.fillRect(120, 240, 400, 70);
        GameUtility.drawWorld(g, "按空格开始游戏", Color.YELLOW  , 35, 150, 290);
    }
    //暂停
    if(state == 2){
        g.fillRect(120, 240, 400, 70);
        GameUtility.drawWorld(g, "按空格继续游戏", Color.YELLOW, 35, 150, 290);
    }
    //失败
    if(state == 3){
        g.fillRect(120, 240, 400, 70);
        GameUtility.drawWorld(g, "游戏失败,按空格重新开始", Color.RED, 35, 150, 290);
    }
    //通关
    if(state == 4){
        g.fillRect(120, 240, 400, 70);
        GameUtility.drawWorld(g, "六百六十六", Color.GREEN, 35, 150, 290);
    }
}
//游戏重置
void reset(){
    //关闭当前窗口
    this.dispose();
    //开启新窗口
    String[] args = {};
    main(args);
}

public static void main (String[] args) {
    GameFrame game = new GameFrame();
    game.printFrame();
	}
}

传输图片的功能类

​ 通过传入HeadBody包获得角色的头部以及身体的模型(设计过程中由于图片包的位置不正确导致无法传入,可以用注释的语句System.getProperty(“user.dir”) 输出工作目录,将图片包放到工作目录下面即可),设计全部用到static关键词用于全局,最后也描绘了游戏中的文字

package one.SnackGame;

import java.awt.*;
import java.awt.image.BufferedImage;

public class GameUtility {
//查看当前工作目录
//    static {
//        System.out.println("当前工作目录: " + System.getProperty("user.dir"));
//    }

    //蛇头 ai帮助放缩
    public static Image upImage = Toolkit.getDefaultToolkit().getImage("HeadBody/d8be05b7180f9fd59f63ec06cc7c483a_compress.jpg");
    //蛇身
    public static Image bodyImage = Toolkit.getDefaultToolkit().getImage("HeadBody/f506cb9a75e2542bbcd4901ec826fda6_compress.jpg");
    //食物
    public static Image foodImage = Toolkit.getDefaultToolkit().getImage("HeadBody/85ad23c79c909e5cf34b602785f1990b_compress.jpg");
    //绘制文字
    public static void drawWorld(Graphics g, String str, Color color, int size, int x, int y){
        g.setColor(color);
        g.setFont(new Font("宋体", Font.BOLD, size));
        g.drawString(str, x, y);
    }
}

角色的头

​ 比较麻烦,首先继承了GameObj,通过键盘录入操纵了游戏角色的上下左右的移动public void changeDirection(KeyEvent e),但也要设计判断如果角色正在向上移动就无法直接进行向下移动,以此类推,通过坐标记录蛇头和舍身的位置,判断如果蛇头和蛇身的位置重合(即蛇头咬到蛇尾)即算失败,由于设计的格子为30*30,所以通过坐标记录移动就是坐标向该方向+30,然后重写方法paintSelf设计如果蛇头和果子位置坐标相同(即表示角色吃到了果子),则蛇身加长一,在达到一定分数后获得游戏胜利,最后判断了角色的越界处理,由于设计的游戏界面为600的,所以条件语句判断,在坐标为570时重新让坐标为0,即如果到达上面的最顶端越界,就要从下面出来且X坐标不变,如果从右边越界就要从左边出来且Y坐标不变

package one.SnackGame;

import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.List;

public class SnackHead extends GameObj {
    //方向 up down left right
    private String direction = "right";

    public String getDirection() {
        return direction;
    }

    public void setDirection(String direction) {
        this.direction = direction;
    }

    public SnackHead(Image image, int x, int y, GameFrame gf) {
        super(image, x, y, gf);
        //键盘操纵
        this.gf.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                changeDirection(e);
            }
        });
    }

    //控制操纵方向  WSAD
    public void changeDirection(KeyEvent e) {
        switch (e.getKeyCode()) {
            case KeyEvent.VK_A:
                if (!"right".equals(direction)) {
                    direction = "left";
                }
                break;
            case KeyEvent.VK_D:
                if (!"left".equals(direction)) {
                    direction = "right";
                }
                break;
            case KeyEvent.VK_W:
                if (!"down".equals(direction)) {
                    direction = "up";
                }
                break;
            case KeyEvent.VK_S:
                if (!"up".equals(direction)) {
                    direction = "down";
                }
                break;
            default:
                break;
        }
    }

    //蛇的移动
    public void move() {
        //蛇身移动
        java.util.List<SnackBody> snackBodies = this.gf.snackBodies;
        for (int i = snackBodies.size() - 1; i >= 1; i--) {
            snackBodies.get(i).x = snackBodies.get(i - 1).x;
            snackBodies.get(i).y = snackBodies.get(i - 1).y;
            //蛇头和蛇身相撞
            if (this.x == snackBodies.get(i).x && this.y == snackBodies.get(i).y) {
                //失败
                GameFrame.state = 3;
            }
        }
        snackBodies.get(0).x = this.x;
        snackBodies.get(0).y = this.y;
        //蛇头的移动
        switch (direction) {
            case "up":
                y -= 30;
                break;
            case "down":
                y += 30;
                break;
            case "left":
                x -= 30;
                break;
            case "right":
                x += 30;
                break;
        }
    }

    @Override
    public void paintSelf(Graphics g) {
        super.paintSelf(g);
        //蛇吃掉食物
        SnackFood food = this.gf.snackFood;
        //蛇身最后一节的坐标
        Integer newX = null;
        Integer newY = null;
        if (this.x == food.x && this.y == food.y) {
            //食物重新生成
            this.gf.snackFood = food.getFood();
            if (this.x == food.x && this.y == food.y){
                this.gf.snackFood = food.getFood();
                //获取蛇身最后一节
                SnackBody lastBody = this.gf.snackBodies.get(this.gf.snackBodies.size()-1);
                newX = lastBody.x;
                newY = lastBody.y;
                //分数加一
                this.gf.score++;
            }
            //通关判断
            if (this.gf.score == 15){
                //通关
                GameFrame.state = 4;
            }
        }
            move();
        if (newX != null && newY != null){
            this.gf.snackBodies.add(new SnackBody(GameUtility.bodyImage, newX, newY, this.gf));
        }
            //越界处理
            if (x < 0) {
                x = 570;
            } else if (x > 570) {
                x = 0;
            }
            if (y < 30) {
                y = 570;
            } else if (y > 570) {
                y = 30;
            }
        }
    }

角色的身体

​ 首先也是继承了GameObj类,运用到了重写父类的paintSelf方法,身体使用到集合,在吃到果子后可以加一个存储,使得身体增长

package one.SnackGame;

import java.awt.*;

public class SnackBody extends GameObj{
    public SnackBody(Image image, int x, int y, GameFrame gf) {
        super(image, x, y, gf);
    }

    @Override
    public void paintSelf(Graphics g) {
        super.paintSelf(g);
    }
}

果子类

​ 首先也是继承了GameObj类,重写了父类的paintSelf方法,同时由于果子是在游戏界面随机位置生成的所以用到了import java.util.Random; 通过功能类传输了果子的图片,并在游戏中随机产生

package one.SnackGame;

import java.awt.*;
import java.util.Random;

public class SnackFood extends GameObj{
    //随机函数
    Random random = new Random();

    public SnackFood() {
        super();
    }

    public SnackFood(Image image, int x, int y, GameFrame gf) {
        super(image, x, y, gf);
    }

    //获取食物
    //random.nextInt(20)随机数0-19
    public  SnackFood getFood(){
        return new SnackFood(GameUtility.foodImage, random.nextInt(20)*30,(random.nextInt(19)+1)*30,gf);
    }

    @Override
    public void paintSelf(Graphics g) {
        super.paintSelf(g);
    }
}

游戏的工具类

​ 工具类就没有什么好说的,主要就是set get方法在这里面,别的类需要用到直接继承重写即可不需要过于繁琐

设计总结

​ 该设计有六个类组成,分别为窗口类GameFrame、功能类GameUnility、头部类SnackHead、身体类SnackBody、食物类SnackFood、工具类GameObj

​ 通过设计该GUI窗口小游戏,首先相对较难的就是SnackHead类,不仅要定义头部信息,还要注意身体信息以及键盘的录入操纵方向并作判断,还有蛇头与蛇身相撞失败,同时还要越界处理进行条件语句解决;其次就是绘制窗口GameFrame类,要继承JFrame类来用画笔绘制窗口;还有一个较难的点就是身体的集合要不断增加在相应的位置,代码里用到newX、newY来记录蛇身的最后一个位置并在后面加长;

​ 设计中我觉得比较好玩的就是1.设计用到的画笔g,它可以画出窗口,写出文字,绘制出角色模型,还可以调节颜色、字体,或许还有更多用处目前未了解;2.还有让我思路提升的就是坐标的运用,通过这个窗口小游戏,我也是知道了可以通过坐标运用条件判断语句来呈现出蛇头咬到蛇尾的这一游戏特色,也可以通过坐标让蛇身在特定的位置伸长,以及越界处理;3.还有一个就是键盘的录入,通过wsad来操纵游戏角色;

​ 以及设计中用到的各种思路,例如:

    Integer newX = null;
    Integer newY = null;
    if (this.x == food.x && this.y == food.y) {
        //食物重新生成
        this.gf.snackFood = food.getFood();
        if (this.x == food.x && this.y == food.y){
            this.gf.snackFood = food.getFood();
            //获取蛇身最后一节
            SnackBody lastBody = this.gf.snackBodies.get(this.gf.snackBodies.size()-1);
            newX = lastBody.x;
            newY = lastBody.y;
            //分数加一
            this.gf.score++;

​ 这一段代码先定义了蛇身的最后最后一段坐标为newX、newY先初始都为null,然后判断如果蛇头坐标(this.x / this.y)和食物坐标(food.x / food.y)相等即重合,则说明蛇头吃到了食物,然后先重新随机生成食物,然后记录蛇身最后一节的 x / y 坐标,用于后续新增蛇身节的位置,最后分数加一。最后,java在GUI窗口设计这里还有很多可以用到的已经设计好的类和方法,或许不仅仅只有画笔这些功能,需要在学习中不断地去探索学习

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值