You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现Graphics2D绘制的弹球(Oval)平滑移动?

实现弹球平滑移动的解决方案

问题根源分析

你的代码存在几个导致瞬移、动画不流畅的核心问题:

  • 用Thread.sleep()阻塞主线程,打乱Swing事件调度线程(EDT)的正常工作,导致UI更新延迟
  • 弹球坐标采用int类型,单次移动步长过大(比如ySpeed=10),视觉上呈现瞬移效果
  • 在paint()方法中直接调用repaint(),引发无节制的重复绘制,加重UI线程负担

具体改进方案

1. 用Swing Timer替代线程休眠

javax.swing.Timer是Swing专门为UI动画设计的工具,它会在EDT线程上定时触发更新,不会阻塞UI,能保证动画流畅性。

2. 改用浮点型存储坐标

将Ball类的x、y、xSpeed、ySpeed改为double类型,这样可以用更小的步长移动,视觉上实现平滑过渡。绘制时可将浮点值转为int,或直接使用Graphics2D的浮点绘制方法提升精度。

3. 分离更新与渲染逻辑

不在paint()中调用repaint(),而是让Timer定时触发状态更新后再调用repaint(),以此控制渲染频率(比如60帧/秒)。

4. 优化碰撞检测

小步长移动后,碰撞检测会更精准,不会出现弹球跳过碰撞边界的情况。每次小步更新后即可检查碰撞,而非瞬移后再处理。


修改后的代码示例

Main类

import Flipper.Ball;
import Flipper.Cabinet;
import javax.swing.Timer;

public class Main {
    public static Cabinet cabinet;
    public static Ball ball;
    public static Timer gameTimer;

    public static void main(String[] args) {
        new Frame();
        cabinet = new Cabinet();
        ball = new Ball();

        // 约60帧/秒,每16ms触发一次更新
        gameTimer = new Timer(16, e -> update());
        gameTimer.start();
    }

    private static void update() {
        ball.calcSpeed();
        ball.move();
        // 通知UI重绘
        Frame.getPanel().repaint();
    }
}

Frame类(增加获取Panel的方法)

import javax.swing.*;

public class Frame extends JFrame {
    private static Panel gamePanel;

    Frame() {
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        gamePanel = new Panel();
        this.add(gamePanel);
        this.pack();
        this.setLocationRelativeTo(null);
        this.setVisible(true);
    }

    public static Panel getPanel() {
        return gamePanel;
    }
}

Panel类(移除paint内的repaint,改用paintComponent)

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

public class Panel extends JPanel implements KeyListener, MouseListener {

    Panel() {
        this.setPreferredSize(new Dimension(Constant.WIDTH, Constant.HEIGHT));
        this.setFocusable(true);
        this.addMouseListener(this);
        this.addKeyListener(this);
    }

    @Override
    public void paintComponent(Graphics g) { // Swing推荐用paintComponent替代paint
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;

        g2.setColor(Color.BLACK);
        g2.draw(Main.cabinet.getShape());

        g2.setColor(Color.BLUE);
        // 浮点转int绘制,或用g2.fill(new Ellipse2D.Double(...))实现更高精度
        g2.fillOval((int) Main.ball.getX(), (int) Main.ball.getY(), Constant.BALLSIZE, Constant.BALLSIZE);
    }

    // 省略未使用的KeyListener/MouseListener方法
}

Ball类(改用double类型存储坐标与速度)

package Flipper;

import main.Constant;

public class Ball {
    private double x;
    private double y;
    private double xSpeed = 0.5; // 减小步长实现平滑移动
    private double ySpeed = 3.0;

    public Ball() {
        this.x = 230;
        this.y = 160;
    }

    public void calcSpeed() {
        // 碰撞边界时反弹,同时修正位置避免球卡在边界外
        if (y >= Constant.HEIGHT - Constant.BALLSIZE) {
            ySpeed = Math.abs(ySpeed) * -1;
            y = Constant.HEIGHT - Constant.BALLSIZE;
        }
        if (y <= 0) {
            ySpeed = Math.abs(ySpeed);
            y = 0;
        }
    }

    public void move() {
        y += ySpeed;
        x += xSpeed; // 启用x方向移动
    }

    // Getters
    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

    public double getxSpeed() {
        return xSpeed;
    }

    public double getySpeed() {
        return ySpeed;
    }

    // Setters
    public void setX(double x) {
        this.x = x;
    }

    public void setY(double y) {
        this.y = y;
    }

    public void setxSpeed(double xSpeed) {
        this.xSpeed = xSpeed;
    }

    public void setySpeed(double ySpeed) {
        this.ySpeed = ySpeed;
    }
}

额外提示

  • 始终使用paintComponent()而非paint()绘制Swing组件,这是Swing绘制的标准规范
  • 若需要更精准的碰撞检测,可使用Ellipse2D.Double表示弹球形状,与其他形状做交集检测
  • 可调整Timer的延迟参数(比如16ms对应60帧,33ms对应30帧),平衡流畅度与程序性能

内容的提问来源于stack exchange,提问作者Tonda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 03:01:07