2015-05-04 32 views
3

我正在嘗試開發使用Java的2D遊戲。到目前爲止,我已經成功設置了遊戲以使用全屏獨佔模式並在自定義線程中執行主動渲染。我決定使用的遊戲循環是類型固定的時間步變量渲染。這種類型的遊戲循環應該儘可能快地呈現設備可以處理的情況,但我並不完全滿意。所以我試圖用Thread.sleep()限制幀率。Java thread.sleep(1)睡眠時間超過1毫秒

如果我關閉所有渲染,並簡單地更新遊戲循環中的遊戲,Thread.sleep(1)成功睡眠約1 ms。但是,如果我打開渲染,有時Thread.sleep(1)睡眠方式比1 ms更長,如15 ms。 我加入開啓/關閉渲染/刪除行:

BufferedImage drawImage = render(Math.min(1d, lag/TIME_PER_UPDATE)); 
drawToScreen(drawImage); 

是什麼原因造成的線程睡眠時間過長?

這是我第一次在這些論壇發帖,所以請告訴我,如果我在我的文章中做了錯誤的事,或者這是重複的(我還沒有設法找到類似的帖子)。

import java.awt.Color; 
import java.awt.DisplayMode; 
import java.awt.Frame; 
import java.awt.Graphics2D; 
import java.awt.GraphicsDevice; 
import java.awt.GraphicsEnvironment; 
import java.awt.RenderingHints; 
import java.awt.event.KeyEvent; 
import java.awt.event.KeyListener; 
import java.awt.image.BufferStrategy; 
import java.awt.image.BufferedImage; 

public class Main implements KeyListener 
{ 

    private static final long serialVersionUID = 1L; 
    private boolean gameRunning = false; 
    private final double UPDATE_RATE = 60; 
    private final double TIME_PER_UPDATE = 1000000000/UPDATE_RATE; 
    private final int MAX_UPDATES_BEFORE_RENDERING = 5; 
    private final int TARGET_FPS = 60; 
    private int windowWidth; 
    private int windowHeight; 
    private GraphicsDevice graphicsDevice; 
    private DisplayMode defaultDisplayMode; 
    private Frame frame; 
    private BufferStrategy bufferStrategy; 
    private Player player; 

    public Main() 
    { 
     GraphicsDevice[] screenDevices = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices(); 
     this.graphicsDevice = screenDevices[0]; 

     // This is later used to restore the original display mode when closing 
     // the game 
     defaultDisplayMode = this.graphicsDevice.getDisplayMode(); 

     frame = new Frame("GameTest"); 

     frame.setIgnoreRepaint(true); 
     frame.setResizable(false); 
     frame.setUndecorated(true); 

     // Ensure that the user device supports full screen exclusive mode 
     if (this.graphicsDevice.isFullScreenSupported()) 
     { 
      graphicsDevice.setFullScreenWindow(frame); 
     } 

     windowWidth = frame.getWidth(); 
     windowHeight = frame.getHeight(); 

     frame.createBufferStrategy(2); 

     bufferStrategy = frame.getBufferStrategy(); 

     // The frame receives keyboard event dispatched on the EDT-thread. 
     frame.addKeyListener(this); 

     initGame(); 

     // Starts the gameThread. The updating of the game state and rendering 
     GameThread gameThread = new GameThread(); 
     gameThread.start(); 

    } 

    private void initGame() 
    { 
     player = new Player(300, 300); 
    } 

    private class GameThread extends Thread 
    { 

     @Override 
     public void run() 
     { 
      gameLoop(); 
     } 
    } 

    public static void main(String[] Args) 
    { 
     new Main(); 
    } 

    private void gameLoop() 
    { 
     gameRunning = true; 

     double lastStartTime = System.nanoTime(); 
     double startTime; 
     double elapsedTime = 0; 
     double lag = 0; 
     double lastRenderTime; 
     int updateCount = 0; 

     while (gameRunning) 
     { 
      System.out.println(""); 
      System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); 
      System.out.println("New Gameloop"); 
      System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); 

      startTime = System.nanoTime(); 
      elapsedTime = startTime - lastStartTime; 
      lag += elapsedTime; 

      updateCount = 0; 

      while (lag >= TIME_PER_UPDATE && updateCount < MAX_UPDATES_BEFORE_RENDERING) 
      { 
       updateGameState(); 
       lag -= TIME_PER_UPDATE; 
       updateCount++; 
      } 

