2

我正在使用本网站的教程- “固定时间步长”部分。

这是代码 - http://pastebin.com/QaHgcLaR

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class GameLoopTest extends JFrame implements ActionListener
{
   private GamePanel gamePanel = new GamePanel();
   private JButton startButton = new JButton("Start");
   private JButton quitButton = new JButton("Quit");
   private JButton pauseButton = new JButton("Pause");
   private boolean running = false;
   private boolean paused = false;
   private int fps = 60;
   private int frameCount = 0;
   public GameLoopTest()
   {
      super("Fixed Timestep Game Loop Test");
      Container cp = getContentPane();
      cp.setLayout(new BorderLayout());
      JPanel p = new JPanel();
      p.setLayout(new GridLayout(1,2));
      p.add(startButton);
      p.add(pauseButton);
      p.add(quitButton);
      cp.add(gamePanel, BorderLayout.CENTER);
      cp.add(p, BorderLayout.SOUTH);
      setSize(500, 500);
      startButton.addActionListener(this);
      quitButton.addActionListener(this);
      pauseButton.addActionListener(this);
   }
   public static void main(String[] args)
   {
      GameLoopTest glt = new GameLoopTest();
      glt.setVisible(true);
   }
   public void actionPerformed(ActionEvent e)
   {
      Object s = e.getSource();
      if (s == startButton)
      {
         running = !running;
         if (running)
         {
            startButton.setText("Stop");
            runGameLoop();
         }
         else
         {
            startButton.setText("Start");
         }
      }
      else if (s == pauseButton)
      {
        paused = !paused;
         if (paused)
         {
            pauseButton.setText("Unpause");
         }
         else
         {
            pauseButton.setText("Pause");
         }
      }
      else if (s == quitButton)
      {
         System.exit(0);
      }
   }
   //Starts a new thread and runs the game loop in it.
   public void runGameLoop()
   {
      Thread loop = new Thread()
      {
         public void run()
         {
            gameLoop();
         }
      };
      loop.start();
   }
   //Only run this in another Thread!
   private void gameLoop()
   {
      //This value would probably be stored elsewhere.
      final double GAME_HERTZ = 30.0;
      //Calculate how many ns each frame should take for our target game hertz.
      final double TIME_BETWEEN_UPDATES = 1000000000 / GAME_HERTZ;
      //At the very most we will update the game this many times before a new render.
      //If you're worried about visual hitches more than perfect timing, set this to 1.
      final int MAX_UPDATES_BEFORE_RENDER = 5;
      //We will need the last update time.
      double lastUpdateTime = System.nanoTime();
      //Store the last time we rendered.
      double lastRenderTime = System.nanoTime();
      //If we are able to get as high as this FPS, don't render again.
      final double TARGET_FPS = 60;
      final double TARGET_TIME_BETWEEN_RENDERS = 1000000000 / TARGET_FPS;
      //Simple way of finding FPS.
      int lastSecondTime = (int) (lastUpdateTime / 1000000000);
      while (running)
      {
         double now = System.nanoTime();
         int updateCount = 0;
         if (!paused)
         {
             //Do as many game updates as we need to, potentially playing catchup.
            while( now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER )
            {
               updateGame();
               lastUpdateTime += TIME_BETWEEN_UPDATES;
               updateCount++;
            }
            //If for some reason an update takes forever, we don't want to do an insane number of catchups.
            //If you were doing some sort of game that needed to keep EXACT time, you would get rid of this.
            if ( now - lastUpdateTime > TIME_BETWEEN_UPDATES)
            {
               lastUpdateTime = now - TIME_BETWEEN_UPDATES;
            }
            //Render. To do so, we need to calculate interpolation for a smooth render.
            float interpolation = Math.min(1.0f, (float) ((now - lastUpdateTime) / TIME_BETWEEN_UPDATES) );
            drawGame(interpolation);
            lastRenderTime = now;
            //Update the frames we got.
            int thisSecond = (int) (lastUpdateTime / 1000000000);
            if (thisSecond > lastSecondTime)
            {
               System.out.println("NEW SECOND " + thisSecond + " " + frameCount);
               fps = frameCount;
               frameCount = 0;
               lastSecondTime = thisSecond;
            }
            //Yield until it has been at least the target time between renders. This saves the CPU from hogging.
            while ( now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES)
            {
               Thread.yield();
               //This stops the app from consuming all your CPU. It makes this slightly less accurate, but is worth it.
               //You can remove this line and it will still work (better), your CPU just climbs on certain OSes.
               //FYI on some OS's this can cause pretty bad stuttering. Scroll down and have a look at different peoples' solutions to this.
               try {Thread.sleep(1);} catch(Exception e) {}
               now = System.nanoTime();
            }
         }
      }
   }
   private void updateGame()
   {
      gamePanel.update();
   }
   private void drawGame(float interpolation)
   {
      gamePanel.setInterpolation(interpolation);
      gamePanel.repaint();
   }
   private class GamePanel extends JPanel
   {
      float interpolation;
      float ballX, ballY, lastBallX, lastBallY;
      int ballWidth, ballHeight;
      float ballXVel, ballYVel;
      float ballSpeed;
      int lastDrawX, lastDrawY;
      public GamePanel()
      {
         ballX = lastBallX = 100;
         ballY = lastBallY = 100;
         ballWidth = 25;
         ballHeight = 25;
         ballSpeed = 25;
         ballXVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
         ballYVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
      }
      public void setInterpolation(float interp)
      {
         interpolation = interp;
      }
      public void update()
      {
         lastBallX = ballX;
         lastBallY = ballY;
         ballX += ballXVel;
         ballY += ballYVel;
         if (ballX + ballWidth/2 >= getWidth())
         {
            ballXVel *= -1;
            ballX = getWidth() - ballWidth/2;
            ballYVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
         }
         else if (ballX - ballWidth/2 <= 0)
         {
            ballXVel *= -1;
            ballX = ballWidth/2;
         }
         if (ballY + ballHeight/2 >= getHeight())
         {
            ballYVel *= -1;
            ballY = getHeight() - ballHeight/2;
            ballXVel = (float) Math.random() * ballSpeed*2 - ballSpeed;
         }
         else if (ballY - ballHeight/2 <= 0)
         {
            ballYVel *= -1;
            ballY = ballHeight/2;
         }
      }
      public void paintComponent(Graphics g)
      {
         //BS way of clearing out the old rectangle to save CPU.
         g.setColor(getBackground());
         g.fillRect(lastDrawX-1, lastDrawY-1, ballWidth+2, ballHeight+2);
         g.fillRect(5, 0, 75, 30);
         g.setColor(Color.RED);
         int drawX = (int) ((ballX - lastBallX) * interpolation + lastBallX - ballWidth/2);
         int drawY = (int) ((ballY - lastBallY) * interpolation + lastBallY - ballHeight/2);
         g.fillOval(drawX, drawY, ballWidth, ballHeight);
         lastDrawX = drawX;
         lastDrawY = drawY;
         g.setColor(Color.BLACK);
         g.drawString("FPS: " + fps, 5, 10);
         frameCount++;
      }
   }
   private class Ball
   {
      float x, y, lastX, lastY;
      int width, height;
      float xVelocity, yVelocity;
      float speed;
      public Ball()
      {
         width = (int) (Math.random() * 50 + 10);
         height = (int) (Math.random() * 50 + 10);
         x = (float) (Math.random() * (gamePanel.getWidth() - width) + width/2);
         y = (float) (Math.random() * (gamePanel.getHeight() - height) + height/2);
         lastX = x;
         lastY = y;
         xVelocity = (float) Math.random() * speed*2 - speed;
         yVelocity = (float) Math.random() * speed*2 - speed;
      }
      public void update()
      {
         lastX = x;
         lastY = y;
         x += xVelocity;
         y += yVelocity;
         if (x + width/2 >= gamePanel.getWidth())
         {
            xVelocity *= -1;
            x = gamePanel.getWidth() - width/2;
            yVelocity = (float) Math.random() * speed*2 - speed;
         }
         else if (x - width/2 <= 0)
         {
            xVelocity *= -1;
            x = width/2;
         }
         if (y + height/2 >= gamePanel.getHeight())
         {
            yVelocity *= -1;
            y = gamePanel.getHeight() - height/2;
            xVelocity = (float) Math.random() * speed*2 - speed;
         }
         else if (y - height/2 <= 0)
         {
            yVelocity *= -1;
            y = height/2;
         }
      }
      public void draw(Graphics g)
      {
      }
   }
}

