如何使Java中的多边形对象脉动(如Atari游戏“冒险”中的圣杯)

如何使Java中的多边形对象脉动(如Atari游戏“冒险”中的圣杯),java,graphics,timer,colors,paintcomponent,Java,Graphics,Timer,Colors,Paintcomponent,这就是我在paintComponent中的内容(大多数其他内容都被省略了,只涉及一个名为chalice的项目对象,带有一个多边形字段,if语句的显式参数对于这个问题并不重要) 目前,它显示为纯白,因为我将颜色设置为全部255,但我想让它平滑地逐渐过渡到不同的颜色,而不是笔划,更像是脉动,但我真的不知道这叫什么。我在考虑用数组替换颜色的显式参数,该数组循环遍历该数组中的数字,并以某种方式将其链接到TimerListener,但我对图形不太熟悉,所以我不确定这是否是最好的方法 public void

这就是我在paintComponent中的内容(大多数其他内容都被省略了,只涉及一个名为chalice的项目对象,带有一个多边形字段,if语句的显式参数对于这个问题并不重要) 目前,它显示为纯白,因为我将颜色设置为全部255,但我想让它平滑地逐渐过渡到不同的颜色,而不是笔划,更像是脉动,但我真的不知道这叫什么。我在考虑用数组替换颜色的显式参数,该数组循环遍历该数组中的数字,并以某种方式将其链接到TimerListener,但我对图形不太熟悉,所以我不确定这是否是最好的方法

public void paintComponent(Graphics g) {
Graphics2D sprite = (Graphics2D) g;

if (chalice.getHolding() == true || roomID == chalice.getRoomDroppedIn()) {
            sprite.setColor(new Color(255, 255, 255));
            sprite.fill(chalice.getPoly());
        }
}
一些基本概念

  • 脉动效应需要向两个方向移动,它需要淡入淡出
  • 为了知道应该应用多少“效果”,需要知道两件事。首先,整体效果循环所需的时间(从完全不透明到完全透明再到完全透明)以及动画循环的时间
这不是一件容易管理的事情,有很多“有状态”的信息需要管理和维护,通常情况下,与其他效果或实体分开管理和维护

在我看来,最简单的解决方案是设计某种“时间线”,它沿着时间线管理关键点(关键帧),计算每个点之间的距离及其代表的值

退后一步。我们知道:

  • 0%我们希望完全不透明
  • 50%我们希望完全透明
  • 100%我们希望完全不透明
上面考虑到我们想要“自动反转”动画

使用百分比的原因是,它允许我们定义任何给定持续时间的时间线,时间线将处理其余时间。在可能的情况下,总是使用这样的标准化值,这会使整个过程简单得多

时间线
下面是一个非常简单的“时间线”概念。它具有
持续时间
、播放时间线的时间、关键帧,这些帧提供时间线持续时间内的关键值,以及在时间线生命周期内的特定点计算特定值的方法

此实现还提供“自动”重播功能。也就是说,如果时间线在指定的持续时间内“结束”播放,而不是停止,它将自动重置,并将“结束”的时间量作为下一个循环的一部分(整洁)

我们给出一个指定的
持续时间
,并设置关键帧值。之后,我们只需要“启动”它,并根据播放时间从时间线中获取当前的

对于一个看似简单的问题,这似乎需要做很多工作,但请记住,这是动态的,并且是可重用的

它是动态的,因为您可以提供您想要的任何
持续时间
,从而改变速度,它将“正常工作”,并且可重复使用,因为您可以为多个实体生成多个实例,并且它将被独立管理

例子。。。 下面的示例简单地使用Swing
Timer
作为动画的“主循环”。在每个循环中,它要求
时间线
提供“当前”值,该值仅充当“脉动”效应的
α

TimeLine
类本身已经足够解耦,因此无论“如何”建立“主循环”,只要启动它并在任何时候从中提取“当前”值即可

我会将
时间线
捆绑起来,作为指定实体效果的一部分。这将
时间线
绑定到特定实体,这意味着许多实体都可以有自己的
时间线
来计算不同值和效果的验证

“有更简单的解决方案吗?” 这是一个主观问题。可能有一种“更简单”的方法可以完成同样的工作,但它不会像这种方法那样具有可伸缩性或可重用性

动画是一个复杂的主题,试图让它在复杂的解决方案中工作,运行多个不同的效果和实体只会使问题复杂化

我曾经尝试过将
时间线
设置为通用的,这样就可以根据期望的结果生成各种不同的值,从而使其成为一个更加灵活和可重用的解决方案

