簡體   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