亲宝软件园·资讯

展开

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;//蛇移动时间间隔
}

更多有趣的经典小游戏实现专题,分享给大家:

C++经典小游戏汇总

python经典小游戏汇总

python俄罗斯方块游戏集合

JavaScript经典游戏 玩不停

javascript经典小游戏汇总

加载全部内容

相关教程
猜你喜欢
用户评论