Java 碰壁小球游戏实例教程

Posted Wiiix

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 碰壁小球游戏实例教程相关的知识,希望对你有一定的参考价值。

本教程会举例用Java实现简单的小球碰壁反弹游戏,效果如图所示:


JFrame: 窗体部分

首先创建一个窗体界面,下面举一个简单的例子(Jframe是自己定义的类名,注意和JFrame的区别)

import javax.swing.JFrame; //没导入就没卵用
public class Jframe 
 
public static void main(String[] args) 

JFrame frame = new JFrame("小球碰壁"); //创建Jframe对象,参数是窗体标题
frame.setSize(300, 300); //设置窗口大小
frame.setVisible(true); //设置可见性,没设置的话就看不见
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //这个是退出时的操作,没加这一行可能关闭后会继续运行

 


通过继承JFrame也可以

import javax.swing.JFrame;
public class Game extends JFrame //继承父类Jframe

 
public static void main(String[] args) 
Game tennis=new Game(); //创建game对象
 

Game() //自己的构造函数,同时也会调用父类的构造函数来创建对象

this.setTitle("碰壁球游戏");
this.setSize(500,500);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);



如图:

 

 

 

JPanel: 绘图部分

有窗体接下来就是绘图部分,绘图就需要用一个画布JPanel(Jpanel是自己定义的类名,注意和JPanel的区别)

 

import javax.swing.JPanel;
import javax.swing.JFrame;
import java.awt.*;
public class Jpanel extends JPanel
public void paint(Graphics g)  //paint方法中的Graphics g是从Graphic继承的对象
 
g.setColor(Color.BLUE); //设置颜色
g.fillOval(0, 0, 30, 30); //参数分别为坐标x,y,球的宽度,高度
g.drawString("这是一个小球", 0, 50); //参数分别为字符串,坐标x,y

public static void main(String[] args) 
JFrame frame = new JFrame("Tennis");
frame.add(new Jpanel());
frame.setSize(300, 300);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);



如图:

 

 

最后整合一下前面两部分所学的知识创建一个Game类。因为Java不支持多继承所以创建多一个继承JPanelDraw类。

import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
public class Game extends JFrame //继承父类Jframe

 
public static void main(String[] args) 
Game tennis=new Game(); //创建game对象
 

Game() //自己的构造函数,同时也会调用父类的构造函数来创建对象

Draw ball=new Draw();
this.add(ball);
this.setTitle("碰壁球游戏");
this.setSize(600,600);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);


class Draw extends JPanel //这里创建一个继承JPanel的子类

public void paint(Graphics g) 
super.paint(g); //调用父类的paint
Graphics2D g2d = (Graphics2D) g; //进行对象转型,因为Graphic2D的方法比较好也比较丰富,所以这里用Graphics2D,Graphics是旧的类
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON); //这个是防止边缘有锯齿的
g2d.fillOval(0, 0, 60, 60); 



g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)的效果图.

 

 

接下来要让球移动,球的位置是根据当前所赋值的坐标决定的,所以每次绘制小球的时候我们需要通过更改x,y让小球达到移动的效果,这样的话我们就把刚才的代码改成g2d.fillOval(x, y, 30, 30);然后再通过repaint()方法重新绘制。

原理就是一直更新并重新绘制新的小球使其达到移动的效果。

因此这里打算写一个moveBall()的方法来不断改变x,y的值。

稍微修改一下之前的代码:

import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
 
public class Game extends JFrame //继承父类Jframe

public static void main(String[] args) throws InterruptedException //这个是抛出异常用来防止线程异常的,需要加

Game tennis=new Game(); //创建game对象
Draw ball=new Draw(); //绘制一个新的小球,因为要循环调用所以放在Draw类里面x,y会一直重新定义成0所以在这里定义
while (true) 
tennis.add(ball); //循环添加新的小球
ball.moveBall(); //更新小球的位置(只是更新位置,还没绘制);
tennis.repaint(); //重新绘制
Thread.sleep(5); //延迟5毫秒再绘制,不然小球会移动很快一闪而过


Game() 

this.setTitle("碰壁球游戏");
this.setSize(600,600);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);


class Draw extends JPanel 

 int x=0; //小球的默认位置
 int y=0;
void moveBall() //这个方法就是不断更新小球的位置

x++;
y++;

