質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
JavaFX

JavaFXとは、Java仮想マシン上で動作するリッチインターネットアプリケーション (RIA) のGUIライブラリです。Swingとは異なり、FXMLと呼ばれる XMLとCSSを併用してデザインを記述します。

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

1回答

1593閲覧

JavaFXで画面更新が止まる

KEN_RP

総合スコア14

JavaFX

JavaFXとは、Java仮想マシン上で動作するリッチインターネットアプリケーション (RIA) のGUIライブラリです。Swingとは異なり、FXMLと呼ばれる XMLとCSSを併用してデザインを記述します。

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

0グッド

1クリップ

投稿2018/02/18 13:01

編集2018/02/19 06:49

問題の概要

JavaFXを使ってSnake Gameを作っているのですが、突然画面更新が止まります。
色々試してみたのですが特に決まった発生条件は特定出来ませんでした。
描画以外の処理はちゃんと実行されているようで、ウィンドウだけ固まってる感じです。
エラーメッセージなどは出ません。
対処法がありましたらご教授ください。
よろしくお願いします。

該当のソースコード

Java

1//package,import等省略 2public class Runner extends Application { 3 static ResourceBundle rb = ResourceBundle.getBundle("settings"); 4 5 final int WIDTH = Integer.parseInt(rb.getString("width")); 6 final int HEIGHT = Integer.parseInt(rb.getString("height")); 7 final int plus = (Integer.parseInt(rb.getString("plus")))*10; 8 int cw = WIDTH; 9 int ch = HEIGHT; 10 int size = Integer.parseInt(rb.getString("size")); 11 int speed = Integer.parseInt(rb.getString("speed")); 12 boolean grid = Boolean.parseBoolean(rb.getString("grid")); 13 boolean grad = Boolean.parseBoolean(rb.getString("color")); 14 boolean debug = Boolean.parseBoolean(rb.getString("debug")); 15 String mode = rb.getString("gameMode"); 16 17 int cx = 0; // マークの座標 18 int cy = 0; 19 int dx = size; // マークの進む量 20 int dy = 0; 21 int length = 1; //蛇の長さ 22 int data[][] = new int[WIDTH][HEIGHT]; 23 int point = 0; 24 25 Random o = new Random(); 26 int fx = ((WIDTH/size)/2)-2; 27 int fy = ((HEIGHT/size)/2)-2;//餌の座標 28 29 Alert dlg = new Alert(AlertType.INFORMATION); 30 31 public static void main(String[] args) { 32 launch(args); 33 } 34 35 @Override 36 public void start(Stage stage) throws Exception { 37 stage.setTitle("Snake Game in Java FX!!"); 38 stage.setWidth(WIDTH); 39 stage.setHeight(HEIGHT); 40 stage.initStyle(StageStyle.UTILITY); 41 stage.centerOnScreen(); 42 stage.setResizable(false); 43 44 Group root = new Group(); 45 46 final Canvas canvas = new Canvas(WIDTH, HEIGHT); 47 GraphicsContext gc = canvas.getGraphicsContext2D(); 48 49 canvas.setOnKeyPressed(event -> onKeyPressed(event)); 50 canvas.setFocusTraversable(true); 51 52 root.getChildren().add(canvas); 53 54 stage.setScene(new Scene(root)); 55 stage.show(); 56 57 // クライアント領域の幅と高さ 58 ch = (int) stage.getScene().getHeight(); 59 cw = (int) stage.getScene().getWidth(); 60 // スタート時の座標 61 cx = cw / 2; 62 cy = ch / 2; 63 if(mode.equals("time")) { 64 point+=1; 65 } 66 67 Timer timer = new Timer(); 68 class GameTask extends TimerTask { 69 70 private BooleanProperty gameover = 71 new SimpleBooleanProperty(this, "gameover", false); 72 73 public ReadOnlyBooleanProperty gameoverProperty() { 74 return gameover; 75 } 76 private void setGameover(boolean value) { 77 gameover.set(value); 78 } 79 80 @Override 81 public void run() { 82 gc.setFill(Color.WHITE); 83 gc.fillRect(0,0,cw,ch); 84 gc.setStroke(Color.GRAY); 85 cx += dx; 86 cy += dy; 87 try { 88 System.out.println("(dx,dy,data[cx][cy]):("+dx+","+dy+","+data[cx][cy]+")"); 89 }catch(Exception e) {} 90 if (isTheGameOver()) { 91 Toolkit.getDefaultToolkit().beep(); 92 Platform.runLater(new Runnable() { 93 @Override 94 public void run() { 95 if(debug) { 96 System.out.println("(cx,cy,cw,ch):("+cx+","+cy+","+cw+","+ch+")"); 97 98 } 99 setGameover(true); 100 } 101 }); 102 this.cancel(); 103 }//ゲームオーバーかどうか(行く先が安全かどうか)確認 104 gc.setFill(Color.BLACK); 105 gc.fillRect(cx, cy, size, size); 106 try { 107 data[cx][cy] = length; 108 }catch(Exception e) {} 109 //先端に描画 110 if(cx==((cw/2)%size)+(fx*size) && cy==((ch/2)%size)+(fy*size)) { 111 length++; 112 fx = o.nextInt((cw/size)-4)+2; 113 fy = o.nextInt((ch/size)-4)+2; 114 if(mode.equals("feed"))point += plus; 115 }//食餌判定 116 //すべてのマスについてデータを-1 117 for(int x=(cw / 2)%size;x<WIDTH;x+=size) { 118 for(int y=(ch / 2)%size;y<HEIGHT;y+=size) { 119 data[x][y]--; 120 if(data[x][y]<0) { 121 data[x][y]=0; 122 //gc.setFill(Color.WHITE); 123 //gc.fillRect(x, y, size, size); 124 if(grid) { 125 gc.strokeRect(x, y, size, size); 126 } 127 }else if(data[x][y]!=length-1 && grad){ 128 gc.setFill(Color.color(0.15,0.15,0.15)); 129 gc.fillRect(x, y, size, size); 130 } 131 if(debug) { 132 gc.strokeText(String.valueOf(data[x][y]), x, y+10); 133 } 134 if(x==((cw/2)%size)+(fx*size) && y==((ch/2)%size)+(fy*size)) { 135 gc.setFill(Color.BLUE); 136 gc.fillRect(x, y, size, size); 137 } 138 } 139 } 140 if(mode.equals("time"))point += plus; 141 gc.setStroke(Color.BLACK); 142 gc.strokeText(String.valueOf(point)+"point", 20+size,20+size); 143 } 144 boolean isTheGameOver(){ 145 if (cx < 0) return true; 146 if (cy < 0) return true; 147 if (cx > cw) return true; 148 if (cy > ch-2) return true; 149 if (data[cx][cy] > 0) return true; 150 return false; 151 } 152 } 153 154 GameTask task = new GameTask(); 155 stage.setOnCloseRequest(event -> { 156 if (task != null) 157 task.cancel(); 158 if (timer != null) 159 timer.cancel(); 160 }); 161 timer.schedule(task, 1000, speed); 162 163 task.gameoverProperty().addListener(new ChangeListener<Boolean>() { 164 @Override 165 public void changed(ObservableValue<? extends Boolean> arg0, 166 Boolean arg1, Boolean arg2) { 167 if (task.gameoverProperty().get()) 168 gameOver(); 169 } 170 }); 171 } 172 173 // キーが押された時の処理 174 void onKeyPressed(KeyEvent event) 175 { 176 if (event.getCode() == KeyCode.UP) { dx = 0; dy = -size;} 177 if (event.getCode() == KeyCode.DOWN) { dx = 0; dy = size;} 178 if (event.getCode() == KeyCode.RIGHT) { dx = size; dy = 0;} 179 if (event.getCode() == KeyCode.LEFT) { dx =- size; dy = 0;} 180 } 181 182 // ゲームオーバーになった時の処理 183 void gameOver() 184 { 185 dlg.setTitle("ゲームオーバー"); 186 dlg.setHeaderText(String.format("ゲームオーバー : 得点 : %d", point)); 187 dlg.showAndWait(); 188 Platform.exit(); 189 System.exit(0); 190 } 191}

