Java Swing制作古老的打砖块游戏

 最近研究了一下古老的Java Swing,研究之余,突发奇想开发了一个打砖块小游戏。

 其实游戏的原理,自己理解就是屏幕的游戏画面范围内,游戏中的物体根据与用户的交互不断改变位置,改变位置后,屏幕清空一帧的显示内容,然后游戏根据新的位置重新进行显示,道理和动画非常相似,一直在不停的擦除重绘,然后在合适的物体碰撞时机配合音效播放,加上其他如物理系统,也就是仿现实世界的重力、摩擦力……等的影响进行渲染或音效的播放(愤怒的小鸟游戏体现的就挺明显的),就形成了有意思的游戏软件,真是有意思的软件。

 粒子特效更是实现了好多美轮美奂的游戏的效果,经过了解发现,游戏中的特效根数学联系都密切的,牵扯到好多数学公式,擅长数学的人做游戏更有优势吧。当然这只是和用户交互的部分,现在的网络游戏还有很重要的服务器部分,了解有限,就不说了。

 首先看一下效果图brick game
在这里插入图片描述

1.游戏框架搭建

 整个游戏的画面的绘制渲染都位于JFrame上面,通过重写JFrame的paint(Graphics g)方法,完成所有游戏对象的绘制和各种效果的处理。注意不要调用super.paint(g)方法。

[在这个过程中参考了一个大牛的Java 2D游戏系列文章,在此表示由衷的感谢。]具体参考:XcantloadX

1.1步骤

  1. 这个过程主要是启动了一个游戏运行的窗口;
  2. 为了让游戏中的对象动起来增加了一个渲染线程,按固定频率刷新;
  3. 为与用户交互,控制挡板,窗口增加了键盘事件的监听和处理;
  4. 当游戏中的对象动起来的时候出现闪烁现象,增加了双缓冲处理解决之;

2.开发过程

 为了方便和简单,在游戏开发过程中最终没有使用图片,也尝试了图片的小球、挡板、砖块,但是最终决定用最简单的几何图形绘制来表现各种游戏对象,在打砖块中只有简单的圆形和矩形,没有复杂的形状。

2.1各种游戏对象的绘制

(1)小球的绘制直接使用圆形,给定一个固定的半径,然后画圆,Swing的画圆是根据一个矩形,绘制矩形的内切圆,给出的坐标是矩形左上角的坐标,因此,如果需要获得圆心坐标,还需要在此基础只是各增加半径得到。

(2)挡板和砖块的绘制均为直接使用矩形,给出宽度、高度、坐标,然后画矩形。

2.2游戏对象的运动

(1)挡板的左右移动主要是靠键盘的左右方向键来控制,在挡板的左右移动中主要注意挡板到达边界的处理,不要移动到可视区域外面即可。

(2)按住方向键(up)游戏开始,小球开始运动。小球的运动范围位于整个窗口范围内,碰到墙后相应的改变方向,碰到挡板上面后继续弹起,如果落到挡板以下,则Game Over,碰到游戏内的砖块,显示一个爆炸效果并播放音效,砖块消失。

2.3小球和砖块的碰撞检测

 在实现小球和砖块碰撞效果的时候,自己直接使用逻辑判断来实现,然而常常没有考虑全面,出现了一些违反常理的现象,比如小球没有碰到砖块呢,可是砖块它消失了。而且觉得小球和砖块的碰撞情况也很复杂,没有想出全面的方法,小球碰到砖块的四个角是碰撞,碰到砖块的四个面那也是碰撞,感觉颇为棘手。

 于是借助了搜索引擎找到了一篇CSDN大牛的文章2D游戏中的碰撞检测:圆形与矩形碰撞检测(Javascript&C++版),作者是在开发塔防游戏中涉及到此功能,给出了原理讲解和JavaScript的实现,于是借鉴之,改为Java实现,运行之后发现,这碰撞处理,简直完美!在此表示由衷的感谢。

 概括的说就是简化为圆形和矩形的碰撞,然后根据他们的坐标判断2者是否在平面直角坐标系的同一个象限内,只有2种情况,一种是矩形全部位于一个象限,另一种是矩形跨越了2个或2个以上的象限。

