如何在 java 中缓慢移动我的子弹?

HOw to move my bullet slowly in java?

您好,我正在开发一款战斗机左右移动并射击的游戏。对于射击部分,我尝试使用 for 循环来减慢速度,让用户可以看到子弹。但这还不够。我也用过 sleep 但不是一个好的答案。现在我不知道该怎么办。 这是我的 paintComponent 类:

package game;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.JPanel;



public class PaintComponent extends JPanel implements KeyListener {
    int dx = 200-15;
    int dy = 450;
    int my = 450;

    ArrayList<Bullet> bullets = new ArrayList<>();
    public Rectangle2D rec =new Rectangle2D.Double(dx , dy, 30, 10);
    Rectangle2D recB = new Rectangle2D.Double(dx+13 , my, 6, 6);

//    public Polygon pol = new Polygon
    private BufferedImage imageBg, imageFi, imageBu;


    public PaintComponent() {
        this.addKeyListener(this);
        this.setFocusable(true);
        this.setBackground(Color.white);

        try {                
          imageBg = ImageIO.read(new File("C:\Java\NetbeansProjects\Game\bg.jpg"));
          imageBu = ImageIO.read(new File("C:\Java\NetbeansProjects\Game\bul.png"));
          imageFi = ImageIO.read(new File("C:\Java\NetbeansProjects\Game\fi.png"));
       } catch (IOException ex) {
            System.out.println("No background image is available!");
       }
    }

    public void shoot(){
        if(bullets != null){
            for(int i=0; i<bullets.size(); i++){
                for(int j=0; j<200; j++){
                    bullets.get(i).setdy(my-j);
                }
                System.out.println(bullets.get(i).getdy());
            }
        }
    }
    public void moveRec(KeyEvent evt){
        switch(evt.getKeyCode()){
            case KeyEvent.VK_LEFT:
            dx -= 5;
            rec.setRect(dx, dy, 30, 10);
            recB.setRect(dx+13, dy, 6, 6);
            repaint();
            break;
        case KeyEvent.VK_RIGHT:
            dx += 5;
            rec.setRect(dx, dy, 30, 10);
            recB.setRect(dx+13, dy, 6, 6);
            repaint();
            break;
        case KeyEvent.VK_SPACE:
            Bullet b = new Bullet(dx, dy);
            bullets.add(b);
            shoot();
            break;

    }
}

        @Override
        public void paintComponent(Graphics grphcs)
            {super.paintComponent(grphcs);
            Graphics2D gr = (Graphics2D) grphcs;
                int X = (int) rec.getCenterX();
                int Y = (int) rec.getCenterY();
                gr.drawImage(imageBg, 0, 0, null);
                gr.drawImage(imageFi, X-50, Y-75, null);
                gr.setColor(Color.GRAY);
                if(bullets != null){
                    for(int i=0;i<bullets.size();i++){
                   gr.drawImage(imageBu, null, bullets.get(i).getdx(), bullets.get(i).getdy());
                   repaint();
                    }
                }
                gr.draw(recB);
            }

    @Override
    public void keyTyped(KeyEvent e) {
    }
    @Override
    public void keyPressed(KeyEvent e) 
    {moveRec(e);}
    @Override
    public void keyReleased(KeyEvent e) 
    {}
}

这是我的项目符号:

package game;

public class Bullet {
    private int x, y, newy;

    public Bullet(int x, int y){
        this.x = x;
        this.y = y;
    }

    public int getdy(){
        return y;
    }
    public void setdy(int newy){
        y = newy;
    }
    public int getdx(){
        return x;
    }
}

在你的游戏循环中让它每隔这么多刻更新一次子弹。您很可能永远不想使用 sleep 来减慢游戏中的某些内容,因为它需要一个新线程,否则它会在整个游戏中休眠。

如果您不知道什么是游戏循环,游戏循环基本上就是一个循环,不断获取输入、更新游戏(例如子弹)、渲染所有内容,然后暂停整个程序(您可以使用sleep) 循环剩余的时间量。您还想根据自上次更新以来经过的滴答数或毫秒数来更新游戏。这将防止游戏在不同计算机上 运行 更快或更慢。

我还没有完全读完这篇文章,但 THIS 可能会有所帮助。

此外,不确定这是否正确,我认为使用 Canvas 而不是 JPanel 更适合制作游戏。这是我在第一个 java 游戏中使用的。

编辑:

如果你想使用 JPanel 而没有游戏循环,你可以让 shoot 方法创建一个新线程并使用 sleep 来减慢它的速度。此外,如果射击多颗子弹,设置射击方法的方式可能会导致问题,因为它将在两个单独的循环中循环遍历每颗子弹,如果您有一个单独的线程,则可以在一个线程中循环时修改子弹数组,从而导致一个错误。

试试这个:

public void shoot(Bullet bullet){
      new Thread(new Runnable(){
            for(int j=0; j<200; j++){
                bullet.setdy(my-j);
                try{
                  Thread.sleep(time);//set the time
                catch(Exception e){
                  e.printStackTrace();
                }
            }
            System.out.println(bullet.getdy());
            getBullets().remove(bullet);
        }).start();
    }

创建一个名为 getBullets() 的方法。确保它有同步修饰符。

protected synchronized ArrayList<Bullet> getBullets(){
  return bullets;
}

这将确保它一次只能由一个线程修改。

最后在玩家按下space的地方,将bullets.add(b);改为getBullets().add(b);

我认为你放慢循环速度是错误的。您最不想做的就是放慢游戏循环或让游戏循环休眠。这将影响你在游戏中的所有对象。

有多种方法可以解决这个问题:

每次刻度的增量较小

您可以做的最明显的事情之一就是减小子弹的增量。让我们看看你的 shoot();方法:

  public void shoot(){
    if(bullets != null){
        for(int i=0; i<bullets.size(); i++){
            for(int j=0; j<200; j++){
                bullets.get(i).setdy(my-j);
            }
            System.out.println(bullets.get(i).getdy());
        }
    }
}

据我所知,您对所有项目符号进行了 200 次迭代,项目符号的 y 轴的每个刻度都会发生变化,使用公式“my - j”或“450 - 刻度指数”

为了减慢子弹的速度,您需要将 j 除以一定的数字以获得所需的子弹速度。例如:“my - (j / 2)”会影响子弹的速度。尝试使用这些变量来获得所需的速度。

添加速度修改器

很多游戏都需要一个速度修改器或每个射弹的基本速度。这可能对你有用,我唯一注意到你有点试图模拟速度损失。要实现此结果,您需要另一个变量。我们暂时称其为“生存时间”。

因此,如果我们修改项目符号 class,它将看起来像这样。注意到我们还有一个名为 Move(); 的新函数。这将根据变量计算下一步行动。

public class Bullet {
private int x, y, newy;
private speed, ttl; //ttl = time to live

public Bullet(int x, int y, int speed){
    this.x = x;
    this.y = y;
    this.speed= speed;
    this.ttl = 250;
}

public int Move()
{
    //Do some calculation to perform loss of velocity within a reasonable range. Because these number might be overkill
    this.speed -= (ttl / 100);
    y += this.speed;
    ttl--;
}

public int getdy(){
    return y;
}
public void setdy(int newy){
    y = newy;
}
public int getdx(){
    return x;
}
}

代码现在所做的是根据生存时间变量计算速度,子弹生存时间越长,速度就越慢。调整速度变量可以让您更好地控制子弹。我自己也这么说,拍摄方法看起来更整洁:

public void shoot(){
    if(bullets != null){
        for(int i=0; i<bullets.size(); i++){
            bullets.get(i).Move();
        }
    }
}

当然还有更多,比如检查速度和生存时间是否超出范围之类的,但我认为你足够聪明,可以解决这个问题;)

运行 关闭定时器

正如 ControlAltDel 所说,您可以实现一个计时器,我不是 java 方面的专家,因此我不会对此进行深入探讨。但它肯定是有可能的。无非就是在定时器的tick函数里面实现当前的shoot方法。当然删除 for i<200 循环。因为它不是很有效。

总之

如果我确实弄错了或误解了(甚至语法错误:))问题,我很抱歉。如果有任何问题,我很乐意听取您的意见 ;)。

请注意,这是未经测试的代码,我只是在这里解释您可以尝试使其按预期工作的事情!

此致,

合成。

更新:

一些关于如何更新项目符号的解释。 为了更新项目符号,我们需要使其 运行 脱离循环。因为在这种情况下,主循环也是绘图发生的地方,即“paintComponent”方法。绘制组件已经有一个循环来绘制子弹,我们唯一要做的就是添加关于 .Move(); 的逻辑。方法。

绘画组件将如下所示(+ 我还修复了制表符):

@Override
public void paintComponent(Graphics grphcs)
{
    super.paintComponent(grphcs);

    Graphics2D gr = (Graphics2D) grphcs;
    int X = (int) rec.getCenterX();
    int Y = (int) rec.getCenterY();
            
    gr.drawImage(imageBg, 0, 0, null);
    gr.drawImage(imageFi, X-50, Y-75, null);
    gr.setColor(Color.GRAY);
            
    if(bullets != null)
    {
        for(int i=0;i<bullets.size();i++)
        {
            //Here is were we loop over the bullet list, lets add the move method
            bullets.get(i).Move();

            gr.drawImage(imageBu, null, bullets.get(i).getdx(), bullets.get(i).getdy());
            repaint();
        }
    }
    
    gr.draw(recB);
}

添加的东西是“bullets.get(i).Move();”。现在每帧 运行。这在理论上是可行的(inb4 im 不测试这些代码)。假设您使用了子弹的多个实例 class,每个 class 都应该封装自己的速度和生存时间变量。

执行此操作将使 shoot 方法过时。您可以做的是将与拍摄相关的代码移至 paintComponent 中,并将其移至拍摄功能。

关于生存时间变量,我想在代码中再添加一段。这将负责子弹的垃圾收集。从现在起他们无限期地生活:

for(int i=0;i<bullets.size();i++)
{
    Bullet b = bullets.get(i);
    if(b.ttl >= 1)
    {
        bullets.get(i).Move();
        gr.drawImage(imageBu, null, b.getdx(), b.getdy());
    }
    else
    {
        //Remove the bullet from the list
        //In C# its bullets.Remove(b);
    }

    repaint();
}

希望这能解决子弹不动的问题。以及由于子弹未被销毁而导致的潜在性能问题。以前,有任何问题我都喜欢听他们说! ;)

最后,我在我的项目符号 class 中添加了一个计时器,并在我的 paintcomponent 方法中添加了 repaint()!

    package game;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;

public class Bullet {
    private int x, y;
    private int speed, ttl;
    public final Timer timer1;

    public Bullet(int x, int y, int speed){

        this.x = x;
        this.y = y;
        this.speed= speed;
        this.ttl = 250;

        ActionListener actListener = new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                Move();
            }
        };
        this.timer1 = new Timer(50, actListener);
        timer1.start();
    }

    public int getdy(){
        return y;
    }
    public void setdy(int newy){
        y = newy;
    }
    public int getdx(){
        return x;
    }
    public int Move(){
    //Do some calculation to perform loss of velocity within a reasonable range. Because these number might be overkill
    this.speed -= (ttl / 100);
    y += this.speed;
    ttl--;
    return y;
    }
}