我一直想弄清楚如何能够逐渐加速按下按键上的精灵,然后一旦按键被释放,逐渐减速停止,就像在船上一样小行星。如果可能的话,我想在没有任何游戏引擎的情况下这样做。我在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
我只是不确定如何正确编程,因为我只能想办法增加相同的值而不是逐渐加速。
所以这是我的目标(逐渐加速,然后逐渐减速):
我是初学者,对所有想法持开放态度,但我可能没有正确处理这个问题的事实可能是因为我不像其他人那么了解
答案 0 :(得分:2)
基本上加速和减速是速度随时间的变化。
这假设了一些事情。
此示例使用Swing Timer
作为主要"游戏循环"。这用于持续更新玩家对象的状态。
它使用键绑定API来更改GameState
对象,该对象将有关当前输入的信息传递到游戏中,Player
使用它来决定应该应用哪些增量。< / p>
该示例具有相当大的最大速度/旋转,因此您可以使用这些,但如果您释放所有输入,游戏对象将减速回到&#34;中立&#34;位置(0
delta输入)。
如果您在短时间内向一个方向应用输入,然后向相反方向应用输入,则播放器需要向下减速0
然后向相反方向加速(达到最大速度) ),所以它打破了
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<GameInput> 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<GameInput> 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<GameInput> getInputs() {
return new ArrayList<GameInput>(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();
}
}
}
答案 1 :(得分:1)
有两个&#34;概念&#34;来自您需要实施的物理学: 速度和摩擦力。
这是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逻辑。
答案 2 :(得分:1)
保留两个变量。
你的速度。这是每次游戏“滴答”时你移动船的距离。
你的加速度。这是每次游戏开始时你增加速度的程度。
模仿船舶做这样的事情。
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之间划分速度,轻松将其升级为二维或三维空间。
您还需要注意夹紧值。您可能需要maxAcceleration
和maxVelocity
。如果船不能反过来,那么你要确保currentVelocity
永远不会小于0。
这也只代表不断加速。你在放气前的第一秒就会得到相同的速度变化,就像你放手一样。我不是一个汽车人,但我认为大多数车辆在他们开始工作一段时间之后加速得更快。您可以通过使用分段函数来计算加速度,其中函数f(x)将howManyTicksTheForwardKeyHasBeenPressedFor放入x并获得应用于您的速度的加速度。也许前三个刻度加1,而另一个刻度加2你的速度。
玩得开心!
答案 3 :(得分:1)
以前的问题几乎涵盖了这个主题,但如果你想要完美,还有一件事你需要注意:非均匀的时间片,即不同持续时间的滴答。
通常,您的tick()方法应该接受当前时间和最后一次刻度的持续时间作为参数。 (如果没有,那么你需要通过查询当前时间并记住最后一次滴答发生的时间来计算最后一次滴答的持续时间,这样你就可以从另一次滴答中减去一个。)
因此,在每个刻度线上,您不应该只是将当前速度添加到您的位置;你应该在每个刻度上做什么,将当前速度加上最后一个刻度的持续时间加到你的位置。
这样,如果一个嘀嗒声发生得非常快,而另一个嘀嗒声需要很长时间才能完成,那么你的宇宙飞船的运动仍然会是均匀的。