首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 圖形程式 FPS 精準計算與遊戲迴圈優化實戰

     編輯:Java 相關知識

FPS(Frames Per Second)是圖形應用、遊戲效能的核心指標。本文實作一套基於 System.nanoTime() 的高精度 FPS 統計元件 FPSListen,結合固定時間步長遊戲迴圈,展示 50 顆彈球碰撞邊界的流暢動畫,實測單獨執行可達 380 FPS,IDE 內約 280 FPS。

效能展示

FPS 測試畫面:50 顆紅球飛舞,左上角顯示即時 FPS

核心類別設計

FPSListen:納秒級幀率統計

public class FPSListen {
    public static final int TARGET_FPS = 500;
    public static final long PERIOD_NS = 1_000_000_000L / TARGET_FPS; // 2,000,000 ns = 2ms
    public static final long MAX_INTERVAL_NS = 1_000_000_000L; // 1秒

    private double currentFPS = 0;
    private long frameCount = 0, intervalAcc = 0, lastTime = System.nanoTime();
    private DecimalFormat df = new DecimalFormat("0.0");

    public void makeFPS() {
        frameCount++;
        intervalAcc += PERIOD_NS;
        if (intervalAcc >= MAX_INTERVAL_NS) {
            long now = System.nanoTime();
            long realElapsed = now - lastTime;
            currentFPS = (frameCount * 1_000_000_000.0) / realElapsed;
            frameCount = 0; intervalAcc = 0; lastTime = now;
        }
    }
    public String getFPS() { return df.format(currentFPS); }
    // 可選:啟用 OpenGL 管線加速
    public void enableOpenGL() {
        System.setProperty("sun.java2d.opengl", "True");
        System.setProperty("sun.java2d.translaccel", "True");
    }
}

關鍵點:

  • 使用 nanoTime() 而非 currentTimeMillis(),精度從毫秒提升至納秒。
  • 累積理論幀時間 PERIOD_NS,每滿 1 秒統計一次實際 FPS,避免每幀計算開銷。
  • 公式 FPS = frameCount * 1e9 / realElapsedNs 精確反映真實繪製速率。

BallPanel:固定時間步長遊戲迴圈

public class BallPanel extends Panel implements Runnable {
    private Ball[] balls = new Ball[50];
    private FPSListen fps = new FPSListen();
    private Image offscreen; Graphics offG;

    public void run() {
        long before = System.nanoTime(); fps.setTime(before);
        while (running) {
            gameUpdate();           // 邏輯更新
            repaint();              // 觸發繪製
            long after = System.nanoTime();
            long diff = after - before;
            long sleep = (FPSListen.PERIOD_NS - diff) / 1_000_000; // ns -> ms
            if (sleep > 0) { try { Thread.sleep(sleep); } catch (InterruptedException ignored) {} }
            else { Thread.yield(); } // 超時補償
            before = System.nanoTime();
            fps.makeFPS();
        }
    }

    public void paint(Graphics g) {
        offG.setColor(Color.WHITE); offG.fillRect(0,0,WIDTH,HEIGHT);
        offG.setColor(Color.BLUE); offG.drawString("FPS: " + fps.getFPS(), 4, 16);
        for (Ball b : balls) b.draw(offG);
        g.drawImage(offscreen, 0, 0, this);
    }
}

迴圈特色:

  • 固定時間步長:目標 2ms/幀(500 FPS 上限),睡眠補償實際耗時。
  • 超時處理:若邏輯+繪製超過預算,放棄睡眠改 Thread.yield() 讓出 CPU,避免雪球效應。
  • 雙緩衝:offscreen 畫好再一次 drawImage 送顯示。

Ball:簡單彈球物理

public class Ball {
    private int x, y, vx, vy; // 位置、速度
    public void move() {
        x += vx; y += vy;
        if (x < 0 || x > WIDTH - SIZE) vx = -vx;
        if (y < 0 || y > HEIGHT - SIZE) vy = -vy;
    }
    public void draw(Graphics g) { g.fillOval(x, y, SIZE, SIZE); }
}

優化建議

  • 啟用 -Dsun.java2d.opengl=True 使用 OpenGL 管線(Windows/Linux 需驅動支援)。
  • 改用 BufferStrategy(Frame/Canvas.createBufferStrategy(2))替代手動 BufferedImage,獲取硬體頁面翻轉。
  • 邏輯與繪製分離執行緒(生產者-消費者模式),多核心發揮更大效能。
  • 物件池複用 Ball、Rectangle 等高頻分配物件,減輕 GC 壓力。

本文轉載自「Java究竟怎麼玩」博客,原文地址:https://cping1982.blog.51cto.com/601635/116709

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved