一起用Java写一个贪吃蛇小游戏

学习过java基础后,我们可以试着锻炼锻炼自己独立编程的能力。从构思到逻辑的实现,来体会java是如何编写一个贪吃蛇小游戏。

项目分析

分析一个游戏应该有的需求,逻辑。贪吃蛇无非就是等待键盘操作,根据操作更改画面。其实不管是什么游戏都少不了这两个步骤,所以我们可以把这两个操作抽象出来定义一个规则,这就是经常听说的游戏引擎

一个java实现的简易游戏引擎

文章用到该游戏引擎。来完成一个简单的贪吃蛇游戏。

使用到的技术

JavaSE GUI

架构设计

游戏中的元素:蛇,苹果,地图。
接下来需要考虑怎么把这些元素画出来?毫无疑问需要用到java中的gui技术。

  • 怎么样才能让蛇的长度动态的增加和减少呢?
  • 怎么才能确定蛇和苹果在地图中的位置?
  • 怎么才能在地图中随机生成苹果呢?

问题一:我们可以把蛇看成一个有多个节点组成的链表。这样就可以通过的加和减少链表中的节点数来控制蛇的长度动态的变化。

问题二:我们可以把地图画成一个网格,这样就可以通过x、y轴坐标来表示蛇的各个节点和苹果节点的位置。

问题三:有了坐标系就可以使用随机数来生成随机坐标。

解决了这三个问题,其余的画面更新,读取操作指令就叫给游戏引擎。让我来看看具体的代码。

代码实现

节点:Node

import java.awt.*;

public class Node {
    int x; //节点的x坐标
    int y; //节点的y坐标

    public Node(int x,int y){
        this.x = x;
        this.y = y;
    }

    /**
     * 在地图中画出节点
     * @param g2d
     */
    public void draw(Graphics2D g2d){
        g2d.fillRect(x*Config.GRID_SIZE,y*Config.GRID_SIZE,
                Config.GRID_SIZE,Config.GRID_SIZE);
    }
}

地图表格:MapGrid(注:表格的行列是自定义数值的,通过Config配置类中静态常量来控制,方便修改,这里也可以使用配置文件来设置也可以)

import java.awt.*;

public class MapGrid {

    /**
     * 画出表格  自定义行列
     * @param g2d
     */
    public void draw(Graphics2D g2d){
        for (int i = 0; i <Config.ROW ; i++) {
            g2d.drawLine(0,Config.GRID_SIZE*i,Config.SCREEN_WIDTH,Config.GRID_SIZE*i);
        }
        for (int i = 0; i < Config.COLOUM; i++) {
            g2d.drawLine(Config.GRID_SIZE*i,0,Config.GRID_SIZE*i,Config.SCREEN_HEIGHT);
        }
    }
}

Config配置:

public class Config {
    public static final int SCREEN_WIDTH = 500;
    public static final int SCREEN_HEIGHT = 500;
    public static final int GRID_SIZE = 10; //一个格子占多少像素
    public static final int COLOUM = SCREEN_WIDTH/GRID_SIZE;
    public static final int ROW = SCREEN_HEIGHT/GRID_SIZE;
    public static final int DRICT_RIGHT = 1;
    public static final int DRICT_LEFT = 2;
    public static final int DRICT_UP = 3;
    public static final int DRICT_DOWN = 4;
}

蛇:Snake

import java.awt.*;
import java.util.LinkedList;

public class Snake {
    LinkedList<Node> list = new LinkedList<>();
    int headDriction = 1; //代表移动的方向(1向右、2向左、3向上、4向下)  
    public Snake(){
        list.add(new Node(6,4));
        list.addLast(new Node(5,4));
        list.addLast(new Node(4,4));
        list.addLast(new Node(3,4));
    }

    /**
     * 右移
     * @param isHaveApple 是否苹果的存在
     */
    public void moveRight(boolean isHaveApple){
        if(headDriction == Config.DRICT_LEFT){
            System.out.println("不能反方向移动");
            return;
        }
        headDriction = Config.DRICT_RIGHT;
        Node newHead = new Node(list.getFirst().x+1,list.getFirst().y);
        list.addFirst(newHead);
        if (!isHaveApple){
            list.removeLast();
        }

    }

    /**
     * 左移
     * @param isHaveApple
     */
    public void moveLeft(boolean isHaveApple){
        if(headDriction == Config.DRICT_RIGHT){
            System.out.println("不能反方向移动");
            return;
        }
        headDriction = Config.DRICT_LEFT;
        Node newHead = new Node(list.getFirst().x-1,list.getFirst().y);
        list.addFirst(newHead);
        if (!isHaveApple){
            list.removeLast();
        }


    }

    /**
     * 上移
     * @param isHaveApple
     */
    public void moveUp(boolean isHaveApple){
        if(headDriction == Config.DRICT_DOWN){
            System.out.println("不能反方向移动");
            return;
        }
        headDriction = Config.DRICT_UP;
        Node newHead = new Node(list.getFirst().x,list.getFirst().y-1);
        list.addFirst(newHead);
        if (!isHaveApple){
            list.removeLast();
        }

    }

