用JAVA实现一个简易的打砖块小游戏

概述

利用java实现打砖块小游戏,游戏界面包括以下元素

  • 顶部的各色砖块
  • 底部的挡板
  • 小球
  • 游戏信息展示面板
  • 玩法包括

使用鼠标或者键盘移动挡板

  • 打掉砖块,得分
  • 每一局有3次机会,挡板没有接到小球,机会减1
  • 打掉黑色小球,会掉落道具,挡板接到道具,挡板会变长
  • 可以选择游戏难度等级

功能实现

首先,整个Class继承自 GraphicsProgram, 程序启动后,先执行init() 方法,然后执行 run() 方法

public class Arkanoid extends GraphicsProgram
{
    
    
    public void init()
    {
    
    
        // 进行初始化
    }

    public void run()
    {
    
    
        // 具体执行
    }
}

然后,在 init() 中,进行游戏界面的创建。

创建砖块

创建砖块前,先准备一个存储颜色和颜色对应的分值的容器,这里用Map来实现,键保存Color对象,值保存分值

Map<Color,Integer> brickColors;

colors=new Color[]{
    
    
        Color.BLACK,Color.BLUE,Color.CYAN,Color.DARK_GRAY,Color.GRAY,Color.GREEN,
        Color.LIGHT_GRAY,Color.MAGENTA,Color.ORANGE,Color.PINK,Color.RED,Color.YELLOW
};
brickColors=new HashMap<>();
for(int i=0;i<colors.length;i++)
{
    
    
    Color color=colors[i];
    // 黑色砖块是特殊砖块,打掉不得分,但是可以掉落胶囊
    if(color.getRGB() == Color.BLACK.getRGB())
    {
    
    
        brickColors.put(color,0);
    }
    else
    {
    
    
        brickColors.put(color,colors.length%(i+1)+1);
    }
}

有了砖块颜色,接下来的就是创建指定行,列的砖块了,这里用 GRect 表示砖块。

需要注意的一点是,GRect 默认有1像素的边框,如果不去掉它,在计算砖块的宽度时,要考虑到这1像素。

final int ROWS=6;
final int COLUMNS=12;

// 砖块实心部分的宽,高
brickW=GAME_PANE_WIDTH/COLUMNS;
brickH=brickW/2;

for(int i=0;i<ROWS;i++)
{
    
    
    for(int j=0;j<COLUMNS;j++)
    {
    
    
        GRect brick=new GRect(brickW,brickH);
        brick.setFilled(true);
        brick.setColor(colors[randomGenerator.nextInt(0,brickColors.size()-1)]);

        brick.setLineWidth(0);
        double x=j*brickW;
        double y=i*brickH;
        add(brick,x,y);
    }
}

创建挡板和小球

创建挡板和小球,分别使用 GRect和GOval,然后将其添加到适合的位置即可。

创建游戏信息展示面板

在游戏信息展示面板中,可以根据游戏要实现的功能,添加一些面板来展示信息。

这里创建了包括【游戏机会】【得分】【游戏难度】【再来一局】等面板。

最外部使用JPanel创建,布局方式为GirdLayout,然后将每一项添加到其中即可。

使用鼠标或键盘操纵挡板移动

要想实现让挡板跟随鼠标移动的功能,需要为添加鼠标监听器,直接添加到游戏界面最外层组件上即可.

调用方法 addMouseListeners() 然后重写 mouseMoved(MouseEvent) 方法,在其中根据鼠标位置,设置挡板的位置即可。注意限制挡板的移动范围,不让其超出游戏界面。

addMouseListeners();

public void mouseMoved(MouseEvent e)
{
    
    
    if(gameover) // 游戏结束,恢复挡板在底部正中央
    {
    
    
        paddle.setLocation(GAME_PANE_WIDTH/2.-paddle.getWidth()/2,GAME_PANE_HEIGHT-paddle.getHeight()-30);
    }
    else // 游戏运行中,动态改变挡板水平方向的位置
    {
    
    
        //paddle.setLocation(e.getX()-PADDLE_W/2,e.getY()-PADDLE_H/2);
        // 限制挡板左侧和右侧不能超出游戏界面的范围
        double x=e.getX()-paddle.getWidth()/2;
        if(x<0)
        {
    
    
            x=0;
        }
        if(x>GAME_PANE_WIDTH-paddle.getWidth())
        {
    
    
            x=GAME_PANE_WIDTH-paddle.getWidth();
        }
        paddle.setLocation(x,paddle.getY());
    }
}

与使用鼠标类似,使用键盘操作挡板的移动,也需要监听按键事件监听器。

addKeyListeners();

@Override
public void keyTyped(KeyEvent e)
{
    
    
    double x=paddle.getX();

    char keyChar=e.getKeyChar();
    if(keyChar == '1') // 左移
    {
    
    
        x-=paddle.getWidth()*1.5;
        if(x<=0)
        {
    
    
            x=0;
        }
    }
    else if(keyChar == '2') // 右移
    {
    
    
        x+=paddle.getWidth()*1.5;
        if(x>GAME_PANE_WIDTH-paddle.getWidth())
        {
    
    
            x=GAME_PANE_WIDTH-paddle.getWidth();
        }
    }
    paddle.setLocation(x,paddle.getY());
}

移动小球

移动小球,只需要在一个循环中,只要游戏还未结束,就一直调用 move() 即可。

需要在游戏开始前,给小球一个初始移动速度,并且每次游戏的速度不同。

VELOCITY_X=3;
VELOCITY_Y=5;
boolean gameover=false;

public void init()
{
    
    
    int offset=randomGenerator.nextInt(0,3);
    vx=VELOCITY_X+offset;        // 水平速度
    vy=-(VELOCITY_Y+offset);        // 竖直速度
}