      if (startTime - lastStartTime > TIME_PER_UPDATE) 
      { 
       lastStartTime = startTime - TIME_PER_UPDATE; 
      } 

      BufferedImage drawImage = render(Math.min(1d, lag/TIME_PER_UPDATE)); 
      drawToScreen(drawImage); 
      lastRenderTime = System.nanoTime(); 

      double currentFPS = 1000000000d/(lastRenderTime - startTime); 

      //Sleeps until target FPS is reached 
      System.out.println(""); 
      System.out.println("Before sleeping"); 
      System.out.println(""); 
      System.out.println("Current FPS:"); 
      System.out.println(currentFPS); 

      while (currentFPS > TARGET_FPS && (lastRenderTime - startTime) < TIME_PER_UPDATE) 
      { 

       //Lets the CPU rest 
       Thread.yield(); 

       double beginSleepTime = System.nanoTime(); 

       try 
       { 

        Thread.sleep(1);   

       } catch (Exception e) 
       { 
        e.printStackTrace(); 
       } 

       double endSleepTime = System.nanoTime(); 

       lastRenderTime = System.nanoTime(); 
       currentFPS = 1000000000d/(lastRenderTime - startTime); 

       System.out.println(""); 
       System.out.println("--------------------------------"); 
       System.out.println("Sleeping"); 
       System.out.println(""); 
       System.out.println("Time slept in ms:"); 
       System.out.println(""); 
       System.out.println((endSleepTime - beginSleepTime)/1000000d); 
       System.out.println(""); 
       System.out.println("current FPS"); 
       System.out.println(""); 
       System.out.println(currentFPS);  
      } 

      lastStartTime = startTime; 
     } 
    } 

    private void updateGameState() 
    { 
     player.update(); 
    } 

    private void drawToScreen(BufferedImage drawImage) 
    { 
     try 
     { 
      Graphics2D g2d = (Graphics2D) bufferStrategy.getDrawGraphics(); 

      g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); 

      g2d.clearRect(0, 0, windowWidth, windowHeight); 
      g2d.setBackground(Color.BLACK); 

      g2d.drawImage(drawImage, 0, 0, windowWidth, windowHeight, null); 

      g2d.dispose(); 

      if (!bufferStrategy.contentsLost()) 
      { 
       bufferStrategy.show(); 
      } 

     } catch (Exception e) 
     { 
      e.printStackTrace(); 
     } 
    } 

    private BufferedImage render(double delta) 
    { 
     BufferedImage drawImage = new BufferedImage(windowWidth, windowHeight, BufferedImage.TYPE_INT_ARGB); 
     drawImage.createGraphics(); 
     Graphics2D g = (Graphics2D) drawImage.getGraphics(); 

     g.setBackground(Color.WHITE); 
     g.clearRect(0, 0, windowWidth, windowHeight); 

     //Render player 
     g.setColor(Color.BLUE); 
     g.fillRect((int) Math.round(player.getLocX() + delta * player.getSpeedX()), (int) Math.round(player.getLocY() + delta * player.getSpeedY()), 64, 64); 

     g.dispose(); 

     return drawImage; 
    } 

    @Override 
    public void keyPressed(KeyEvent keyEvent) 
    { 
     switch (keyEvent.getKeyCode()) 
     { 

     case KeyEvent.VK_ESCAPE: 
      graphicsDevice.setDisplayMode(defaultDisplayMode); 
      System.exit(0); 
      break; 
     case KeyEvent.VK_A: 
      player.setSpeedX(-player.getMoveSpeed()); 
      break; 
     case KeyEvent.VK_D: 
      player.setSpeedX(player.getMoveSpeed()); 
      break; 
     case KeyEvent.VK_W: 
      player.setSpeedY(-player.getMoveSpeed()); 
      break; 
     case KeyEvent.VK_S: 
      player.setSpeedY(player.getMoveSpeed()); 
      break; 
     case KeyEvent.VK_SPACE: 

      break; 
     case KeyEvent.VK_LESS: 

      break; 
     case KeyEvent.VK_I: 

      break; 

     } 
    } 

    @Override 
    public void keyReleased(KeyEvent keyEvent) 
    { 
     switch (keyEvent.getKeyCode()) 
     { 
     case KeyEvent.VK_A: 
      player.setSpeedX(0); 
      break; 
     case KeyEvent.VK_D: 
      player.setSpeedX(0); 
      break; 
     case KeyEvent.VK_W: 
      player.setSpeedY(0); 
      break; 
     case KeyEvent.VK_S: 
      player.setSpeedY(0); 
      break; 
     case KeyEvent.VK_SPACE: 

      break; 
     case KeyEvent.VK_LESS: 

      break; 
     case KeyEvent.VK_I: 

      break; 
     } 
    } 

    @Override 
    public void keyTyped(KeyEvent keyEvent) 
    { 

    } 

    private class Player 
    { 
     protected double speedX; 
     protected double speedY; 
     protected double locX; 
     protected double locY; 
     protected double moveSpeed; 

     public Player(int locX, int locY) 
     { 
      speedX = 0; 
      speedY = 0; 
      this.locX = locX; 
      this.locY = locY; 

      moveSpeed = 3d; 
     } 

     public void update() 
     { 
      locY += speedY; 

      locX += speedX; 
     } 

     public void setSpeedX(double speedX) 
     { 
      this.speedX = speedX; 
     } 

     public void setSpeedY(double speedY) 
     { 
      this.speedY = speedY; 
     } 

     public double getSpeedX() 
     { 
      return speedX; 
     } 


     public double getSpeedY() 
     { 
      return speedY; 
     } 

     public double getLocX() 
     { 
      return locX; 
     } 

     public double getLocY() 
     { 
      return locY; 
     } 

     public double getMoveSpeed() 
     { 
      return moveSpeed; 
     } 
    } 
} 
+0

