Java 移动一个长方形时,会出现令人难以置信的起伏

Java 移动一个长方形时,会出现令人难以置信的起伏,java,jframe,jpanel,Java,Jframe,Jpanel,我创建了一个简单的程序,可以画一个矩形,它以恒定的速率从屏幕上落下。我首先运行Main.java: package highst; public class Main { public static void main(String args[]){ new GameFrame(); } } 这将创建GameFrame.java的新实例: package highst; import javax.swing.JFrame; public class G

我创建了一个简单的程序,可以画一个矩形,它以恒定的速率从屏幕上落下。我首先运行Main.java:

package highst;

public class Main {

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

}
这将创建GameFrame.java的新实例:

package highst;

import javax.swing.JFrame;

public class GameFrame extends JFrame {

    public GameFrame() {
        super("Falling rectangle");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setSize(800, 600);
        GameLogic game = new GameLogic();
        this.getContentPane().add(game);
        this.setVisible(true);
        game.run();
    }
}
从而创建GameLogic.java的新实例:

package highst;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JPanel;

public class GameLogic extends JPanel implements Runnable, KeyListener {

    Marvin marvin;
    private enum GameState{
        Running, Dead
    }

    GameState state = GameState.Running;

    public GameLogic(){
        marvin = new Marvin(50, 50);
        Thread thread = new Thread(this);
        thread.start();
        addKeyListener(this);
        setFocusable(true);
        this.setBackground(Color.black);
    }

    public void paintComponent(Graphics g){
        super.paintComponent(g);
        g.setColor(Color.white);
        g.fillRect(marvin.getX(), marvin.getY(), 50, 50);
    }

    @Override
    public void keyPressed(KeyEvent e) {
        if(e.getKeyCode() == KeyEvent.VK_SPACE){
            marvin.jump();
        }

    }

    @Override
    public void keyReleased(KeyEvent e) {
        // TODO Auto-generated method stub

    }

    @Override
    public void keyTyped(KeyEvent e) {
        // TODO Auto-generated method stub

    }

    @Override
    public void run() {
        if(state == GameState.Running){
            while(true){
                marvin.update();
                repaint();
                try {
                    Thread.sleep(17);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }

}
它最终利用了我的可玩角色Marvin.java,它现在是一个白色矩形:

package highst;

public class Marvin {

    private int x, y;

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

    public void update(){
        y -= -1;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }

    public void jump() {
        x += 1;
    }

}

它运行正常,但矩形在从页面上落下时绘制不平滑。它似乎一次跳几个像素。我认为让线程休眠17毫秒会使所有内容都平滑渲染。我做错了什么

要使动画平滑,您需要以恒定速率更新屏幕

在这里,您正在进行图形重新绘制,这可能需要任何时间,然后无论如何等待17毫秒。这会导致每帧占用不同的时间。第一帧可能在2ms内完成,下一帧可能需要5ms,然后是3ms,依此类推。。。您的帧将显示19ms然后22ms然后20ms

您需要的是一个专用线程,其唯一任务是等待适当的时间,然后向主线程发出重新绘制的信号。然后你的画框(只要画幅不超过17ms)每17ms就出来一次,完全按照提示

,您应该会发现它是相关的。

试试这些:

1> 减少线程的休眠时间以查看效果,并获得最佳的下降速度

2> 使用双缓冲(屏幕首先在内存中绘制,然后绘制到显示屏上的概念):

3> 简单建议:避免使用sleep()。取而代之的是使用计时器。它是线程非常有趣和强大的替代品。此外,它不会在游戏开发的后期阶段产生问题。请查看以下链接:

4> 查看以下有关Java动画的有趣教程:


有几件事不对

  • 您忽略了Swing的功能,没有在EDT上下文中启动UI,这导致
  • 您正在对
    GameLogic
    实例调用
    run
    ,并创建一个
    线程
    ,该线程将再次调用
    run
    ,设置为循环。这真是愚蠢的运气。这样做是调用
    marvin.update
    两次…以及随机间隔,这意味着对象以不一致的速率移动
  • 基本上,您应该删除行
    game.run()
    并包装
    newgameframe()
    EventQueue.invokeLater
    调用的上下文中

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }
    
                GameFrame frame = new GameFrame();
            }
        });
    }
    
    就个人而言,我建议不要直接从
    JFrame
    扩展,不要向类添加任何功能,只需在
    main
    中创建一个实例,然后将
    GameLogic
    面板添加到该实例中即可。这使得游戏在部署方面更加灵活,因为你没有将自己锁定在一个容器中


    我还建议您不要使用
    KeyListener
    ,而是使用,因为它解决了与
    KeyListener

    相关的焦点问题,您应该实现某种双缓冲以避免闪烁。你试过完全不睡觉吗?@NeplatnyUdaj Swing组件由default1进行双缓冲-我认为OP已经有一个经过训练的线程在指定的时间内等待。你是对的,他们没有考虑更新和重新绘制一个框架所需的时间,但是考虑到几乎不可能知道最后一个框架是什么时候在Swing中绘制的(并且知道我们谈论的是哪个框架),你最好希望知道更新需要多长时间……仅供参考,Swing组件由双缓冲default@MadProgrammer例如我不知道。但是在我正在进行的一个项目中,我不得不显式地使用双缓冲来解决闪烁和滞后的问题。@MadProgrammer:也许你是对的。我需要进一步研究一下。我已经实现了您的更改并删除了
    game.run()
    ,但它仍然不稳定。真的吗?真奇怪。我觉得可能是我的笔记本电脑在装傻。我什么也没做,只是费心在上面运行东西。你能把你的程序上传到某个地方,这样我就可以在我这边测试它吗?记住,Swing使用一个被动渲染引擎,所以当你调用repaint时,不能保证实际会发生绘制,只能保证在furpture中的某个时间会发生绘制