Flipping Rotations and Images

后端 未结 1 2047
囚心锁ツ
囚心锁ツ 2020-12-02 02:56

I\'m programming a platform game in Java, and I am hand-coding the player animations. I am animating each limb separately, changing the position and rotation. This works fin

相关标签:
1条回答
  • 2020-12-02 03:53

    So the basic idea is to simply flip/mirror the Graphics context by scaling one of the axis by a negative value (-1).

    This can be accomplished by simply using Graphics#scale, for example, to flip the graphics horizontally, you would simply use

    graphics.scale(-1, 1);
    

    You would need to translate the Graphics by the width of the viewable area as well using Graphics#translate to reposition the image within the viewable area.

    Everything painted after this will be affected by the change. Because of this, you should be taking snapshots of the Graphics context before every significant change, just make sure you dispose of it when you're done...

    Here is a (rather pathetic animation wise) example...Basically, when you press the left or right arrows, a flag is flipped and the scene repainted. Depending on the flag, the Graphics context is flipped/mirrored...

    Pony Run

    import java.awt.BorderLayout;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Point;
    import java.awt.RenderingHints;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.event.KeyEvent;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import javax.imageio.ImageIO;
    import javax.swing.AbstractAction;
    import javax.swing.ActionMap;
    import javax.swing.InputMap;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.KeyStroke;
    import javax.swing.Timer;
    import javax.swing.UIManager;
    import javax.swing.UnsupportedLookAndFeelException;
    
    public class AnimateCharacter {
    
        public static void main(String[] args) {
            new AnimateCharacter();
        }
    
        public AnimateCharacter() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    }
    
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.setLayout(new BorderLayout());
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class TestPane extends JPanel {
    
            private BufferedImage body;
            private BufferedImage[] legs;
    
            private double[] angels;
            private double[] deltas;
    
            private Point[] pivots = new Point[]{
                // Foreground...
                new Point(100, 227), // Foreleg
                new Point(155, 201), // Hindleg
                // Background...
                new Point(93, 218), // Foreleg
                new Point(143, 195), // Hindleg
            };
    
            private Point[] locations = new Point[]{
                // Foreground...
                new Point(67, 221), // Foreleg
                new Point(124, 172), // Hindleg
                // Background...
                new Point(60, 219), // Foreleg
                new Point(112, 166), // Hindleg
            };
    
            private int direction = 1;
    
            public TestPane() {
                legs = new BufferedImage[4];
                angels = new double[]{
                    45,
                    40,
                    -5,
                    0
                };
                deltas = new double[]{
                    -4,
                    -4,
                    4,
                    4
                };
                try {
                    body = ImageIO.read(getClass().getResource("/Body.png"));
                    // Foreground...
                    legs[0] = ImageIO.read(getClass().getResource("/ForeLeg.png"));
                    legs[1] = ImageIO.read(getClass().getResource("/HindLeg.png"));
                    // Background...
                    legs[2] = ImageIO.read(getClass().getResource("/ForeLeg.png"));
                    legs[3] = ImageIO.read(getClass().getResource("/HindLeg.png"));
                } catch (IOException exp) {
                    exp.printStackTrace();
                }
    
                Timer timer = new Timer(40, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        for (int index = 0; index < angels.length; index++) {
                            angels[index] += deltas[index];
                            if (angels[index] < -45) {
                                angels[index] = -45;
                                deltas[index] *= -1;
                            } else if (angels[index] > 45) {
                                angels[index] = 45;
                                deltas[index] *= -1;
                            }
                        }
                        repaint();
                    }
                });
                timer.start();
    
                InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
                ActionMap am = getActionMap();
    
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "left");
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "right");
    
                am.put("left", new AbstractAction() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        direction = 1;
                        repaint();
                    }
                });
                am.put("right", new AbstractAction() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        direction = -1;
                        repaint();
                    }
                });
            }
    
            @Override
            public Dimension getPreferredSize() {
                return body == null ? new Dimension(200, 200) : new Dimension(body.getWidth() + 50, body.getHeight() + 50);
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g.create();
                applyQualityRenderingHints(g2d);
    
                int x = (getWidth() - body.getWidth()) / 2;
                int y = (getHeight() - body.getHeight()) / 2;
    
                if (direction < 0) {
                    g2d.scale(-1, 1);
                    g2d.translate(-getWidth(), 0);
                }
    
                // Background legs...
                drawLegs(g2d, x, y, 2);
    
                g2d.drawImage(body, x, y, this);
    
                // Foreground legs...
                drawLegs(g2d, x, y, 0);
    
                g2d.dispose();
            }
    
            protected void drawLegs(Graphics2D g2d, int x, int y, int offset) {
    
                for (int index = 0; index < 2; index++) {
                    Graphics2D copy = (Graphics2D) g2d.create();
                    copy.translate(x, y);
                    int leg = index + offset;
                    int pivotX = pivots[leg].x;
                    int pivotY = pivots[leg].y;
                    copy.rotate(Math.toRadians(angels[leg]), pivotX, pivotY);
                    copy.drawImage(
                            legs[leg],
                            locations[leg].x,
                            locations[leg].y,
                            this);
                    copy.dispose();
                }
    
            }
    
            public void applyQualityRenderingHints(Graphics2D g2d) {
    
                g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
                g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
                g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
                g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
                g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
    //        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    
            }
        }
    
    }
    

    The magic happens in a few places...

    The Graphics context is first copied and then (if required) flipped using scale...

    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();
        //...
        if (direction < 0) {
            g2d.scale(-1, 1);
            g2d.translate(-getWidth(), 0);
        }
    

    I also use the same technique when painting the legs...

    protected void drawLegs(Graphics2D g2d, int x, int y, int offset) {
    
        for (int index = 0; index < 2; index++) {
            Graphics2D copy = (Graphics2D) g2d.create();
            copy.translate(x, y);
            //...
            copy.rotate(Math.toRadians(angels[leg]), pivotX, pivotY);
            copy.drawImage(
                    legs[leg],
                    locations[leg].x,
                    locations[leg].y,
                    this);
            copy.dispose();
        }
    

    This isolates the changes to the copy of the Graphics context, each copy will inherit the current state of it's parent, making this a really useful technique...

    0 讨论(0)
提交回复
热议问题