具有预定目的地的重力

我在二维环境中使用重力来进行游戏。 我在游戏中使用的对象都有x和y坐标,它们可以被“抛出”到一个级别,意味着实例化,然后给定一个特定的原点位置,然后给每个帧的新坐标具有以下重力:

public void throwObject(float delta) { setX(getX() + velocity.x * delta); setY(getY() + velocity.y * delta); velocity.y += gravity.y * delta; } 

以下内容:

 Vector2 GRAVITY = new Vector2(0, -10); 

从给定的originx和originy,对象“相应地”移动,这很好。

现在我想让对象移动到给定目的地,例如:

 destinationx = 50; destinationy = 350; 

如果我使用静态原点x和原点,我如何计算velocity.x和velocity.y,以便使用抛射曲线将对象抛向指定的目标坐标?

编辑:我在确定velocity.x的计算方面取得了一些进展:

  velocity.x = (destinationx - originx) / 100; 

其中100是我设置为静态的帧数。 这很好用。

对于velocity.y,我尝试过:

 velocity.y = (destinationy - originy) / 100 * delta + Math.sqrt(gravity) * 2; 

它给出的结果看起来与正确的公式非常接近,但并不完全相同

首先:有无数的解决方案。 包括“平凡”的一个,其中速度只是源和目标位置的差异,除以“delta”(也就是说,由于速度为“目标将在一个步骤中到达的解决方案”)无限高“)。

但是直观地可以想象你想要达到的目标,所以我会停止挑剔:

您想在笛卡尔坐标中指定速度:(deltaX,deltaY)。 用坐标来考虑这个速度更方便:(角度,功率),其中“功率”代表初始速度的大小 。 (有关转换规则,请参阅维基百科:极坐标系 )。

给定固定的初始速度,射弹将击中目标时有零个,一个或两个角度。 给定固定的初始角度,射弹将击中目标时可能存在零个或一个初始速度。 但是如果你可以选择速度和角度两者 ,那么从原点开始并穿过目标就会有无数个弹道轨迹。

我使用维基百科中的一些公式(主要来自维基百科:弹道的轨迹:)从我以前的一个答案中略微扩展了一个弹丸射击示例 :

图片

您可以使用滑块更改角度和功率,并使用鼠标拖动原点和目标。 该文本包含两个重要的输出:

  • 所需角度 :这是可以击中目标的两个射击角度,假设未修改功率。 如果没有解决方案(例如,当目标超出范围时,无论角度如何),则这些角度可以是NaN
  • 所需功率 :这是击中目标所需的功率,假设角度未被修改。 如果没有解决方案(例如,当前角度指向错误的方向时),则此功率可以是NaN