public void paint(Graphics g) 
super.paint(g); 
Graphics2D g2d = (Graphics2D) g; 
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.fillOval(x, y, 60, 60); 

 


运行一下小球开始移动了,可以看见小球不断循环的向右下角移动.

 

然后再也回不来了~

 

然而并没有什么卵用,我们要让小球碰壁再弹回来,改变小球的方向和速度。这里只要改一下moveBall()方法便可以实现。

 

class Draw extends JPanel 

 int x=0; //小球的默认位置
 int y=0;
 int incx=1; //这是小球位置要移动的方向
 int incy=1;
void moveBall() //这个方法就是不断更新小球的位置

if(x+incx>getWidth()-60) //如果小球移动后的位置超出窗体范围的话,移动方向就是一直-1;因为要考虑球的大小所以-60
incx=-1;
if(x+incx<0) //同理
incx=1;
if(y+incy>getHeight()-60)
incy=-1;
if(y+incy<0)
incy=1;
x+=incx;
y+=incy;

public void paint(Graphics g) 
super.paint(g); 
Graphics2D g2d = (Graphics2D) g; 
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.fillOval(x, y, 60, 60); 

 


 

现在小球碰到窗体边缘就会改变方向了,看起来就像是碰壁反弹。

为了使代码看起来更简洁这里另外创建一个Ball类用来存放小球的各种属性方法。这里改了一下窗体高度。

现在总共有两个类。

Game

import javax.swing.JFrame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
 
public class Game extends JFrame //继承父类Jframe

Ball ball=new Ball(this); //这里创建一个Ball对象,this作为参数让Ball类可以获取Game的成员信息
private void move() //这里是move方法用来调用Ball类中的moveBall
 
ball.moveBall();

public static void main(String[] args) throws InterruptedException //这个是抛出异常用来防止线程异常的,需要加

Game tennis=new Game(); //创建game对象
while (true) 
tennis.move(); //因为访问限制所以通过move方法来调用Ball类中的moveBall方法
tennis.repaint(); //重新绘制
Thread.sleep(5); //延迟5毫秒再绘制,不然小球会移动很快一闪而过


Game() 

this.setTitle("碰壁球游戏");
this.setSize(600,800);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

public void paint(Graphics g) 
super.paint(g);  //不添加这个的话旧的小球不会被擦除
Graphics2D g2d = (Graphics2D) g; 
ball.paint(g2d); //调用ball类中的paint方法
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
 



Ball

import javax.swing.*;
import java.awt.Graphics2D;
 
public class Ball extends JPanel
 int x=0; //小球的默认位置
 int y=0;
 int incx=1; //这是小球位置要移动的方向
 int incy=1;
 private Game tennis;
public Ball(Game tennis)

this.tennis=tennis;

void moveBall() //这个方法就是不断更新小球的位置

 
if(x+incx>tennis.getWidth()-60) //如果小球移动后的位置超出窗体范围的话,移动方向就是一直-1;因为要考虑球的大小所以-60
incx=-1;
if(x+incx<0) //同理
incx=1;
if(y+incy>tennis.getHeight()-60)
incy=-1;
if(y+incy<0)
incy=1;
x+=incx;
y+=incy;

public void paint(Graphics2D g)

g.fillOval(x, y, 60, 60); 



运行结果跟刚才的一样。

小球碰壁游戏有一个长形状方块可以由玩家控制,碰到小球的话可以让小球反弹回去。所以接下来就是事件监听部分,监听键盘的事件才可以控制方块要移动的方向。

这里举出一个简单的键盘监听事件的例子

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
 
import javax.swing.JFrame;
import javax.swing.JPanel;
 
@SuppressWarnings("serial")
public class Keyboard extends JPanel 
String str="还没按";
public Keyboard()  

KeyListener listener = new MyKeyListener(this); //创建一个键盘监听器对象
addKeyListener(listener);  //注册
setFocusable(true);  //

public static void main(String[] args) 
JFrame frame = new JFrame("键盘监听器");
Keyboard keyboardExample = new Keyboard();
frame.add(keyboardExample); //添加对象
frame.setSize(200, 200);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

public void paint(Graphics g) 
super.paint(g);  
Graphics2D g2d = (Graphics2D) g; 
g2d.setColor(Color.black);
  g.setFont(new Font("宋体",Font.BOLD,60));  
g2d.drawString(str,100,70);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
 

public class MyKeyListener implements KeyListener  //这个是实现接口,所以必须重新定义里面的方法

private Keyboard kb;
 MyKeyListener (Keyboard kb)
 
this.kb=kb;
 
 @Override //检查下面的方法在父类中是否存在
public void keyTyped(KeyEvent e) 


 
@Override
public void keyPressed(KeyEvent e) //按下键盘事件

str=KeyEvent.getKeyText(e.getKeyCode());
kb.repaint();
//System.out.println("按下的按键"+KeyEvent.getKeyText(e.getKeyCode()));

 
@Override
public void keyReleased(KeyEvent e) //放开键盘事件

//System.out.println("松开的按键="+KeyEvent.getKeyText(e.getKeyCode()));




运行结果如图:


所按下的按键对应的文本都会显示在窗体上。

 

现在按的按键都能监听到了,因为上面的MyKeyListener类中只用了一次,因此我们可以改用为匿名对象并简化代码。

package Tutorial;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
@SuppressWarnings("serial")
public class Keyboard2 extends JPanel 
public Keyboard2() 
KeyListener listener = new KeyListener() 
@Override
public void keyTyped(KeyEvent e) 

@Override
public void keyPressed(KeyEvent e) 
System.out.println("你按下了"+KeyEvent.getKeyText(e.getKeyCode()));

@Override
public void keyReleased(KeyEvent e) 
System.out.println("你松开了"+KeyEvent.getKeyText(e.getKeyCode()));

;
addKeyListener(listener);
setFocusable(true);

 
public static void main(String[] args) 
JFrame frame = new JFrame("键盘监听器");
Keyboard2 keyboardExample = new Keyboard2();
frame.add(keyboardExample); 
frame.setSize(200, 200);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

 


然后是创建球拍了,ball类一样,创建一个球拍Racquet类,所做的更动只有以下两个类,Game类添加一个键盘监听和Racquet对象,Ball类不变.

Racquet

import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import javax.swing.JPanel;
public class Racquet extends JPanel
int x=0;
int xa=0;
private Game game;
public Racquet(Game game)

this.game=game;

public void move()

if(x+xa<game.getWidth()-120 && x+xa>0)
x+=xa;

public void paint(Graphics2D g)

g.fillRect(x, 660, 120, 20);

public void KeyReleased(KeyEvent e)

xa=0;

public void KeyPressed(KeyEvent e)

if(e.getKeyCode()==KeyEvent.VK_LEFT)
xa=-2;
if(e.getKeyCode()==KeyEvent.VK_RIGHT)
xa=2;



Game

import javax.swing.JFrame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.*;
public class Game extends JFrame //继承父类Jframe

Ball ball=new Ball(this); //这里创建一个Ball对象,this作为参数让Ball类可以获取Game的成员信息
Racquet racquet=new Racquet(this);
public Game()

this.setTitle("碰壁球游戏");
this.setSize(600,800);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
KeyListener listener=new KeyListener()

 
@Override
public void keyPressed(KeyEvent e) 
// TODO Auto-generated method stub
racquet.KeyPressed(e);

 
@Override
public void keyReleased(KeyEvent e) 
// TODO Auto-generated method stub
racquet.KeyReleased(e);

 
@Override
public void keyTyped(KeyEvent arg0) 
// TODO Auto-generated method stub

;
addKeyListener(listener);
setFocusable(true);

private void move() //这里是move方法用来调用Ball类中的moveBall
 
ball.moveBall();
racquet.move();

public static void main(String[] args) throws InterruptedException //这个是抛出异常用来防止线程异常的,需要加

Game tennis=new Game(); //创建game对象
while (true) 
tennis.move(); //因为访问限制所以通过move方法来调用Ball类中的moveBall方法
tennis.repaint(); //重新绘制
Thread.sleep(5); //延迟5毫秒再绘制,不然小球会移动很快一闪而过


 
public void paint(Graphics g) 
super.paint(g);  //不添加这个的话旧的小球不会被擦除
Graphics2D g2d = (Graphics2D) g; 
ball.paint(g2d); //调用ball类中的paint方法
racquet.paint(g2d);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
 



 

运行如图:

 

这里我们先在Game类中添加GameOver()函数。

public void gameOver() 
 	JOptionPane.showMessageDialog(this, "Game Over", "Game Over", JOptionPane.YES_NO_OPTION);
 	System.exit(ABORT);