(1)矩形全部在一个象限内是圆形的圆心坐标和矩形的某个角的坐标之间距离计算处理。
矩形全部在一个象限内
这里示例了矩形全部在第一象限,任何一个象限都是这个原理,只要矩形全部在这个象限内。

这种情况比较好解决,首先,我们计算出矩形每个角的坐标,然后用勾股定理依次算出这个角到圆心的距离是否小于或者等于半径。设这个角与圆心横坐标之差为d1,纵坐标之差为d2,半径为r,公式表达如下
距离公式
如果有一个角满足要求则说明产生碰撞。
怎么判断矩形是不是在一个象限内呢?很简单,只要判断这个矩形左上角和右下角是否在同一个象限内就可以了。代码方法如下:

    /**
     * 判断矩形是不是在一个象限里面(参考CSDN某大牛的碰撞检测JS代码,作者牛批)
     *
     * @param center 圆形小球的圆心坐标
     * @param objA 矩形的左上角坐标
     * @param objB 矩形的右下角坐标
     * @return 圆形小球和矩形是否处于同一个直角坐标系象限
     */
    protected boolean isSameQuadrant(Point center, Point objA, Point objB) {
        int cX = center.x;
        int cY = center.y;
        int xoA = objA.x, yoA = objA.y, xoB = objB.x, yoB = objB.y;

        int deltaACX = xoA - cX;
        int deltaBCX = xoB - cX;
        int deltaACY = yoA - cY;
        int deltaBCY = yoB - cY;
        if (deltaACX > 0 && deltaBCX > 0) {
            if ((deltaACY > 0 && deltaBCY > 0) || (deltaACY < 0 && deltaBCY < 0)) {
                return true;
            }
            return false;
        } else if (deltaACX < 0 && deltaBCX < 0) {
            if ((deltaACY > 0 && deltaBCY > 0) || (deltaACY < 0 && deltaBCY < 0)) {
                return true;
            }
            return false;
        } else {
            return false;
        }
    }

(2)矩形跨越2个或2个以上象限,将圆形看做一个正方形,简化为2个矩形中心点坐标距离的计算和处理。
矩形跨越2个或更多象限
矩形碰撞算法如图:
矩形碰撞算法
 如果要横向判断碰撞的话,判断(x1-x2)的绝对值是否小于或者等于w1/2+w2/2,如果是则横向则有碰撞。纵向判断是一样的,判断(y1-y2)的绝对值是否小于或等于h1/2+h2/2即可。
代码片段如下

boolean is = isSameQuadrant(ball.getCenter(), getLeftTop(), getRightBottom());
if (is) {
	int r = ball.getRadius();
	Point lt = getLeftTop();
	Point lb = getLeftBottom();
	Point rt = getRightTop();
	Point rb = getRightBottom();
	Point c = ball.getCenter();
	int dx1 = Math.abs(c.x - lt.x), dy1 = Math.abs(c.y - lt.y);
	int dx2 = Math.abs(c.x - lb.x), dy2 = Math.abs(c.y - lb.y);
	int dx3 = Math.abs(c.x - rt.x), dy3 = Math.abs(c.y - rt.y);
	int dx4 = Math.abs(c.x - rb.x), dy4 = Math.abs(c.y - rb.y);

	if(((dx1*dx1) + (dy1*dy1) <= r*r)
	||((dx2*dx2) + (dy2*dy2) <= r*r)
	||((dx3*dx3) + (dy3*dy3) <= r*r)
	||((dx4*dx4) + (dy4*dy4) <= r*r)){
		System.out.println("发生了碰撞");
	}

} else {
	Point c = ball.getCenter();
	int squareW = ball.getRadius() * 2;
	int squareH = squareW;

	int brcx = x + getWidth() / 2;
	int brcy = y + getHeight() / 2;

	if((Math.abs(c.x - brcx) <= (squareW + getWidth())*0.5)
			&&(Math.abs(c.y - brcy) <= (squareH + getHeight())*0.5)
	){
		System.out.println("......发生碰撞");
	}

}

