ちょっと古めかしい言葉を出してしまいましたが、MSのVisual StudioやNetBeansのMatisseのようなWYSIWYG環境でのソフトウェ開発が、JavaFXでも可能になりました。
それは、NetBeansなどのIDE用に提供されている、JavaFX追加接続(plug-in)プログラムの下見(preview)機能を利用した開発のことです。Visual StudioやMatisseのような、直接操作方式ではありませんが、編集作業と並行して実行時の姿が実時間で見られるので、かなり開発効率が上がります。
事前評価(preview)版のJavaFX追加接続にもその原型はあったのですが、原始コードの編集領域を分割して表示するため編集領域が狭くなる、コードによってはうまく表示ができないなど、正直言って「今一」の感が否めませんでした。しかし、JavaFX 1.0対応版では、これらの点かなり改善さているようです(まだ、それほど試した訳ではないので、問題点の有無を把握し切れてはいませんが)。
少なくとも、下見画面がIDEの窓とは独立した専用の窓に表示されるため、編集領域が狭くなることはありません。直接操作方式ではありませんが、元々JavaFX言語は宣言型の言語であり、VBやJavaに比べれば、画面表示とコードの対応が取りやすいこともあって、それほど苦にはなりません(もちろん、図形部品をマウスで引っ張りまわして好きな位置に配置したり、寸法を自由に変えられることに越したことはないのですが)。
2008年12月24日水曜日
2008年12月23日火曜日
JavaFX 1.0 SDK Update 1公開
JavaFX 1.0 SDK Update 1が、12月19日に公開されました。公開直後は、導入用プログラムが更新前のものしか公開されていなかったのですが、今日改めて見てみたら、JavaFX 1.0 SDK Update 1用の導入プログラムに変更されていましたので、早速導入してしまいました。
2008年12月16日火曜日
狼おじさんになってしまうところでした
昨日、私の不注意から、JavaFXのcontinue式に不具合があるとの投稿をしてしまいました。
再度確認したところ、continue式には、私が指摘したような不具合はないことが分かりました。
ここに、訂正して、お詫びいたします。
なお、誤った情報をこれ以上広めたくありませんので、まことに勝手ながら昨日の投稿は削除いたしました。
貴重なご指摘をいただいた、雪羽 さんどうもありがとうございました。ここに、お礼を述べさせていただきます。
再度確認したところ、continue式には、私が指摘したような不具合はないことが分かりました。
ここに、訂正して、お詫びいたします。
なお、誤った情報をこれ以上広めたくありませんので、まことに勝手ながら昨日の投稿は削除いたしました。
貴重なご指摘をいただいた、雪羽 さんどうもありがとうございました。ここに、お礼を述べさせていただきます。
2008年12月13日土曜日
NetBeans用JavaFX追加接続プログラム日本語表示不具合
JavaFX言語の入門書「JavaFXプログラミング言語の基礎」を書き始め、まだ第1章だけですがWebページで公開しました。
そこに載せている見本コードの動作確認をしていて分かったのですが、NetBeans用JavaFX追加接続プログラム(JavaFX 1.0相当版:JavaFX 1.0バージョン1.4.3、JavaFX SDK for Windowsバージョン1.3.3)の日本語表示には、まだ未修正の不具合があります。
これは、以前の投稿で記したものですが、標準出力に日本語文字列を表示するようなプログラムを一括変換すると誤りが発生するというものです。同じプログラムを、JavaFX 1.0 SDKのjavafxcでコマンド行から一括変換すると正常に変換でき、日本語が表示できます。したがって、Windows上で動作するNetBeans用JavaFX追加接続プログラムの標準出力への日本語表示に、問題が残っているようです。ちなみに、枠付き窓に日本語が表示できない不具合は、修正されています。
話は変わりますが、以前から、JavaFX言語は、表現力が高いばかりでなく、初心者がプログラミングを習得するのにも適した言語であると思っています。Java言語ですと、何はともあれ、「class」を定義しなければなりません。そこで、クラスとは何なのかの話をいきなり始めるか、「とりあえず、おまじない(?)と思って記述してください」と、クラスに関する説明を後回しにするかの選択をしなければなりません。
いずれにしても、説明する方も、また説明される方もあまりすっきりしません。
その点、JavaFXなら、println()関数の説明をするぐらいで「Hello world!」プログラムが書けますので、話が簡単です。また、JavaFX 1.0では、データ型の推定機能が復活しましたので、その点でもプログラミングが楽になります。しかも、動画や多重媒体応用プログラムが簡潔に書けるまでになります。
「JavaFX言語でプログラミングを学ぶ」といった講座が一般的になる、そんな日が早く来るといいのですが。
そこに載せている見本コードの動作確認をしていて分かったのですが、NetBeans用JavaFX追加接続プログラム(JavaFX 1.0相当版:JavaFX 1.0バージョン1.4.3、JavaFX SDK for Windowsバージョン1.3.3)の日本語表示には、まだ未修正の不具合があります。
これは、以前の投稿で記したものですが、標準出力に日本語文字列を表示するようなプログラムを一括変換すると誤りが発生するというものです。同じプログラムを、JavaFX 1.0 SDKのjavafxcでコマンド行から一括変換すると正常に変換でき、日本語が表示できます。したがって、Windows上で動作するNetBeans用JavaFX追加接続プログラムの標準出力への日本語表示に、問題が残っているようです。ちなみに、枠付き窓に日本語が表示できない不具合は、修正されています。
話は変わりますが、以前から、JavaFX言語は、表現力が高いばかりでなく、初心者がプログラミングを習得するのにも適した言語であると思っています。Java言語ですと、何はともあれ、「class」を定義しなければなりません。そこで、クラスとは何なのかの話をいきなり始めるか、「とりあえず、おまじない(?)と思って記述してください」と、クラスに関する説明を後回しにするかの選択をしなければなりません。
いずれにしても、説明する方も、また説明される方もあまりすっきりしません。
その点、JavaFXなら、println()関数の説明をするぐらいで「Hello world!」プログラムが書けますので、話が簡単です。また、JavaFX 1.0では、データ型の推定機能が復活しましたので、その点でもプログラミングが楽になります。しかも、動画や多重媒体応用プログラムが簡潔に書けるまでになります。
「JavaFX言語でプログラミングを学ぶ」といった講座が一般的になる、そんな日が早く来るといいのですが。
2008年12月5日金曜日
JavaFX 1.0 SDKが公開されました
JavaFX 1.0 SDKが公開されましたので、早速導入してみました。
JavaFX 1.0 SDKは、JavaFX.comのStartページから導入できます。なお、同ページには、「Learning the JavaFX Script Programming Language」という指導書への連結が載っていますが、その指導書の中に記載されているJavaFX 1.0 SDK導入ページの連結をたどると、本日(12/5)現在では、事前評価(Preview)版の導入ページに案内されてしまいます。上記のStartページから導入すると、正式版が導入できます。
また、上記の指導書には、JDK 5またはJDK 6が必要と記されていますが、JavaFX 1.0 SDKを導入しようとすると、最新のJDK 6u11を導入するように求められます。
JavaFX 1.0 SDKは、JavaFX.comのStartページから導入できます。なお、同ページには、「Learning the JavaFX Script Programming Language」という指導書への連結が載っていますが、その指導書の中に記載されているJavaFX 1.0 SDK導入ページの連結をたどると、本日(12/5)現在では、事前評価(Preview)版の導入ページに案内されてしまいます。上記のStartページから導入すると、正式版が導入できます。
また、上記の指導書には、JDK 5またはJDK 6が必要と記されていますが、JavaFX 1.0 SDKを導入しようとすると、最新のJDK 6u11を導入するように求められます。
2008年10月8日水曜日
JavaFX Air Hockey program
I made a Air Hockey game in JavaFX.
As for the program, named AirHockey.fx, plese refer to the code (modified to conform to JavaFX 1.0).
When you run the program, an air hockey game table with two aqua color mallets are displayed on your screen. Computer's goal (the upper one) and your goal (the lower one) are displayed in burlywood color.
Push the "SET" button to start the game. Then, a white puck appears on your side.
You can manipulate your mallet by the mouse as long as it resides in your area.
A velocity of your mallet is computed from its displacement by applying the scaling transformation. The scaling is modulated by the velocityScaling factor. You can make your mallet's velocity more real by moving the factor closer to 1.0.
You can also regulate the bounce back speed by adjusting the elastic modulus elastic.
In this program, a computer simply move its mallet back and forth. It has no ability to take the offensive. It is your business to make your opponent cool.
Enjoy yourself!
2008年10月7日火曜日
JavaFXで空中ホッケー遊技(Air hockey game)を作る
頭の中が沸騰しそうになったとき、しばらく空っぽにするのにいいですよ。
プログラムを実行すると、右の図のような空中ホッケーの卓が表示されます。薄茶色で表示されているところが得点圏(goal)で、青緑色で表示されているのが槌(mallet)です。
「SET」ボタンを押すと、図の位置に白い円盤(puck)が表示されます(プログラムを実行した直後や、得点した後は、円盤が表示されません)。
人間様用の槌(下側が「人間様」の領域で、上側が「計算機様」の領域です)をマウスで動かして(人間様の領域内にマウスを持っていくと、その後を槌が追います)、円盤を打ってみてください。
槌を速く動かしすぎると、槌で円盤を引きずるような動作をします。これは、マウスの位置の変化量から槌の速度を計算する際に、スケーリング変換を行っているためです。velocityScalingの値で、変換係数を調節してください。
また、円盤が卓の壁や槌に衝突してはね返されるとき、弾性係数を考慮して動きを計算しています。elasticの値で、弾性率を変えてみてください。
今回のものでは、計算機様は、ただ槌を往復させるだけで、何の芸も持ち合わせていません。計算機様にどんな芸を持たせるかが、これからの楽しみです。何の制限も持たせなければ、計算機様をいくらでも強くできますが、それでは勝負になりません。強過ぎても弱過ぎてもだめですし、(乱数に因らない)偶然の要素もないと面白くない、その辺りが工夫のしどころです。
最後に、原始コードを載せておきます。なお、前回の投稿で、仕立て節に定義した関数がうまく機能しないと記しましたが、(部品化を考慮して)仕立て節を別の台本ファイルで定義したことがまずかっただったようです。このプログラムのように、同じ台本ファイル内に仕立て節を定義する限り、特に問題はありませんでした。
/*
* AirHockey.fx
* The air hockey game.
*
* Created and modified:
* V 1.0.0 2008/09/25
*/
package game;
import javafx.animation.*;
import javafx.application.*;
import javafx.input.*;
import javafx.scene.*;
import javafx.scene.effect.*;
import javafx.scene.effect.light.*;
import javafx.scene.geometry.*;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import java.lang.Math;
import java.lang.System;
/**
* @author terra
*/
var goalCorners: Disc[];
var mouseEvent: MouseEvent;
var tableWidth: Number = 300;
var tableHeight: Number = 400;
var goalWidth: Number = 80;
var goalCornerRadius: Number = 5;
var puckRadius: Number = 10;
var malletRadius: Number = 15;
var hittingAreaDepth: Number = tableHeight / 2 - malletRadius;
var elastic: Number = 0.95;
var velocityScaling = 0.35;
var malletCMaxV = 2; // Maximum velocity of the computer's mallet.
var timingGenerator: Timeline = Timeline {
repeatCount: Timeline.INDEFINITE
keyFrames: KeyFrame {
time: 20ms
action: function(): Void {
malletC.move();
puck.move();
malletH.calcuVelocity();
puck.collide(malletH);
puck.collide(malletC);
for (corner in goalCorners) {
puck.collide(corner);
}
score.judgeGoal();
}
}
};
var puck: Puck = Puck {
// x: goalCornerRadius + tableWidth / 2
// y: goalCornerRadius + 2 * tableHeight/ 3
x: -(puckRadius + 1)
y: goalCornerRadius + tableHeight / 2
radius: puckRadius
vX: 0, vY: 0
}
var malletH: MalletH = MalletH { // Human's mallet.
x: bind mouseEvent.getStageX()
y: bind mouseEvent.getStageY()
radius: malletRadius
color: Color.AQUA
}
var malletC: MalletC = MalletC { // Computer's mallet.
x: goalCornerRadius + tableWidth / 2
y: goalCornerRadius + malletRadius + puckRadius
radius: malletRadius
color: Color.AQUA
vX: malletCMaxV, vY: 0
}
for (i in [0..1]) {
for (j in [0..1]) {
insert Disc {
x: (tableWidth - goalWidth) / 2 + j * goalWidth
y: i * (2 * goalCornerRadius + tableHeight)
radius: goalCornerRadius
color: Color.LIGHTGREY
} into goalCorners;
}
}
var score: Score = Score{}
Frame {
title: "Air hockey"
width: 2 * goalCornerRadius + tableWidth + 56 as Integer
height: 2 * goalCornerRadius + tableHeight + 28 as Integer
visible: true
closeAction: function(): Void {System.exit(0);}
stage: Stage {
width: 2 * goalCornerRadius + tableWidth as Integer
height: 2 * goalCornerRadius + tableHeight as Integer
fill: Color.LIGHTGRAY
content: [
Rectangle { // Jointed goal area.
x: (tableWidth - goalWidth) / 2
y: 0
width: goalWidth
height: 2 * goalCornerRadius + tableHeight +2
fill: Color.BURLYWOOD
},Rectangle { // Table.
x: goalCornerRadius, y: goalCornerRadius
width: tableWidth, height: tableHeight
fill: Color.OLIVE
},
Rectangle { // Human's hittig area.
x: goalCornerRadius + malletRadius
y: goalCornerRadius + tableHeight - hittingAreaDepth
width: tableWidth - 2 * malletRadius
height: hittingAreaDepth - malletRadius
fill: Color.OLIVE
onMouseMoved: function(ev: MouseEvent): Void {
mouseEvent = ev;
}
},
Line { // Center line.
startX: goalCornerRadius
startY: goalCornerRadius + tableHeight / 2
endX: goalCornerRadius + tableWidth -1
endY: goalCornerRadius + tableHeight / 2
stroke: Color.BLUE
},
puck,
malletH,
malletC,
goalCorners,
ScoreIndicator {
x: 2 * goalCornerRadius + tableWidth + 5 as Integer
y: goalCornerRadius + tableHeight / 2 - 24 as Integer
score: bind score.scoreC
},
ScoreIndicator {
x: 2 * goalCornerRadius + tableWidth + 5 as Integer
y: goalCornerRadius + tableHeight / 2 + 5 as Integer
score: bind score.scoreH
},
Rectangle { // "SET" button.
x: 2 * goalCornerRadius + tableWidth + 5 as Integer
y: goalCornerRadius + tableHeight - 30 as Integer
width: 35, height: 30
arcWidth: 8, arcHeight: 8
fill: Color.DARKGREY
effect: Lighting {
light: DistantLight {
azimuth: 225, elevation: 50
}
}
onMousePressed: function(ev: MouseEvent): Void {
puck.x = goalCornerRadius + tableWidth / 2;
puck.y = goalCornerRadius + 2 * tableHeight/ 3;
puck.vX = 0;
puck.vY = 0;
timingGenerator.start();
}
},
Text {
x: 2 * goalCornerRadius + tableWidth + 10 as Integer
y: goalCornerRadius + tableHeight - 20 as Integer
textOrigin: TextOrigin.TOP
fill: Color.YELLOW
font: Font {
size: 14
name: "Monospaced", style: FontStyle.BOLD
}
effect: DropShadow {
offsetX: 3, offsetY: 3, radius: 2
color: Color.BLACK
}
content: "SET"
}
]
}
}
class Disc extends CustomNode {
attribute x: Number;
attribute y: Number;
attribute radius: Number;
attribute color: Color = Color.WHITE;
attribute vX: Number;
attribute vY: Number;
attribute lastX: Number = x;
attribute lastY: Number = y;
function create(): Node {
return Circle {
centerX: bind x, centerY: bind y
radius: bind radius
fill: bind color
};
}
}
class Puck extends Disc {
function move(): Void {
x += vX;
y += vY;
if (x + radius > goalCornerRadius + tableWidth) {
x = goalCornerRadius + tableWidth - radius;
vX *= -1.0 * elastic;
} else if (x - radius < goalCornerRadius) {
x = goalCornerRadius + radius;
vX *= -1.0 * elastic;
}
if (x >= 0) {
if (y > 2 * goalCornerRadius + tableHeight) {
// It crossed the Human's goal line.
x = -(radius + 1);
y = 2 * goalCornerRadius + tableHeight + radius;
vX = 0;
vY = 0;
} else if (y + radius > tableHeight + goalCornerRadius) {
if ((x < (tableWidth - goalWidth) / 2)
or (x > (tableWidth + goalWidth) / 2)) {
y = tableHeight + goalCornerRadius - radius;
vY *= -1.0 * elastic;
}
} else if (y < 0) {
// It crossed the computer's goal line.
x = -(radius + 1);
y = -radius;
vX = 0;
vY = 0;
} else if (y - radius < goalCornerRadius) {
if ((x < (tableWidth - goalWidth) / 2)
or (x > (tableWidth + goalWidth) / 2)) {
y = goalCornerRadius + radius;
vY *= -1.0 * elastic;
}
}
}
}
function collide(disc: Disc): Void {
var distX: Number = disc.x - x;
var distY: Number = disc.y - y;
var minDist: Number = disc.radius + radius;
var dist2: Number = distX * distX + distY * distY;
var minDist2: Number = minDist * minDist;
if (dist2 < minDist2) {
var colAngle: Number = Math.atan2(distY, distX);
var sinColAngle: Number = Math.sin(colAngle);
var cosColAngle: Number = Math.cos(colAngle);
var expres1: Number = disc.vX * cosColAngle + disc.vY * sinColAngle;
var expres2: Number = vX * sinColAngle - vY * cosColAngle;
var expres3: Number = vX * cosColAngle + vY * sinColAngle;
x = disc.x - minDist * cosColAngle;
y = disc.y - minDist * sinColAngle;
vX = (1+ elastic) * expres1 * cosColAngle + expres2 * sinColAngle
- elastic * expres3 * cosColAngle;
vY = (1+ elastic) * expres1 * sinColAngle - expres2 * cosColAngle
- elastic * expres3 * sinColAngle;
}
}
}
class MalletH extends Disc {
function calcuVelocity(): Void {
vX = velocityScaling * (x - lastX);
vY = velocityScaling * (y - lastY);
lastX = x;
lastY = y;
}
}
class MalletC extends Disc {
function move(): Void {
x += vX;
if (x + radius > 2 * goalCornerRadius + tableWidth / 2
+ goalWidth / 2) {
x = 2 * goalCornerRadius + tableWidth / 2 + goalWidth / 2
- radius;
vX *= -1.0;
} else if (x - radius < tableWidth / 2 - goalWidth / 2) {
x = tableWidth / 2 - goalWidth / 2 + radius;
vX *= -1.0;
}
}
}
class Score {
attribute scoreC: Integer = 0; // Computer's score.
attribute scoreH: Integer = 0; // Human's score.
function judgeGoal(): Void {
if (puck.x < 0) {
if (puck.y > 2 * goalCornerRadius + tableHeight) {
scoreC += 1;
} else if (puck.y < 0) {
scoreH += 1;
}
puck.y = goalCornerRadius + tableHeight / 2;
timingGenerator.stop();
}
}
}
class ScoreIndicator extends CustomNode {
attribute score: Integer = 0;
attribute x: Integer = 0;
attribute y: Integer = 0;
attribute width: Integer = 21;
attribute height: Integer = 18;
attribute fillColor: Color = Color.WHITE;
attribute borderColor: Color = Color.YELLOWGREEN;
attribute scoreTextColor: Color = Color.BLACK;
function create(): Node {
Group {
content: [
Rectangle {
x: x, y: y
width: width, height: height
arcWidth: 6, arcHeight: 6
stroke: borderColor, fill: fillColor
},
Text {
x: x + 4 * height / 18 as Integer
y: y + 5 * height / 18 as Integer
textOrigin: TextOrigin.TOP
fill: scoreTextColor
font: Font {
size: 14 * height / 18 as Integer
name: "Monospaced", style: FontStyle.BOLD
}
content: bind "{%2d score}"
}
]
}
}
}
登録:
投稿 (Atom)