1. 程式人生 > 程式設計 >java實現貪吃蛇小遊戲

java實現貪吃蛇小遊戲

本文例項為大家分享了java實現貪吃蛇小遊戲的具體程式碼,供大家參考,具體內容如下

java實現貪吃蛇小遊戲

這是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,this.grid.getHeight());
  }
  graphics.setColor(Color.red);
  graphics.fillRect(0,this.grid.width,Settings.DEFAULT_NODE_SIZE);
  graphics.fillRect(0,Settings.DEFAULT_NODE_SIZE,this.grid.height);
  graphics.fillRect(this.grid.width,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,Color color) {
 graphics.setColor(color);
 int size = Settings.DEFAULT_NODE_SIZE;
 graphics.fillOval(squareArea.getX(),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經典小遊戲彙總

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。