java贪吃蛇 java实现贪吃蛇小游戏
AdvancedPawn 人气:0这是MVC模式的完整Java项目,编译运行SnakeApp.java即可开始游戏。
可扩展功能:
1、积分功能:可以创建得分规则的类(模型类的一部分), 在GameController的run()方法中计算得分
2、变速功能:比如加速功能,减速功能,可以在GameController的keyPressed()方法中针对特定的按键设置每一次移动之间的时间间隔,将Thread.sleep(Settings.DEFAULT_MOVE_INTERVAL);替换为动态的时间间隔即可
3、更漂亮的游戏界面:修改GameView中的drawXXX方法,比如可以将食物渲染为一张图片,Graphics有drawImage方法
View
SnakeApp.java
/* * 属于View,用来根据相应的类展示出对应的游戏主界面,也是接收控制信息的第一线。 */ public class SnakeApp { public void init() { //创建游戏窗体 JFrame window = new JFrame("一只长不大的蛇"); //初始化500X500的棋盘,用来维持各种游戏元素的状态,游戏的主要逻辑部分 Grid grid = new Grid(50*Settings.DEFAULT_NODE_SIZE,50*Settings.DEFAULT_NODE_SIZE); //传入grid参数,新建界面元素对象 GameView gameView = new GameView(grid);//绘制游戏元素的对象 //初始化面板 gameView.initCanvas(); //根据棋盘信息建立控制器对象 GameController gameController = new GameController(grid); //设置窗口大小 window.setPreferredSize(new Dimension(526,548)); //往窗口中添加元素,面板对象被加入到窗口时,自动调用其中的paintComponent方法。 window.add(gameView.getCanvas(),BorderLayout.CENTER); //画出蛇和棋盘和食物 GameView.draw(); //注册窗口监听器 window.addKeyListener((KeyListener)gameController); //启动线程 new Thread(gameController).start(); //窗口关闭的行为 window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //设置窗口大小不可变化 window.setResizable(false); //渲染和显示窗口 window.pack(); window.setVisible(true); } //可以忽略,以后每个类中都有这么一个测试模块 public static void main(String[] args) { SnakeApp snakeApp = new SnakeApp(); snakeApp.init(); } }
GameView.java
/* * 属于View,用于绘制地图、蛇、食物 */ /* Graphics 相当于一个画笔。对象封装了 Java 支持的基本呈现操作所需的状态信息。此状态信息包括以下属性: 要在其上绘制的 Component 对象。 呈现和剪贴坐标的转换原点。 当前剪贴区。 当前颜色。 当前字体。 当前逻辑像素操作函数(XOR 或 Paint)。 当前 XOR 交替颜色 */ /* java.awt.Component的repaint()方法 作用:更新组件。 如果此组件不是轻量级组件,则为了响应对 repaint 的调用,AWT 调用 update 方法。可以假定未清除背景。 Component 的 update 方法调用此组件的 paint 方法来重绘此组件。为响应对 repaint 的调用而需要其他工作的子类通常重写此方法。重写此方法的 Component 子类应该调用 super.update(g),或者直接从其 update 方法中调用 paint(g)。 图形上下文的原点,即它的(0,0)坐标点是此组件的左上角。图形上下文的剪贴区域是此组件的边界矩形。 */ public class GameView { private final Grid grid; private static JPanel canvas;//画板,用于在这上面制作画面,然后返回。 public GameView(Grid grid) { this.grid = grid; } //重新绘制游戏界面元素,不断重新调用paintComponent方法覆盖原本的面板。 public static void draw() { canvas.repaint(); } //获取画板对象的接口 public JPanel getCanvas() { return canvas; } //对画板进行初始化 public void initCanvas() { canvas = new JPanel() { //指向一个方法被覆盖的新面板子类对象 //paintComponent()绘制此容器中的每个组件,Swing会在合适的时机去调用这个方法,展示出合适的界面,这就是典型的回调(callback)的概念。 public void paintComponent(Graphics graphics) { super.paintComponent(graphics); //这里必须调用一下父类 也就是 container的重绘方法,否则表现为之前的绘图不会覆盖 drawGridBackground(graphics);//画出背景网格线 drawSnake(graphics, grid.getSnake());//画蛇 drawFood(graphics, grid.getFood());//画食物 } }; } //画蛇 public void drawSnake(Graphics graphics, Snake snake) { for(Iterator<Node> i = snake.body.iterator();i.hasNext();) { Node bodyNode = (Node)i.next(); drawSquare(graphics, bodyNode,Color.BLUE); } } //画食物 public void drawFood(Graphics graphics, Node food) { drawCircle(graphics,food,Color.ORANGE); } //画格子背景,方便定位Snake运动轨迹,横竖各以10为单位的50个线。 public void drawGridBackground(Graphics graphics) { graphics.setColor(Color.GRAY); canvas.setBackground(Color.BLACK); for(int i=0 ; i < 50 ; i++) { graphics.drawLine(0, i*Settings.DEFAULT_NODE_SIZE, this.grid.getWidth(), i*Settings.DEFAULT_NODE_SIZE); } for(int i=0 ; i <50 ; i++) { graphics.drawLine(i*Settings.DEFAULT_NODE_SIZE, 0, i*Settings.DEFAULT_NODE_SIZE , this.grid.getHeight()); } graphics.setColor(Color.red); graphics.fillRect(0, 0, this.grid.width, Settings.DEFAULT_NODE_SIZE); graphics.fillRect(0, 0, Settings.DEFAULT_NODE_SIZE, this.grid.height); graphics.fillRect(this.grid.width, 0, Settings.DEFAULT_NODE_SIZE,this.grid.height); graphics.fillRect(0, this.grid.height, this.grid.width+10,Settings.DEFAULT_NODE_SIZE); } /* * public abstract void drawLine(int x1,int y1,int x2,int y2) 在此图形上下文的坐标系中,使用当前颜色在点 (x1, y1) 和 (x2, y2) 之间画一条线。 参数: x1 - 第一个点的 x 坐标。 y1 - 第一个点的 y 坐标。 x2 - 第二个点的 x 坐标。 y2 - 第二个点的 y 坐标。 */ //提供直接出现游戏结束的选项框的功能。 public static void showGameOverMessage() { JOptionPane.showMessageDialog(null,"游戏结束","短暂的蛇生到此结束", JOptionPane.INFORMATION_MESSAGE); } //画图形的具体方法: private void drawSquare(Graphics graphics, Node squareArea, Color color) { graphics.setColor(color); int size = Settings.DEFAULT_NODE_SIZE; graphics.fillRect(squareArea.getX(), squareArea.getY(), size - 1, size - 1); } private void drawCircle(Graphics graphics, Node squareArea, Color color) { graphics.setColor(color); int size = Settings.DEFAULT_NODE_SIZE; graphics.fillOval(squareArea.getX(), squareArea.getY(), size, size); } }
Controller
GameController
/* * 接收窗体SnakeApp传递过来的有意义的事件,然后传递给Grid,让Grid即时的更新状态。 * 同时根据最新状态渲染出游戏界面让SnakeApp显示 * */ public class GameController implements KeyListener, Runnable{ private Grid grid; private boolean running; public GameController(Grid grid){ this.grid = grid; this.running = true; } @Override public void keyPressed(KeyEvent e) { int keyCode = e.getKeyCode(); switch(keyCode) { case KeyEvent.VK_UP: grid.changeDirection(Direction.UP); break; case KeyEvent.VK_DOWN: grid.changeDirection(Direction.DOWN); break; case KeyEvent.VK_LEFT: grid.changeDirection(Direction.LEFT); break; case KeyEvent.VK_RIGHT: grid.changeDirection(Direction.RIGHT); break; } isOver(grid.nextRound()); GameView.draw(); } private void isOver(boolean flag) { if(!flag) {//如果下一步更新棋盘时,出现游戏结束返回值(如果flag为假)则 this.running = false; GameView.showGameOverMessage(); System.exit(0); } } @Override /*run()函数中的核心逻辑是典型的控制器(Controller)逻辑: 修改模型(Model):调用Grid的方法使游戏进入下一步 更新视图(View):调用GameView的方法刷新页面*/ public void run() { while(running) { try { Thread.sleep(Settings.DEFAULT_MOVE_INTERVAL); isOver(grid.nextRound()); GameView.draw(); } catch (InterruptedException e) { break; } // 进入游戏下一步 // 如果结束,则退出游戏 // 如果继续,则绘制新的游戏页面 } running = false; } @Override public void keyTyped(KeyEvent e) { } @Override public void keyReleased(KeyEvent e) { } }
Model
Grid
/* * 随机生成食物,维持贪吃蛇的状态,根据SnakeApp中的用户交互来控制游戏状态。 */ public class Grid { private Snake snake; int width; int height; Node food; private Direction snakeDirection =Direction.LEFT; public Grid(int length, int high) { super(); this.width = length; this.height = high; initSnake(); food = creatFood(); } //在棋盘上初始化一个蛇 private void initSnake() { snake = new Snake(); int x = width/2; int y = height/2; for(int i = 0;i<5;i++) { snake.addTail(new Node(x, y)); x = x+Settings.DEFAULT_NODE_SIZE; } } //棋盘上随机制造食物的功能。 //一直循环获取随机值,直到三个条件都不满足。 private Node creatFood() { int x,y; do { x =(int)(Math.random()*100)+10; y =(int)(Math.random()*100)+10; System.out.println(x); System.out.println(y); System.out.println(this.width); System.out.println(this.height); }while(x>=this.width-10 || y>=this.height-10 || snake.hasNode(new Node(x,y))); food = new Node(x,y); return food; } //提供下一步更新棋盘的功能,移动后更新游戏和蛇的状态。 public boolean nextRound() { Node trail = snake.move(snakeDirection); Node snakeHead = snake.getBody().removeFirst();//将头部暂时去掉,拿出来判断是否身体和头部有重合的点 if(snakeHead.getX()<=width-10 && snakeHead.getX()>=10 && snakeHead.getY()<=height-10 && snakeHead.getY()>=10 && !snake.hasNode(snakeHead)) {//判断吃到自己和撞到边界 if(snakeHead.equals(food)) { //原本头部是食物的话,将move操作删除的尾部添加回来 snake.addTail(trail); food = creatFood(); } snake.getBody().addFirst(snakeHead); return true;//更新棋盘状态并返回游戏是否结束的标志 } return false; } public Node getFood() { return food; } public Snake getSnake() { return snake; } public int getWidth() { return width; } public int getHeight() { return height; } //提供一个更改贪吃蛇前进方向的方法 public void changeDirection(Direction newDirection){ snakeDirection = newDirection; } }
Snake
/* * 蛇类,实现了自身数据结构,以及移动的功能 */ public class Snake implements Cloneable{ public LinkedList<Node> body = new LinkedList<>(); public Node move(Direction direction) { //根据方向更新贪吃蛇的body //返回移动之前的尾部Node(为了吃到时候后增加尾部长度做准备) Node n;//临时存储新头部移动方向的结点 switch (direction) { case UP: n = new Node(this.getHead().getX(),this.getHead().getY()-Settings.DEFAULT_NODE_SIZE); break; case DOWN: n = new Node(this.getHead().getX(),this.getHead().getY()+Settings.DEFAULT_NODE_SIZE); break; case RIGHT: n = new Node(this.getHead().getX()+Settings.DEFAULT_NODE_SIZE,this.getHead().getY()); break; default: n = new Node(this.getHead().getX()-Settings.DEFAULT_NODE_SIZE,this.getHead().getY()); } Node temp = this.body.getLast(); this.body.addFirst(n); this.body.removeLast(); return temp; } public Node getHead() { return body.getFirst(); } public Node getTail() { return body.getLast(); } public Node addTail(Node area) { this.body.addLast(area); return area; } public LinkedList<Node> getBody(){ return body; } //判断参数结点是否在蛇身上 public boolean hasNode(Node node) { Iterator<Node> it = body.iterator(); Node n = new Node(0,0); while(it.hasNext()) { n = it.next(); if(n.getX() == node.getX() && n.getY() == node.getY()) { return true; } } return false; } }
Direction
/* * 用来控制蛇的移动方向 */ public enum Direction { UP(0), DOWN(1), LEFT(2), RIGHT(3); //调用构造方法对方向枚举实例进行代码初始化 //成员变量 private final int directionCode; //成员方法 public int directionCode() { return directionCode; } Direction(int directionCode){ this.directionCode = directionCode; } }
Node
public class Node { private int x; private int y; public Node(int x, int y) { this.x = ((int)(x/10))*10; this.y = ((int)(y/10))*10; }//使用这种方法可以使得节点坐标不会出现个位数 public int getX() { return x; } public int getY() { return y; } @Override //判断两个Node是否相同 public boolean equals(Object n) { Node temp; if(n instanceof Node) { temp = (Node)n; if(temp.getX()==this.getX() && temp.getY()==this.getY()) return true; } return false; } }
Settings
public class Settings { public static int DEFAULT_NODE_SIZE = 10;//每一个节点方块的单位 public static int DEFAULT_MOVE_INTERVAL = 200;//蛇移动时间间隔 }
更多有趣的经典小游戏实现专题,分享给大家:
加载全部内容