按键上逐渐加速精灵; 钥匙释放逐渐减速

我一直试图弄清楚如何在按下按键时逐渐加速精灵,然后一旦释放按键,逐渐减速停止,就像小行星中的船一样。 如果可能的话,我想在没有任何游戏引擎的情况下这样做。 我在SO上搜索了这个并找到了相关的问题,但他们并没有在我看来完全回答我的问题。

到目前为止我所想到的是:

//while the key is being pressed //move by increasing y value //but continue to increase the amount y increases by as you hold this down, //until it reaches certain maxSpeed //when the key is released, gradually decelerate to zero 

我只是不确定如何正确编程,因为我只能想办法增加相同的值而不是逐渐加速。

所以这是我的目标( 逐渐加速,然后逐渐减速)

加速精灵

我是初学者,对所有想法持开放态度,但我可能没有正确处理这个问题的事实可能是因为我不像其他人那么了解

之前的问题几乎涵盖了这个主题,但是如果你想要完美,还有一件事你需要注意:非均匀时间片,即不同持续时间的滴答。

通常,您的tick()方法应该接受当前时间和最后一次刻度的持续时间作为参数。 (如果没有,那么你需要通过查询当前时间并记住最后一次滴答发生的时间来计算最后一次滴答的持续时间,这样你就可以从另一次滴答中减去一个。)

因此,在每个刻度线上,您不应该只是将当前速度添加到您的位置; 你应该在每个刻度上做什么,将当前速度加上最后一个刻度的持续时间加到你的位置。

这样,如果一个嘀嗒声发生得非常快,而另一个嘀嗒声需要很长时间才能完成,那么你的宇宙飞船的运动仍将是均匀的。

基本上加速和减速是速度随时间的变化。

这假设了一些事情。

  1. 您正在定期更新状态,因此加速/减速可以应用于对象当前速度
  2. 您可以监视给定输入的状态,以便了解应该应用的时间和内容

此示例使用Swing Timer作为主要的“游戏循环”。 这用于连续更新播放器对象的状态。

它使用键绑定API来更改GameState对象,该对象将有关当前输入的信息传递到游戏中, Player使用它来决定应该应用哪些增量。

该示例具有相当大的最大速度/旋转,因此您可以使用这些,但是如果释放所有输入,游戏对象将减速回到“中性”位置( 0输入)。

如果您在一个方向上短时间应用输入,然后向相反方向应用输入,则播放器需要向下减速到0然后在相反方向上加速(达到最大速度),因此它会断开

Ponioids

 import java.awt.Container; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.FontMetrics; 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 java.awt.image.ImageObserver; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; 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 Ponies { public static void main(String[] args) { new Ponies(); } public Ponies() { 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 GameView()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); } public class GameView extends JPanel { private GameState gameState; private Player player; public GameView() { gameState = new GameState(); addKeyBindingForInput(GameInput.DOWN, KeyEvent.VK_S); addKeyBindingForInput(GameInput.UP, KeyEvent.VK_W); addKeyBindingForInput(GameInput.LEFT, KeyEvent.VK_A); addKeyBindingForInput(GameInput.RIGHT, KeyEvent.VK_D); addKeyBindingForInput(GameInput.ROTATE_LEFT, KeyEvent.VK_LEFT); addKeyBindingForInput(GameInput.ROTATE_RIGHT, KeyEvent.VK_RIGHT); try { player = new Player(400, 400); Timer timer = new Timer(40, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { player.update(GameView.this, gameState); repaint(); } }); timer.start(); } catch (IOException ex) { ex.printStackTrace(); } } protected void addKeyBindingForInput(GameInput input, int virtualKey) { InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW); ActionMap actionMap = getActionMap(); inputMap.put(KeyStroke.getKeyStroke(virtualKey, 0, false), input + ".pressed"); actionMap.put(input + ".pressed", new GameInputAction(gameState, input, true)); inputMap.put(KeyStroke.getKeyStroke(virtualKey, 0, true), input + ".released"); actionMap.put(input + ".released", new GameInputAction(gameState, input, false)); } @Override public Dimension getPreferredSize() { return new Dimension(400, 400); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g.create(); player.paint(g2d, this); List inputs = gameState.getInputs(); FontMetrics fm = g2d.getFontMetrics(); int y = getHeight() - (fm.getHeight() * inputs.size()); for (GameInput input : inputs) { String text = input.name(); g2d.drawString(text, getWidth() - fm.stringWidth(text), y + fm.getAscent()); y += fm.getHeight(); } g2d.dispose(); } } public class GameInputAction extends AbstractAction { private final GameState gameState; private final GameInput input; private final boolean pressed; public GameInputAction(GameState gameState, GameInput input, boolean pressed) { this.gameState = gameState; this.input = input; this.pressed = pressed; } @Override public void actionPerformed(ActionEvent e) { if (pressed) { gameState.addInput(input); } else { gameState.removeInput(input); } } } public enum GameInput { LEFT, RIGHT, UP, DOWN, ROTATE_LEFT, ROTATE_RIGHT } public class GameState { private Set inputs; public GameState() { inputs = new HashSet<>(25); } public boolean hasInput(GameInput input) { return inputs.contains(input); } public void addInput(GameInput input) { inputs.add(input); } public void removeInput(GameInput input) { inputs.remove(input); } public List getInputs() { return new ArrayList(inputs); } } public static class Player { protected static final int MAX_DELTA = 20; protected static final int MAX_ROTATION_DELTA = 20; protected static final int MOVE_DELTA = 4; protected static final int ROTATION_DELTA = 4; private int x, y; private int xDelta, yDelta; private BufferedImage sprite; private double angle; private double rotationDelta; public Player(int width, int height) throws IOException { sprite = ImageIO.read(getClass().getResource("/PlayerSprite.png")); x = (width - sprite.getWidth()) / 2; y = (height - sprite.getHeight()) / 2; } public void update(Container container, GameState state) { if (state.hasInput(GameInput.LEFT)) { xDelta -= MOVE_DELTA; } else if (state.hasInput(GameInput.RIGHT)) { xDelta += MOVE_DELTA; } else if (xDelta < 0) { xDelta++; } else if (xDelta > 0) { xDelta--; } if (state.hasInput(GameInput.UP)) { yDelta -= MOVE_DELTA; } else if (state.hasInput(GameInput.DOWN)) { yDelta += MOVE_DELTA; } else if (yDelta < 0) { yDelta++; } else if (yDelta > 0) { yDelta--; } if (state.hasInput(GameInput.ROTATE_LEFT)) { rotationDelta -= MOVE_DELTA; } else if (state.hasInput(GameInput.ROTATE_RIGHT)) { rotationDelta += MOVE_DELTA; } else if (rotationDelta < 0) { rotationDelta++; } else if (rotationDelta > 0) { rotationDelta--; } xDelta = Math.max(-MAX_DELTA, Math.min(xDelta, MAX_DELTA)); yDelta = Math.max(-MAX_DELTA, Math.min(yDelta, MAX_DELTA)); rotationDelta = Math.max(-MAX_ROTATION_DELTA, Math.min(rotationDelta, MAX_ROTATION_DELTA)); x += xDelta; y += yDelta; angle += rotationDelta; if (x < -sprite.getWidth()) { x = container.getWidth(); } else if (x + sprite.getWidth() > container.getWidth() + sprite.getWidth()) { x = 0; } if (y < -sprite.getHeight()) { y = container.getHeight(); } else if (y + sprite.getHeight() > container.getHeight() + sprite.getHeight()) { y = 0; } } public void paint(Graphics2D g2d, ImageObserver io) { Graphics2D copy = (Graphics2D) g2d.create(); copy.translate(x, y); copy.rotate(Math.toRadians(angle), sprite.getWidth() / 2.0d, sprite.getHeight() / 2.0d); copy.drawImage(sprite, 0, 0, io); copy.dispose(); } } } 