颜色混合。。。。 我不知道这是否是一项要求,但如果您有一系列要混合的颜色,那么
时间线也会对您有所帮助(您不太需要持续时间)。可以设置一系列颜色(用作关键帧),并根据动画的进度计算要使用的颜色


混合颜色有点麻烦,我花了很多时间试图找到一种适合我的算法,在和中演示了这一点。您需要更多信息。基本上,您需要知道动画将播放多长时间,以及在整个循环中播放多长时间。您最好使用Swing的替代品。它有更多的动画、色彩插值等课程。@ZackarySchrage这只是一个想法,整合起来需要一些工作,但会给你一个跳跃点
public class TimeLine {

    private Map<Float, KeyFrame> mapEvents;

    private Duration duration;
    private LocalDateTime startedAt;

    public TimeLine(Duration duration) {
        mapEvents = new TreeMap<>();
        this.duration = duration;
    }

    public void start() {
        startedAt = LocalDateTime.now();
    }
    
    public boolean isRunning() {
        return startedAt != null;
    }

    public float getValue() {
        if (startedAt == null) {
            return getValueAt(0.0f);
        }
        Duration runningTime = Duration.between(startedAt, LocalDateTime.now());
        if (runningTime.compareTo(duration) > 0) {
            runningTime = runningTime.minus(duration);
            startedAt = LocalDateTime.now().minus(runningTime);
        }
        long total = duration.toMillis();
        long remaining = duration.minus(runningTime).toMillis();
        float progress = remaining / (float) total;
        return getValueAt(progress);
    }

    public void add(float progress, float value) {
        mapEvents.put(progress, new KeyFrame(progress, value));
    }

    public float getValueAt(float progress) {

        if (progress < 0) {
            progress = 0;
        } else if (progress > 1) {
            progress = 1;
        }

        KeyFrame[] keyFrames = getKeyFramesBetween(progress);

        float max = keyFrames[1].progress - keyFrames[0].progress;
        float value = progress - keyFrames[0].progress;
        float weight = value / max;
        
        float blend = blend(keyFrames[0].getValue(), keyFrames[1].getValue(), 1f - weight);
        return blend;
    }

    public KeyFrame[] getKeyFramesBetween(float progress) {

        KeyFrame[] frames = new KeyFrame[2];
        int startAt = 0;
        Float[] keyFrames = mapEvents.keySet().toArray(new Float[mapEvents.size()]);
        while (startAt < keyFrames.length && keyFrames[startAt] <= progress) {
            startAt++;
        }

        if (startAt >= keyFrames.length) {
            startAt = keyFrames.length - 1;
        }

        frames[0] = mapEvents.get(keyFrames[startAt - 1]);
        frames[1] = mapEvents.get(keyFrames[startAt]);

        return frames;

    }

    protected float blend(float start, float end, float ratio) {
        float ir = (float) 1.0 - ratio;
        return (float) (start * ratio + end * ir);
    }

    public class KeyFrame {

        private float progress;
        private float value;

        public KeyFrame(float progress, float value) {
            this.progress = progress;
            this.value = value;
        }

        public float getProgress() {
            return progress;
        }

        public float getValue() {
            return value;
        }

        @Override
        public String toString() {
            return "KeyFrame progress = " + getProgress() + "; value = " + getValue();
        }

    }

}
timeLine = new TimeLine(Duration.ofSeconds(5));
timeLine.add(0.0f, 1.0f);
timeLine.add(0.5f, 0.0f);
timeLine.add(1.0f, 1.0f);
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.TreeMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private TimeLine timeLine;
        private float alpha = 0;

        public TestPane() {
            timeLine = new TimeLine(Duration.ofSeconds(5));
            timeLine.add(0.0f, 1.0f);
            timeLine.add(0.5f, 0.0f);
            timeLine.add(1.0f, 1.0f);
            Timer timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (!timeLine.isRunning()) {
                        timeLine.start();
                    }
                    alpha = timeLine.getValue();
                    repaint();
                }
            });
            timer.start();
        }

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

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setComposite(AlphaComposite.SrcOver.derive(alpha));
            g2d.setColor(Color.RED);
            g2d.fill(new Rectangle(45, 45, 110, 110));
            g2d.dispose();

            g2d = (Graphics2D) g.create();
            g2d.setColor(getBackground());
            g2d.fill(new Rectangle(50, 50, 100, 100));
            g2d.setColor(Color.BLACK);
            g2d.draw(new Rectangle(50, 50, 100, 100));
            g2d.dispose();
        }

    }
}