作用就是当游戏结束会弹出一个提示窗口,点击后并退出游戏。

 


现在按键盘方向键可以控制球拍的运行了,那么问题来了,球和方块之间并不会产生碰撞。

我们会用两个矩形来检测物体之间的碰撞。java.awt.Rectangle里面有提供一个intersects方法可以用来判断两个方形是否相交,如图3,4会返回true。在图四中小球是没有碰到球拍的,但是看做方形的话有碰到球拍,所以还是会返回true。可以不用在意这些细节,因为本教程的目的主要是用来学习。

改一下Racquet

import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
 
import javax.swing.JPanel;
 
public class Racquet extends JPanel
private static final int y= 660; //添加三个球拍属性的final变量,因为已经确定下来了不会再改
private static final int WIDTH = 120;
private static final int HEIGHT = 20;
int x=0;
int xa=0;
private Game game;
public Racquet(Game game)

this.game=game;

public void move()

if(x+xa<game.getWidth()-120 && x+xa>0)
x+=xa;

public void paint(Graphics2D g)

g.fillRect(x, y, WIDTH, HEIGHT);

public void KeyReleased(KeyEvent e)

xa=0;

public void KeyPressed(KeyEvent e)

if(e.getKeyCode()==KeyEvent.VK_LEFT)
xa=-2;
if(e.getKeyCode()==KeyEvent.VK_RIGHT)
xa=2;

public Rectangle getBounds()  //返回当前Rectangle类型的球拍

return new Rectangle(x, y, WIDTH, HEIGHT);

 
public int getTopY()  //返回球拍所在的水平线

return y;



再改一下Ball

import javax.swing.*;
import java.awt.Graphics2D;
import java.awt.Rectangle;
 
public class Ball extends JPanel
private static final int ballsize=60;
 int x=0; //小球的默认位置
 int y=0;
 int incx=1; //这是小球位置要移动的方向
 int incy=1;
 private Game tennis;
public Ball(Game tennis)

this.tennis=tennis;

void moveBall() //这个方法就是不断更新小球的位置

 
if(x+incx>tennis.getWidth()-ballsize) //如果小球移动后的位置超出窗体范围的话,移动方向就是一直-1;因为要考虑球的大小所以-60
incx=-1;
if(x+incx<0) //同理
incx=1;
if(y+incy>tennis.getHeight()-ballsize)
tennis.gameOver(); // 如果碰到底部就游戏结束
if(y+incy<0)
incy=1;
if (collision()) //如果检测到碰撞就改变方向

incy = -1;
y = tennis.racquet.getTopY() - ballsize;//这个是矫正球的位置,为了防止碰撞导致的就球拍和小球重叠

x+=incx;
y+=incy;

public void paint(Graphics2D g)

g.fillOval(x, y, ballsize, ballsize); 

public Rectangle getBounds()//返回Rectangle类型的小球

return new Rectangle(x,y,ballsize,ballsize);

private boolean collision()  //这个是检测碰撞

return tennis.racquet.getBounds().intersects(getBounds()); //用intersects方法判断小球是否和球拍相交



运行玩玩一下,效果图:


 

 

到这里基本大功告成了。接下来就是分数部分,稍微添加些代码就可以了。在Game中定义一个score变量用来计数,在collisiontrue时分数递增,然后通过paint绘制出来。

最终成果:

Class Game

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.*;
public class Game extends JFrame //继承父类Jframe

Ball ball=new Ball(this); //这里创建一个Ball对象,this作为参数让Ball类可以获取Game的成员信息
Racquet racquet=new Racquet(this);
static int score;
public Game()

this.setTitle("碰壁球游戏");
this.setSize(600,800);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
KeyListener listener=new KeyListener()

 
@Override
public void keyPressed(KeyEvent e) 
// TODO Auto-generated method stub
racquet.KeyPressed(e);

 
@Override
public void keyReleased(KeyEvent e) 
// TODO Auto-generated method stub
racquet.KeyReleased(e);

 
@Override
public void keyTyped(KeyEvent arg0) 
// TODO Auto-generated method stub

;
addKeyListener(listener);
setFocusable(true);

private void move() //这里是move方法用来调用Ball类中的moveBall
 
ball.moveBall();
racquet.move();

