之前呢我们用Python的Pygame做过这个Pong游戏
这一次,我们用Java的Swing来实现类似的效果
首先我们列出本次的项目结构
这个程序分为四个部分,一个程序入口,一个模型,一个刷新帧,一个视图,模型里面放入球和挡板的类,视图里面放入主窗口Frame和主面板Panel
接下来是项目目录
src资源下面,我们把东西全部写到com.mr包下,main里的Start是主入口,model里面分别是Ball和Board,service下是刷新帧的服务,view视图下分别为主窗体和主面板
好啦,现在先来写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,850,1000); | |
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); | |
container=getContentPane(); | |
panel=new GamePanel(); | |
addKeyListener(panel); | |
container.add(panel); | |
setVisible(true); | |
} | |
} |
首先啊,还是和平常一样声明package,导入一些东西,然后主窗体继承自JFrame,设置以下标题和窗口大小,还有关闭后退出程序的设置(setDefaultCloseOperation)然后实例化GamePanel面板(待会写),然后绑定事件并添加到container容器中,设置窗口可见
然后是GamePanel的代码,也是整个程序的核心,在这里我们要做出对赛点、得分、球体碰撞等检测,并绘制图形
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 GraphicsD g2; | |
public ArrayList<Object> select; | |
private Fresh fresh; | |
private Board b; | |
private Board b; | |
private Ball ball; | |
private int score; | |
private int score; | |
private int winPoint; | |
private int matchPoint; //表示没有赛点,1表示玩家1的赛点,2表示玩家2的赛点 | |
private int winner; | |
public GamePanel() { | |
img=new BufferedImage(,1000,BufferedImage.TYPE_INT_BGR); | |
g=img.createGraphics(); | |
select=new ArrayList<>(); | |
b=new Board(0); | |
b=new Board(1); | |
ball=new Ball(); | |
score=0; | |
score=0; | |
matchPoint=; | |
winPoint=; | |
winner=; | |
fresh=new Fresh(this); | |
fresh.start(); | |
} | |
private void paintImage() { | |
g.setColor(Color.WHITE); | |
if (winner==) { | |
int width=; | |
g.fillRect(425-width/2,0,width,1000); | |
g.fillRect(b1.x,b1.y,b1.width,b1.height); | |
g.fillRect(b2.x,b2.y,b2.width,b2.height); | |
g.fillOval(ball.x-ball.r,ball.y-ball.r,ball.r*2,ball.r*2); | |
} | |
g.setFont(new Font("黑体",Font.BOLD,56)); | |
g.drawString(String.valueOf(score1),295,150); | |
g.drawString(String.valueOf(score2),525,150); | |
g.setFont(new Font("黑体",Font.PLAIN,22)); | |
if (winner==) { | |
switch (matchPoint) { | |
case: | |
g.drawString("赛点",295,200); | |
break; | |
case: | |
g.drawString("赛点",525,200); | |
break; | |
default: | |
break; | |
} | |
} else { | |
g.drawString("玩家"+String.valueOf(winner)+"获胜",winner==1?295:525,200); | |
} | |
} | |
public void paint(Graphics g) { | |
if (winner==) { | |
move(); | |
ball.move(b.getBound(),b2.getBound()); | |
checkPoint(); | |
checkMatchPoint(); | |
b.checkBound(); | |
b.checkBound(); | |
} | |
g.setColor(Color.BLACK); | |
g.fillRect(0,0,850,1000); | |
paintImage(); | |
g.drawImage(img,,0,this); | |
} | |
private void checkPoint() { | |
if (ball.x<=) { | |
score+=1; | |
} else if (ball.x+ball.r*>=850) { | |
score+=1; | |
} else { | |
return; | |
} | |
ball=new Ball(); | |
} | |
private void checkMatchPoint() { | |
if (score==winPoint) { | |
winner=; | |
return; | |
} else if (score==winPoint) { | |
winner=; | |
return; | |
} | |
if (score+1==winPoint&&score2+1!=winPoint) { | |
matchPoint=; | |
} else if (score+1!=winPoint&&score2+1==winPoint) { | |
matchPoint=; | |
} else if (score+1==winPoint&&score2+1==winPoint) { | |
matchPoint=; | |
winPoint++; | |
} else if (score+1!=winPoint&&score2+1!=winPoint) { | |
matchPoint=; | |
} | |
} | |
private void move() { | |
for (Object code:select) { | |
if (code==(Object)KeyEvent.VK_W) { | |
b.y-=b1.speed; | |
} else if (code==(Object)KeyEvent.VK_S) { | |
b.y+=b1.speed; | |
} else if (code==(Object)KeyEvent.VK_UP) { | |
b.y-=b2.speed; | |
} else if (code==(Object)KeyEvent.VK_DOWN) { | |
b.y+=b2.speed; | |
} | |
} | |
} | |
public void keyPressed(KeyEvent event) { | |
if (select.indexOf(event.getKeyCode())==-) { | |
select.add(event.getKeyCode()); | |
} | |
} | |
public void keyReleased(KeyEvent event) { | |
select.remove((Object)event.getKeyCode()); | |
} | |
public void keyTyped(KeyEvent event) { | |
; | |
} | |
} |
声明com.mr.view包下,导入一些东西,然后创建主类GamePanel,继承自JPanel并实现KeyListener事件,接下来就是声明变量,一个主图片img以及对应的g2,select用于储存按下的按键,这个待会讲,接下来是fresh刷新帧线程,两块板,一个球,两个玩家的分数,胜利所需要的分数,赛点归属于谁,是否出现了赢家等。接下来来到构造函数,先创建img主图片850x1000还有g2,初始化一些东西,这里Board的0和1表示归属于哪个玩家,0则为左手边的,1则为右手边的。接下来创建球(ps.这些类待会就来写),然后分数初始化,赛点为0,表示没有人获得赛点,获胜分数为11分,和乒乓球一样,然后winner为0表示没有赢家,创建线程,这里传入了自己,是为了待会可以通过repaint方式不断重绘,然后启动线程。paint中,没有获胜,则移动板(move),移动球,检查得分,检查赛点,保持两块板处于场内(窗口内),然后设置颜色为黑色,填充背景为黑色,绘制,并把主图片画在g中。paintImage中对板、球、得分、中线进行绘制,不难理解。好,GamePanel的最后,我们来讲讲刚刚的那个select,因为我们要同时检测做个键盘按钮,但是这个KeyListener只支持一次性按下一个,按下多个也只会获取到一个,所以我们用一种方法来解决,我们创建一个select数组,按下按键且按键不在select中则添加到select中,松开则删除,这样同时按下多个按钮,这些按钮(就像排着队伍一样)相继添加到select中,这样也便于了我们移动板的if判断。
接下来我们来看看一个同样非常重要的Fresh刷新帧线程
package com.mr.service; | |
import com.mr.view.GamePanel; | |
public class Fresh extends Thread { | |
public final int INTERVAL=; | |
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(); | |
} | |
} | |
} | |
} |
这个就while循环实现啦~~~
然后是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_speed; | |
private int min_speed; | |
public Ball() { | |
x=; | |
y=; | |
r=; | |
Random rd=new Random(); | |
xspeed=rd.nextInt(,3)==1?rd.nextInt(4,7):rd.nextInt(-6,-3); | |
yspeed=rd.nextInt(,3)==1?rd.nextInt(4,7):rd.nextInt(-6,-3); | |
max_speed=; | |
min_speed=; | |
max_speed=-3; | |
min_speed=-12; | |
} | |
public void move(Rectangle b,Rectangle b2) { | |
x+=xspeed; | |
y+=yspeed; | |
Random rd=new Random(); | |
if (getBound().intersects(b)||getBound().intersects(b2)) { | |
xspeed=-xspeed; | |
yspeed+=rd.nextInt(-,3); | |
} | |
else if (y-r<=||y+r>=965) { | |
yspeed=-yspeed; | |
xspeed+=rd.nextInt(-,3); | |
} | |
if (xspeed>max_speed&&xspeed>) { | |
xspeed=max_speed; | |
} | |
if (yspeed>max_speed&&yspeed>) { | |
yspeed=max_speed; | |
} | |
if (xspeed<min_speed&&xspeed>) { | |
xspeed=min_speed; | |
} | |
if (yspeed<min_speed&&yspeed>) { | |
yspeed=min_speed; | |
} | |
if (xspeed>max_speed&&xspeed<0) { | |
xspeed=max_speed; | |
} | |
if (yspeed>max_speed&&yspeed<) { | |
yspeed=max_speed; | |
} | |
if (xspeed<min_speed&&xspeed<) { | |
xspeed=min_speed; | |
} | |
if (yspeed<min_speed&&yspeed<) { | |
yspeed=min_speed; | |
} | |
} | |
private Rectangle getBound() { | |
return new Rectangle(x-r,y-r,r*,r*2); | |
} | |
} |
这个模型嘛,一般情况下都有x和y还有大小,因为这个是个圆,所以我们用半径r,然后xspeed和yspeed表示各个方向的速度从而实现斜着移动,还有max_speed和min_speed用于把动态变换的速度限制于这个范围内,待会每碰到一次墙壁或板,就会适当增加或减少速度,所以要把速度限制在特定范围内,max_speed2和min_speed2也一样,前2者是用于正数速度的,后2者是用于负数速度的,然后移动的时候就对一些碰撞等情况进行检测就好了,getBound用于返回对象的Rect长方形对象,用于检测碰撞。
最后是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=; | |
height=; | |
speed=; | |
y=-height/2; | |
if (type==) { | |
x=; | |
} else { | |
x=-15-width; | |
} | |
} | |
public void checkBound() { | |
if (y+height>=) { | |
y=-height; | |
} | |
if (y<=) { | |
y=; | |
} | |
} | |
public Rectangle getBound() { | |
return new Rectangle(x,y,width,height); | |
} | |
} |
Board和Ball也有着差不多的一些参数和类成员和方法
现在全部就都写完了,最后画上点睛之笔,写上最后的程序入口就大功告成啦!!!
Start.java:
package com.mr.main; | |
import com.mr.view.GameFrame; | |
public class Start { | |
public static void main(String[] args) { | |
GameFrame gameFrame=new GameFrame(); | |
} | |
} |