読者です 読者をやめる 読者になる 読者になる

いろふさん絵描き歌 by JavaFX その 2

このエントリーは JavaFX Advent Calendar の 22 日目のエントリーです。

昨日は @tomo_taka01 さんの JavaFXでLambdaを使ってみる(ProgressBar) です。

明日は @fukai_yas さんです。

ちなみに、このエントリーも いろふ Advent Calendar ではありません。あしからず。

なぜ、その 2 なのかというと、前回の いろふさん絵描き歌 by JavaFX は悔いが残っているからです。というのも、絵描き歌なのに動きがない!

JavaFX といえばアニメーションというはずなのに、そのアニメーションがまったくないなんて、許せないわけです!!!

というわけで、動きをつけてみました。

たとえば、直線のアニメーションは Line で作っています。アニメーション開始時は Line の endX, endY プロパティを starX, startY プロパティと同じにしておきます。そして、最後に目的となる座標に設定しています。

円を描く場合は、Circle ではなく Arc を使っています。ようするに Arc の length を 0 から 360 にアニメーションで変更しているわけです。これで円や半円を書くことができます。

もちろん、これらのアニメーションは Transition ではできないので、すべて Timeline を使っています。

たとえば、はじめの枠線を描く部分は次のようになります (実際のコードではなく、抜粋でも動くように書き直してあります)。

        Line topBorder = new Line(0.0, 0.0, 0.0, 0.0);
        topBorder.setStrokeWidth(5.0);
        container.getChildren().add(topBorder);

        new Timeline(
            new KeyFrame(Duration.ZERO, new KeyValue(topBorder.endXProperty(), 0.0)),
            new KeyFrame(new Duration(500), new KeyValue(topBorder.endXProperty(), 300.0))
        ).play();

はじめの枠線は x 軸方向にしか移動しないので、endX だけを動かしています。

ただし、一番始めに線などをすべてコンテナに登録してしまうと、点だけが表示されてしまいます。そこで、一つ前のアニメーションが終わる時に、次にアニメーションする要素をコンテナに追加する処理を書いています。

たとえば、topBorder の後に rightBorder をアニメションさせるとすると、上の Timeline の部分が下のようになります。

        Line topBorder = new Line(0.0, 0.0, 0.0, 0.0);
        topBorder.setStrokeWidth(5.0);
        container.getChildren().add(topBorder);

        new Timeline(
            new KeyFrame(Duration.ZERO, new KeyValue(topBorder.endXProperty(), 0.0)),
                new KeyFrame(new Duration(500), new EventHandler<ActionEvent>() {
                        @Override
                        public void handle(ActionEvent t) {
                            container.getChildren().add(rightBorder);
                        }
                    },
                    new KeyValue(topBorder.endXProperty(), 300.0))
        ).play();

KeyFrame では、その時間に達した時に行なう処理を EventHandler で書くことができるので、そこでコンテナに追加する処理を記述しています。

さて、今回、一番苦労したのが、吹き出しの部分。特に吹き出しのベジェ曲線で描いている部分です。

1 つ目のベジェはいいのですが、次のベジェが困ってしまうわけです。なぜかというと、単に終点を移動させているだけだと、1 つ目のベジェにくっついてしまうわけです。

終点と始点を結んだ線を赤で描いてみると、前のベジェに沿うようになってしまっていることが分かります。ここを終点が移動してしまうので、下の図のように P を逆さまにしたように感じになってしまいます。

でも、本来やりたいことは下の図の赤線のところを辿っていくようにしたいわけです。

こういう曲線をアニメーションで描くには PathTransition があります。ところが PathTransition はアニメーションさせるノード全体がパス上で移動してしまいます。

ここでやりたいのはノード全体を動かすのではなく、ベジェ曲線の終点だけを動かしたいのです。

そこで、考えたのが、見えない丸をベジェ曲線の上を移動させて、その中心点と移動量を足し合わせたものに、終点をバインドさせるということ。

        final CubicCurve curve2 = new CubicCurve(129, 141, 90, 135, 66, 120, 129, 141);
        curve2.setFill(null);
        curve2.setStroke(Color.BLACK);
        curve2.setStrokeWidth(10.0);
        curve2.setStrokeLineCap(StrokeLineCap.ROUND);
        
        final CubicCurve drawPath = new CubicCurve(129, 141, 90, 135, 66, 120, 81, 90);
        final Circle circle = new Circle(129, 141, 1);

        // 円の中心と移動量を足したものをベジェ曲線の終点とバインドさせる
        curve2.endXProperty().bind(Bindings.add(circle.centerXProperty(),
                                               circle.translateXProperty()));
        curve2.endYProperty().bind(Bindings.add(circle.centerYProperty(),
                                               circle.translateYProperty()));

        // 円を動かす
        PathTransition transition = new PathTransition(new Duration(500), drawPath);
        transition.setNode(circle);

すると、円が動くとベジェ曲線の終点がそれに応じてちゃんと曲線上を移動していくわけです。上の図がそうですね。

ただし、ベジェ曲線の制御点を最終的なポイントにしてしまうとアニメーション中に曲線が大きく曲がってしまうことがあります。そこで、制御点もアニメーションと一緒に移動させています。

ただ、この制御点のアニメーションはいまいちで、アニメーションの終わりに急にベジェ曲線が膨らむようなアニメーションになってしまっています ^ ^;;

下に表示されている歌詞は ゆきーんさんの歌 そのままです。ゆきーんさん (id:lpczclt)、無断で使ってしまってスイマセン。

歌があればもっといいのですが...

id:taizy さんの JavaFX Media playerのちょっと面白い機能 で紹介されたように MediaPlayer でタイマーでイベントを起こすことができ、これを使えば適当な歌詞のところでアニメーションが始めるようなこともできたはずです。

でも、おべんとうばこのうたはメロディがないんですよね (ラップか?)。フリーの MIDI がないかどうか探してみたのですが、メロディがないので MIDI を作りにくいらしく見つけられませんでした。だれか作ってくれれば、それに合わせてアニメーションするように改造します!!
ということで、できたのはこちらです。

ソースは gist においてあります。

https://gist.github.com/49b71f2371570be55cd5

gist 記法がなぜか動かないので、URL です。スイマセン。