根据您对速度的自由度,您可能会提取相关公式。 (对于遗失的评论道歉,这个“SSCCE”(简短,自包含,可编辑,示例)已逐渐变成“LSCCE”…)

 import java.awt.BorderLayout; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JSlider; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; public class ProjectileShooterTest { public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { createAndShowGUI(); } }); } private static void createAndShowGUI() { JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final ProjectileShooter projectileShooter = new ProjectileShooter(); final ProjectileShooterPanel projectileShooterPanel = new ProjectileShooterPanel(projectileShooter); projectileShooter.setPaintingComponent(projectileShooterPanel); ProjectileShooterMouseControl mouseControl = new ProjectileShooterMouseControl( projectileShooter, projectileShooterPanel); projectileShooterPanel.addMouseMotionListener(mouseControl); projectileShooterPanel.addMouseListener(mouseControl); JPanel controlPanel = new JPanel(new GridLayout(1,0)); controlPanel.add(new JLabel("Angle: ", SwingConstants.RIGHT)); final JSlider angleSlider = new JSlider(0, 180, 45); angleSlider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { int angleDeg = angleSlider.getValue(); projectileShooter.setAngle(Math.toRadians(angleDeg)); } }); controlPanel.add(angleSlider); controlPanel.add(new JLabel("Power:", SwingConstants.RIGHT)); final JSlider powerSlider = new JSlider(0, 50, 25); controlPanel.add(powerSlider); powerSlider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { int power = powerSlider.getValue(); projectileShooter.setPower(power); } }); JButton shootButton = new JButton("Shoot"); shootButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { projectileShooter.shoot(); } }); controlPanel.add(shootButton); f.getContentPane().setLayout(new BorderLayout()); f.getContentPane().add(controlPanel, BorderLayout.NORTH); f.getContentPane().add(projectileShooterPanel, BorderLayout.CENTER); f.setSize(1200,800); f.setLocationRelativeTo(null); f.setVisible(true); } } class ProjectileShooterMouseControl implements MouseListener, MouseMotionListener { private final ProjectileShooter projectileShooter; private final ProjectileShooterPanel projectileShooterPanel; private boolean draggingOrigin = false; private boolean draggingTarget = false; ProjectileShooterMouseControl( ProjectileShooter projectileShooter, ProjectileShooterPanel projectileShooterPanel) { this.projectileShooter = projectileShooter; this.projectileShooterPanel = projectileShooterPanel; } private Point2D toWorld(Point2D screen) { return new Point2D.Double(screen.getX(), projectileShooterPanel.getHeight() - screen.getY()); } @Override public void mouseMoved(MouseEvent e) { } @Override public void mouseDragged(MouseEvent e) { Point2D p = toWorld(e.getPoint()); if (draggingOrigin) { projectileShooter.setOrigin(p); } else if (draggingTarget) { projectileShooter.setTarget(p); } } @Override public void mousePressed(MouseEvent e) { Point2D p = toWorld(e.getPoint()); Point2D origin = projectileShooter.getOrigin(); Point2D target = projectileShooter.getTarget(); if (origin.distance(p) < 10) { draggingOrigin = true; } else if (target.distance(p) < 10) { draggingTarget = true; } } @Override public void mouseReleased(MouseEvent e) { draggingOrigin = false; draggingTarget = false; } @Override public void mouseClicked(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } } class ProjectileShooter { private static final double TIME_SCALE = 20; static final double GRAVITY = 9.81 * 0.1; private double angleRad = Math.toRadians(45); private double power = 25; private final Point2D origin = new Point2D.Double(50, 50); private final Point2D target = new Point2D.Double(500, 100); private Projectile projectile; private JComponent paintingComponent; void setPaintingComponent(JComponent paintingComponent) { this.paintingComponent = paintingComponent; } void setOrigin(Point2D origin) { this.origin.setLocation(origin); this.paintingComponent.repaint(); } Point2D getOrigin() { return new Point2D.Double(origin.getX(), origin.getY()); } void setTarget(Point2D target) { this.target.setLocation(target); this.paintingComponent.repaint(); } Point2D getTarget() { return new Point2D.Double(target.getX(), target.getY()); } void setAngle(double angleRad) { this.angleRad = angleRad; this.paintingComponent.repaint(); } double getAngle() { return angleRad; } void setPower(double power) { this.power = power; this.paintingComponent.repaint(); } double getPower() { return power; } double computeY(double x) { // http://de.wikipedia.org/wiki/Wurfparabel // #Mathematische_Beschreibung double g = GRAVITY; double b = angleRad; double v0 = power; if (b > Math.PI / 2) { b = Math.PI - b; } double cb = Math.cos(b); return x * Math.tan(b) - g / (2 * v0 * v0 * cb * cb) * x * x; } double computeRange(double h0) { // http://de.wikipedia.org/wiki/Wurfparabel // #Reichweite_bei_von_Null_verschiedener_Anfangsh.C3.B6he double g = GRAVITY; double b = angleRad; double v0 = power; double sb = Math.sin(b); double f0 =(v0 * v0) / (g + g) * Math.sin(b + b); double i = 1.0 + (2 * g * h0) / (v0 * v0 * sb * sb); double f1 = 1.0 + Math.sqrt(i); return f0 * f1; } Point2D computeRequiredAngles() { // http://en.wikipedia.org/wiki/Trajectory_of_a_projectile // #Angle_required_to_hit_coordinate_.28x.2Cy.29 double v0 = power; double g = GRAVITY; double vv = v0 * v0; double dx = target.getX() - origin.getX(); double dy = target.getY() - origin.getY(); double radicand = vv * vv - g * (g * dx * dx + 2 * dy * vv); double numerator0 = vv + Math.sqrt(radicand); double numerator1 = vv - Math.sqrt(radicand); double angle0 = Math.atan(numerator0 / (g*dx)); double angle1 = Math.atan(numerator1 / (g*dx)); return new Point2D.Double(angle0, angle1); } double computeRequiredPower() { // WolframAlpha told me so... double R = target.getX() - origin.getX(); double h0 = origin.getY() - target.getY(); double g = GRAVITY; double b = angleRad; double sb = Math.sin(b); double isb = 1.0 / sb; double v0 = Math.sqrt(2) * Math.sqrt(g) * R * Math.sqrt(1 / Math.sin(2*b)) / Math.sqrt(h0 * Math.sin(2*b) * isb * isb + 2*R); return v0; } void shoot() { Thread t = new Thread(new Runnable() { @Override public void run() { executeShot(); } }); t.setDaemon(true); t.start(); } private static Point2D polarToCartesian( Point2D polar, Point2D cartesian) { double x = Math.cos(polar.getX()) * polar.getY(); double y = Math.sin(polar.getX()) * polar.getY(); if (cartesian == null) { cartesian = new Point2D.Double(); } cartesian.setLocation(x, y); return cartesian; } private void executeShot() { if (projectile != null) { return; } projectile = new Projectile(); projectile.setPosition(origin); Point2D velocity = polarToCartesian( new Point2D.Double(angleRad, power), null); projectile.setVelocity(velocity); long prevTime = System.nanoTime(); while (projectile.getPosition().getY() >= 0) { long currentTime = System.nanoTime(); double dt = TIME_SCALE * (currentTime - prevTime) / 1e9; projectile.performTimeStep(dt); prevTime = currentTime; paintingComponent.repaint(); try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } } projectile = null; paintingComponent.repaint(); } Projectile getProjectile() { return projectile; } } class Projectile { private static final Point2D ACCELERATION = new Point2D.Double(0, -ProjectileShooter.GRAVITY); private final Point2D position = new Point2D.Double(); private final Point2D velocity = new Point2D.Double(); public Point2D getPosition() { return new Point2D.Double(position.getX(), position.getY()); } public void setPosition(Point2D point) { position.setLocation(point); } public void setVelocity(Point2D point) { velocity.setLocation(point); } void performTimeStep(double dt) { scaleAddAssign(velocity, dt, ACCELERATION); scaleAddAssign(position, dt, velocity); //System.out.println("Now at "+position+" with "+velocity); } private static void scaleAddAssign( Point2D result, double factor, Point2D addend) { double x = result.getX() + factor * addend.getX(); double y = result.getY() + factor * addend.getY(); result.setLocation(x, y); } } class ProjectileShooterPanel extends JPanel { private final ProjectileShooter projectileShooter; public ProjectileShooterPanel(ProjectileShooter projectileShooter) { this.projectileShooter = projectileShooter; } @Override protected void paintComponent(Graphics gr) { super.paintComponent(gr); Graphics2D g = (Graphics2D)gr; double angleDeg = Math.toDegrees(projectileShooter.getAngle()); double power = projectileShooter.getPower(); Point2D origin = projectileShooter.getOrigin(); Point2D target = projectileShooter.getTarget(); double range = projectileShooter.computeRange(origin.getY()); double impactX = range+origin.getX(); g.setColor(Color.BLACK); int textY = 20; g.drawString("Drag origin and target with mouse ", 10, textY+=16); g.drawString("Angle: "+angleDeg, 10, textY+=16); g.drawString("Power: "+power, 10, textY+=16); g.drawString("Range: "+range, 10, textY+=16); g.drawString("Origin: "+origin, 10, textY+=16); g.drawString("Impact: "+impactX, 10, textY+=16); g.drawString("Target: "+target, 10, textY+=16); Point2D requiredAngles = projectileShooter.computeRequiredAngles(); double requiredAngleDeg0 = Math.toDegrees(requiredAngles.getX()); double requiredAngleDeg1 = Math.toDegrees(requiredAngles.getY()); g.drawString("Assuming fixed power:", 10, textY+=16); g.drawString("Required angle 0: "+requiredAngleDeg0, 10, textY+=16); g.drawString("Required angle 1: "+requiredAngleDeg1, 10, textY+=16); double requiredPower = projectileShooter.computeRequiredPower(); g.drawString("Assuming fixed angle:", 10, textY+=16); g.drawString("Required power: "+requiredPower, 10, textY+=16); Projectile projectile = projectileShooter.getProjectile(); if (projectile != null) { g.setColor(Color.RED); Point2D position = projectile.getPosition(); int px = (int)position.getX(); int py = getHeight() - (int)position.getY(); g.fillOval(px-10, py-10, 20, 20); } g.setColor(Color.GREEN); int ox = (int)origin.getX(); int oy = getHeight() - (int)origin.getY(); g.fillOval(ox-10, oy-10, 20, 20); g.setColor(Color.BLUE); int tx = (int)target.getX(); int ty = getHeight() - (int)target.getY(); g.fillOval(tx-10, ty-10, 20, 20); g.setColor(Color.BLACK); g.drawLine((int)impactX, 0, (int)impactX, getHeight()); g.setColor(Color.GRAY); Shape trajectory = createTrajectory( Math.min(origin.getX(), impactX), Math.max(origin.getX(), impactX)); g.draw(trajectory); } private Shape createTrajectory(double x0, double x1) { Path2D path = new Path2D.Double(); Point2D origin = projectileShooter.getOrigin(); double y0 = projectileShooter.computeY(Math.abs(origin.getX()-x0)); path.moveTo(x0, getHeight()-(y0+origin.getY())); for (double x=x0; x<=x1; x++) { double y = projectileShooter.computeY(Math.abs(origin.getX()-x)); path.lineTo(x, getHeight()-(y+origin.getY())); } return path; } }