请查看如何使用键绑定以及如何使用Swing Timers获取更多详细信息

保留两个变量。

  1. 你的速度。 这是每次游戏“滴答”时你移动船的距离。

  2. 你的加速度。 这是每次游戏滴答时你增加速度的程度。

模仿船做这样的事情。

 tick() { if(forward key is pressed) { howManyTicksTheForwardKeyHasBeenPressedFor++; currentAcceleration += howManyTicksTheForwardKeyHasBeenPressedFor++; //This is the slow ramp of speed. Notice that as you hold the key longer, your acceleration grows larger. currentVelocity += currentAcceleration; //This is how the ship actually moves faster. } else { howManyTicksTheForwardKeyHasBeenPressedFor--; currentAcceleration -= howManyTicksTheForwardKeyHasBeenPressedFor; //This is the slow loss of speed. You'll need to make a decision about how this works when a user goes from holding down the forward key to letting go. Here I'm assuming that the speed bleeds off at a constant rate the same way it gets added. currentVelocity += currentAcceleration; //This is how the ship actually slows down. } ship.position += currentVelocity; //This is how you actually move the ship. } 

这仅适用于沿直线沿单一方向行驶的船舶。 通过跟踪船的指向位置,并在x和y和/或z之间划分速度,您应该能够轻松地将其升级到二维或三维空间。

您还需要注意夹紧值。 您可能需要maxAccelerationmaxVelocity 。 如果船不能反转,那么你要确保currentVelocity永远不会小于0。

这也仅代表恒定的加速度。 你在放气前的第一秒就会得到相同的速度变化,就像你放手一样。 我不是一个汽车人,但我认为大多数车辆在他们开始工作一段时间之后加速得更快。 您可以通过使用分段函数来计算加速度,其中函数f(x)将howManyTicksTheForwardKeyHasBeenPressedFor放入x并获得应用于您的速度的加速度。 也许前三个刻度加1,而另一个刻度加2你的速度。

玩得开心!

物理学中有两个“概念”需要实现:速度和摩擦力。

这是2D中的一个简单示例。 您还可以使用包装类将x / y变量组合到单个对象中,并提供方便的方法来更改其内容。

每个对象都需要有一个位置和一个速度变量。 我们还需要摩擦力,这是每种材料的常数(因为您的物体可能总是以相同的材料行进,我们只是模拟摩擦力是恒定的)。 在这个简单的模拟中,当摩擦力达到1时,摩擦力变弱。这意味着在摩擦力= 1时你没有摩擦力,在摩擦力为0时你的物体会立即停止:

 public class PhysicsObject{ public static final double FRICTION = 0.99; private double posX; private double posY; private double speedX = 0; private double speedY = 0; public PhysicsObject(double posX, double posY){ this.posX = posX; this.posY = posY; } public void accelerate(double accelerationX, double accelerationY){ speedX += accelerationX; speedY += accelerationY; } public void update(){ posX += speedX; posY += speedY; speedX *= FRICTION; speedY *= FRICTION; } public double getPosX(){ return posX; } public double getPosY(){ return posY; } } 

请注意,您的对象具有更新方法。 需要定期在场景中的所有对象上调用此方法,以应用移动。 在这种方法中你也可以处理碰撞检测,敌人可以做他们的AI逻辑。