    /**
     * 下移
     * @param isHaveApple
     */
    public void moveDown(boolean isHaveApple){
        if(headDriction == Config.DRICT_UP){
            System.out.println("不能反方向移动");
            return;
        }
        headDriction = Config.DRICT_DOWN;
        Node newHead = new Node(list.getFirst().x,list.getFirst().y+1);
        list.addFirst(newHead);
        if (!isHaveApple){
            list.removeLast();
        }

    }

    /**
     * 判断蛇头是否与苹果相遇
     * @param apple 被检查的苹果
     * @return 相遇返回ture 否则返回false
     */
    public boolean isHaveApple(Apple apple){
        if((list.getFirst().x== apple.getX())&&(list.getFirst().y== apple.getY())){
            apple.getNextApple();
            return true;
        }
        return false;
    }

    /**
     * 画出链表即蛇
     * @param g2d
     */
    public void draw(Graphics2D g2d){
        for (Node node:list){
            node.draw(g2d);
        }
    }
}

苹果:Apple

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

public class Apple {
    Random random = new Random();
    Node apple = null;

    public Apple(){
        apple = new Node(random.nextInt(Config.ROW),random.nextInt(Config.COLOUM));
    }
    public int getX(){
        return apple.x;
    }
    public int getY(){
        return apple.y;
    }

    public void draw(Graphics2D g2d){
        g2d.setColor(Color.RED);
        apple.draw(g2d);
        g2d.setColor(Color.black);
    }

    /**
     * 产生一个新苹果
     */
    public void getNextApple(){
        apple = new Node(random.nextInt(Config.ROW),random.nextInt(Config.COLOUM));
    }

}

贪吃蛇游戏引擎:SnakeGameEngine (实现游戏引擎GameEngine抽象类,重写方法)

import java.awt.*;
import java.awt.event.KeyEvent;

public class SnakeGameEngine extends GameEngine {

    int keycode;
    boolean ishaveapple;
    MapGrid grid = new MapGrid();
    Snake snake = new Snake();
    Apple apple = new Apple();

    /**
     * 读取、更新 操作
     */
    @Override
    public void updateLogic() {
        keycode = Game.getCurrentKeyCode();
        ishaveapple = snake.isHaveApple(apple);
        if (keycode == KeyEvent.VK_RIGHT){
            snake.moveRight(ishaveapple);
        }
        else if (keycode == KeyEvent.VK_LEFT){
            snake.moveLeft(ishaveapple);
        }
        else if (keycode == KeyEvent.VK_UP){
            snake.moveUp(ishaveapple);
        }
        else if (keycode == KeyEvent.VK_DOWN){
            snake.moveDown(ishaveapple);
        }
        else {
            return;
        }
    }

    /**
     * 更新画面
     * @param g2d
     */
    @Override
    public void renderUI(Graphics2D g2d) {
        grid.draw(g2d);
        snake.draw(g2d);
        apple.draw(g2d);
    }
}

主函数:SnakeGame (Game为游戏公共类也属于游戏引擎中的一部分,本质是一个画板,用来初始化画板)

public class SnakeGame {
    public static void main(String[] args) {
        Game.init("贪吃蛇", Config.SCREEN_WIDTH, Config.SCREEN_HEIGHT,new SnakeGameEngine());
    }
}

游戏引擎代码:
GameEngine:

import java.awt.*;

/**
 * 游戏引擎抽象模板类
 * <br>提供两个抽象方法
 * <br>updateLogic
 * <br>renderUI
 */
public abstract class GameEngine {

    /**
     * 逻辑更新
     */
    public abstract void updateLogic();

    /**
     * 画面更新
     */
    public abstract void renderUI(Graphics2D g2d);
}

Game:

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

/**
 * 游戏公共类
 */
public class Game extends JPanel {

    static GameEngine myGameEngine;
    static int keycode;
    /**
     * 初始化方法,展现游戏界面
     * @param title     窗体标题
     * @param width     窗体宽度
     * @param height    窗体高度
     * @param engine    游戏引擎
     */
    public static void init(String title,int width,int height,GameEngine engine){
        myGameEngine = engine;
        JFrame frame =new JFrame(title);
        Game game = new Game();
        game.setPreferredSize(new Dimension(width,height));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(game);
        frame.pack();

        //匿名内部类创建键盘监听器
        frame.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                keycode = e.getKeyCode();
            }

            @Override
            public void keyReleased(KeyEvent e) {
                keycode = -1;
            }
        });

        frame.setVisible(true);
        while (true){
            engine.updateLogic();
            game.repaint(); //自动重绘
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 返回所按的按键码
     * @return int类型的按键码
     */
    public static int getCurrentKeyCode(){
        return keycode;
    }

    /**
     * 游戏结束,结束JVM进程
     * @param message 退出时所显示的文字,例:游戏失败.
     */
    public static void GameOver(String message){
        JOptionPane.showMessageDialog(null,message);
        System.exit(0);
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        myGameEngine.renderUI(g2d);
    }

}

关于本文用到的游戏引擎和公共类在下面的文章给出了具体分析。
【架构】Java实现游戏引擎

与其临渊羡鱼,不如退而结网。——《史记汉书董仲舒传》

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

指针指指针

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值