試したこと

各種変数の追跡など(異常なし)
いろいろな箇所でログを出力(こちらも異常なし)

補足情報

実行環境はJavaSE-9(Java9)です。
settings.propertiesという名前のファイルでコンフィグを設定出来るようにしてあり、内容は以下に示します。

#ver 0.0.2 width = 1200 height = 700 !画面の縦、横の長さをピクセル単位で指定します。 !一般的に、縦横比は16:9程度がちょうどいいです。 speed = 100 !1フレームの長さをミリ秒単位で指定します。 !数値が小さすぎると動作が不安定になります。 !一般的に、50~300程度がちょうどいいです。 size = 40 !ゲームの単位となる正方形の一辺の長さを指定します。 !一般的に、widthの1/30程度がちょうどいいです。 gameMode = feed !ゲームモードを指定します。 !v1.0.2現在、"feed","time"の二つが指定出来ます。 !"feed"モードでは、餌を食べただけポイントが増えます。 !"time"モードでは、生き残った時間だけポイントが増えます。 !なお、"time"モードでは得点の下一桁が'1'となります。 plus = 2 !ポイントの増え幅です。 !実際はここに入力した整数値の10倍のポイントとなります。 grid = true !グリッド線の表示/非表示切り替え。 !true/falseで設定します。 color = true !蛇の先端とそれ以外で色分けをするかどうかをtrue/falseで設定します。 !表示がちょっと不安定。 debug = true !デバッグモードです。 !開発者向け。めちゃくちゃ重くなります。 #

###追記(2018/02/19)
部室のそこそこ高性能なPCで動かしたところ問題なく動いたのですが、マス目の数を増やして負荷をかけていくと固まりました。
処理が追い付いていないと考えられます。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

###原因(推測)
CanvasのGraphicsContextに対する描画命令のスタック(gc.fillRectなど)をJavaFXアプリケーションスレッド以外のスレッドでやっているのが原因ではないでしょうか?

JavaFXに限らずたいていのGUIシステムでは画面操作に関する操作は特定のスレッドで実行することが前提になっていることが多いと思います。それゆえPlatform.runLaterのような機能で「画面に関する処理を特定スレッドで実行できるような機能」が提供されているわけです。

画面操作には様々なものがあります。アプリケーション側から行うようなNodeの状態変更(GraphicContextに対する描画命令のスタックもそう)、だったりJavaFXランタイム内部で行われる「シーングラフの状態をスクリーンへ反映する処理」だったりします。アプリケーションから呼び出される画面操作関連機能では(デバッグしやすいように)「適切なスレッドで実行されているか」をチェックしてくれるような機能もありますが、多くの場合はアプリケーションが正しく設計されていると仮定し、呼び出し元スレッドが適切かを一々チェックしません。よってこの基本ルールを守らないと「何が起こるか予測不能」なことになります。

推測ですが、高速なPCだと「本来同期しなければならないような処理が同時に行われる」確率が低く、低速なPCだとその確率が高くなるため、高速なPCで正常動作しているように見えるのではないでしょうか?

###対処
本来ならTimerTaskを使わずJavaFX用の時間関連クラスを使った方が「より正しいプログラム」になる気はします。しかしとりあえずはTimerTaskのrunなどJavaFXアプリケーションスレッドではないスレッドで走行する処理のところをPlatform.runLaterに包んでしまうのが簡単な対処だと思います。

class GameTask extends TimerTask { ... @Override public void run() { Platform.runLater(() -> runInFxAppThread()); } private void runInFxAppThread() { // このメソッドは元のrunメソッドの内容 // 一部の処理で行っているPlatform.runLaterは直接実行に書き換えます。 gc.setFill(Color.WHITE); gc.fillRect(0,0,cw,ch); ... } ... }

自分のPCでは設定をデフォルトのままで、スコアが4000ぐらいになったあたりで再描画に異常をきたしましたが上記のように修正したところ、とりあえずは現象が再現しなくなったように見えました。画面処理が全てJavaFXアプリケーションスレッドで実行されるようになっているかについて細かなところまでチェックしていませんので全体を見直してみてください。


過去の質問
https://teratail.com/questions/85843
でTimer/TimerTaskとJavaFxのTimelineクラスの扱いの違いについてコメントしてみたのですが、そのあたりも参考になるかも知れません。

投稿2018/02/20 04:51

KSwordOfHaste

総合スコア18394

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KEN_RP

2018/02/20 13:31

ありがとうございます! 無事解決しました。 実際教書にTimerTaskを使うと書いてあり、何も考えずに使っていた部分もあったので、これを機にもっと深く勉強してみたいと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問