2.4砖块被碰撞后的爆炸效果

 爆炸效果的实现方法,爆炸现象一般是以一个点为中心呈辐射状向周围发散。基于此可以创建一堆粒子,每个粒子模拟一个碎片,粒子为圆形,半径在一个范围内随机给出,每一帧修改一次粒子圆心的x,y坐标,粒子向四周发散出去速度也在一个范围内随机给出,这样就会更加的像一个爆炸的效果,粒子运动固定的帧数后消失。

 爆炸的起始点为被撞砖块的中心点。

 粒子的颜色要和砖块的颜色相同。

	// 粒子的初始化
    public ExplodeObject(){
        for (int i = 0; i < DEFAULT_COUNT; i++) {
            Particle p = new Particle(x, y, random.nextInt(3) + 2);
            p.setSpeed(random.nextInt(3) + 2);
            p.setNewRate(0.1);
            list.add(p);
        }
    }

粒子坐标的变化

        for (int i = 0; i < list.size(); i++) {
            Particle p = list.get(i);
            double temp = DEFAULT_RADIUS * p.getNewRate();
            double angle = (2 * Math.PI / DEFAULT_COUNT)*i;
            p.x = x+getWidth()/2+(int) Math.round(temp*Math.sin(angle));
            p.y = y+getHeight()/2+(int) Math.round(temp * Math.cos(angle));
            //System.out.println(String.format("p.x:%d, p.y:%d, p.radius:%d", p.x,p.y,p.radius));
            p.setNewRate(p.getNewRate() + 0.05*p.getSpeed());
        }

3.测试、完善。

  窗口初始的宽高和实际游戏对象运动范围的宽高是不一样的,如果不注意小球可能会跑出去一点,到顶部甚至就看不到了。通过获取JFrame的Insets(外镶边)可以得到每个边界的值,相应处理即可。

  目前小球在碰到砖块的角的时候,还缺乏一个改变小球方向的功能,参考了2个流行的打砖块游戏发现,有一个打砖块游戏改变了小球的方向,另外一个也并没有处理方向问题,碰到砖块的角砖块消失,小球继续前进,目前也是采用了这一做法。

  在小球落到挡板下面,game over之后,需要鼠标点击重新开始重新进行游戏,目前这个鼠标落点是硬编码,有待改进。

  放一张代码的结构图
结构图

