2007/06/27

今日のお題

このエントリーをはてなブックマークに追加

で、もらったお題をここでやってみようと思います。

マウスに追従する Duke

マウスの動きに追随するようなコンポーネントです。

考え方としては、Java でいうところの MouseMotionListener#mouseMoved が発生したら、そのマウスカーソルの位置をアイコンなどに反映させるようにします。

このとき、マウスイベントが発生するコンポーネントとアイコンを扱うコンポーネントの同期を行なわなければいけません。これにはモデルのクラスとバインドを使用しました。

ソース: MouseFollower.fx

import javafx.ui.*;
import javafx.ui.canvas.*;
import java.lang.*;
 
// モデルとなるクラス
class PointModel {
    // アトリビュートとして ImageView の座標を持つ
    attribute x: Number;
    attribute y: Number;
}
 
// モデルの初期値
attribute PointModel.x = 100;
attribute PointModel.y = 100;
 
Frame {
    // モデルのインスタンス化
    var model = PointModel {}
 
    width: 800
    height: 600
    onClose: operation() {
        System.exit(0);
    }
 
    content: [
 Canvas {
            content:[
            ImageView {
                // モデルの値と ImageView の位置を同期させる
                transform: bind translate(model.x, model.y)
                animated: true
                image: {url: "{__DIR__}/duke.running.gif"}
            }          
            ]
 
            // Canvas 上でマウスが移動した場合の処理
            onMouseMoved: operation(e) {
                // マウスカーソルの位置をモデルに反映させる
                model.x = e.x;
                model.y = e.y;
            }
        }
    ]
  
    visible: true
}

モデルとなっているのが PointModel です。PointModel はアトリビュートとして x と y を持っています。この x と y と絵を表す ImageView の位置をバインドして、同期させるわけです。これを行っているのが赤字で示した部分です。bind というキーワードで同期させることができます。

また、マウスの位置が変化した場合、onMouseMoved がコールされます。onMouseMoved はオペレーション型のアトリビュートなので、オペレーション (Java でいうところのメソッドです) を代入できます。それが青字で示した部分です。

これを実行すると、マウスカーソルに常にイメージがくっついてしまいます。マウスカーソルがイメージの左上に位置しているのはご愛嬌ということで ^^;;

Java Web Start で起動

Mouse Follower

マウスカーソルに近づいていく Duke

このままでもいいといえばいいのですが、なんだか味気ないのです。常にピッタリとマウスカーソルとイメージがくっついているのは動き的にもおもしろくありません。

で、マウスカーソルに近づくように動くイメージを作ってみました。

ポイントは無限アニメーション。

無限につづくアニメーションには 2 種類あります。

  1. 0 から 100 までを何度も繰り返す
  2. 単に繰り返す

JavaFX でも Timing Framework でも上記の 2 種類は区別して扱われています。

何度も繰り返すほうは岡崎さんが説明していたので、ここでは単に繰り返すほうを説明します。

単に繰り返しは x = [0..100] dur 1000 のような書き方では記述できません (たぶん)。もう 1 つの for (unitinterval unit in dur 1000) { ... } の書き方を使用します。

単純に繰り返すということは、dur の後に記述する時間が無限大であることと同じ意味です。したがって、0 から 100 までの値をとりうるとしたら、0 の次の値は 100/∞ になってしまいます。したがって、値が変化しながらアニメーションということはできないのです。

もう 1 つの unitinterval を使った場合も、通常 unit は 0 から 1 に変化するのですが、無限アニメーションの場合は常に 0 のままです。

で、どう書けばいいのかというと次のように書きます。

    for (unitinterval unit in dur -1) { ... }

御託は長いくせにそれだけかい、といわれてしまいそうですが ^^;; dur の後の期間を -1 にするだけです。なぜこれに気がついたかというと、Timing Framework も期間を -1 (Animation.INFINITE という定数が用意されていますが、値は -1 です) だったからです。

たまたま、やってみたら当たりだったようです。

ここまでくれば後は簡単です。

この無限ループの中でマウスカーソルの位置を取得します。そして、イメージの位置と差分を取って、イメージを移動させます (実際にはバインドしてあるので、勝手に移動してくれます)。

ソース: MouseFollower2.fx

import javafx.ui.*;
import javafx.ui.canvas.*;
import java.awt.MouseInfo;
import java.awt.PointerInfo;
import java.lang.*;
 
// モデルのクラス
class PointModel {
    attribute x: Number;
    attribute y: Number;
 
    // 絶対座標と相対座標の差
    attribute diffX: Number;
    attribute diffY: Number;
 
    // ImageView を移動させるオペレーション
    operation move();
}

// モデルの初期値
attribute PointModel.x = 100;
attribute PointModel.y = 100;

// ImageView を移動させるオペレーション
operation PointModel.move() {
    var e: Number = 0;

    // dur -1 とすることで常に繰りかえす
    // unit は常に 0
    for (unitinterval unit in dur -1) {
        // マウスの現在位置を取得 (絶対座標)
        var info = MouseInfo.getPointerInfo();
        // Canvas における相対座標に変換
        var xx = info.getLocation().x - diffX;
        var yy = info.getLocation().y - diffY;
 
        // 移動の差分
        var dx = if xx > x then xx - x else x - xx;
        var dy = if yy > y then yy - y else y - yy;
        // 移動の向き
        var sx = if xx > x then 1 else -1;
        var sy = if yy > y then 1 else -1;

        // 移動分を計算
        if (dx > dy) {
            x += sx;
            e += 2 * dy;
            if (e >= 0) {
                y +=sy;
                e -= 2 * dx;
            }
        } else {
            y += sy;
            e += 2 * dx;
            if (e >= 0) {
                x += sx;
                e -= 2 * dy;
            }
        }
    }
}
 
// モデルのインスタンス化
var model = PointModel {};
 
Frame {
 
    title: "Mouse Followed Duke 2"
    width: 800
    height: 600
    onClose: operation() {
        System.exit(0);
    }
 
    content: [
 Canvas {
            content:[
            ImageView {
                transform: bind translate(model.x, model.y)
                animated: true
                image: {url: "{__DIR__}/duke.running.gif"}
            }          
            ]
  
            onMouseEntered: operation(e) {
                // Canvas とスクリーン座標の差を計算する
                model.diffX = e.source.getXOnScreen() - e.x;
                model.diffY = e.source.getYOnScreen() - e.y;
            }
        }
    ]
 
    visible: true
}
 
// 移動の開始
model.move();

赤字の部分が無限アニメーションの開始部分です。

ここでは、マウスカーソルの位置を取得するのに java.awt.MouseInfo クラスを使用しています。

ただし、取得できるのはスクリーンの左上を原点としたスクリーン座標なので、この値をここで使用している Canvas の原点からの座標に変換しなければいけません。

Canvas のスクリーン座標を取得する方法が分からなかったので、マウスイベントが発生したときにスクリーン座標と Cavas の座標の差分を取得しています。

Java Web Start で起動

Mouse Follower

これで、Duke がマウスカーソルを追いかけまわすようになりました。マウスカーソルに向かう軌跡がいまいちな点や、左に移動していても Duke は右を向いている点や、マウスカーソルに追いついてしまった後のアクションなど、いろいろ改良するところはあると思いますが...

今日はここまで。

0 件のコメント: