[英]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++;
}
}
這沒有動畫的原因是沒有任何東西觸發組件更新和重繪自身。 有幾點需要考慮:
有些東西需要調用update
方法。 通常,這是通過調用組件上的repaint()
來觸發的,但這段代碼中沒有任何內容調用該方法。
調用super.update(g)
以確保調用默認行為(清除 canvas 並再次繪制它)對於重寫的update
方法很重要。
Animation 有一個時間分量:橢圓應該在一段時間內移動。 這需要納入邏輯。 AWT 沒有內置的定時行為機制。
如果您能夠使用 Swing 中的類,則javax.swing.Timer
class 對 Z62F1C25ED192099 非常有用。 它在 AWT 線程上執行您的回調,因此意味着您不必采取特殊措施來確保線程安全。
如果不能使用 Swing,可以使用java.util.Timer
或自定義線程,但需要直接管理線程同步。
一旦橢圓到達 canvas 的邊緣,您可能還希望 animation 停止。
這是一個使用javax.swing.Timer
的示例(假設 Java 8 或更高版本)。 請注意,所有 animation 邏輯都在附加到Timer
的ActionListener
中,因此已刪除覆蓋的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
字段。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 是原始的 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 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.