Java實戰(zhàn)之飛翔的小鳥小游戲
前言
一個簡單的單機(jī)小游戲:flypybird ,用來鞏固java基礎(chǔ)。
涉及主要知識點(diǎn):JFrame 、 JPanel 、 繼承、 鍵盤/鼠標(biāo)監(jiān)聽 、 多線程 、 工具類設(shè)計
提示:這是大致的實現(xiàn)過程,實際實現(xiàn)過程有一定的修改,具體以源碼為準(zhǔn)。
一、大體思路
1、首先要有一個框架,作為主程序入口,這里使用 JFrame 類。
2、然后需要有一個畫布,用來把游戲場景畫上去,然后在上面添加鍵盤/鼠標(biāo)監(jiān)聽來控制,這里使用的是 JPenal 類。
3、需要創(chuàng)建幾個類:小鳥、地面、障礙物柱子、一個獲取圖片的工具類
4、然后逐步添加到畫布中,實現(xiàn)對應(yīng)的功能
二、具體步驟
2.1 創(chuàng)建窗體類
相當(dāng)于窗戶的框架,有了框架才能裝玻璃。然后也是主程序執(zhí)行的入口
2.1.1 具體代碼
public class MainFrame extends JFrame {
/* 圖標(biāo) */
BufferedImage Icon;
/*
* 構(gòu)造器用來初始化框架*/
public MainFrame() throws IOException {
/* 設(shè)置圖標(biāo) */
Icon = ImageUtil.getImage("bird1_1.png");
setIconImage(Icon);
/* 設(shè)置關(guān)閉 */
setDefaultCloseOperation(EXIT_ON_CLOSE);
/* 設(shè)置標(biāo)題 */
setTitle("飛翔的小鳥");
/* 設(shè)置尺寸*/
setSize(298, 550);
/* 設(shè)置大小不可變 */
setResizable(false);
/* 設(shè)置窗體居中 */
setLocationRelativeTo(null);
}
/*
* 主程序
* */
public static void main(String[] args) throws IOException {
MainFrame mainFrame = new MainFrame();
mainFrame.setVisible(true);
}
}
2.1.2 效果展示

2.1.3 小結(jié)
大體框架做好,考慮到后面還需要使用比較多的圖片,因此接下來先建一個工具類,用來獲取圖片資源。
三、創(chuàng)建一個獲取圖片的工具類
3.1 具體代碼
/*
* 工具類,用來獲取圖片
* */
public class ImageUtil {
public static BufferedImage getImage(String name) throws IOException {
return ImageIO.read(new BufferedInputStream(new FileInputStream("birdGame/flyBird/" + name)));
}
}
3.2 小結(jié)
圖片獲取方式改為用工具類獲取,只需要輸入圖片名+格式。后期方便減少重復(fù)代碼的書寫。
四、創(chuàng)建畫布
使用 Jpanel 類,創(chuàng)建畫布(相當(dāng)于玻璃) ,就能在上面畫游戲的畫面了。后期還需要在上面添加鼠標(biāo)/鍵盤監(jiān)聽。
4.1 具體代碼
public class GameJPenal extends JPanel {
/*
* 各種參數(shù)應(yīng)該設(shè)置在這
* */
BufferedImage bg;
/*
* 構(gòu)造方法用來完成數(shù)據(jù)的初始化
* */
public GameJPenal() throws IOException {
bg = ImageUtil.getImage("bg_day.png");
}
/*
* 開始游戲的方法
* */
public void start() {
gameStart = true;
Thread myThread = new Thread(new MyThread());
myThread.start();
}
//繪制的方法
@Override
public void paint(Graphics g) {
g.drawImage(bg, 0, 0, 288, 512, null); //背景
}
}
4.2 效果展示
先在main方法中創(chuàng)建對象,把畫布添加到框架里面,注意要重新在最后設(shè)置可見,否者看不到背景
/*
* 主程序
* */
public static void main(String[] args) throws IOException {
MainFrame mainFrame = new MainFrame();
GameJPenal gameJPenal = new GameJPenal();
mainFrame.add(gameJPenal); /* 畫布添加到框架上 */
mainFrame.setVisible(true);
gameJPenal.requestFocus(); /* 請求屏幕焦點(diǎn) ,否則無法實現(xiàn)鍵盤監(jiān)聽 */
}
接下來就可以運(yùn)行,效果如下