它可能發生,軟件通常容易出現這樣的錯誤。 http://stackoverflow.com/questions/8240876/thread-sleep-sleeps-for-longer?rq=1。我認爲任何時候承諾只適用於理想的情況。但是,如果我們用一百萬個proc等等阻塞系統,一切都可能磨損 – Coffee

+1

'Thread.Sleep(n)意味着至少在n毫秒內可以發生的次數(或線程量)的數量阻塞當前線程。時間片的長度在不同的版本......和不同的處理器上是不同的,一般範圍從15到30毫秒。這意味着線程幾乎可以保證阻塞超過n毫秒。你的線程在n毫秒後重新喚醒的可能性幾乎是不可能的。因此,Thread.Sleep對於時間安排毫無意義。' - http://stackoverflow.com/a/8241018/763029 – Coffee

回答

5

java中的sleep()方法將當前正在執行的線程(處於運行狀態)休眠1 ms。

線程來運行的狀態(能夠運行)後1毫秒,現在它取決於調度時,從可運行狀態,走線並執行它(即,運行狀態)。

出於這個原因,你可以假設線程再次運行前睡覺最少爲1ms。

下面的圖描述了不同線程狀態enter image description here

+1

這讓事情變得更清晰,謝謝。 – joachimwedin

+1

@skich歡迎您,請接受並點贊回答:) –

+1

如果您依賴線程執行時間,您將會遇到一段糟糕的時間! – tmn

0

的Java文檔清楚地狀態(Thread.sleep()方法)

使當前執行的線程休眠(暫停執行)爲指定的毫秒數加納秒指定數量的受系統計時器和調度器的精確性和準確性的限制。

在這裏,您處於系統內調度和系統時序的擺佈之中。確保時間的唯一方法是確保線程阻止執行並阻止循環,但這會在別處創建問題。

爲了什麼它的價值我覺得普遍不好睡線程固定數額的時間,除非有事情應該由其他事件不是在固定的時間間隔觸發的絕對理由這樣做。

0

據的Javadoc:

Thread.sleep()使得當前執行的線程休眠(暫停執行)爲指定的毫秒數,受制於系統定時器和調度器的精確性和準確性。

因此,該Thread.sleep(ms)具有較低的準確性。

其次,請注意,此方法會拋出檢查異常ThreadInterruptedException。這可以由spurious wakeup觸發。所以即使是Thread.sleep(1000)也可以在ms後完成。

另一種解決方案更好的精度LockSupport.parkNanos()。但是對於這種方法,您應該注意其他線程的中斷。 PS:還有一個Thread.sleep(ms,nanos),它與Thread.sleep(ms)具有相同的低精確度(納秒僅四捨五入至ms)。