簡単なゲームを書いていますが、ループにCPUを消費させずに、フレームレートを60fpsに制限したいと考えています。どうすればよいですか?
ゲームループの記事 を読むことができます。何かを実装する前に、まずゲームループのさまざまな方法論を理解することが非常に重要です。
@cherouvimが投稿した ゲームループの記事 を取り、「ベスト」戦略を取り、それをJava Runnable、のために働いているようです)のために書き直そうとしました私
double interpolation = 0;
final int TICKS_PER_SECOND = 25;
final int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
final int MAX_FRAMESKIP = 5;
@Override
public void run() {
double next_game_tick = System.currentTimeMillis();
int loops;
while (true) {
loops = 0;
while (System.currentTimeMillis() > next_game_tick
&& loops < MAX_FRAMESKIP) {
update_game();
next_game_tick += SKIP_TICKS;
loops++;
}
interpolation = (System.currentTimeMillis() + SKIP_TICKS - next_game_tick
/ (double) SKIP_TICKS);
display_game(interpolation);
}
}
簡単な答えは、Java.util.Timerを17ミリ秒ごとに起動し、タイマーイベントで作業を行うように設定することです。
これが私がC++でそれをした方法です...あなたがそれを適応させることができると確信しています。
_void capFrameRate(double fps) {
static double start = 0, diff, wait;
wait = 1 / fps;
diff = glfwGetTime() - start;
if (diff < wait) {
glfwSleep(wait - diff);
}
start = glfwGetTime();
}
_
ループごとに1回capFrameRate(60)
で呼び出すだけです。スリープ状態になるので、貴重なサイクルを無駄にしません。 glfwGetTime()
は、プログラムが開始されてからの時間を秒単位で返します... Javaどこかに同等のものが見つかると確信しています。
Swingを使用し、タイマーを使用せずに適度に正確なFPSを取得するためのヒントを次に示します。
まず、イベントディスパッチャスレッドでゲームループを実行しないでください。ゲームループは、UIの邪魔にならないように、ハードワークを実行する独自のスレッドで実行する必要があります。
ゲームを表示するSwingコンポーネントは、このメソッドをオーバーライドしてそれ自体を描画する必要があります。
@Override
public void paintComponent(Graphics g){
// draw stuff
}
キャッチは、ゲームループでは、後で発生する可能性があるペイントのみを要求するcomponent.repaint()を呼び出すだけなので、イベントディスパッチャスレッドが実際にペイントアクションの実行にいつ到達したかを認識しないことです。
Wait/notifyを使用すると、redrawメソッドで、要求されたペイントが発生するまで待機してから続行することができます。
上記の方法を使用してJFrame全体で文字列を単純に移動する https://github.com/davidmoten/game-exploration の完全な動作コードは次のとおりです。
import Java.awt.BorderLayout;
import Java.awt.Component;
import Java.awt.Graphics;
import Java.awt.Image;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* Self contained demo swing game for stackoverflow frame rate question.
*
* @author dave
*
*/
public class MyGame {
private static final long REFRESH_INTERVAL_MS = 17;
private final Object redrawLock = new Object();
private Component component;
private volatile boolean keepGoing = true;
private Image imageBuffer;
public void start(Component component) {
this.component = component;
imageBuffer = component.createImage(component.getWidth(),
component.getHeight());
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
runGameLoop();
}
});
thread.start();
}
public void stop() {
keepGoing = false;
}
private void runGameLoop() {
// update the game repeatedly
while (keepGoing) {
long durationMs = redraw();
try {
Thread.sleep(Math.max(0, REFRESH_INTERVAL_MS - durationMs));
} catch (InterruptedException e) {
}
}
}
private long redraw() {
long t = System.currentTimeMillis();
// At this point perform changes to the model that the component will
// redraw
updateModel();
// draw the model state to a buffered image which will get
// painted by component.Paint().
drawModelToImageBuffer();
// asynchronously signals the Paint to happen in the awt event
// dispatcher thread
component.repaint();
// use a lock here that is only released once the paintComponent
// has happened so that we know exactly when the Paint was completed and
// thus know how long to pause till the next redraw.
waitForPaint();
// return time taken to do redraw in ms
return System.currentTimeMillis() - t;
}
private void updateModel() {
// do stuff here to the model based on time
}
private void drawModelToImageBuffer() {
drawModel(imageBuffer.getGraphics());
}
private int x = 50;
private int y = 50;
private void drawModel(Graphics g) {
g.setColor(component.getBackground());
g.fillRect(0, 0, component.getWidth(), component.getHeight());
g.setColor(component.getForeground());
g.drawString("Hi", x++, y++);
}
private void waitForPaint() {
try {
synchronized (redrawLock) {
redrawLock.wait();
}
} catch (InterruptedException e) {
}
}
private void resume() {
synchronized (redrawLock) {
redrawLock.notify();
}
}
public void Paint(Graphics g) {
// Paint the buffered image to the graphics
g.drawImage(imageBuffer, 0, 0, component);
// resume the game loop
resume();
}
public static class MyComponent extends JPanel {
private final MyGame game;
public MyComponent(MyGame game) {
this.game = game;
}
@Override
protected void paintComponent(Graphics g) {
game.Paint(g);
}
}
public static void main(String[] args) {
Java.awt.EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
MyGame game = new MyGame();
MyComponent component = new MyComponent(game);
JFrame frame = new JFrame();
// put the component in a frame
frame.setTitle("Demo");
frame.setSize(800, 600);
frame.setLayout(new BorderLayout());
frame.getContentPane().add(component, BorderLayout.CENTER);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
game.start(component);
}
});
}
}
JavaでFPSを制御するためのタイマーが不正確です。私はそれを中古で見つけました。独自のタイマーを実装するか、制限付きでリアルタイムFPSを実行する必要があります。ただし、タイマーは100%正確ではないため、タスクを適切に実行できないため、使用しないでください。
ここにはすでに多くの良い情報がありますが、私がこれらの解決策で抱えていた1つの問題と、その解決策を共有したいと思いました。これらすべての解決策にもかかわらず、ゲーム/ウィンドウがスキップするように見える場合(特にX11では)、フレームごとに1回Toolkit.getDefaultToolkit().sync()
を呼び出してみてください。
Javaでは、System.currentTimeMillis()
の代わりに、glfwGetTime()
を実行してミリ秒単位で時間を取得できます。
Thread.sleep(time in milliseconds)
は、わからない場合にスレッドを待機させます。スレッドはtry
ブロック内にある必要があります。
私がやったことは、ループを続けて、最後にアニメーションを作成したのはいつかを追跡することです。少なくとも17ミリ秒経過している場合は、アニメーションシーケンスを実行します。
このようにして、ユーザー入力をチェックし、必要に応じて音符をオン/オフにすることができました。
しかし、これは子供たちに音楽を教えるのに役立つプログラムであり、私のアプリケーションはフルスクリーンであるという点でコンピューターを占有していたため、他の人とうまく再生できませんでした。
Java Swingを使用している場合、60 fpsを達成する最も簡単な方法は、このようにjavax.swing.Timerを設定することであり、ゲームを作成する一般的な方法です。
public static int DELAY = 1000/60;
Timer timer = new Timer(DELAY,new ActionListener() {
public void actionPerformed(ActionEvent event)
{
updateModel();
repaintScreen();
}
});
そして、コードのどこかで、このタイマーを繰り返して開始するように設定する必要があります。
timer.setRepeats(true);
timer.start();
1秒あたり1000ミリ秒あり、これをfps(60)で除算し、この遅延(1000/60 = 16ミリ秒を切り捨て)でタイマーを設定すると、ある程度固定されたフレームレートが得られます。これは、上記のupdateModel()およびrepaintScreen()の呼び出しで何を行うかに大きく依存するため、「ある程度」と言います。
より予測可能な結果を得るには、タイマーで2つの呼び出しの時間を計り、60fpsを維持するために16ミリ秒以内に終了することを確認してください。メモリのスマートな事前割り当て、オブジェクトの再利用などを使用すると、ガベージコレクターの影響も軽減できます。しかし、それは別の質問です。