基于Java Swing制作一個(gè)Pong小游戲
之前呢我們用Python的Pygame做過(guò)這個(gè)Pong游戲
這一次,我們用Java的Swing來(lái)實(shí)現(xiàn)類(lèi)似的效果
首先我們列出本次的項(xiàng)目結(jié)構(gòu)
這個(gè)程序分為四個(gè)部分,一個(gè)程序入口,一個(gè)模型,一個(gè)刷新幀,一個(gè)視圖,模型里面放入球和擋板的類(lèi),視圖里面放入主窗口Frame和主面板Panel
接下來(lái)是項(xiàng)目目錄
src資源下面,我們把東西全部寫(xiě)到com.mr包下,main里的Start是主入口,model里面分別是Ball和Board,service下是刷新幀的服務(wù),view視圖下分別為主窗體和主面板
好啦,現(xiàn)在先來(lái)寫(xiě)GameFrame的代碼
package com.mr.view; import javax.swing.*; import java.awt.*; public class GameFrame extends JFrame { private Container container; private GamePanel panel; public GameFrame() { setTitle("Pong"); setBounds(300,300,850,1000); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); container=getContentPane(); panel=new GamePanel(); addKeyListener(panel); container.add(panel); setVisible(true); } }
首先啊,還是和平常一樣聲明package,導(dǎo)入一些東西,然后主窗體繼承自JFrame,設(shè)置以下標(biāo)題和窗口大小,還有關(guān)閉后退出程序的設(shè)置(setDefaultCloseOperation)然后實(shí)例化GamePanel面板(待會(huì)寫(xiě)),然后綁定事件并添加到container容器中,設(shè)置窗口可見(jiàn)
然后是GamePanel的代碼,也是整個(gè)程序的核心,在這里我們要做出對(duì)賽點(diǎn)、得分、球體碰撞等檢測(cè),并繪制圖形
package com.mr.view; import com.mr.model.Ball; import com.mr.model.Board; import com.mr.service.Fresh; import javax.swing.*; import java.awt.*; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.image.BufferedImage; import java.util.ArrayList; public class GamePanel extends JPanel implements KeyListener { private BufferedImage img; private Graphics2D g2; public ArrayList<Object> select; private Fresh fresh; private Board b1; private Board b2; private Ball ball; private int score1; private int score2; private int winPoint; private int matchPoint; // 0表示沒(méi)有賽點(diǎn),1表示玩家1的賽點(diǎn),2表示玩家2的賽點(diǎn) private int winner; public GamePanel() { img=new BufferedImage(850,1000,BufferedImage.TYPE_INT_BGR); g2=img.createGraphics(); select=new ArrayList<>(); b1=new Board(0); b2=new Board(1); ball=new Ball(); score1=0; score2=0; matchPoint=0; winPoint=11; winner=0; fresh=new Fresh(this); fresh.start(); } private void paintImage() { g2.setColor(Color.WHITE); if (winner==0) { int width=10; g2.fillRect(425-width/2,0,width,1000); g2.fillRect(b1.x,b1.y,b1.width,b1.height); g2.fillRect(b2.x,b2.y,b2.width,b2.height); g2.fillOval(ball.x-ball.r,ball.y-ball.r,ball.r*2,ball.r*2); } g2.setFont(new Font("黑體",Font.BOLD,56)); g2.drawString(String.valueOf(score1),295,150); g2.drawString(String.valueOf(score2),525,150); g2.setFont(new Font("黑體",Font.PLAIN,22)); if (winner==0) { switch (matchPoint) { case 1: g2.drawString("賽點(diǎn)",295,200); break; case 2: g2.drawString("賽點(diǎn)",525,200); break; default: break; } } else { g2.drawString("玩家"+String.valueOf(winner)+"獲勝",winner==1?295:525,200); } } public void paint(Graphics g) { if (winner==0) { move(); ball.move(b1.getBound(),b2.getBound()); checkPoint(); checkMatchPoint(); b1.checkBound(); b2.checkBound(); } g2.setColor(Color.BLACK); g2.fillRect(0,0,850,1000); paintImage(); g.drawImage(img,0,0,this); } private void checkPoint() { if (ball.x<=0) { score2+=1; } else if (ball.x+ball.r*2>=850) { score1+=1; } else { return; } ball=new Ball(); } private void checkMatchPoint() { if (score1==winPoint) { winner=1; return; } else if (score2==winPoint) { winner=2; return; } if (score1+1==winPoint&&score2+1!=winPoint) { matchPoint=1; } else if (score1+1!=winPoint&&score2+1==winPoint) { matchPoint=2; } else if (score1+1==winPoint&&score2+1==winPoint) { matchPoint=0; winPoint++; } else if (score1+1!=winPoint&&score2+1!=winPoint) { matchPoint=0; } } private void move() { for (Object code:select) { if (code==(Object)KeyEvent.VK_W) { b1.y-=b1.speed; } else if (code==(Object)KeyEvent.VK_S) { b1.y+=b1.speed; } else if (code==(Object)KeyEvent.VK_UP) { b2.y-=b2.speed; } else if (code==(Object)KeyEvent.VK_DOWN) { b2.y+=b2.speed; } } } @Override public void keyPressed(KeyEvent event) { if (select.indexOf(event.getKeyCode())==-1) { select.add(event.getKeyCode()); } } @Override public void keyReleased(KeyEvent event) { select.remove((Object)event.getKeyCode()); } @Override public void keyTyped(KeyEvent event) { ; } }
聲明com.mr.view包下,導(dǎo)入一些東西,然后創(chuàng)建主類(lèi)GamePanel,繼承自JPanel并實(shí)現(xiàn)KeyListener事件,接下來(lái)就是聲明變量,一個(gè)主圖片img以及對(duì)應(yīng)的g2,select用于儲(chǔ)存按下的按鍵,這個(gè)待會(huì)講,接下來(lái)是fresh刷新幀線程,兩塊板,一個(gè)球,兩個(gè)玩家的分?jǐn)?shù),勝利所需要的分?jǐn)?shù),賽點(diǎn)歸屬于誰(shuí),是否出現(xiàn)了贏家等。接下來(lái)來(lái)到構(gòu)造函數(shù),先創(chuàng)建img主圖片850x1000還有g(shù)2,初始化一些東西,這里Board的0和1表示歸屬于哪個(gè)玩家,0則為左手邊的,1則為右手邊的。接下來(lái)創(chuàng)建球(ps.這些類(lèi)待會(huì)就來(lái)寫(xiě)),然后分?jǐn)?shù)初始化,賽點(diǎn)為0,表示沒(méi)有人獲得賽點(diǎn),獲勝分?jǐn)?shù)為11分,和乒乓球一樣,然后winner為0表示沒(méi)有贏家,創(chuàng)建線程,這里傳入了自己,是為了待會(huì)可以通過(guò)repaint方式不斷重繪,然后啟動(dòng)線程。paint中,沒(méi)有獲勝,則移動(dòng)板(move),移動(dòng)球,檢查得分,檢查賽點(diǎn),保持兩塊板處于場(chǎng)內(nèi)(窗口內(nèi)),然后設(shè)置顏色為黑色,填充背景為黑色,繪制,并把主圖片畫(huà)在g中。paintImage中對(duì)板、球、得分、中線進(jìn)行繪制,不難理解。好,GamePanel的最后,我們來(lái)講講剛剛的那個(gè)select,因?yàn)槲覀円瑫r(shí)檢測(cè)做個(gè)鍵盤(pán)按鈕,但是這個(gè)KeyListener只支持一次性按下一個(gè),按下多個(gè)也只會(huì)獲取到一個(gè),所以我們用一種方法來(lái)解決,我們創(chuàng)建一個(gè)select數(shù)組,按下按鍵且按鍵不在select中則添加到select中,松開(kāi)則刪除,這樣同時(shí)按下多個(gè)按鈕,這些按鈕(就像排著隊(duì)伍一樣)相繼添加到select中,這樣也便于了我們移動(dòng)板的if判斷。
接下來(lái)我們來(lái)看看一個(gè)同樣非常重要的Fresh刷新幀線程
package com.mr.service; import com.mr.view.GamePanel; public class Fresh extends Thread { public final int INTERVAL=20; private GamePanel panel; public Fresh(GamePanel panel) { this.panel=panel; } public void run() { while (true) { panel.repaint(); try { Thread.sleep(INTERVAL); } catch (InterruptedException e) { e.printStackTrace(); } } } }
這個(gè)就while循環(huán)實(shí)現(xiàn)啦~~~
然后是Ball
package com.mr.model; import java.awt.*; import java.util.Random; public class Ball { public int x; public int y; public int r; private int xspeed; private int yspeed; private int max_speed; private int min_speed; private int max_speed2; private int min_speed2; public Ball() { x=425; y=500; r=15; Random rd=new Random(); xspeed=rd.nextInt(1,3)==1?rd.nextInt(4,7):rd.nextInt(-6,-3); yspeed=rd.nextInt(1,3)==1?rd.nextInt(4,7):rd.nextInt(-6,-3); max_speed=12; min_speed=3; max_speed2=-3; min_speed2=-12; } public void move(Rectangle b1,Rectangle b2) { x+=xspeed; y+=yspeed; Random rd=new Random(); if (getBound().intersects(b1)||getBound().intersects(b2)) { xspeed=-xspeed; yspeed+=rd.nextInt(-2,3); } else if (y-r<=0||y+r>=965) { yspeed=-yspeed; xspeed+=rd.nextInt(-2,3); } if (xspeed>max_speed&&xspeed>0) { xspeed=max_speed; } if (yspeed>max_speed&&yspeed>0) { yspeed=max_speed; } if (xspeed<min_speed&&xspeed>0) { xspeed=min_speed; } if (yspeed<min_speed&&yspeed>0) { yspeed=min_speed; } if (xspeed>max_speed2&&xspeed<0) { xspeed=max_speed2; } if (yspeed>max_speed&&yspeed<0) { yspeed=max_speed2; } if (xspeed<min_speed&&xspeed<0) { xspeed=min_speed2; } if (yspeed<min_speed&&yspeed<0) { yspeed=min_speed2; } } private Rectangle getBound() { return new Rectangle(x-r,y-r,r*2,r*2); } }
這個(gè)模型嘛,一般情況下都有x和y還有大小,因?yàn)檫@個(gè)是個(gè)圓,所以我們用半徑r,然后xspeed和yspeed表示各個(gè)方向的速度從而實(shí)現(xiàn)斜著移動(dòng),還有max_speed和min_speed用于把動(dòng)態(tài)變換的速度限制于這個(gè)范圍內(nèi),待會(huì)每碰到一次墻壁或板,就會(huì)適當(dāng)增加或減少速度,所以要把速度限制在特定范圍內(nèi),max_speed2和min_speed2也一樣,前2者是用于正數(shù)速度的,后2者是用于負(fù)數(shù)速度的,然后移動(dòng)的時(shí)候就對(duì)一些碰撞等情況進(jìn)行檢測(cè)就好了,getBound用于返回對(duì)象的Rect長(zhǎng)方形對(duì)象,用于檢測(cè)碰撞。
最后是Board
package com.mr.model; import java.awt.*; public class Board { public int x; public int y; public int width; public int height; public int speed; public Board(int type) { width=8; height=120; speed=15; y=500-height/2; if (type==0) { x=0; } else { x=850-15-width; } } public void checkBound() { if (y+height>=985) { y=985-height; } if (y<=0) { y=0; } } public Rectangle getBound() { return new Rectangle(x,y,width,height); } }
Board和Ball也有著差不多的一些參數(shù)和類(lèi)成員和方法
現(xiàn)在全部就都寫(xiě)完了,最后畫(huà)上點(diǎn)睛之筆,寫(xiě)上最后的程序入口就大功告成啦!??!
Start.java:
package com.mr.main; import com.mr.view.GameFrame; public class Start { public static void main(String[] args) { GameFrame gameFrame=new GameFrame(); } }
到此這篇關(guān)于基于Java Swing制作一個(gè)Pong小游戲的文章就介紹到這了,更多相關(guān)Java Swing制作Pong游戲內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot+RabbitMQ方式收發(fā)消息的實(shí)現(xiàn)示例
這篇文章主要介紹了SpringBoot+RabbitMQ方式收發(fā)消息的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09使用Mybatis接收Integer參數(shù)的問(wèn)題
這篇文章主要介紹了使用Mybatis接收Integer參數(shù)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03spring cloud升級(jí)到spring boot 2.x/Finchley.RELEASE遇到的坑
這篇文章主要介紹了spring cloud升級(jí)到spring boot 2.x/Finchley.RELEASE遇到的坑,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08java數(shù)學(xué)歸納法非遞歸求斐波那契數(shù)列的方法
這篇文章主要介紹了java數(shù)學(xué)歸納法非遞歸求斐波那契數(shù)列的方法,涉及java非遞歸算法的使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07java編程調(diào)用存儲(chǔ)過(guò)程中得到新增記錄id號(hào)的實(shí)現(xiàn)方法
這篇文章主要介紹了java編程調(diào)用存儲(chǔ)過(guò)程中得到新增記錄id號(hào)的實(shí)現(xiàn)方法,涉及Java數(shù)據(jù)庫(kù)操作中存儲(chǔ)過(guò)程的相關(guān)使用技巧,需要的朋友可以參考下2015-10-10Java創(chuàng)建、識(shí)別條形碼和二維碼方法示例
這篇文章主要給大家介紹了關(guān)于Java創(chuàng)建、識(shí)別條形碼和二維碼的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09java poi設(shè)置生成的word的圖片為上下型環(huán)繞以及其位置的實(shí)現(xiàn)
這篇文章主要介紹了java poi設(shè)置生成的word的圖片為上下型環(huán)繞以及其位置的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09Java線程池FutureTask實(shí)現(xiàn)原理詳解
這篇文章主要介紹了Java線程池FutureTask實(shí)現(xiàn)原理詳解,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02