Gradually speeding a sprite

2019-01-20 06:53发布

问题:

I'm trying to make the sprite speed-up gradually on button press and not to move constant speed only. Also set a max-speed limit. I hope you understand what i mean.

timer = new Timer(5, this);
timer.start();

public void paint(Graphics g) {
    super.paint(g);
    Graphics2D g2d = (Graphics2D)g;
    g2d.drawImage(image, x, y, this); //x,y = position
    Toolkit.getDefaultToolkit().sync();
    g.dispose();
}

private class TAdapter extends KeyAdapter { 
    public void keyPressed(KeyEvent e) {
        int key = e.getKeyCode();
        if (key == KeyEvent.VK_LEFT) {
            dx = -1;
        }
        if (key == KeyEvent.VK_RIGHT) {
            dx = 1;
        }
        if (key == KeyEvent.VK_UP) {
            dy = -1;
        }
        if (key == KeyEvent.VK_DOWN) {
            dy = 1;
        }
    }
}   
    public void actionPerformed(ActionEvent e) {
        x += dx;   
        y += dy;
        repaint();  
    }

回答1:

There are several things (initially) wrong with your example code...

  1. You are overriding the paint method. It is recommend that you override the paintComponent method instead. If you are overriding the paint method of a top level container, like JFrame, then it is recommended that you don't. Instead, use something like JPanel as the bases for your custom painting...
  2. You are disposing of the Graphics context that is past to you. This is VERY dangerous, as this will prevent anything else from been painted. The Graphics context is a shared resources, everything that needs to be updated during this repaint cycle will using the same Graphics context.
  3. You are using a KeyListener. KeyListener suffers from focus issues. This can easily be remedied through the use of the Key Binding API. Key bindings are also more flexible, as they separate the physical key from the action, allowing you to associate the action with different keys with little effort and/or reuse the underlying action (such as with buttons).

So. For your question. You need to know...

  • The current speed...
  • The minimum allowable speed...
  • The maximum allowable speed...

You will also want to maintain the current position of the object you are altering.

This example doesn't actually move the "player" so much as it moves the background. The background position is altered by the xDelta, which is the speed of change...

import java.awt.BorderLayout;
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.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
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 TestSpeed {

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

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

                JFrame frame = new JFrame("Test");
                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 background;
        // The current position of the background
        private int xPos = 0;
        // The speed/delta that the xPos is changed...
        private int xDelta = 0;

        public TestPane() {
            Timer timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (xPos < -(getWidth())) {
                        xPos = 0;
                    }
                    xPos -= xDelta;
                    repaint();
                }
            });
            timer.setRepeats(true);
            timer.setCoalesce(true);
            timer.start();
            InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "slower");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "faster");

            ActionMap am = getActionMap();
            am.put("slower", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    setSpeed(-1);
                }
            });
            am.put("faster", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    setSpeed(1);
                }
            });
        }

        protected void setSpeed(int delta) {
            xDelta += delta;
            // Check the change in speed to ensure it's within the appropriate range
            if (xDelta < 0) {
                xDelta = 0;
            } else if (xDelta > 9) {
                xDelta = 9;
            }
        }

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

        @Override
        public void invalidate() {
            background = null;
            super.invalidate();
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            int x = xPos;
            g.setColor(Color.DARK_GRAY);
            while (x < getWidth()) {
                g.drawLine(x, 0, x, getHeight());
                x += 15;
            }

            int width = getWidth();
            int height = getHeight();
            x = (width / 2) - 5;
            int y = (height / 2) - 5;
            g.setColor(Color.RED);
            g.fillOval(x, y, 10, 10);
        }        
    }    
}


回答2:

You need to define and store the values for max speed, actual speed and the speed increment. The simplest way to define the speed increment, and should try it first, is to define a constant speed increment. Based on the provided code:

int maxspeed = 5;
int speed = 1;
int acceleration = 1;

timer = new Timer(5, this);
timer.start();

public void paint(Graphics g) {
    super.paint(g);
    Graphics2D g2d = (Graphics2D)g;
    g2d.drawImage(image, x, y, this); //x,y = position
    Toolkit.getDefaultToolkit().sync();
    g.dispose();
}

private class TAdapter extends KeyAdapter { 
    public void keyPressed(KeyEvent e) {
        int key = e.getKeyCode();
        if (key == KeyEvent.VK_LEFT) {
            dx = -acceleration;
        }
        if (key == KeyEvent.VK_RIGHT) {
            dx = acceleration;
        }
        if (key == KeyEvent.VK_UP) {
            dy = -acceleration;
        }
        if (key == KeyEvent.VK_DOWN) {
            dy = acceleration;
        }
    }
}

public void actionPerformed(ActionEvent e) {
    if (speed < maxspeed) {
        speed += acceleration;
    }
    x += dx * speed;   
    y += dy * speed;
    repaint();  
}

As I don't really know the context of the problem, or the goal to achieve, I didn't include any way to slow the sprite down again, once maxspeed is hit.

Some interval for speed gains may also be considered. With the above code, the updates to speed would be fast enough that you probably wouldn't notice them.