I'm building a game in Java and I need to synchronize the animation executions.
Currently they will occur simultaneously, while accessing the same data.
I'd prefer that the animations stack up, and execute one at a time.

在这个游戏中,当你 Select 一个积木时,它会随机向左或向右旋转90度.

我正在使用以下代码.

static class GUI extends JFrame {
    JPanel panel = new JPanel(new GridLayout(10, 10));

    static class Box extends JLabel {...}

    GUI() {
        for (int i = 0; i < 100; i++) panel.add(new Box());
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setResizable(false);
        add(panel);
        pack();
        setVisible(true);
    }
}

And, here is the Box class.
Variables bg and c are background and color, a is angle, and d is direction where true is clockwise.

static class Box extends JLabel {
    static Random r = new Random();
    int bg, c = r.nextInt(bg = 0xffffff);
    int a0, a = 90 * r.nextInt(1, 4);
    boolean d;

    Box() {
        setPreferredSize(new Dimension(100, 100));
        setBackground(bg());
        setBorder(BorderFactory.createLineBorder(c(), 2, true));
        setOpaque(true);
        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                super.mouseClicked(e);
                EventQueue.invokeLater(() -> {
                    d = r.nextInt(2) == 0;
                    bg -= 0x0f0f0f;
                    animate();
                    setBackground(bg());
                });
            }
        });
    }

    void animate() {
        a0 = a;
        new Timer(10, e -> {
            a += d ? -5 : 5;
            if (Math.abs(a - a0) == 90) {
                ((Timer) e.getSource()).stop();
                if (a == 360) a = 0;
            }
            repaint();
        }).start();
    }

    Color c() { return new Color(c); }
    Color bg() { return new Color(bg); }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.setColor(c());
        g2.setStroke(new BasicStroke(10));
        g2.rotate(Math.toRadians(a), 50, 50);
        g2.draw(new Line2D.Double(50, 50, 50, 0));
        g2.setBackground(bg());
    }
}

When I double-click a JLabel it rotates indefinitely.  How do I synchronize this?
A single click works correctly.

基本上,我希望每次点击鼠标都会产生一个新的100,直到前一个完成后才会开始.

On a final note, should I be using the volatile keyword for a0 and a?
And, if anyone has any recommendations on my code, feel free.


编辑

我相信我找到了解决办法.

I've created a Box inner-class, Animation, which implements Runnable.
In the run method I make the adjustments to Box, and start the Timer object.

The enclosing Box class, now has a List queue, which is appended to for each mouse-click.
When the Animation object finishes, it will remove itself from queue, and start the next, if any.

For a0 and a I am using the AtomicInteger class.
This has significantly improved the performance of the rotation.

AtomicInteger a0 = new AtomicInteger();
AtomicInteger a = new AtomicInteger(90 * r.nextInt(1, 4));
class Animation implements Runnable {
    @Override
    public void run() {
        d = r.nextInt(2) == 0;
        bg -= 0x0f0f0f;
        setBackground(bg());
        setText(String.valueOf(s));
        a0.set(a.get());
        new Timer(10, l -> {
            a.set(a.get() + (d ? -5 : 5));
            repaint();
            if (Math.abs(a.get() - a0.get()) == 90) {
                ((Timer) l.getSource()).stop();
                if (a.get() == 360) a.set(0);
                queue.remove(0);
                if (queue.size() != 0) queue.get(0).run();
            }
        }).start();
    }
}

此外,我创建了一个方法rotate,以从mouseClicked方法调用.

void rotate() {
    queue.add(new Animation());
    if (queue.size() == 1) queue.get(0).run();
}

Here is the complete code for Box, re-factored.
I've additionally added text to the box, to count the clicks.

static class Box extends JLabel {
    static Random r = new Random();
    int bg, c = r.nextInt(bg = 0xffffff), s = 0;
    AtomicInteger a0 = new AtomicInteger();
    AtomicInteger a = new AtomicInteger(90 * r.nextInt(1, 4));
    boolean d;
    List<Animation> queue = new ArrayList<>();

    Box() {
        setPreferredSize(new Dimension(100, 100));
        setBackground(bg());
        setBorder(BorderFactory.createLineBorder(c(), 2, true));
        setOpaque(true);
        setFont(new Font("Mono", Font.PLAIN, 100));
        setForeground(c());
        setText(String.valueOf(s));
        setHorizontalAlignment(SwingConstants.CENTER);
        setVerticalTextPosition(SwingConstants.CENTER);
        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                super.mouseClicked(e);
                s++;
                rotate();
            }
        });
    }

    void rotate() {
        queue.add(new Animation());
        if (queue.size() == 1) queue.get(0).run();
    }

    Color c() { return new Color(c); }
    Color bg() { return new Color(bg); }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g.create();
        g2.setColor(c());
        g2.setStroke(new BasicStroke(10));
        g2.rotate(Math.toRadians(a.get()), 50, 50);
        g2.draw(new Line2D.Double(50, 50, 50, 0));
        g2.setBackground(bg());
        g2.dispose();
    }

    class Animation implements Runnable {...}
}

推荐答案

我能够得出一个解决方案.

I've created a Box inner-class, Animation, which extends the Thread class.
In the run method I make the adjustments to Box atomically, and start the Timer object.

Every time the player clicks a Box, the animation queue is incremented.
And, when queue is 1, the Animation thread is started.
Subsequently, the Timer routine will reset delta when queue is > 1; continuing the rotation.
This reduces the object creation significantly.

此外,以这种方式机械化物体客观上比操纵100更有智慧.

因为我要从Animation个可运行对象中解析angledeltaanimator,所以我已经将它们转换为100101个对象.

And, I've removed the Math#abs call by adjusting the math.
The delta variable now carries an accumulating value, 0 through 90.

class Animation extends Thread {
    @Override
    public void run() {
        super.run();
        delta.set(0);
        animator.set(
            new Timer(1, l -> {
                angle.set(angle.get() + 2);
                repaint();
                if (delta.addAndGet(2) == 90) {
                    if (angle.get() == 360) angle.set(0);
                    if (queue.get() > 1) delta.set(0);
                    else ((Timer) l.getSource()).stop();
                    queue.set(queue.get() - 1);
                }
            }));
        animator.get().start();
    }
}

此外,我还能够通过重新分解Box#paintComponent方法来提高渲染性能.

I've added the Graphics#dispose invocation, replaced the Math#toRadians call, and removed the Graphics2D and BasicStroke objects.
Additionally, c is now a Color object, color, so the c method is removed.

BasicStroke stroke 
    = new BasicStroke(((w + h) / 2f) / 3f, CAP_SQUARE, JOIN_ROUND);

@Override
protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    ((Graphics2D) g).rotate(0.017453292519943295 * angle.get(), wm, hm);
    g.setColor(color);
    ((Graphics2D) g).setStroke(stroke);
    ((Graphics2D) g).draw(path);
    g.dispose();
}

I've also been updating the game, so there are other adjustments.
Here is the full re-factor.