public void run()
{
    
    
    while(!gameover)
    {
    
    
        ball.move(vx,vy);
        pause(DELAY);
    }
}

检测小球是否撞到墙面

根据游戏界面的宽高和小球当前的坐标,判断小球是否接触到游戏界面的边框,以此来决定小球是否撞到了墙面。

如果小球撞到的上或下墙面,就取反y方向的速度,如果撞到左或右墙面,就取反x方向的速度

代码类似以下

if(hitBottomWall() || hitTopWall())
{
    
    
    vy=-vy;
}
if(hitLeftWall() || hitRightWall())
{
    
    
    vx=-vx;
}

小球撞到砖块反弹,砖块消失

首先,获取到小球撞到的砖块。

需要检测4个顶点,这里只给出示例代码

GObject getCollidingObject()
{
    
    
    GObject element=getElementAt(x,y);
    if(element!=null)
        return element;
    return null;
}

然后,根据撞到砖块的位置,改变小球的移动轨迹

1块砖块根据4条边延伸出去的线,可以将其周围分为8个区域:

  • 正上,正下
  • 正左,正右
  • 左上,左下,右上,右下

如果小球球心处于正上或正下区域,只取反其y方向的速度即可;

如果小球球心处于正左或正右区域,只取反其x方向的速度即可;

如果小球球心处于其他区域,说明它撞到的正好是砖块的4个顶点,x,y方向的速度同时取反。

boolean changeX=false;
boolean changeY=false;

private void changeXY(GObject element,double center_x,double center_y)
{
    
    
    double left_brick=element.getX();
    double top_brick=element.getY();
    double right_brick=left_brick+brickW;
    double bottom_brick=top_brick+brickH;

    if(center_y>top_brick && center_y<bottom_brick) // 小球在砖块的左侧,或右侧
    {
    
    
        changeX=true;
    }
    else if(center_x>left_brick && center_x<right_brick) // 小球在砖块的上侧,或xia侧
    {
    
    
        changeY=true;
    }
    else // 小球在砖块的4个顶点范围
    {
    
    
        changeX=true;
        changeY=true;
    }
}

最后,在run方法中,根据变量 changeX 和 changeY,改变vx 和 vy

GObject element=getCollidingObject();
if(element != null)
{
    
    
    if(element == paddle) // 如果打到挡板,反弹
    {
    
    
        vy=-vy;
        // 加一下速
        if(vx>0)
            vx+=va;
        else
            vx-=va;
        if(vy>0)
            vy+=va;
        else
            vy-=va;
        continue;
    }
    else
    {
    
    
        if(changeX)
            vx=-vx;
        if(changeY)
            vy=-vy;
        remove(element);
    }
}

得分

在打到砖块时,只需要根据砖块的颜色,获取到该颜色对应的分数,将其加到得分中即可

GObject element=getCollidingObject();
if(element != null)
{
    
    
    Color color=element.getColor();
    int point=brickColors.get(color);
    score+=point;
    scoreLabel_num.setText(""+score);
}

掉落道具,接到道具,挡板变长

打到黑色砖块,在砖块下边出现一个道具,竖直下落;如果在道具降到下边墙之前,使用挡板接到道具,挡板将变长。

首先定义一个数组列表,用于存储掉落的道具;

打到黑色砖块后,创建一个道具,将其添加到容器中;

最后,让容器中的道具“落下”;

如果挡板接到了道具,重新设置其尺寸。

ArrayList<GImage> falls;

if(color.getRGB() == Color.BLACK.getRGB())// 如果是黑砖块,落下道具
{
    
    
    Toolkit toolkit=Toolkit.getDefaultToolkit();
    Image image=toolkit.createImage("fall.png");
    GImage fall=new GImage(image);
    fall.setSize(PADDLE_W,PADDLE_W);
    add(fall,element.getX(),element.getY());
    falls.add(fall);
}

for(GImage fall:falls)
{
    
    
    if(fall != null)
    {
    
    
        fall.move(0,5);

        // 接到了道具
        if(Math.abs(paddle.getY()-fall.getY()-fall.getHeight())<=0.1 && (Math.abs(fall.getX()-paddle.getX())<=paddle.getWidth()-0.1))
        {
    
    
            remove(fall);
            double newWidth=paddle.getWidth()*1.5;
            if(newWidth>=GAME_PANE_WIDTH)
            {
    
    
                newWidth=GAME_PANE_WIDTH;
            }
            paddle.setSize(newWidth,paddle.getHeight());
        }
    }
}

设置游戏难度等级

这里的难度,只是简单的在小球被挡板反弹时,增加其速度。根据所加速的幅度大小,分为
【简单】【一般】【困难】3个难度等级。

创建3个单选按钮,设置其监听器,在其选中时改变加速幅度

double va=0;

JRadioButton simpleBtn=new JRadioButton("简单",true);
JRadioButton middleBtn=new JRadioButton("一般");
JRadioButton diffcultyBtn=new JRadioButton("困难");

simpleBtn.addActionListener(e ->
{
    
    
    va=0;
});
middleBtn.addActionListener(e ->
{
    
    
    va=1;
});
diffcultyBtn.addActionListener(e ->
{
    
    
    va=2;
});

然后,在run方法中,将加速加到vx,vy中

if(element == paddle) // 如果打到挡板,反弹
{
    
    
    vy=-vy;
    // 加一下速
    if(vx>0)
        vx+=va;
    else
        vx-=va;
    if(vy>0)
        vy+=va;
    else
        vy-=va;
}

猜你喜欢

转载自blog.csdn.net/weixin_52308504/article/details/113615281