貪吃蛇
思路
-
首先構思游戲布局,計算合理的坐標系,
-
繪制靜態資料(廣告、初始小蛇、提示資訊、棋盤)

-
添加鍵盤監聽事件,改變游戲狀態以及小蛇運動方向
-
添加定時器,讓小蛇在一段時間內移動一定的距離
-
隨機產生食物,并監聽食物狀態是否被吃
-
處理游戲結束事件
-
擴展相關游戲機制(積分、等級)
- 定義資料
- 繪制影像
- 事件監聽
注意事項
-
匯入檔案資源時,通過類的相對路徑獲取
-
URL headURL = Data.class.getResource("header.png"); // 這是放在源代碼同一個包下的檔案
-
URL headURL = Data.class.getResource("/header.png"); // 這是放在專案根目錄下的檔案
-
-
鍵盤監聽時,需要自動獲取焦點,
- this.setFocusable(true); // 獲取焦點
this.addKeyListener(new MyKeyListener());
- this.setFocusable(true); // 獲取焦點
-
在修改資料之后,需要repaint重繪圖形
-
小蛇運動時,需要注意邊界問題
-
食物隨機產生的坐標也要限制在游戲區域內
-
添加游戲的可玩性
具體實作
Data.java 存放所有影像資料
package snake;
import javax.swing.*;
import java.net.URL;
/**
* 資料中心
*/
public class Data {
// 相對路徑
// 絕對路徑 / 當前專案-->"GUI編程目錄"
public static URL headURL = Data.class.getResource("header.png");
public static URL upURL = Data.class.getResource("up.png");
public static URL downURL = Data.class.getResource("down.png");
public static URL leftURL = Data.class.getResource("left.png");
public static URL rightURL = Data.class.getResource("right.png");
public static URL bodyURL = Data.class.getResource("body.png");
public static URL foodURL = Data.class.getResource("food.png");
public static URL foodURL2 = Data.class.getResource("food2.png");
public static ImageIcon header = new ImageIcon(headURL);
public static ImageIcon up = new ImageIcon(upURL);
public static ImageIcon down = new ImageIcon(downURL);
public static ImageIcon left = new ImageIcon(leftURL);
public static ImageIcon right = new ImageIcon(rightURL);
public static ImageIcon body = new ImageIcon(bodyURL);
public static ImageIcon food = new ImageIcon(foodURL);
public static ImageIcon food2 = new ImageIcon(foodURL2);
}
StartGame.java 游戲啟動類
package snake;
import javax.swing.*;
import java.awt.*;
public class StartGame {
public static void main(String[] args) {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
int screenWidth = (int) screenSize.getWidth();
int screenHeight = (int) screenSize.getHeight();
System.out.println("螢屏寬度:" + screenWidth + ",螢屏高度:" + screenHeight);
// 本游戲固定表單大小(900,720)不可變
int x = (screenWidth - 900) / 2;
int y = (screenHeight - 720) / 2;
System.out.println("相對坐標x:" + x + ",y:" + y);
JFrame jFrame = new JFrame("貪吃蛇");
jFrame.setBounds(x, y, 900, 720);
jFrame.setResizable(false);
jFrame.add(new GamePanel());
jFrame.setVisible(true);
jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
}
GamePanel.java 處理游戲邏輯
package snake;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Random;
public class GamePanel extends JPanel {
// 蛇的資料結構
int length;
int[] snakeX = new int[600];
int[] snakeY = new int[500];
char direction;
// 食物的坐標
int foodx;
int foody;
Random random = new Random();
// 計數器
int count;
int score;
int worth;
boolean isStart;
boolean isFail;
// 定時器,每隔100毫秒執行一次引數的事件,
int interval = 100;
Timer timer = new Timer(interval, new MyActionLinstener());
public void init() {
// 默認游戲未開始
this.isStart = false;
this.isFail = false;
// 初始化速度
interval = 200;
timer.setDelay(interval);
// 初始化小蛇資料
this.length = 3;
this.score = 0;
this.snakeX[0] = 100;
this.snakeY[0] = 100;
this.snakeX[1] = 75;
this.snakeY[1] = 100;
this.snakeX[2] = 50;
this.snakeY[2] = 100;
this.direction = 'R';
// 隨機產生事物的坐標
this.foodx = 25 + 25 * random.nextInt(34);
this.foody = 75 + 25 * random.nextInt(24);
this.count = 1; // 統計食物數量
// 獲得焦點和鍵盤監聽事件
this.setFocusable(true);
// 添加鍵盤監聽事件
this.addKeyListener(new MyKeyListener());
// 開啟定時器
timer.start();
}
public GamePanel() {
init();
}
// 自動繪制面板
// paintComponent()是swing的一個方法,相當于圖形版的main(),是會自執行的,
@Override
protected void paintComponent(Graphics g) {
//清屏
super.paintComponent(g);
this.setBackground(Color.WHITE);
//繪制靜態面板
// 頂部廣告
Data.header.paintIcon(this, g, 25, 11);
// 游戲區域
g.fillRect(25, 75, 850, 600);
// 畫積分
g.setColor(Color.white);
g.setFont(new Font("微軟雅黑", Font.BOLD, 18));
g.drawString("長度:" + length, 750, 30);
g.drawString("分數:" + score, 750, 56);
// 繪制食物
if (count % 5 != 0) {
Data.food.paintIcon(this, g, foodx, foody);
worth = 1;
} else {
Data.food2.paintIcon(this, g, foodx, foody);
worth = 3;
}
// 游戲狀態 默認為暫停 需要提示資訊
if (!isStart) {
g.setColor(Color.white);
g.setFont(new Font("微軟雅黑", Font.BOLD, 40));
g.drawString("按下空格開始游戲", 300, 300);
}
// 畫小蛇 初始化向右 長度為3
// 選擇頭部的方向
ImageIcon head = Data.right;
switch (this.direction) {
case 'U':
head = Data.up;
break;
case 'D':
head = Data.down;
break;
case 'L':
head = Data.left;
break;
case 'R':
head = Data.right;
break;
default:
break;
}
head.paintIcon(this, g, snakeX[0], snakeY[0]);
for (int i = 1; i < this.length; i++) {
Data.body.paintIcon(this, g, snakeX[i], snakeY[i]);
}
// 結束狀態
if (isFail) {
g.setColor(Color.red);
g.setFont(new Font("微軟雅黑", Font.BOLD, 100));
g.drawString("Game Over", 150, 200);
g.drawString("按空格重新開始", 100, 400);
}
}
/**
* 內部類監聽鍵盤事件
*/
class MyKeyListener extends KeyAdapter {
// 只需要監聽鍵盤按下
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
// System.out.println(keyCode);
if (keyCode == KeyEvent.VK_SPACE) {
if (isFail) {
// 重新開始
init();
isStart = true;
} else {
isStart = !isStart;
}
repaint();
}
// 內層增加一個判斷,180度轉向 不生效
switch (keyCode) {
case KeyEvent.VK_UP:
if (direction != 'D') {
direction = 'U';
}
break;
case KeyEvent.VK_DOWN:
if (direction != 'U') {
direction = 'D';
}
break;
case KeyEvent.VK_LEFT:
if (direction != 'R') {
direction = 'L';
}
break;
case KeyEvent.VK_RIGHT:
if (direction != 'L') {
direction = 'R';
}
break;
default:
break;
}
// 調整速度 小鍵盤的 + -
if (keyCode == 107) {
interval = interval > 10 ? interval - 10 : 10;
timer.setDelay(interval);
System.out.println("加速" + interval);
} else if (keyCode == 109) {
interval += 20;
timer.setDelay(interval);
System.out.println("減速" + interval);
}
}
}
class MyActionLinstener implements ActionListener {
// 事件監聽類
@Override
public void actionPerformed(ActionEvent e) {
// 通過事件重繪界面
// 如果游戲開始且未失敗,則重繪界面
if (isStart && !isFail) {
// 吃食物 蛇頭與食物重合
if (snakeX[0] == foodx && snakeY[0] == foody) {
length++; // 吃了邊長
foodx = 25 + 25 * random.nextInt(34);
foody = 75 + 25 * random.nextInt(24);
count++; // 統計食物數量
score += worth;
// 這里可以設定積分到一定值,增加移動速度
if (score < 5) {
interval = 200;
timer.setDelay(interval);
System.out.println("一級速度");
} else if (score < 15) {
interval = 150;
timer.setDelay(interval);
System.out.println("二級速度");
} else if (score < 25) {
interval = 100;
timer.setDelay(interval);
System.out.println("三級速度");
} else if (score < 50) {
interval = 50;
timer.setDelay(interval);
System.out.println("四級速度");
}
}
for (int i = length - 1; i > 0; i--) {
// 從最后一節開始繼承前一節的位置
snakeX[i] = snakeX[i - 1];
snakeY[i] = snakeY[i - 1];
}
// 頭部找新的路,利用三元運算子判斷邊界重置
switch (direction) {
case 'U':
snakeY[0] = snakeY[0] <= 75 ? 650 : snakeY[0] - 25;
break;
case 'D':
snakeY[0] = snakeY[0] >= 650 ? 75 : snakeY[0] + 25;
break;
case 'L':
snakeX[0] = snakeX[0] <= 25 ? 850 : snakeX[0] - 25;
break;
case 'R':
snakeX[0] = snakeX[0] % 850 + 25;
break;
default:
break;
}
// 失敗判定
for (int i = 1; i < length; i++) {
if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
isFail = true;
}
}
repaint();
}
}
}
}
游戲效果

該版本已知bug
小蛇不能反向運動,玩家操作過快導致的意外死亡,
比如小蛇向↑運動程序中,玩家按 ↓ 是無效的,但如果按 ← 的瞬間按 ↓
由于定時器的重繪速度沒有跟上玩家的操作,游戲幀尚未重繪就已經有了下一次操作,
誤判為小蛇回頭撞了自己,游戲結束,
第二個問題是
空格本應該具有暫停/繼續/重玩的功能,
游戲局數為偶數時,空格鍵暫停功能失效,
打包游戲
首先通過IDEA生成Jar包,再通過exe4j將jar打包為exe

具體操作跳轉至Java桌面應用程式打包,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/193596.html
標籤:Java