import static java.awt.BasicStroke.CAP_SQUARE;
import static java.awt.BasicStroke.JOIN_ROUND;
import static java.awt.geom.Path2D.WIND_EVEN_ODD;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.GeneralPath;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class X {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(GUI::new);
    }

    static int seed;
    final static Random r
        = new Random() {
            { setSeed(seed = nextInt(0x111111, 0xffffff + 1)); }
        };

    static int n = 10, m = 10, dim = n * m;
    static int w = 100, wm = (int) (w / 2d);
    static int h = 100, hm = (int) (h / 2d);

    static class GUI extends JFrame {
        static JPanel top = new JPanel(new GridLayout(1, 1));
        static JPanel bottom = new JPanel(new GridLayout(n, m));

        GUI() {
            setLayout(new BorderLayout());
            JLabel label = new JLabel();
            label.setFont(new Font(Font.SERIF, Font.PLAIN, (int) ((w + h) / 2d)));
            label.setForeground(new Color(seed));
            label.setText("0x%x".formatted(seed));
            top.add(label);
            add(top, BorderLayout.PAGE_START);
            for (int i = 1; i <= dim; i++) bottom.add(new Box());
            add(bottom, BorderLayout.CENTER);
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            setResizable(false);
            pack();
            setVisible(true);
        }

        static class Box extends JLabel {
            Color bg, color;
            AtomicInteger angle, delta, queue;
            Animation animation;
            AtomicReference<Timer> animator = new AtomicReference<>();
            GeneralPath path;
            BasicStroke stroke = new BasicStroke(((w + h) / 2f) / 3f, CAP_SQUARE, JOIN_ROUND);

            Box() {
                bg = new Color(r.nextInt(0xffffff + 1));
                color = new Color(r.nextInt(0xffffff + 1));
                angle = new AtomicInteger(90 * r.nextInt(1, 4));
                delta = new AtomicInteger();
                queue = new AtomicInteger();
                path = switch (r.nextInt(1, 14)) {
                    case 1, 2, 3, 4 -> Paths.L;
                    case 5, 6, 7, 8 -> Paths.I;
                    case 9, 10, 11, 12 -> Paths.T;
                    default -> Paths.X;
                };
                init();
                setVisible(true);
            }

            void init() {
                setPreferredSize(new Dimension(w, h));
                setOpaque(true);
                setBackground(bg);
                addMouseListener(new MouseAdapter() {
                    @Override
                    public void mouseClicked(MouseEvent e) {
                        super.mouseClicked(e);
                        queue.set(queue.get() + 1);
                        if (queue.get() == 1) {
                            animation = new Animation();
                            animation.setPriority(10);
                            animation.start();
                        }
                    }
                });
            }

            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                ((Graphics2D) g).rotate(0.017453292519943295 * angle.get(), wm, hm);
                g.setColor(color);
                ((Graphics2D) g).setStroke(stroke);
                ((Graphics2D) g).draw(path);
                g.dispose();
            }

            class Animation extends Thread {
                @Override
                public void run() {
                    super.run();
                    delta.set(0);
                    animator.set(
                        new Timer(1, l -> {
                            angle.set(angle.get() + 2);
                            repaint();
                            if (delta.addAndGet(2) == 90) {
                                if (angle.get() == 360) angle.set(0);
                                if (queue.get() > 1) delta.set(0);
                                else ((Timer) l.getSource()).stop();
                                queue.set(queue.get() - 1);
                            }
                        }));
                    animator.get().start();
                }
            }
        }

        static class Paths {
            static GeneralPath X = new GeneralPath(WIND_EVEN_ODD, 4);
            static GeneralPath T = new GeneralPath(WIND_EVEN_ODD, 4);
            static GeneralPath L = new GeneralPath(WIND_EVEN_ODD, 3);
            static GeneralPath I = new GeneralPath(WIND_EVEN_ODD, 2);

            static {
                X.moveTo(0, hm);
                X.lineTo(w, hm);
                X.moveTo(wm, 0);
                X.lineTo(wm, h);
                T.moveTo(0, hm);
                T.lineTo(w, hm);
                T.moveTo(wm, hm);
                T.lineTo(wm, 0);
                L.moveTo(wm, 0);
                L.lineTo(wm, hm);
                L.lineTo(w, hm);
                I.moveTo(0, hm);
                I.lineTo(w, hm);
            }
        }
    }
}

Java相关问答推荐

gitlab ci不会运行我的脚本,因为它需要数据库连接'

查找最大子数组的和

Oracle DUAL表上使用DDL时jOOQ问题的解析'

Select 按位运算序列

ittext pdf延迟签名,签名无效

如何在Java中声明未使用的变量?

扩展到弹出窗口宽度的JavaFX文本字段

连接Quarkus中的两个异步操作

我无法获取我的Java Spring应用程序的Logback跟踪日志(log)输出

相同的Java SerializedLambda为implMethodKind返回不同的结果

无法了解Java线程所消耗的时间

我如何解释这个错误?必需类型:供应商R,提供:收集器对象,捕获?,java.util.List java.lang.Object>>

我的Spring Boot测试显示&IlLegalStateException:无法加载某事的ApplicationContext.

在Eclipse中调试未导出的JDK模块的Java包

如何在 spring 数据的MongoDB派生查询方法中使用$EXISTS

保持标题窗格的箭头可见,即使设置为不可折叠

Eureka客户端无法使用用户/通行证注册到Eureka服务器

双对象供应商

为什么child-pom会创建一个新版本

找不到 jar 文件系统提供程序try 使用 jdeps 和 jlink 创建收缩 Java 映像来运行 Minecraft