追うしょぼちむ

このエントリーは しょぼちむ Advent Calendar 2014 の 8 日目です

昨日は @shiget84 さんの しょぼちむさんをきっかけに価値を届けることについて考えた でした。

明日は @cocoatomo さんです。


はるか昔、neko というアプリケーションがあったのを皆さんごぞんじでしょうか?

wikipedia:Neko (ソフトウェア)

一瞬かわいいのですが、使っているとうざくてしかたないアプリケーションです。

なぜ、こんなアプリケーションを思い出したかというと、うらがみさんにかまって、かまってと言い続けるしょぼちむの存在があったからこそです。

そこで、かつての neko を現代によみがえらせた syobo を作成してみたいと思います。

もちろん、そこは櫻庭が作るものですから、JavaFX です。

ところが、JavaFX では 1 つだけ問題があります。マウスカーソルの位置を検出できないという問題です。

アプリケーションを表示している領域であれば、マウス移動のイベントが取得できるので、マウスカーソルの位置も取得できるのですが、何も表示していない場所ではこのイベントが発生しないのです。

しかたないので、AWT です ><

AWT には MouseInfo というクラスがあり、いつでもマウスカーソルの位置を取得することができます。そこで、タイマで定期的にカーソル位置を取得し、しょぼちむの位置を更新するということを行っていきます。

JavaFX で Swing を扱うのですが、ここでは直接 Swing のコンポーネントを扱うわけではないので、Swing の EDT を使用するだけで大丈夫です。

Swing の Timer クラスで 50 ミリ秒ごとにマウスカーソルの位置を検出するには次のように記述します。

    private void startSwingEDT() {
        // AWTでマウスの位置を 50 秒ごとに検出
        SwingUtilities.invokeLater(() -> {
            Timer timer = new Timer(50, e -> {
                PointerInfo info = MouseInfo.getPointerInfo();
                Platform.runLater(() -> updateLocation(info.getLocation().getX(), info.getLocation().getY()));
            });
            timer.start();
        });
    }

SwingUtilities.invokeLater メソッドで Swing の EDT を起動し、そこでタイマを生成して 50 ミリ秒ごとに処理を行わせています。

MouseInfo クラスの getPinterInfo メソッドマウスカーソルの位置が取得できるので、後は JavaFX のスレッドで位置を更新します。JavaFX のスレッドで処理するためには Platform クラスの runLater メソッドを使用します。

Swing と JavaFX を両方使っていると、スレッドの違いがうざいのですが、しかたありません。

updateLocation メソッドではカーソルの位置に追従して、しょぼちむを表示させます。たいした数学ではないので、分かるでしょう。

    private void updateLocation(double mx, double my) {
        cursor.setTranslateX(mx);
        cursor.setTranslateY(my);

        double tx = syobochim.getTranslateX();
        double ty = syobochim.getTranslateY();

        // 近傍であれば位置の更新を行わない
        double d = (mx - tx) * (mx - tx) + (my - ty) * (my - ty);
        if (d < DISTANCE * DISTANCE) {
            return;
        }

        // カーソルとsyobochimの角度を算出
        double theta = Math.atan2(my - ty, mx - tx);

        // 移動分を算出
        double dx = DISTANCE * Math.cos(theta);
        double dy = DISTANCE * Math.sin(theta);
        syobochim.setTranslateX(tx + dx);
        syobochim.setTranslateY(ty + dy);

        // 角度に応じて回転
        // カーソルの右側にsyobocimが位置している場合は反転
        syobochim.getTransforms().removeIf(trans -> trans.equals(rotate));
        if (theta > PI / 2.0 || theta < -PI / 2.0) {
            syobochim.getTransforms().add(rotate);
            syobochim.setRotate(theta * 180.0 / PI - 180.0);
        } else {
            syobochim.setRotate(theta * 180.0 / PI);
        }
    }

後は、透明ステージにするとか、常にトップにもってくるなどを行ってから表示を行っています。

    @Override
    public void start(Stage stage) {
        Group root = new Group();
        initImage(root);

        // Scene をスクリーンと同サイズに設定
        Screen screen = Screen.getPrimary();
        Scene scene = new Scene(root, screen.getBounds().getWidth(), screen.getBounds().getHeight());
        // 背景を透過にする
        scene.setFill(null);
        // カーソルを表示しない
        scene.setCursor(Cursor.NONE);

        stage.setScene(scene);
        // 透明ステージにする
        stage.initStyle(StageStyle.TRANSPARENT);
        stage.setAlwaysOnTop(true);
        stage.show();

        // Swing の EDT でタイマ処理を行う
        startSwingEDT();
    }

さて、これで完成です。実行してみましょう。

f:id:skrb:20141208211357p:plain

これで、ずっとうらがみさんを追い続けるしょぼちむのできあがりです。

ただ、しょぼちむのイメージはもっと大きくてもよかったなぁ。

それに今回は手抜きなのでちゃんとアニメーションしていないし。時間があったら手や足を動かして、追うようにします。

ついでに、カーソル動かなかったら、寝てしまうというのも取り込みたいなぁ...

ソースは GitHub で公開しています!

GitHub syobo