繁体   English   中英

如何使用 java awt 制作动画

[英]How to animate with java awt

我正在尝试制作一个红色椭圆形的 animation ,它将移动到屏幕的右侧。 但它只是画了椭圆形。 我不知道我做错了什么,而且我真的找不到任何关于如何做到这一点的信息。 任何帮助都会很棒,谢谢。

import java.awt.*;  

public class mainClass  
{    
    public mainClass()    
    {    

        Frame f = new Frame("Canvas Example");   
        f.add(new MyCanvas());    

        f.setLayout(null);    
        f.setSize(400, 400);   
        f.setVisible(true);    
    }    

    public static void main(String args[])    
    {    
        new mainClass();
    }    
}    


class MyCanvas extends Canvas    
{    
    int x = 75;
    public MyCanvas() {    
        setBackground (Color.BLACK);    
        setSize(400, 400);    
    }    

    public void paint(Graphics g)    
    {     
        g.setColor(Color.red);    
        g.fillOval(x, 75, 150, 75);
    }
    public void update(Graphics g)    
    {     
        x++;
    }  
}     

这没有动画的原因是没有任何东西触发组件更新和重绘自身。 有几点需要考虑:

  1. 有些东西需要调用update方法。 通常,这是通过调用组件上的repaint()来触发的,但这段代码中没有任何内容调用该方法。

  2. 调用super.update(g)以确保调用默认行为(清除 canvas 并再次绘制它)对于重写的update方法很重要。

  3. Animation 有一个时间分量:椭圆应该在一段时间内移动。 这需要纳入逻辑。 AWT 没有内置的定时行为机制。

    如果您能够使用 Swing 中的类,则javax.swing.Timer class 对 Z62F1C25ED192099 非常有用。 它在 AWT 线程上执行您的回调,因此意味着您不必采取特殊措施来确保线程安全。

    如果不能使用 Swing,可以使用java.util.Timer或自定义线程,但需要直接管理线程同步。

  4. 一旦椭圆到达 canvas 的边缘,您可能还希望 animation 停止。

这是一个使用javax.swing.Timer的示例(假设 Java 8 或更高版本)。 请注意,所有 animation 逻辑都在附加到TimerActionListener中,因此已删除覆盖的update方法:

import javax.swing.*;
import java.awt.*;

public class MainClass {
    public static final int CANVAS_SIZE = 400;

    public MainClass() {

        Frame f = new Frame("Canvas Example");
        f.add(new MyCanvas(CANVAS_SIZE));

        f.setLayout(null);
        f.setSize(CANVAS_SIZE, CANVAS_SIZE);
        f.setVisible(true);
    }

    public static void main(String[] args) {
        new MainClass();
    }
}


class MyCanvas extends Canvas {
    public static final int INITIAL_POSITION = 75;
    public static final int HEIGHT = 75;
    public static final int WIDTH = 150;

    private static final int TIMER_DELAY_MILLIS = 1000 / 30; // 30 FPS

    private int x = INITIAL_POSITION;
    private final Timer timer;

    public MyCanvas(int canvasSize) {
        setBackground(Color.BLACK);
        setSize(canvasSize, canvasSize);

        timer = new Timer(TIMER_DELAY_MILLIS, (event) -> {
            // ensure the oval stays on the canvas
            if (x + WIDTH < getWidth())  {
                x++;
                repaint();
            } else {
                stopAnimation();
            }
        });
        timer.start();
    }

    public void paint(Graphics g) {
        g.setColor(Color.red);
        g.fillOval(x, INITIAL_POSITION, WIDTH, HEIGHT);
    }

    private void stopAnimation() {
        timer.stop();
    }
}

此代码有一些额外的附带更改。

  • MainClass mainClass前导大写“M”)以符合标准 Java 命名约定。
  • 出于同样的原因,将String args[]更改为String[] args
  • 将数字常量提取到命名为static final字段。
  • 使 canvas 大小成为构造函数参数,由调用者控制。
  • x私有。
  • 较小的格式更改以确保一致的样式。

一种不使用javax.swing.Timer的选项(省略未更改的代码):

private final AtomicInteger x = new AtomicInteger(INITIAL_POSITION);

public MyCanvas(int canvasSize) {
    setBackground(Color.BLACK);
    setSize(canvasSize, canvasSize);

    new Thread(() -> {
        try {
            // ensure the oval stays on the canvas
            while (x.incrementAndGet() + WIDTH < getWidth())  {
                Thread.sleep(TIMER_DELAY_MILLIS);
                repaint();
            }
        } catch (InterruptedException e) {
            // Just let the thread exit
            Thread.currentThread().interrupt();
        }
    }).start();
}

理论

Animation 很难,我的意思是,非常好的 animation 很难。 没有理论可以创建好的 animation,比如地役权、预期、挤压……我可以打开 go,但我自己很无聊。

关键是,简单地增加一个值(AKA 线性进展)对于 animation 来说是一种糟糕的方法。 如果系统很慢、很忙或由于其他原因无法跟上,animation 将因此受到影响(卡顿、暂停等)。

“更好”的解决方案是使用基于时间的进程。 也就是说,您指定从当前 state 移动到新的 state 和持续循环并更新 state 所需的时间,直到您用完为止。

“主循环”

如果你对游戏开发进行任何研究,他们总是谈论这个叫做“主循环”的东西。

“主循环”负责更新游戏 state 并安排绘制通道。

就您的问题而言,您需要一个“主循环”,它可以更新椭圆的 position 直到它达到目标 position。

因为大多数 GUI 框架已经在它们自己的线程上下文中运行,你需要在另一个线程中设置你的“主循环”

AWT

一些理论

AWT 是原始的 GUI 框架,所以它是“旧的”。 虽然 Swing 确实位于它之上,但您会发现更多的人有使用 Swing 的经验,然后他们使用 AWT。

要记住的重要事项之一是, Canvas不是双缓冲的,因此,如果您更新组件的速度足够快,它将是 flash。

为了克服这个问题,您需要实现某种双缓冲工作流程。

可运行示例

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.time.Duration;
import java.time.Instant;

public class Test {
    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                Frame frame = new Frame();
                frame.add(new TestCanvas());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class Ticker implements Runnable {

        public interface Callbck {
            public void didTick(Ticker ticker);
        }

        private boolean isRunning = false;
        private Thread thread;

        private Callbck callback;

        public void setCallback(Callbck tick) {
            this.callback = tick;
        }

        public void start() {
            if (isRunning) {
                return;
            }
            isRunning = true;
            thread = new Thread(this);
            thread.setDaemon(false);
            thread.start();
        }

        public void stop() {
            if (!isRunning) {
                return;
            }
            isRunning = false;
            thread.interrupt();
            thread = null;
        }

        @Override
        public void run() {
            while (isRunning) {
                try {
                    Thread.sleep(5);
                    if (callback != null) {
                        callback.didTick(this);
                    }
                } catch (InterruptedException ex) {
                    isRunning = false;
                }
            }
        }

    }

    public class TestCanvas extends Canvas {

        private BufferedImage buffer;

        int posX;

        private Ticker ticker;
        private Instant startedAt;
        private Duration duration = Duration.ofSeconds(5);

        public TestCanvas() {
            ticker = new Ticker();
            ticker.setCallback(new Ticker.Callbck() {
                @Override
                public void didTick(Ticker ticker) {
                    if (startedAt == null) {
                        startedAt = Instant.now();
                    }
                    Duration runtime = Duration.between(startedAt, Instant.now());
                    double progress = runtime.toMillis() / (double)duration.toMillis();

                    if (progress >= 1.0) {
                        stopAnimation();
                    }

                    posX = (int)(getWidth() * progress);
                    repaint();
                }
            });
        }

        protected void startAnimtion() {
            ticker.start();
        }

        protected void stopAnimation() {
            ticker.stop();
        }

        @Override
        public void setBounds(int x, int y, int width, int height) {
            buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            super.setBounds(x, y, width, height);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        public void addNotify() {
            super.addNotify();
            startAnimtion();
        }

        @Override
        public void removeNotify() {
            super.removeNotify();
            buffer = null;
        }

        @Override
        public void paint(Graphics g) {
            super.paint(g);
            if (buffer == null) {
                return;
            }
            Graphics2D g2d = buffer.createGraphics();
            g2d.setColor(getBackground());
            g2d.fillRect(0, 0, getWidth(), getHeight());
            g2d.setColor(Color.RED);
            int midY = getHeight() / 2;
            g2d.fillOval(posX, midY - 5, 10, 10);
            g2d.dispose();

            g.drawImage(buffer, 0, 0, this);
        }

    }
}

Canvas go 是什么...?

在大多数情况下,由于上述许多原因,您应该避免使用Canvas ,但您可能考虑使用Canvas的原因之一是如果您想完全控制绘画过程。 如果您想创建一个复杂的游戏,并且希望从渲染管道中获得最佳性能,您可能会这样做。

有关更多详细信息,请参阅BufferStrategy 和 BufferCapabilities以及JavaDocs

基于 Swing 的实现

希望我已经说服您 Swing 实现可能是一个更好的解决方案,在这种情况下您应该使用 Swing Timer而不是Thread ,因为 Z198B44A40AA77F2260886010C7 不是线程安全的

有关详细信息,请参阅Swing 中的并发以及如何使用 Swing 计时器

可运行示例

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.Instant;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class Ticker {

        public interface Callbck {
            public void didTick(Ticker ticker);
        }

        private Timer timer;

        private Callbck callback;

        public void setCallback(Callbck tick) {
            this.callback = tick;
        }

        public void start() {
            if (timer != null) {
                return;
            }
            timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (callback == null) {
                        return;
                    }
                    callback.didTick(Ticker.this);
                }
            });
            timer.start();
        }

        public void stop() {
            if (timer == null) {
                return;
            }
            timer.stop();
            timer = null;
        }

    }

    public class TestPane extends JPanel {

        int posX;

        private Ticker ticker;
        private Instant startedAt;
        private Duration duration = Duration.ofSeconds(5);

        public TestPane() {
            ticker = new Ticker();
            ticker.setCallback(new Ticker.Callbck() {
                @Override
                public void didTick(Ticker ticker) {
                    if (startedAt == null) {
                        startedAt = Instant.now();
                    }
                    Duration runtime = Duration.between(startedAt, Instant.now());
                    double progress = runtime.toMillis() / (double) duration.toMillis();

                    if (progress >= 1.0) {
                        stopAnimation();
                    }

                    posX = (int) (getWidth() * progress);
                    repaint();
                }
            });
        }

        protected void startAnimtion() {
            ticker.start();
        }

        protected void stopAnimation() {
            ticker.stop();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        public void addNotify() {
            super.addNotify();
            startAnimtion();
        }

        @Override
        public void removeNotify() {
            super.removeNotify();
            stopAnimation();
        }

        @Override
        public void paint(Graphics g) {
            super.paint(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setColor(Color.RED);
            int midY = getHeight() / 2;
            g2d.fillOval(posX, midY - 5, 10, 10);
            g2d.dispose();
        }

    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM