で、もらったお題をここでやってみようと思います。
マウスに追従する 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 で起動
マウスカーソルに近づいていく Duke
このままでもいいといえばいいのですが、なんだか味気ないのです。常にピッタリとマウスカーソルとイメージがくっついているのは動き的にもおもしろくありません。
で、マウスカーソルに近づくように動くイメージを作ってみました。
ポイントは無限アニメーション。
無限につづくアニメーションには 2 種類あります。
- 0 から 100 までを何度も繰り返す
- 単に繰り返す
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 で起動
これで、Duke がマウスカーソルを追いかけまわすようになりました。マウスカーソルに向かう軌跡がいまいちな点や、左に移動していても Duke は右を向いている点や、マウスカーソルに追いついてしまった後のアクションなど、いろいろ改良するところはあると思いますが...
今日はここまで。