public static void main(String[] args) throws InterruptedException //这个是抛出异常用来防止线程异常的,需要加

Game tennis=new Game(); //创建game对象
while (true) 
 
tennis.move(); //因为访问限制所以通过move方法来调用Ball类中的moveBall方法
tennis.repaint(); //重新绘制
Thread.sleep(2); //延迟5毫秒再绘制,不然小球会移动很快一闪而过


public void gameOver() 
JOptionPane.showMessageDialog(this, "Game Over", "Game Over", JOptionPane.YES_NO_OPTION);
System.exit(ABORT);

public void paint(Graphics g) 
 
super.paint(g);  //不添加这个的话旧的小球不会被擦除
Graphics2D g2d = (Graphics2D) g; 
ball.paint(g2d); //调用ball类中的paint方法
racquet.paint(g2d);
g2d.setColor(Color.GRAY);
g2d.setFont(new Font("Verdana", Font.BOLD, 50));
g2d.drawString(String.valueOf(score), 20, 120);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
 



Class Ball

import javax.swing.*;
import java.awt.Graphics2D;
import java.awt.Rectangle;
 
public class Ball extends JPanel
private static final int ballsize=60;
 int x=0; //小球的默认位置
 int y=0;
 int incx=1; //这是小球位置要移动的方向
 int incy=1;
 private Game tennis;
public Ball(Game tennis)

this.tennis=tennis;

void moveBall() //这个方法就是不断更新小球的位置

 
if(x+incx>tennis.getWidth()-ballsize) //如果小球移动后的位置超出窗体范围的话,移动方向就是一直-1;因为要考虑球的大小所以-60
incx=-1;
if(x+incx<0) //同理
incx=1;
if(y+incy>tennis.getHeight()-ballsize)
tennis.gameOver();
if(y+incy<0)
incy=1;
if (collision()) //如果检测到碰撞就改变方向

incy = -1;
y = tennis.racquet.getTopY() - ballsize;//这个是矫正球的位置,为了防止碰撞导致的就球拍和小球重叠
tennis.score++;

x+=incx;
y+=incy;

public void paint(Graphics2D g)

g.fillOval(x, y, ballsize, ballsize); 

public Rectangle getBounds()//返回Rectangle类型的小球

return new Rectangle(x,y,ballsize,ballsize);

private boolean collision()  //这个是检测碰撞

return tennis.racquet.getBounds().intersects(getBounds()); //用intersects方法判断小球是否和球拍相交



Class Racquet

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
 
import javax.swing.JPanel;
 
public class Racquet extends JPanel
private static final int y= 660; //添加三个球拍属性的final变量,因为已经确定下来了不会再改
private static final int WIDTH = 120;
private static final int HEIGHT = 20;
int x=0;
int xa=0;
private Game game;
public Racquet(Game game)

this.game=game;

public void move()

if(x+xa<game.getWidth()-120 && x+xa>0)
x+=xa;

public void paint(Graphics2D g)

g.fillRect(x, y, WIDTH, HEIGHT);
 

public void KeyReleased(KeyEvent e)

xa=0;

public void KeyPressed(KeyEvent e)

if(e.getKeyCode()==KeyEvent.VK_LEFT)
xa=-2;
if(e.getKeyCode()==KeyEvent.VK_RIGHT)
xa=2;

public Rectangle getBounds()  //返回当前Rectangle的球拍

return new Rectangle(x, y, WIDTH, HEIGHT);

 
public int getTopY()  //返回球拍所在的水平线

return y;



 

 

到这里大家就可以根据自己的喜好对小球,球拍的速度做调整,也可以自己添加一个开始按钮来开始游戏。

希望生成可运行文件的可以通过File>Export>Java>Runnable Jar File 来导出jar文件。

本教程到此结束,讲解中的不足之处请见谅,谢谢大家了~

以上是关于Java 碰壁小球游戏实例教程的主要内容,如果未能解决你的问题,请参考以下文章

java_弹球小游戏

帆布小球碰壁效果

1-Python游戏编程-弹跳小球游戏(教程+源码)steam少儿编程课件

1-Python游戏编程-弹跳小球游戏(教程+源码)steam少儿编程课件

1-Python游戏编程-弹跳小球游戏(教程+源码)steam少儿编程课件

Python程序开发之简单小程序实例(11)小游戏-跳动的小球