4.3 小結(jié)
這里需要注意一個點(diǎn)就是請求屏幕焦點(diǎn):后期如果要做鍵盤監(jiān)聽的話必須有焦點(diǎn),否則無法實現(xiàn)鍵盤控制
五、把地面畫上去
5.1 代碼
public class Ground {
BufferedImage land;
/* x,y 是地面在畫布上的坐標(biāo) */
private int x;
private int y;
private final int SPEED = 4; //控制地面移動的速度
public Ground() throws IOException {
land = ImageUtil.getImage("land.png");
x = 0;
y = 512 - land.getHeight();
}
/*
* 地面移動的效果
* */
public void move() {
if (x == (288 - land.getWidth())) {
x = 0;
}
x-=SPEED;
}
/*
* get方法
* */
public int getX() {
return x;
}
public int getY() {
return y;
}
}
接下來就是把地面的圖片用畫筆畫上去
g.drawImage(land.landImg, land.x, land.y, land.w, land.h, null);
六、創(chuàng)建一個新線程讓畫面動起來
先把變量添加到 GamePanel 類
Land land; //地面
Thread newThread; //線程
boolean gameStart; // 游戲狀態(tài)變量,準(zhǔn)備狀態(tài)為 false ,游戲開始為 true
boolean gameOver; //狀態(tài)變量, 游戲開始為false ,游戲結(jié)束為true
在GamePanel 構(gòu)造器里面初始化變量,創(chuàng)建一個新線程,用死循環(huán)不斷調(diào)用地面移動的方法,然后重畫畫面,實現(xiàn)移動的效果
這里還加了倆個游戲的狀態(tài)變量,主要是為了方便實現(xiàn)游戲的準(zhǔn)備畫面和游戲結(jié)束后可以重新開始游戲:
boolean gameStart; //游戲準(zhǔn)備狀態(tài) boolean gameOver; //游戲結(jié)束狀態(tài)
/*
* 新開一個線程
* */
class MyThread implements Runnable {
@Override
public void run() {
while (true) {
ground.move();
c0.move();
c1.move();
bird.fly();
bird.move();
isGameOver();
repaint();
if (gameOver) {
return;
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
小結(jié):
這時候畫面已經(jīng)能動起來了,接下來就是把其它的對象創(chuàng)建出來添加進(jìn)去:鳥 、 障礙物(柱子)
七、創(chuàng)建柱子類
和創(chuàng)建地面類似,需要有他的坐標(biāo)點(diǎn)和高度,同時為了畫面柱子的連續(xù)性,需要倆組不同的柱子,
/*
* 柱子類
* */
public class Column {
private final int SPEED = 2;
int w1;
int h1;
int x1;
int y1;
BufferedImage img1;
int w2;
int h2;
int x2;
int y2;
BufferedImage img2;
/*
* 構(gòu)造方法
* 初始化對象*/
public Column(int i) throws IOException {
img1 = ImageUtil.getImage("pipe_down.png");
w1 = img1.getWidth() / 2;
h1 = img1.getHeight();
x1 = 288 + i * 150;
y1 = -100 - i * 20;
img2 = ImageUtil.getImage("pipe_up.png");
w2 = img2.getWidth() / 2;
h2 = img2.getHeight();
x2 = 288 + i * 150;
y2 = h2 - i * 25 -20;
}
/*柱子移動的方法
* */
public void move() {
if (x1 == -w1) {
x1 = 288;
}
if (x2 == -w2) {
x2 = 288;
}
x1 -= SPEED;
x2 -= SPEED;
}
}
八、實現(xiàn)柱子在畫布上的移動
和地面的初始化一樣,在畫布上添加成員變量,進(jìn)行初始化,接下來用畫筆在畫布上畫出來,在線程里面的死循環(huán)調(diào)用它移動的方法,實現(xiàn)柱子的移動
小結(jié):
接下來就是把小鳥添加到畫布上來
九、創(chuàng)建小鳥類
小鳥的x軸位置其實是固定的,因此小鳥只需要能在y軸移動就行,這一部分可以通過鼠標(biāo)活著鍵盤監(jiān)聽來控制
小鳥有一個扇翅膀的動作,通過把圖片存在一個集合里面,通過循環(huán),每次畫一個圖片,就能實現(xiàn)扇翅膀的效果
難點(diǎn):小鳥的上下移動的方法的設(shè)計
這里通過設(shè)計一個類似拋物線的動作,每次按下鍵盤Up,就改變它的上拋的初速度,讓小鳥往上飛,經(jīng)過時間 t 開始做落體運(yùn)動
public class Bird {
private double v0; /* 小鳥運(yùn)動的初速度 */
double t; /* 往上運(yùn)動的時間 */
int s; /* 往上運(yùn)動的路程 */
int x;
int y;
int g; /* 重力 */
BufferedImage img;
ArrayList<BufferedImage> list; /* 裝三張不同動作的圖片 */
public Bird() throws IOException {
g = 3;
v0 = 5;
t = 0.3;
img = ImageUtil.getImage("bird1_1.png");
x = 100;
y = 200;
list = new ArrayList<>();
list.add(ImageUtil.getImage("bird1_0.png"));
list.add(ImageUtil.getImage("bird1_1.png"));
list.add(ImageUtil.getImage("bird1_2.png"));
}
/*
* 鳥飛的動作,通過改變每次畫不同動作的圖片來實現(xiàn)
* */
int i = 0;
public void fly() {
if (i >= 3) {
i = 0;
}
img = list.get(i);
i++;
}
/*
* 鳥的上拋移動
* */
public void moveUP() {
v0 = 10;
}
/*
* 鳥的落體運(yùn)動*/
public void move() {
s= (int) (v0*t);
y -= s;
double v2 = v0 - g * t;
v0 = v2;
}
}
十、小鳥添加到畫布
和柱子一樣,不做贅述
效果如下:

十一、實現(xiàn)鍵盤監(jiān)聽
注意:實現(xiàn)加鍵盤監(jiān)聽需要畫布獲得焦點(diǎn)
方法1:main 方法中設(shè)置
gameJPenal.requestFocus();
方法2:直接在GamePanel 類設(shè)置
setFocusable(true);
主要代碼:
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (gameOver) {
if (e.getKeyCode() == KeyEvent.VK_UP) {
gameOver = false;
gameStart = false;
try {
ground = new Ground();
c0 = new Column(0);
c1 = new Column(1);
bird = new Bird();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
} else if (gameStart) {
if (e.getKeyCode() == KeyEvent.VK_UP) {
bird.moveUP();
}
} else {
start();
}
}
});
十二、判斷游戲結(jié)束的方法
判斷游戲結(jié)束其實比較簡單,通過檢測小鳥的x, y 坐標(biāo)以及小鳥圖片的寬度與柱子的x , y 坐標(biāo)加上柱子的寬度做一個碰撞檢測
/*
* 判定游戲是否結(jié)束的方法
* */
public void isGameOver() {
//先判斷是否落地
if (bird.y <= 0 || bird.y >= 512 - ground.land.getHeight() - bird.img.getHeight()) {
gameOver = true;
}
//判斷是否撞柱子
int bh = bird.img.getHeight();
int bw = bird.img.getWidth();
//c0.img1, c0.x1, c0.y1, c0.w1, c0.h1, null
if (c0.x1 <= bird.x + bw && c0.x1 + c0.w1 >= bird.x && c0.y1 + c0.h1 >= bird.y) {
gameOver = true;
}
if (c1.x1 <= bird.x + bw && c1.x1 + c1.w1 >= bird.x && c1.y1 + c1.h1 >= bird.y) {
gameOver = true;
}
if (c0.x2 <= bird.x + bw && c0.x2 + c0.w2 >= bird.x && c0.y2 <= bird.y + bh) {
gameOver = true;
}
if (c1.x2 <= bird.x + bw && c1.x2 + c1.w2 >= bird.x && c1.y2 <= bird.y + bh) {
gameOver = true;
}
}
if (gameStart == false) {
g.drawImage(imageStart, 50, 200, null);
}
if (gameOver == true) {
g.drawImage(imageGameOver, 50, 200, null);
十三、游戲結(jié)束后回到準(zhǔn)備狀態(tài)
這里其實就是狀態(tài)重置,寫在在鍵盤監(jiān)聽事件里面,邏輯就是游戲結(jié)束,只要按了Up鍵,游戲就重置為準(zhǔn)備狀態(tài)。同時,在游戲 paint 方法中加一個判斷,不同的狀態(tài)對應(yīng)不同的圖片
if (gameOver) {
if (e.getKeyCode() == KeyEvent.VK_UP) {
gameOver = false;
gameStart = false;
try {
ground = new Ground();
c0 = new Column(0);
c1 = new Column(1);
bird = new Bird();
score = 0;
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
repaint();
十四、統(tǒng)計分?jǐn)?shù)
分?jǐn)?shù)統(tǒng)計原理:小鳥的x坐標(biāo)和柱子的( x+w)相等,分?jǐn)?shù)就+1
if (bird.x == c0.x1 + c0.w1 || bird.x == c1.x1 + c1.w1) {
score++;
}
總結(jié)
游戲結(jié)構(gòu)并不復(fù)雜,只需要逐步實現(xiàn)每個功能即可。然后用到的主要是一些比較基礎(chǔ)的知識,對于鞏固基礎(chǔ)知識還是有一定幫助的。
第一次寫博客,可能不夠精簡,可讀性不強(qiáng)。下次加油 。
到此這篇關(guān)于Java實戰(zhàn)之飛翔的小鳥小游戲的文章就介紹到這了,更多相關(guān)java飛翔的小鳥內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot框架阿里開源低代碼工具LowCodeEngine
這篇文章主要為大家介紹了springboot框架阿里開源低代碼LowCodeEngine工具使用詳解有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
Java使用Iterator迭代器遍歷集合數(shù)據(jù)的方法小結(jié)
這篇文章主要介紹了Java使用Iterator迭代器遍歷集合數(shù)據(jù)的方法,結(jié)合實例形式分析了java迭代器進(jìn)行集合數(shù)據(jù)遍歷的常見操作技巧,需要的朋友可以參考下2019-11-11
SpringBoot整合Redis之編寫RedisConfig
RedisConfig需要對redis提供的兩個Template的序列化配置,所以本文為大家詳細(xì)介紹了SpringBoot整合Redis如何編寫RedisConfig,需要的可以參考下2022-06-06
MyBatisPlus?TypeHandler自定義字段類型轉(zhuǎn)換Handler
這篇文章主要為大家介紹了MyBatisPlus?TypeHandler自定義字段類型轉(zhuǎn)換Handler示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08

