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

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

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

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

Java

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

Q&A

解決済

2回答

10246閲覧

Java FXでの”待つ”という振る舞いの実装の仕方について

ReiLeiLei1025

総合スコア236

JavaFX

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

Java

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

0グッド

0クリップ

投稿2015/01/07 06:27

JavaFXを使ってカードゲームを作っています。カードをImageViewで作成し、TranslateTransitionクラスを使ってアニメーションさせました。全員にカードが行きわたったところで、1秒間、間をおいてゲームを始めるという振る舞いをさせたいのですがうまくいきません。Transitionを行った後、Thread.sleep()やwait()などで1秒間スレッドを止めると、スレッドを再開した時、1秒後の偏移状態が表現されてしまいます。(カクっとなったり、すでにアニメーションが終わっていたり。)
なので、まず、Transitionクラスのplayメソッドが終わってからスレッドを止めればいいんだと考えたのですが、それがうまくいきません。ImageViewではありませんが下記のようなクラスを作ってみました。

lang

1package jp.ocn; 2 3import javafx.animation.TranslateTransition; 4import javafx.application.Application; 5import javafx.beans.property.BooleanProperty; 6import javafx.beans.property.SimpleBooleanProperty; 7import javafx.event.ActionEvent; 8import javafx.event.EventHandler; 9import javafx.scene.Scene; 10import javafx.scene.control.Button; 11import javafx.scene.control.Label; 12import javafx.scene.layout.AnchorPane; 13import javafx.stage.Stage; 14import javafx.util.Duration; 15 16public class TransitionTestMain extends Application { 17 BooleanProperty flagProperty = new SimpleBooleanProperty(false); 18 int abs = 1; 19 20 @Override 21 public void start(Stage primaryStage) throws Exception { 22 23 Button start_button = new Button("Transition Start"); 24 Label status_label = new Label("Ready"); 25 Label transition_label = new Label("Transition Label"); 26 27 start_button.setLayoutX(250); 28 start_button.setLayoutY(100); 29 status_label.setLayoutX(270); 30 status_label.setLayoutY(50); 31 32 AnchorPane root = new AnchorPane(); 33 root.getChildren().add(start_button); 34 root.getChildren().add(transition_label); 35 root.getChildren().add(status_label); 36 37 start_button.setOnAction(new EventHandler<ActionEvent>() { 38 39 @Override 40 public void handle(ActionEvent event) { 41 42 status_label.setText("runnning"); 43 44 TranslateTransition transition = new TranslateTransition( 45 new Duration(500), transition_label); 46 transition.setToX(500*abs); 47 transition.setToY(375*abs); 48 49 transition.setOnFinished(new EventHandler<ActionEvent>() { 50 51 @Override 52 public void handle(ActionEvent event) { 53 54 flagProperty.set(true); 55 } 56 57 }); 58 59 transition.play(); 60 61// while(!flagProperty.get()){ 62// ここでスレッドの進行を止めようと考えました 63// } 64 65 abs*=-1; 66 status_label.setText("Succeeded"); 67 flagProperty.set(false); 68 } 69 }); 70 71 Scene scene = new Scene(root, 600, 400); 72 primaryStage.setScene(scene); 73 primaryStage.centerOnScreen(); 74 primaryStage.show(); 75 76 } 77 78 public static void main(String[] args) { 79 launch(args); 80 } 81 82}

やってみて、Transitionは非同期処理ではないのだと分かったのですが、では、どのように実装をしたらいいのでしょうか?
transition.play()の後に、ボタン一つのダイアログボックスを表示させ、進行ごとにプレイヤーにクリックしてもらえば、仕様通りにはなりますが、ゲームとして不自然だと思います。
よろしくお願いします。

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

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

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

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

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

guest

回答2

0

ベストアンサー

こんな感じで、非同期スレッドとJavaFXスレッドを組み合わせるとスリープさせることができます。

※(2015-01-09追記)JavaFXの場合は、ForkJoinPoolではなく、PauseTransition (javafx.animation.PauseTransition)を使うべきとのご指摘がありました。
詳しくは、skrbさんのコメントをご参照ください。

lang

1 // 追加するimport 2 // import javafx.application.Platform; 3 // import java.util.concurrent.ForkJoinPool; 4 5 // ここまでJavaFXスレッドで実行 6 7 // ここでスレッドの進行を止めようと考えました 8 9 ForkJoinPool.commonPool().submit(() -> { 10 // 非JavaFXスレッドを非同期で実行 11 try { 12 Thread.sleep(2000L); 13 } catch (Exception e) { 14 e.printStackTrace(); 15 throw new RuntimeException(e); 16 } 17 abs*=-1; 18 Platform.runLater(() -> { 19 // 後でJavaFXスレッドで実行 20 status_label.setText("Succeeded"); 21 }); 22 flagProperty.set(false); 23 });

この場合、flagPropertyabsの状態の一貫性が崩れる可能性がありますので、同期が必要かどうかを検討してください。具体的には、transition.setToX(500*abs);の次の行でabsの符号が反転する可能性があります。
正しく同期をしないとデッドロックも有り得ますのでご注意ください。

投稿2015/01/07 09:08

argius

総合スコア9388

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

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

ReiLeiLei1025

2015/01/07 10:31

教えていただいたコードで思い通りの振る舞いを実装出来ました。 まだ、何も分かっていないので ForkJoinPoolクラスと同期について勉強してきます。 ありがとうございました。
skrb

2015/01/09 07:19

ForkJoin F/Wを使うのは間違いだとまでは言わないですけど、JavaFX的にはないですね。 通常、GUIの一時停止をしたいのであれば、PauseTransitionを使います。 この場合は、TranslateTransitionと一体化することができるので、SequentialTransitionと組み合わせて、次のように記述することができますよ。 public class TransitionTestMain extends Application { int abs = 1; @Override public void start(Stage primaryStage) { Button start_button = new Button("Transition Start"); Label status_label = new Label("Ready"); Label transition_label = new Label("Transition Label"); start_button.setLayoutX(250); start_button.setLayoutY(100); status_label.setLayoutX(270); status_label.setLayoutY(50); AnchorPane root = new AnchorPane(); root.getChildren().add(start_button); root.getChildren().add(transition_label); root.getChildren().add(status_label); start_button.setOnAction(event -> { status_label.setText("runnning"); TranslateTransition transition = new TranslateTransition( Duration.millis(500), transition_label); transition.setToX(500 * abs); transition.setToY(375 * abs); PauseTransition pause = new PauseTransition(Duration.millis(1_000)); pause.setOnFinished(e -> { abs *= -1; status_label.setText("Succeeded"); }); new SequentialTransition(transition, pause).play(); }); Scene scene = new Scene(root, 600, 400); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String... args) { launch(args); } }
argius

2015/01/09 07:48

skrbさん コメントありがとうございます。 PauseTransitionは知りませんでした。 JavaFXの場合は、PauseTransitionを使うべきですね。 というか、アニメーション関連は、まずjavafx.animationのAPIを検討すべきってことですね。 参考になりました。ありがとうございます。
ReiLeiLei1025

2015/01/12 11:06

skrbさん、コメントありがとうございます。 スレを立てたものですが、実装しようとしたところ、理解が足らないのかうまくいきませんでした。僕の考えている振る舞いは、アニメーションが終わるまで処理の進行(スレッドのことでいいはずです)を待たせておきたいというものです。 argiusさんの教えてくださったForkJoinPoolクラスを使ってこのようなコードを組んでみました。 public class TransitionTestMain extends Application { int abs = 1; @Override public void start(Stage primaryStage) throws Exception { Button start_button = new Button("Transition Start"); Label status_label = new Label("Ready"); Label status_label2 = new Label("before sleep"); Label transition_label = new Label("Transition Label"); start_button.setLayoutX(250); start_button.setLayoutY(100); status_label.setLayoutX(270); status_label.setLayoutY(50); status_label2.setLayoutX(270); status_label2.setLayoutY(250); AnchorPane root = new AnchorPane(); root.getChildren().add(start_button); root.getChildren().add(transition_label); root.getChildren().add(status_label); root.getChildren().add(status_label2); start_button.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { status_label.setText("runnning"); status_label2.setText("before sleep"); TranslateTransition transition = new TranslateTransition( new Duration(1500), transition_label); transition.setToX(500 * abs); transition.setToY(375 * abs); transition.play(); Thread nowThread = Thread.currentThread(); System.out.println(nowThread); ForkJoinPool fork=ForkJoinPool.commonPool(); fork.submit(() -> { try { Thread.sleep(2000L); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } abs *= -1; Platform.runLater(() -> { status_label.setText("Succeeded"); }); }); try { fork.shutdown(); fork.awaitQuiescence(10000l, TimeUnit.SECONDS); } catch (Exception e) { e.printStackTrace(); } status_label2.setText("after sleep"); } }); Scene scene = new Scene(root, 600, 400); primaryStage.setScene(scene); primaryStage.centerOnScreen(); primaryStage.show(); } public static void main(String[] args) { launch(args); } } このように書くと、すぐに、status_label2が"after sleep"になりませんので fork.awaitQuiescence(10000l, TimeUnit.SECONDS); の部分でスレッドの進行を止めてくれるみたいです。 タイムアウト10000ミリセコンドと書きましたが、1500ミリセコンドで処理が帰ってくるのでJavaFXスレッドと、Forkしたスレッドがjoinされて帰ってきていると思われます。ただ、Transitionが実行され、偏移している様が見られません。このコードだと1500ミリセコンド経ったら、Transitionが終わった状態で再開されます。forkしたスレッドのsleepを2500ミリセコンドにして、TransitionのDurationを5000ミリセコンドにすると丁度、真ん中ぐらいのTransitionアニメーションが見られます。つまり、forkしたスレッドも正常に動いていて、JavaFXスレッドも正常に動いていて、forkされて帰ってきているのです。ただ、問題は、fork.awaitQuiescence();を通過するまでは(正確には違うと思いますが見かけでは)アニメ処理を見ることができないということです。分かる方がいらしたら教えてください。 自分でも、Transitionがどのように処理されているのか、やJavaFXでのコードの組み方の作法など調べなくてはと思っています。 よろしくお願いします。
skrb

2015/01/14 06:57

「アニメーションが終わるまで処理の進行を待たせる」ということと、「スレッドを待機させる」は別ですよ。 JavaFXなどGUIの処理では、イベントが頻繁に発生しているため、GUIを処理させるスレッド(JavaFXの場合、JavaFX Application Threadという名前です)はスリープさせたり、ブロックさせたりしないようにします。 JATをスリープやブロックをさせずに待機をさせるためには、JATで時間を扱うためのクラス、つまりTransitionやTimelineを使う必要があります。それらのクラスを使って処理を待つような仕組みを作りこんであげなくてはいけません。その例として、前のコメントではPauseTransitionを紹介しました。 もちろん、JavaFXでは非同期にタスクを処理するための機構としてTaskとServiceがありますが(Concurrency Utilitiesを直接扱うことは普通はやりません)、これらのクラスからGUIを直接制御するというのは基本的には行いません。もちろん、Platform.runLaterメソッドを使用してJATにアクセスすることはできますが、通常非同期処理からはGUIに表示するための状態の変化だけを行うようにし、その状態の変化に応じたGUIの描画処理などはJAT側の作りこみで行うようにします。 ゲームを作るのだとしたら、これからもアニメーションを多用することになると思うので、TransitionやTimelineで時間を制御する方法を取得された方がいいと思います。
ReiLeiLei1025

2015/01/15 02:17

Skrbさん、コメントありがとうございます。 >JavaFXなどGUIの処理では、イベントが頻繁に発生しているため、GUIを処理させるスレッド(JavaFXの場合、JavaFX Application Threadという名前です)はスリープさせたり、ブロックさせたりしないようにします。 JavaFXの作法、勉強になりました。 現在のコードの実装状態をお伝えさせていただくと、例えば、カードゲームのデモアプリがあるとして、ユーザーがボタンを1回押したら、後は延々とコンピュータープレーヤー同士がカードゲームをするような仕様だとすると、カードのアニメーションを置き去りにして、手番だけがどんどん進んでしまうといった感じでしょうか。 お話によると、スレッドの停止ではなく、TransitionやTimelineクラスとJATの作りこみによって何とかするものだということなので、ヒントも頂いていることですし、もう少し、自分で考えてみようと思います。(setOnFinishedメソッドがキーポイントだと目をつけています)
ReiLeiLei1025

2015/01/16 05:54

結局、分かりませんでした。ただ、PauseTransitionを調べていたところ、PauseTransitionを使って、Widowを閉じるというコードを見つけたのでそれを改良してこのようなコードを組んでみました。 public class TransitionTestMainOnly extends Application { int abs = 1; @Override public void start(Stage primaryStage) throws Exception { Button start_button = new Button("Transition Start"); Label transition_label = new Label("Transition Label"); start_button.setLayoutX(250); start_button.setLayoutY(50); AnchorPane root = new AnchorPane(); root.getChildren().add(start_button); root.getChildren().add(transition_label); start_button.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { TranslateTransition transition = new TranslateTransition( new Duration(500), transition_label); transition.setToX(500 * abs); transition.setToY(375 * abs); PauseTransition pauseTransition=new PauseTransition(new Duration(500)); SequentialTransition seqentialTransition=new SequentialTransition(transition,pauseTransition); Stage stage = new Stage(); Label label = new Label("Running"); AnchorPane root = new AnchorPane(); root.getChildren().add(label); Scene scene = new Scene(root, 200, 60); stage.setScene(scene); stage.setMaxWidth(200); stage.setMaxHeight(60); stage.setTitle("メッセージ"); stage.centerOnScreen(); stage.initModality(Modality.WINDOW_MODAL); final Window window = stage; seqentialTransition.setOnFinished(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { window.hide(); } }); seqentialTransition.play(); stage.showAndWait(); abs *= -1; } }); Scene scene = new Scene(root, 600, 400); primaryStage.setScene(scene); primaryStage.centerOnScreen(); primaryStage.show(); } public static void main(String[] args) { launch(args); } } 一応、これで、自分のやりたい事は出来たのですが、絶対に、メッセージボックスが出てきてしまいます。メッセージを表示すれば雰囲気的に誤魔化せますが…。
ReiLeiLei1025

2015/01/16 06:16

続けて、レスを失礼します。 stage.setY();で、画面外を設定すれば、表示もされなくなりますね。
guest

0

すいません。追加します。
ダイアログボックスうんぬんの話は、showAndWait()メソッドのことです。

投稿2015/01/07 06:35

ReiLeiLei1025

総合スコア236

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.49%

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

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

質問する

関連した質問