您好,登錄后才能下訂單哦!
這期內容當中小編將會給大家帶來有關Java如何實現年獸大作戰游戲,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
春節要到了,看慣了前端各種小游戲,確實做得很好,很精致。但是我也要為后端程序員稍微做一點貢獻,做一款java版本的【年獸大作戰】。
這個游戲加上編寫文章,上班摸魚時間加上回家的空閑時間,大概花了三天多。
java寫這玩應真的很痛苦,各種狀態位,各種圖片和邏輯判斷,腦袋都快炸了。而且肯定沒有前端的精致,效果一般,偶爾會有卡頓,各位就圖一樂,隨便捧捧場啊。過程大于結果。
源碼gitee地址
進入初始界面,會看到一只大年獸位于正中間,然后是一直小老虎,也就是我們的玩家,點擊【空格】即可開始游戲:
敲擊空格,將進入游戲。從上至下分別是:
年獸的血量【NIAN'S HP】
移動的年獸
最下方的小老虎【玩家】
玩家通過【←】【→】鍵移動小老虎方向,使用【S】鍵發射炮彈:
當擊中年獸后,會有煙花出現在背景:
每擊中年獸三次,年獸會扔下炸彈:
如果玩家被擊中,則直接【game over】 ,通過【空格】鍵重新開始:
當每擊中年獸10次,其血量就會減少一個,年獸會隨機扔下不同種類的爆竹,當前是11種,玩家可以移動方向鍵獲取:
當玩家成功接到炮彈后,再次擊中年獸,會更換背景煙花的種類。原本我想把子彈也換了,后來是實在整不動了。我玩了半天,想截個圖,半天沒成功,給自己心態玩崩了,就是下面的煙火:
當把年獸擊敗后,會出現新年快樂的字樣:
上述就是全部玩法了,其實可以有更多擴展的,java寫這東西實在寫的太痛苦了。
效果不太好,但是學學代碼實現總是好的吧,下面我簡單說說怎么實現的。
使用Frame作為界面的基礎和入口,可以設置大小,標題,展示位置等等,最主要的再次基礎上添加一個面板,是我們游戲的實現:
public static void main(String[] args) { //1.創建窗口對象 Frame frame = new Frame("年獸大作戰"); // 設置窗體大小為900x800 frame.setSize(900, 800); // 設置窗體為居中格式 frame.setLocationRelativeTo(null); // 設置窗體不可改變 frame.setResizable(false); // 在窗體中添加一個面板 frame.add(new GamePanel()); // 設置窗體可見 frame.setVisible(true); // 窗口點擊關閉 frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent arg0) { System.exit(0); } }); }
第一步,定義一個空參構造,需要添加焦點事件,和鍵盤事件監聽,定時器啟動頁面刷新,后面后有定時器的創建:
public GamePanel() { // 獲取焦點事件 this.setFocusable(true); // 添加鍵盤監聽事件 this.addKeyListener(this); // 啟動定時器 timer.start(); }
在啟動類當中,我們在Frame當中添加了一個GamePanel,作用是后面游戲的所有內容展現都在其中,包括頁面,游戲邏輯等。
代碼較為復雜,我只說關鍵點,全部代碼在全篇開頭的gitee鏈接,感興趣自己獲取。
class GamePanel extends JPanel implements KeyListener, ActionListener
如上所示,GamePannel繼承了JPanel,同時實現了KeyListener和ActionListener。
JPanel
這是jdk提供的,使用java進行繪圖的基礎容器。面板不會向除其自身背景以外的任何內容添加顏色。但是,您可以輕松地為它們添加邊框,并以其他方式自定義它們的繪畫。
KeyListener
這個接口是用來監聽鍵盤事件的接口,提供一下幾個方法:
public interface KeyListener extends EventListener { /** * 當按鍵被鍵入時調用 */ public void keyTyped(KeyEvent e); /** * 當按鍵下壓時調用 */ public void keyPressed(KeyEvent e); /** * 當按鍵釋放時調用 public void keyReleased(KeyEvent e); }
本文中我使用了keyPressed和keyReleased。
keyPressed主要用來完成鍵盤操作的移動,和射擊功能。每當我們有按鍵操作,都會被它監聽到,產生相應的事件。
此處有坑: 如果將按鍵一個一個的在此處判斷,比如 if(左鍵) else if(設計) 這樣,那么當你同時按下這兩個按鍵,將會導致它們都失效。
解決辦法如下:
定義一個全局Set,用來存放每次按鍵的事件。
static Set<Integer> keys = new HashSet<>();
當按鍵下壓時添加:
/** * description: 鍵盤按下未釋放 * * @param e * @return: void * @author: weirx * @time: 2022/1/10 14:02 */ @SneakyThrows @Override public void keyPressed(KeyEvent e) { // 添加按鈕下壓事件到set InitProcessor.keys.add(e.getKeyCode()); // 遍歷執行按鈕事件 multiKeys(); }
在執行一個遍歷方法,不斷地去執行業務邏輯判斷:
public void multiKeys() { for (Integer key : InitProcessor.keys) { int keyCode = key; //空格鍵 if (keyCode == KeyEvent.VK_SPACE) { } // 方向左鍵 else if (keyCode == KeyEvent.VK_LEFT) { } // 射擊 else if (keyCode == KeyEvent.VK_S) { } } }
然后在我們釋放按鍵的時候,使用如下的方式將這個set的key釋放掉:
/** * description: 釋放按鍵 * @param e * @return: void * @author: weirx * @time: 2022/1/11 15:39 */ @Override public void keyReleased(KeyEvent e) { //按鈕釋放,則將該事件移除 InitProcessor.keys.remove(e.getKeyCode()); }
動作監聽器【ActionListener】
這個是整個畫面能夠動態呈現的引擎,我們使用定時器的方式,每到定時時間則會監聽到動作事件,進行數據邏輯判斷。
其接口如下:
public interface ActionListener extends EventListener { /** * 當事件發生時 */ public void actionPerformed(ActionEvent e); }
定時器定義:
/** * 定時器 */ private Timer timer = new Timer(15, this);
接口actionPerformed部分代碼展示:
/** * description: 定時器回調位置 * @param e * @return: void * @author: weirx * @time: 2022/1/11 15:38 */ @Override public void actionPerformed(ActionEvent e) { // 當前年獸向右移動的情況 if (InitProcessor.LEFT.equals(InitProcessor.moveDirection)) { // 被擊中,換方向 if (InitProcessor.hit) { InitProcessor.moveDirection = InitProcessor.RIGHT; } // 判斷移動到邊界 if (InitProcessor.nian_x > 30) { InitProcessor.nian_x -= InitProcessor.moveSpeed * 2; } else { InitProcessor.moveDirection = InitProcessor.RIGHT; InitProcessor.nian_x += InitProcessor.moveSpeed * 2; } } else { // 被擊中,換方向 if (InitProcessor.hit) { InitProcessor.moveDirection = InitProcessor.LEFT; } // 當前年獸向左移動的情況 // 判斷移動到邊界 if (InitProcessor.nian_x < 640) { InitProcessor.nian_x += InitProcessor.moveSpeed * 2; } else { InitProcessor.moveDirection = InitProcessor.LEFT; InitProcessor.nian_x -= InitProcessor.moveSpeed * 2; } } //設置煙火的展示時間,定時器刷新50次,不準確,但是至少能明顯感受到煙花存在 if (InitProcessor.hitShow == 50) { InitProcessor.hit = false; InitProcessor.hitShow = 0; } // 自增展示次數 InitProcessor.hitShow++; // 刷新頁面 repaint(); timer.start();//啟動計時器 }
到以上為止,按鍵事件,和定時器事件都完成了,可以說全部的邏輯判斷都在上面去實現。下面我們關注在圖像是如何出現的。
圖像展示基礎【JComponent】
前面我們似乎沒有看到這個組件的身影,那么它是在哪里呢?看下面的類圖:
如上所示,GamePanel繼承JPanel,而JPanel又繼承了JComponent。JComponent有一個方法我們需要重寫,這也就是我們實現圖像展示的方法,其提供了繪制UI的能力,我們重寫即可,部分代碼如下:
/** * description: 畫頁面 * * @param g * @return: void * @author: weirx * @time: 2022/1/10 13:40 */ @Override protected void paintComponent(Graphics g) { // 清屏效果 super.paintComponent(g); // 游戲未開始 if (!InitProcessor.isStared) { background.paintIcon(this, g,0,0); InitProcessor.nian.paintIcon(this, g, 250, 130); InitProcessor.tiger.paintIcon(this, g, 220, 470); // 繪制首頁 // 設置游戲文字 g.setColor(Color.ORANGE); g.setFont(new Font("幼圓", Font.BOLD, 50)); g.drawString("年獸大作戰", 325, 550); // 設置開始提示 g.setColor(Color.GREEN); g.setFont(new Font("幼圓", Font.BOLD, 30)); g.drawString("按【空格】鍵開始游戲", 300, 620); g.drawString("按【←】【→】鍵移動", 300, 660); g.drawString("按【S】鍵發射炮彈", 300, 700); } else if (isGameOver) { //輸出gameover InitProcessor.gameOver.paintIcon(this, g, 10, 10); // 設置開始提示 g.setColor(Color.GREEN); g.setFont(new Font("幼圓", Font.BOLD, 20)); g.drawString("按【空格】再次開始游戲", 340, 600); } }
關鍵點是使用Graphics繪制文字,背景,顏色等等內容。
圖片需要使用ImageIcon類來進行繪畫,我將ImageIcon初始化部分封裝了,所以上面沒顯示,常規使用如下:
ImageIcon nian = new ImageIcon(PATH_PREFIX + "nian.png"); nian.paintIcon(this, g, 250, 130);
為什么這么說是血液呢?因為這個類是我自己實現的一個初始化類,其中的內容是串聯整個游戲的關鍵點,像身體的血液一樣。
通過寫這個游戲,我發現最關鍵的點在于【狀態】,可以說全部的頁面動畫展示都在于一個狀態,無論是子彈的運動,年獸的運動,包括禮花圖片的切換,以及各種圖片的坐標等等。
所以我專門抽象了這個類,用于各種狀態的初始化,部分代碼如下:
/** * @description: 初始化處理器 * @author:weirx * @date:2022/1/11 10:15 * @version:3.0 */ public class InitProcessor { /** * 游戲是否開始,默認是false */ public static Boolean isStared = false; /** * 游戲是否暫停,默認是false */ public static Boolean isStopped = false; /** * 禮花橫坐標 */ public static int youWillBeKill_x = 0; /** * 禮花縱坐標 */ public static int youWillBeKill_y = nian_y + 200; /** * 展示炮彈 */ public static Boolean showYouWillBeKill = false; public static Boolean isGameOver = false; /** * 圖片路徑 */ public final static String PATH_PREFIX = "src/main/java/com/wjbgn/nianfight/pic/"; public static ImageIcon nian = new ImageIcon(PATH_PREFIX + "nian.png"); public static ImageIcon tiger = new ImageIcon(PATH_PREFIX + "tiger\tiger2.png"); public static ImageIcon heart = new ImageIcon(PATH_PREFIX + "blood\heart.png"); /** * 禮花容器 */ public static List<FireworksDO> fireworksDOS = initFireworks(); /** * 花容器 */ public static List<FlowersDO> flowersDOS = initFlowers(); /** * 初始化爆竹 */ private static List<FlowersDO> initFlowers() { List<FlowersDO> list = new ArrayList<>(); list.add(new FlowersDO(1, PATH_PREFIX + "flowers\flower1.png")); list.add(new FlowersDO(2, PATH_PREFIX + "fireworks\flower2.png")); list.add(new FlowersDO(3, PATH_PREFIX + "fireworks\flower3.png")); list.add(new FlowersDO(4, PATH_PREFIX + "fireworks\flower4.png")); list.add(new FlowersDO(5, PATH_PREFIX + "fireworks\flower5.png")); list.add(new FlowersDO(6, PATH_PREFIX + "fireworks\flower6.png")); list.add(new FlowersDO(7, PATH_PREFIX + "fireworks\flower7.png")); list.add(new FlowersDO(8, PATH_PREFIX + "fireworks\flower8.png")); list.add(new FlowersDO(9, PATH_PREFIX + "fireworks\flower9.png")); list.add(new FlowersDO(10, PATH_PREFIX + "fireworks\flower10.png")); list.add(new FlowersDO(11, PATH_PREFIX + "fireworks\flower11.png")); return list; } /** * description: 初始化禮花種類 * * @return: void * @author: weirx * @time: 2022/1/11 10:58 */ public static List<FireworksDO> initFireworks() { List<FireworksDO> list = new ArrayList<>(); list.add(new FireworksDO(1, PATH_PREFIX + "fireworks\fireworks1.png")); list.add(new FireworksDO(2, PATH_PREFIX + "fireworks\fireworks2.png")); list.add(new FireworksDO(3, PATH_PREFIX + "fireworks\fireworks3.png")); list.add(new FireworksDO(4, PATH_PREFIX + "fireworks\fireworks4.png")); list.add(new FireworksDO(5, PATH_PREFIX + "fireworks\fireworks5.png")); list.add(new FireworksDO(6, PATH_PREFIX + "fireworks\fireworks6.png")); list.add(new FireworksDO(7, PATH_PREFIX + "fireworks\fireworks7.png")); list.add(new FireworksDO(8, PATH_PREFIX + "fireworks\fireworks8.png")); list.add(new FireworksDO(9, PATH_PREFIX + "fireworks\fireworks9.png")); list.add(new FireworksDO(10, PATH_PREFIX + "fireworks\fireworks10.png")); list.add(new FireworksDO(11, PATH_PREFIX + "fireworks\fireworks11.png")); return list; } /** * description: 初始化方法,用于重新開始游戲 * * @return: void * @author: weirx * @time: 2022/1/11 10:39 */ public static void init() { isStared = false; isStopped = false; attack = false; nian_x = 325; nian_y = 50; tiger_x = 325; tiger_y = 660; bullet_x = tiger_x + 20; bullet_y = tiger_y - 20; moveSpeed = 1; moveDirection = LEFT; hit = false; hitCount = 0; hitShow = 0; nianBlood = 10; success = false; keys = new HashSet<>(); fireworks_x = 0; fireworks_y = nian_y + 200; showFireworks = false; currentFireworks = null; takeFireworks = false; currentFlowers = null; youWillBeKill = 0; youWillBeKill_x = 0; youWillBeKill_y = nian_y + 200; showYouWillBeKill = false; isGameOver = false; } }
這是兩個實體類,分別定義的爆竹和禮花樣式,用于初始化,代碼如下:
import javax.swing.*; import static com.wjbgn.nianfight.nianshou.InitProcessor.PATH_PREFIX; /** * @description: 花容器 * @author:weirx * @date:2022/1/11 11:05 * @version:3.0 */ public class FlowersDO { private Integer id; private String path; private ImageIcon flower; public ImageIcon getFlower() { return flower; } public void setFlower(ImageIcon flower) { this.flower = flower; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public FlowersDO(Integer id, String path) { this.id = id; this.path = path; this.flower = new ImageIcon(PATH_PREFIX + "flowers\flower" + id + ".png"); } }
/** * @description: 禮花實體類 * @author:weirx * @date:2022/1/11 11:01 * @version:3.0 */ public class FireworksDO { /** * id */ private Integer id; /** * 圖片路徑 */ private String path; public ImageIcon getFirework() { return firework; } public void setFirework(ImageIcon firework) { this.firework = firework; } private ImageIcon firework; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public FireworksDO(Integer id, String path) { this.id = id; this.path = path; this.firework = new ImageIcon(PATH_PREFIX + "fireworks\fireworks" + id + ".png"); } }
游戲中使用了大量的圖片素材,我在網上找了兩個網站不錯,一個是png圖片網站,是免費的,還有一個是免費切圖的,挺好用,都分享給大家
免費png圖片網址(英文):www.cleanpng.com/
免費切圖網址(簡單版直接微信關注):www.uupoop.com/
我使用的素材都在項目的pic目錄下:
寫java好幾年了,其實從沒有使用 javax.swing 和 java.awt 包下面的內容開發過代碼,對于現在用戶體驗為前提的大環境下,綜合編碼體驗,和游戲運行體驗來看,確實是不太友好,不太符合環境背景。但是也是一次不錯的學習過程。
問題總結
目前整個游戲還是存在一些bug的,后面有時間再翻出來調試吧,此處先記錄一下:
子彈有時并不是從老虎正前方射出的,與實際的坐標存在偏差:此問題出現的原因我推測來自線程間共享變量的同步問題。子彈的初始橫坐標取決于小老虎當前所在的橫坐標,這個坐標同步沒做好。
關于按鍵切換、同時兩個按鍵等情況造成卡頓的問題:前面解決兩個按鍵同時按下的方案可能不是最優解,后面還需要優化。
怪獸扔炸彈、爆竹隨著年獸移動存在偏移:炸彈和爆竹的初始橫坐標,是年獸當前的橫坐標,需要一個變量記錄當前年獸的位置,作為炸彈和爆竹的初始橫坐標。
上述就是小編為大家分享的Java如何實現年獸大作戰游戲了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。