JavaSE 实战:贪吃蛇游戏

前言

学习 Java 已经一个月了,作为一个 GameBoy ,梦想之一就是能做出来自己的游戏,于是决定尝试编写贪吃蛇来作为阶段性总结。

经过一天的奋(zhua)战(kuang),终于实现了基本的功能,晚餐愉快地给自己加了鸡腿~

不过万里长城不是一天筑成的,自己的水平还非常有限,想做出合格的游戏还十分困难。这次把代码放出来其实还是蛮羞涩的,毕竟有各种问题,欢迎各路大神批评指正!不过,希望和我一样新学编程的同学们能够找到编程的乐趣从而坚持下去,程序猿永不言败!

总体思路

贪吃蛇作为一个经典游戏,玩法想必是为大家所熟知的。我个人曾经接触过N多个版本,其中最惊艳的是之前老爸(借的)诺基亚手机上的八方向3D科技风贪吃蛇,那时我还在上小学,智能机还没有出世,这个游戏各种华丽的特效让我叹为观止,玩法也独树一帜,既有传统因素,又有探险、收集、竞速等元素在里面。现在应该很难找到了,也不记得具体叫什么名字。。

不过,我还是正视现实老老实实做一个基础简陋款吧。。

游戏规则:

  • 蛇不停移动,过程中可以变换方向,但不得直接变换到相反方向
  • 场上有食物,蛇碰触(吃)到食物就会生长一节
  • 蛇在运动过程中若碰触到边界、障碍或是自己的身体便会死亡,游戏结束
  • 不同的关卡可以设置不同的障碍、不同的移动速度、不同的食物数量,也可以用时间来控制难度

技术选型:

  • 编程语言:JavaSE
  • IDE:eclipse
  • 设计模式:呃开玩笑的并没有设计模式
  • 框架:没用框架纯手打

结构设计:

├──Snake
     ├──src
          ├──com.gx
          │   ├──GameUtil.java          #游戏工具类
          │   ├──GameObject.java        #游戏物体父类
          │   ├──SnakeNode.java         #蛇结点(单元)类
          │   ├──Food.java              #食物类
          │   ├──SnakeGameFrame.java    #游戏主窗体类
          └──images        

P.S. 上面的结构是套用网上的打飞机(真的是打飞机!!!)游戏教程,有很多小游戏也是这种结构。虽然在本项目中感觉有些冗余的部分,但还是值得借鉴的。

主要代码:

1.GameUtil

/**
 * 游戏工具类
 * @author Gao
 * 实现游戏背景和物体的图片资源加载
 */

public class GameUtil {

    private GameUtil() {

    }

    public static Image getImage(String path) {
         BufferedImage bi = null;
         try {
            URL u = GameUtil.class.getClassLoader().getResource(path);
            bi = ImageIO.read(u);
        } catch (IOException e) {
            e.printStackTrace();
        }
         return bi;
    }
}

2.GameObject

其实这个类我没怎么用上,虽然食物和蛇强行继承了一波,但又重新建了私有变量。。昨天没有意识到,只顾闷着头写了,这里就不改啦,小伙伴们可以把这块优化一下~

/**
 * 游戏物体类
 * @author Gao
 * 构建、绘制游戏物体、碰撞检测
 */

public class GameObject {

    int width;
    int height;
    int x;
    int y;
    private Image img;

    public GameObject(Image img, int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        this.img = img;
    }

    public GameObject(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }

    public GameObject() {

    }

    public void drawSelf(Graphics g) {
        g.drawImage(img, x, y, width, height, null);
    }

    public Rectangle getRectangle() {
        return new Rectangle(x, y, width, height);
    }

}

3.SnakeNode

继承自GameObject,但其实没用好。

这也是最重要的一个类了。原本我的思路是根据蛇的长度画矩形,但发现转弯的情况十分复杂,于是把蛇分割成一个一个结点(单元),转弯时,每个单元依次获得前面单元的坐标(也是借鉴网上的做法),这样只要控制蛇的头部就可以啦~

/**
 * 蛇结点类
 * @author Gao
 * 蛇的结点(单元)生成以及移动状态判断
 */

public class SnakeNode extends GameObject {
    int x;
    int y;
    private int width;
    private int height;
    boolean left, up, right , down, live = true;
    int speed = 20;

    public SnakeNode(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }

    public SnakeNode() {

    }

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

    //在按下一个方向键时,要先把其它方向上的速度清除,不然就叠加啦。而根据规则,如果原来是相反的方向,是不能被清除的
    public void minusDirection(KeyEvent e) {
        switch(e.getKeyCode()) {
        case 37:
            up = false;
            //right = false;
            down = false;
            break;
        case 38:
            left = false;
            right = false;
            //down = false;
            break;
        case 39:
            //left = false;
            up = false;
            down = false;
            break;
        case 40:
            left = false;
            //up = false;
            right = false;
            break;
        }
    }

    //根据按下的键给蛇前进方向
    public void addDir(KeyEvent e) {
        switch(e.getKeyCode()) {
        case 37:
            if(!right)
            left = true;
            break;
        case 38:
            if(!down)
            up = true;
            break;
        case 39:
            if(!left)
            right = true;
            break;
        case 40:
            if(!up)
            down = true;
            break;
        }
    }

    public void drawBody(Graphics g) {
        g.fillRect(x, y, width, height);
    }

    //头部的移动在每次重绘时进行
    public void drawHead(Graphics g) {
        if (left) {
            x -= speed; 
        }
        if (right) {
            x += speed;
        }
        if (up) {
            y -= speed;
        }
        if (down) {
            y += speed;
        }
        g.fillRect(x, y, width, height);
    }

    //碰撞检测
    public Rectangle getRectangle() {
        return new Rectangle(x, y, 20, 20);
    }

4.Food

食物类,这个好说。不过依旧没有发挥继承的作用。

/**
 * 食物类
 * @author Gao
 * 在随机位置生成食物
 */

public class Food extends GameObject{
    public boolean isEaten = false;

    //控制生成的食物不超过屏幕范围,并且能够和蛇相遇(蛇由20*20的矩形组成)
    public Food() {
        x = (int)(Math.random() * 50)*20;
        y = (int)(Math.random() * 30)*20;
    }

    public void draw(Graphics g) {
        Color c = g.getColor();
        g.setColor(Color.GREEN);
        g.fillRect(x, y, 20, 20);
        g.setColor(c);
    }

    public Rectangle getRectangle() {
        return new Rectangle(x, y, 20, 20);
    }


}

5.SnakeGameFram

游戏的主窗体类,利用了awt、swing、多线程、事件监听等技术,也是游戏的主体算法部分。

/**
 * 贪吃蛇主窗体
 * @author Gao
 *
 */

public class SnakeGameFrame extends JFrame{
    Image bg = GameUtil.getImage("images/bg.jpg");
    SnakeNode head = null;
    SnakeNode[] body = null;
    Food[] foods = new Food[6];
    private int snakeLength;
    PaintThread paintThread = null;

    public SnakeGameFrame() {
        this.setTitle("贪吃蛇 by Gao");
        this.setBounds(300, 300, 1024, 683);
        this.setVisible(true);
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
    //初始化
    public void initialSnake() {
        int x1 = 180, y1 = 200;
        head = new SnakeNode(200, 200, 20, 20);
        body = new SnakeNode[50];
        snakeLength = 4;
        for(int i = 0;i < snakeLength;i++) {
            body[i] = new SnakeNode(x1, y1, 20, 20);
            x1-=20;
        }
        foods[1] = new Food();
        for(int i=0;i<6;i++) {
            foods[i] = new Food();
        }
        addKeyListener(new KeyMonitor());
    }

    public void paint(Graphics g) {
        g.drawImage(bg, 0, 0, 1024, 683, null);
        Color c = g.getColor();
        g.setColor(Color.RED);
        head.drawHead(g);
        g.setColor(Color.CYAN);

        //绘制蛇神时判断蛇身是否与蛇头碰撞
        for(int i = 0;i< snakeLength;i++) {
            body[i].drawBody(g);
            if(body[i].getRectangle().intersects(head.getRectangle())) {
                head.live = false;
            }
        }

        g.setColor(c);

        //判断蛇是否吃到食物
        for(int i=0;i<6;i++) {
            if(foods[i].getRectangle().intersects(head.getRectangle())) {
                snakeLength ++;
                body[snakeLength - 1] = new SnakeNode(body[snakeLength - 2].x, body[snakeLength - 2].y, 20, 20);
                System.out.println("wow");
                foods[i].isEaten = true;
            }
            if(foods[i].isEaten == false)foods[i].draw(g);
        }
    }

    //在线程中控制游戏状态
    class PaintThread extends Thread{
        public void run() {
            while(true) {       
                if(head.live == false) {
                    JOptionPane.showMessageDialog(null, "游戏结束,这都能死,下次小心点哦");
                    break;
                }

                try {   
                    for(int i = snakeLength - 1;i > 0;i--) {
                        body[i].setLocation(body[i-1].x, body[i-1].y);
                    }
                    body[0].setLocation(head.x, head.y);
                    if(head.x < 0 || head.x > 1004 || head.y < 0 || head.y > 663) {
                        head.live = false;
                    }
                    repaint();
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }       
            }
        }
    }

    //监听键盘事件,当第一次按键时开始游戏
    class KeyMonitor extends KeyAdapter {
        public void keyPressed(KeyEvent e) {
            head.minusDirection(e);
            head.addDir(e);
            if(paintThread == null) {
                paintThread = new PaintThread();
                paintThread.start();
            }
        }
    }

    public static void main(String[] args) {
        SnakeGameFrame snakeGameFrame = new SnakeGameFrame();
        snakeGameFrame.initialSnake();
    }
}

总结

因为只给自己安排了一天时间,所以关卡的设置和计分计时没有完成,对面向对象的思想理解还不够深入,继承等技术使用不熟练,代码也有许多需要完善的地方,做出来的效果也有些丑,不过在过程中还是通过努力解决了一些问题的!下周开始打算学习 JavaEE 的内容了,希望能够顺利!

这次实战的收获如下:

  • 巩固了JavaSE的基本语法
  • 稍微理解了一点程序的结构,写代码也感觉更顺手了
  • 找到了编程的乐趣,感受到了解决问题的喜悦,满足了自我证明的欲望

另外,昨天我把项目上传到了 gayhub github上,感兴趣的小伙伴可以下载下来跑一下(如果不怕麻烦的话文章的代码也可以直接复制),网址如下:

https://github.com/Antabot/Snake-v1.0-

最后,还是欢迎各路大神批评指正!

猜你喜欢

转载自blog.csdn.net/Neuf_Soleil/article/details/80872051
今日推荐