项目已托管到github: https://github.com/ximen502/BrickGame,欢迎浏览和star。项目中有一个压缩包rar文件(已经改为zip),下载解压后点击里面的runGame.bat即可愉快的开始游戏了。当然Linux系统和Mac系统也是支持的哈,修改一下bat脚本文件内容,存储为shell脚本即可。备用https://gitee.com/ximen502/BrickGame

  • 5
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Java打砖块游戏是一个经典的小游戏,下面是一个简单的Java打砖块游戏的代码示例: ```java import javax.swing.*; import java.awt.*; import java.awt.event.*; public class BrickBreaker extends JFrame implements ActionListener { private Timer timer; private boolean play = false; private int score = 0; private int paddleX = 310; private int ballPosX = 120; private int ballPosY = 350; private int ballXDir = -1; private int ballYDir = -2; private int[][] bricks; private int brickRows = 3; private int brickCols = 7; public BrickBreaker() { bricks = new int[brickRows][brickCols]; for (int i = 0; i < brickRows; i++) { for (int j = 0; j < brickCols; j++) { bricks[i][j] = 1; } } setTitle("Brick Breaker"); setResizable(false); setDefaultCloseOperation(EXIT_ON_CLOSE); setSize(800, 600); setLocationRelativeTo(null); addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_RIGHT) { if (paddleX >= 600) { paddleX = 600; } else { moveRight(); } } if (e.getKeyCode() == KeyEvent.VK_LEFT) { if (paddleX <= 10) { paddleX = 10; } else { moveLeft(); } } if (e.getKeyCode() == KeyEvent.VK_ENTER) { if (!play) { play = true; ballPosX = 120; ballPosY = 350; ballXDir = -1; ballYDir = -2; paddleX = 310; score = 0; bricks = new int[brickRows][brickCols]; for (int i = 0; i < brickRows; i++) { for (int j = 0; j < brickCols; j++) { bricks[i][j] = 1; } } repaint(); } } } }); timer = new Timer(8, this); timer.start(); setVisible(true); } public void paint(Graphics g) { // 绘制背景 g.setColor(Color.black); g.fillRect(1, 1, 792, 592); // 绘制砖块 for (int i = 0; i < brickRows; i++) { for (int j = 0; j < brickCols; j++) { if (bricks[i][j] == 1) { g.setColor(Color.white); g.fillRect(j * 110 + 30, i * 50 + 50, 100, 40); } } } // 绘制球拍 g.setColor(Color.green); g.fillRect(paddleX, 550, 100, 8); // 绘制球 g.setColor(Color.yellow); g.fillOval(ballPosX, ballPosY, 20, 20); // 绘制得分 g.setColor(Color.white); g.setFont(new Font("serif", Font.BOLD, 25)); g.drawString("" + score, 700, 30); if (ballPosY > 570) { play = false; ballXDir = 0; ballYDir = 0; g.setColor(Color.red); g.setFont(new Font("serif", Font.BOLD, 30)); g.drawString("Game Over, Score: " + score, 190, 300); g.setFont(new Font("serif", Font.BOLD, 20)); g.drawString("Press Enter to Restart", 290, 350); } g.dispose(); } public void moveRight() { play = true; paddleX += 20; } public void moveLeft() { play = true; paddleX -= 20; } public void actionPerformed(ActionEvent e) { timer.start(); if (play) { if (new Rectangle(ballPosX, ballPosY, 20, 20).intersects(new Rectangle(paddleX, 550, 100, 8))) { ballYDir = -ballYDir; } A: for (int i = 0; i < brickRows; i++) { for (int j = 0; j < brickCols; j++) { if (bricks[i][j] == 1) { int brickX = j * 110 + 30; int brickY = i * 50 + 50; int brickWidth = 100; int brickHeight = 40; Rectangle rect = new Rectangle(brickX, brickY, brickWidth, brickHeight); Rectangle ballRect = new Rectangle(ballPosX, ballPosY, 20, 20); Rectangle brickRect = rect; if (ballRect.intersects(brickRect)) { bricks[i][j] = 0; score += 5; if (ballPosX + 19 <= brickRect.x || ballPosX + 1 >= brickRect.x + brickRect.width) { ballXDir = -ballXDir; } else { ballYDir = -ballYDir; } break A; } } } } ballPosX += ballXDir; ballPosY += ballYDir; if (ballPosX < 0) { ballXDir = -ballXDir; } if (ballPosY < 0) { ballYDir = -ballYDir; } if (ballPosX > 770) { ballXDir = -ballXDir; } } repaint(); } public static void main(String[] args) { new BrickBreaker(); } } ``` 这段代码实现了一个简单的打砖块游戏,包括砖块、球拍、球和得分的绘制,以及键盘事件的处理。你可以在Java环境中运行该代码,通过方向键控制球拍的移动,按下回车键开始游戏。当球碰到砖块时,砖块消失并增加得分。当球触底时,游戏结束并显示最终得分。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值