运行这段代码后,球有一点延迟,但仍有 60 FPS。在我将鼠标移到应用程序的窗口上并沿随机方向移动后,球正在平稳移动。即使窗口应用程序没有聚焦,它也会发生!怎么了?可以修复吗?

我正在使用带有 Oracle JDK7 的 Ubuntu 13.04。我发现每个应用程序都会发生这种情况。即使在 LWJGL 应用程序中也会发生类似的事情,但“滞后”的影响远小于摇摆应用程序。

17 秒视频显示我的问题

http://www.youtube.com/watch?v=J8SBjKncgRw

4

4 回答 4

9

我在 Kubuntu 13.04 下遇到了同样的问题


我用谷歌搜索了一些对我有用的东西:http ://www.java-gaming.org/index.php?topic=19224.0

基本思想是Toolkit.getDefaultToolkit().sync();画完之后放东西。在您的代码中,它应该在drawGame(interpolation);.

解释似乎是窗口系统管理更新间隔,所以这不是 Java 的错,只发生在某些窗口管理器中。

于 2014-03-26T11:43:52.380 回答
3

应该从基于 Swing 的 触发重绘Timer,以确保在 EDT 上调用 GUI 更新。有关更多详细信息,请参阅Swing 中的并发。

于 2013-09-08T13:41:55.910 回答
0

这似乎是自 Java 6 以来 VM 中的一个错误。我遇到了同样的问题,发现了一个丑陋但简单的解决方法:创建一个 Robot 对象并让它在每个循环中按下一个键或定位鼠标。然后动画将顺利运行。例子:

final Robot robot = new Robot();
javax.swing.Timer timer = new javax.swing.Timer(initialDelay, new ActionListener()
{
    public void actionPerformed(ActionEvent e)
    {
        // update your image...

        robot.keyPress(62);
     }
});

另请参阅:不移动鼠标光标时 Java 动画卡顿

于 2013-10-23T14:00:21.683 回答
0

您可以调用setIgnoreRepaint(true)并使用 aBufferStrategy来绘制该特定组件。ABufferStrategy允许您随时执行绘图。您还可以paintComponent在绘图方法中调用方法。

于 2015-12-22T19:23:10.040 回答