美文网首页
一颗小球的畅想

一颗小球的畅想

作者: 小猿君的算法笔记 | 来源:发表于2021-01-01 11:57 被阅读0次

    最近学了一些 Java 的图形界面编程,因此想自己写一个小游戏试试身手,思来想去,弹球小游戏还是挺简单的,一个球拍,一个小球,使用球拍接住小球,保证小球不掉下去。在网上找了一些参考资料,感觉设计的都不怎么“面向对象”,于是自己也写了一个——一颗小球的畅想,对原来的游戏做了一些功能的增强。目前的功能还有许多不足的地方,因此暂定为 v1.0 版本。

    游戏运行效果图

    游戏开始界面

    image.png
    游戏结束界面
    image.png
    功能需求分析

    弹球游戏需要以下功能:
    (1)游戏界面展示:游戏界面具有两个球拍,界面上方一个,界面下方一个,同时界面中央有一个红色的小球。
    (2)移动小球:游戏开始时小球能够按照一定的速度朝某一个方向进行移动,当小球遇到障碍物,能够自动的反弹。
    (3)移动球拍:通过点击键盘左键和键盘右键可以实现两个球拍的左右移动。

    面向对象程序设计

    通过以上的分析,我将这个程序的实体类进行了提取,并划分为不同的模块。


    image.png
    • Constant 类:用于存放游戏中的常量数据
    • Ball 类:球类
    • GameView 类:游戏视图类
    • Racket 类:球拍类
    • Rule 类:游戏规则类
    • GameApplication 类:游戏启动类
    代码实现

    「Constant 类的设计与实现」
    首先看一下 Constant 类,其包含以下信息,这些信息是后来添加的,但为了方便后续的代码查看,先放出来。

    public class Constant {
        // 桌面宽高
        public static final int TABLE_WIDTH = 400;
        public static final int TABLE_HEIGHT = 600;
    
        // 球的大小
        public static final int BALL_SIZE = 16;
        // 球的颜色
        public static final Color BALL_COLOR = Color.RED;
        // 球的速度
        public static final int speedX = 5;
        public static final int speedY = 10;
    
        // 球拍长宽
        public static final int racketWidth = 60;
        public static final int racketHeight = 20;
        // 球拍颜色
        public static final Color RACKET_COLOR = Color.LIGHT_GRAY;
        // 球拍每次移动距离
        public static final int moveLength = 10;
    
    }
    

    「球类 Ball 的设计与实现」
    球类需要包含以下属性:球的大小、球的颜色、球的位置坐标、球的 X 轴 Y 轴速度大小。

        // 大小
        private int ballSize = Constant.BALL_SIZE;
    
        // 颜色
        private Color color = Constant.BALL_COLOR;
    
        // 坐标位置
        private int ballX;
        private int ballY;
    
        // 速度大小
        private int speedX = Constant.speedX;
        private int speedY = Constant.speedY;
    

    在构造小球时,需要传入两个参数,ballX 和 ballY 来确定小球的位置。

    public Ball(int ballX, int ballY) {
        this.ballX = ballX;
        this.ballY = ballY;
    }
    

    小球移动,需要根据速度 speedX 和 speedY 来改变小球的位置,当小球碰到左右两边墙或者上下两个球拍时,其速度方向需要发生改变。这里为了确定球拍的位置,需要传入一个参数 racketX 代表球拍所在 X 轴的位置。

    /**
     * 小球移动
     * @param racketX :球拍位置,目前游戏中上球拍和下球拍racketX位置相同
     */
    public void moveBall(int racketX) {
        // 小球速度方向改变
        if (ballX <= 0 || ballX >= Constant.TABLE_WIDTH - ballSize) { // X轴方向速度
            speedX = -speedX;
        }
        if ((ballY <= Constant.racketHeight || ballY >= Constant.TABLE_HEIGHT - Constant.racketHeight - ballSize)
                && ballX > racketX - ballSize && ballX < racketX + Constant.racketWidth) { // Y轴方向速度
            speedY = -speedY;
        }
    
        ballX += speedX;
        ballY += speedY;
    }
    

    「球拍类 Racket 的设计与实现」
    球拍类需要包含的属性有:球拍长宽、球拍颜色、球拍位置。

        // 长宽
        private int racketWidth = Constant.racketWidth;
        private int racketHeight = Constant.racketHeight;
    
        // 颜色
        private Color color = Constant.RACKET_COLOR;
    
        // 位置
        private int racketX;
        private int racketY;
    

    在构造球拍类时,需要传入 racketX 和 racketY 来确定球拍的位置。

    public Racket(int racketX, int racketY) {
        this.racketX = racketX;
        this.racketY = racketY;
    }
    

    球拍需要进行移动,通过 moveLength 参数告知球拍移动的距离。

    /**
     * 左移动球拍
     * @param moveLength 移动长度
     */
    public void moveLeft(int moveLength) {
        if (racketX > 0) {
            racketX -= moveLength;
        }
    }
    
    /**
     * 右移动球拍
     * @param moveLength 移动长度
     */
    public void moveRight(int moveLength) {
        if (racketX < Constant.TABLE_WIDTH - racketWidth) {
            racketX += moveLength;
        }
    }
    

    「游戏规则类 Rule 的设计与实现」
    游戏规则类用于进行游戏结束的判断、游戏功能的设置。对于游戏结束的判断,需要传入两个参数,ball 和 racketX,ball 代表球类,racketX 代表球拍 X 轴的位置。

    /**
     * 游戏是否结束
     * @param ball 球类
     * @param racketX 球拍位置,目前游戏中上球拍和下球拍racketX位置相同
     */
    public boolean isOver(Ball ball, int racketX) {
        // 游戏结束
        if ((ball.getBallY() <= Constant.racketHeight || ball.getBallY() >= Constant.TABLE_HEIGHT - Constant.racketHeight - ball.getBallSize())
                && (ball.getBallX() <= racketX - ball.getBallSize() || ball.getBallX() >= racketX + Constant.racketWidth)) {
            return true;
        }
        return false;
    }
    

    「游戏视图类 GameView 的设计与实现」
    GameView 需要继承 Canvas 类来实现游戏视图绘制的功能。游戏视图类包含以下属性,分别是视图桌面宽高、游戏规则、球、上球拍、下球拍。同时需要为视图类声明一个定时器,使其能够定时地对视图进行重绘。

        // 桌面宽高
        private final int TABLE_WIDTH = Constant.TABLE_WIDTH;
        private final int TABLE_HEIGHT = Constant.TABLE_HEIGHT;
    
        // 游戏规则
        private Rule rule;
        // 球
        private Ball ball;
        // 上球拍
        private Racket racketTop;
        // 下球拍
        private Racket racketBottom;
    
        // 声明定时器
        private Timer timer;
    

    在游戏视图类的构造方法中对游戏的各个参数进行初始化。

    public GameView(Ball ball, Racket racketTop, Racket racketBotton) {
        // 设置桌面
        this.setPreferredSize(new Dimension(TABLE_WIDTH, TABLE_HEIGHT));
        this.rule = new Rule();
        this.ball = ball;
        this.racketTop = racketTop;
        this.racketBottom = racketBotton;
    
        // 定时刷新任务
        timer = new Timer(100, event -> {
            // 小球移动
            ball.moveBall(racketBotton.getRacketX());
            this.repaint();
        });
        timer.start();
    }
    

    为了实现我们需要的绘制,我们需要重写 paint 方法。这里通过规则类 Rule 的 isOver 方法来判断游戏是否结束。如果游戏没有结束,调用 paintGame 方法,绘制游戏界面。如果游戏结束,需要调用 paintGameOver 方法绘制游戏结束界面,同时停止定时器的运行。

    @Override
    public void paint(Graphics g) {
        if (!rule.isOver(ball, racketBottom.getRacketX())) { // 游戏进行
            paintGame(g);
        } else { // 游戏结束
            paintGameOver(g);
            timer.stop();
        }
    }
    

    paintGame 方法如下,其核心是对小球、球拍的重绘:

    /**
     * 绘制游戏
     */
    private void paintGame(Graphics g) {
        // 绘制小球
        g.setColor(ball.getColor());
        g.fillOval(ball.getBallX(), ball.getBallY(), ball.getBallSize(), ball.getBallSize());
    
        // 绘制上球拍
        g.setColor(racketTop.getColor());
        g.fillRect(racketTop.getRacketX(), racketTop.getRacketY(), racketTop.getRacketWidth(), racketTop.getRacketHeight());
    
        // 绘制下球拍
        g.setColor(racketBottom.getColor());
        g.fillRect(racketBottom.getRacketX(), racketBottom.getRacketY(), racketBottom.getRacketWidth(), racketBottom.getRacketHeight());
    }
    

    paintGameOver 方法如下,绘制游戏结束界面:

    /**
     * 绘制游戏结束
     */
    private void paintGameOver(Graphics g) {
        g.setColor(Color.BLUE);
        g.setFont(new Font("隶书", Font.BOLD, 30));
        g.drawString("游戏结束", 130, 300);
    }
    

    「游戏启动类的设计与实现」
    游戏启动类需要定义以下属性:

    Frame frame = new Frame("一颗小球的畅想");
    private Ball ball;
    private Racket racketTop;
    private Racket racketBottom;
    private GameView gameView;
    // 球拍每次点击移动距离
    private int moveLength = Constant.moveLength;
    

    在 init 方法中对这些参数进行初始化,这里对游戏视图类进行了构造,同时构建了两个监听,一个是对游戏关闭的监听,还有一个是对键盘左右移动按键事件的监听,来进行球拍的移动。

    private void init() {
        ball = new Ball(192, 300);
        racketTop = new Racket(170, 0);
        racketBottom = new Racket(170, 580);
        gameView = new GameView(ball, racketTop, racketBottom);
    
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) { // 关闭窗口
                System.exit(0);
            }
        });
    
        // 操作键盘
        KeyListener listener = new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                int keyCode = e.getKeyCode();
                if (keyCode == KeyEvent.VK_LEFT) { // 按左键,球拍左移动
                    racketTop.moveLeft(moveLength);
                    racketBottom.moveLeft(moveLength);
                } else if (keyCode == KeyEvent.VK_RIGHT) { // 按右键,球拍右移动
                    racketTop.moveRight(moveLength);
                    racketBottom.moveRight(moveLength);
                }
            }
        };
    
        frame.addKeyListener(listener);
        frame.add(gameView);
        frame.pack();
        frame.setVisible(true);
    }
    
    总结

    游戏的设计与实现到此也就基本完成了,但是游戏的功能其实还有许多需要完善的地方,例如游戏的开始、暂停等操作;球的初始速度、方向不是随机的,导致球的运行基本是可预测的;两个球拍的移动受同一个按键控制,这就导致球拍的左右移动是同步的,后续可以对球拍的控制进行分离,这就可以成为一个双人游戏;游戏没有计分的规则;球的速度、球拍的长度不能随着游戏的进度发生变化;游戏中没有随机事件:例如通过小球来吃其他具有特殊功能的东西等。以上这些都是当前版本中所不具有的,因此需要在后续的版本中进行不断的完善。

    欢迎关注

    技术号:小猿君的算法笔记

    如果需要获得完整的代码,也可以关注我的技术号 小猿君的算法笔记,发送 一颗小球的畅想 获取,让我们一起学习,一起成长。

    相关文章

      网友评论

          本文标题:一颗小球的畅想

          本文链接:https://www.haomeiwen.com/subject/azqjoktx.html